killbill-memoizeit

Merge branch 'integration'

5/15/2012 8:42:32 PM

Changes

account/src/main/java/com/ning/billing/account/api/user/AccountBuilder.java 171(+0 -171)

account/src/main/resources/com/ning/billing/account/dao/AccountHistorySqlDao.sql.stg 15(+0 -15)

analytics/pom.xml 23(+23 -0)

beatrix/pom.xml 39(+39 -0)

bin/cleanAndInstall 23(+23 -0)

bin/db-helper 156(+156 -0)

bin/start-server 99(+99 -0)

catalog/pom.xml 6(+6 -0)

doc/api.html 91(+91 -0)

doc/css/prettify.css 118(+118 -0)

doc/design.html 100(+100 -0)

doc/js/prettify.js 28(+28 -0)

doc/setup.html 97(+97 -0)

doc/user.html 102(+102 -0)

entitlement/pom.xml 54(+24 -30)

entitlement/src/main/java/com/ning/billing/entitlement/api/billing/DefaultEntitlementBillingApi.java 222(+0 -222)

entitlement/src/test/java/com/ning/billing/entitlement/api/ApiTestListener.java 191(+0 -191)

entitlement/src/test/java/com/ning/billing/entitlement/api/billing/TestDefaultEntitlementBillingApi.java 269(+0 -269)

index.html 120(+120 -0)

invoice/pom.xml 42(+13 -29)

jaxrs/pom.xml 97(+97 -0)

junction/pom.xml 114(+114 -0)

overdue/pom.xml 118(+118 -0)

payment/pom.xml 29(+14 -15)

payment/src/main/java/com/ning/billing/payment/util/EventBusFuture.java 75(+0 -75)

payment/src/test/java/com/ning/billing/payment/util/TestSyncWaitOnEventBus.java 102(+0 -102)

pom.xml 108(+92 -16)

server/pom.xml 434(+434 -0)

util/pom.xml 24(+24 -0)

util/src/main/resources/com/ning/billing/util/customfield/dao/CustomFieldAuditSqlDao.sql.stg 17(+0 -17)

util/src/main/resources/com/ning/billing/util/customfield/dao/CustomFieldHistorySqlDao.sql.stg 18(+0 -18)

util/src/main/resources/com/ning/billing/util/tag/dao/TagAuditSqlDao.sql.stg 21(+0 -21)

Details

diff --git a/account/src/main/java/com/ning/billing/account/api/DefaultAccount.java b/account/src/main/java/com/ning/billing/account/api/DefaultAccount.java
index a22491b..54cfd9c 100644
--- a/account/src/main/java/com/ning/billing/account/api/DefaultAccount.java
+++ b/account/src/main/java/com/ning/billing/account/api/DefaultAccount.java
@@ -21,13 +21,12 @@ import java.util.UUID;
 
 import com.ning.billing.util.callcontext.CallContext;
 import com.ning.billing.util.customfield.CustomField;
+import com.ning.billing.util.dao.ObjectType;
 import com.ning.billing.util.entity.ExtendedEntityBase;
-import org.joda.time.DateTime;
 import org.joda.time.DateTimeZone;
 
 import com.ning.billing.catalog.api.Currency;
-
-import javax.annotation.Nullable;
+import com.ning.billing.junction.api.BlockingState;
 
 public class DefaultAccount extends ExtendedEntityBase implements Account {
     private final String externalKey;
@@ -47,65 +46,31 @@ public class DefaultAccount extends ExtendedEntityBase implements Account {
 	private final String country;
 	private final String postalCode;
 	private final String phone;
-    private final String updatedBy;
-    private final DateTime updatedDate;
-
-	//intended for creation and migration
-	public DefaultAccount(final String createdBy, final DateTime createdDate,
-                          final String updatedBy, final DateTime updatedDate,
-                          final AccountData data) {
-		this(UUID.randomUUID(), createdBy, createdDate, updatedBy, updatedDate, data);
-	}
+    private final boolean isMigrated;
+    private final boolean isNotifiedForInvoices;
 
     public DefaultAccount(final AccountData data) {
-		this(UUID.randomUUID(), null, null, null, null, data);
-	}
-
-    public DefaultAccount(final UUID id, final AccountData data) {
-		this(id, null, null, null, null, data);
+		this(UUID.randomUUID(), data);
 	}
 
 	/**
 	 * This call is used to update an existing account
-	 *  
+	 *
 	 * @param id UUID id of the existing account to update
 	 * @param data AccountData new data for the existing account
 	 */
-	public DefaultAccount(final UUID id, @Nullable final String createdBy, @Nullable final DateTime createdDate,
-                          @Nullable final String updatedBy, @Nullable final DateTime updatedDate,
-                          final AccountData data) {
+	public DefaultAccount(final UUID id, final AccountData data) {
 		this(id, data.getExternalKey(), data.getEmail(), data.getName(), data.getFirstNameLength(),
 				data.getCurrency(), data.getBillCycleDay(), data.getPaymentProviderName(),
 				data.getTimeZone(), data.getLocale(),
 				data.getAddress1(), data.getAddress2(), data.getCompanyName(),
 				data.getCity(), data.getStateOrProvince(), data.getCountry(),
-				data.getPostalCode(), data.getPhone(), createdBy, createdDate,
-                updatedBy, updatedDate);
+				data.getPostalCode(), data.getPhone(), data.isMigrated(), data.isNotifiedForInvoices());
 	}
 
-	/**
+	/*
 	 * This call is used for testing and update from an existing account
-	 * @param id
-	 * @param externalKey
-	 * @param email
-	 * @param name
-	 * @param firstNameLength
-	 * @param currency
-	 * @param billCycleDay
-	 * @param paymentProviderName
-	 * @param timeZone
-	 * @param locale
-	 * @param address1
-	 * @param address2
-	 * @param companyName
-	 * @param city
-	 * @param stateOrProvince
-	 * @param country
-	 * @param postalCode
-	 * @param phone
-	 * @param createdDate
-	 * @param updatedDate
-	 */
+     */
 	public DefaultAccount(final UUID id, final String externalKey, final String email,
                           final String name, final int firstNameLength,
                           final Currency currency, final int billCycleDay, final String paymentProviderName,
@@ -113,10 +78,8 @@ public class DefaultAccount extends ExtendedEntityBase implements Account {
                           final String address1, final String address2, final String companyName,
                           final String city, final String stateOrProvince, final String country,
                           final String postalCode, final String phone,
-                          final String createdBy, final DateTime createdDate,
-                          final String updatedBy, final DateTime updatedDate) {
-
-		super(id, createdBy, createdDate);
+                          final boolean isMigrated, final boolean isNotifiedForInvoices) {
+		super(id);
 		this.externalKey = externalKey;
 		this.email = email;
 		this.name = name;
@@ -134,8 +97,8 @@ public class DefaultAccount extends ExtendedEntityBase implements Account {
 		this.postalCode = postalCode;
 		this.country = country;
 		this.phone = phone;
-        this.updatedBy = updatedBy;
-        this.updatedDate = updatedDate;
+        this.isMigrated = isMigrated;
+        this.isNotifiedForInvoices = isNotifiedForInvoices;
 	}
 
     @Override
@@ -154,8 +117,8 @@ public class DefaultAccount extends ExtendedEntityBase implements Account {
     }
 
     @Override
-	public String getObjectName() {
-		return ObjectType;
+	public ObjectType getObjectType() {
+		return ObjectType.ACCOUNT;
 	}
 
 	@Override
@@ -239,13 +202,13 @@ public class DefaultAccount extends ExtendedEntityBase implements Account {
 	}
 
     @Override
-    public String getUpdatedBy() {
-        return updatedBy;
+    public boolean isMigrated() {
+        return this.isMigrated;
     }
 
     @Override
-    public DateTime getUpdatedDate() {
-        return updatedDate;
+    public boolean isNotifiedForInvoices() {
+        return isNotifiedForInvoices;
     }
 
     @Override
@@ -255,7 +218,7 @@ public class DefaultAccount extends ExtendedEntityBase implements Account {
 
     @Override
 	public MutableAccountData toMutableAccountData() {
-	    return new MutableAccountData(this);
+	    return new DefaultMutableAccountData(this);
 	}
     
 	@Override
@@ -277,8 +240,13 @@ public class DefaultAccount extends ExtendedEntityBase implements Account {
 				", stateOrProvince=" + stateOrProvince +
 				", postalCode=" + postalCode +
 				", country=" + country +
-				", tags=" + tags +
-                ", fields=" + fields +
-                "]";
+				", tags=" + tagStore +
+				", fields=" + fields +
+				"]";
+	}
+
+	@Override
+	public BlockingState getBlockingState() {
+	    throw new UnsupportedOperationException();
 	}
 }
\ No newline at end of file
diff --git a/account/src/main/java/com/ning/billing/account/api/DefaultAccountEmail.java b/account/src/main/java/com/ning/billing/account/api/DefaultAccountEmail.java
new file mode 100644
index 0000000..d511b26
--- /dev/null
+++ b/account/src/main/java/com/ning/billing/account/api/DefaultAccountEmail.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2010-2011 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.account.api;
+
+import com.ning.billing.util.entity.UpdatableEntityBase;
+
+import java.util.UUID;
+
+public class DefaultAccountEmail extends UpdatableEntityBase implements AccountEmail {
+    private final UUID accountId;
+    private final String email;
+
+    public DefaultAccountEmail(UUID accountId, String email) {
+        super();
+        this.accountId = accountId;
+        this.email = email;
+    }
+
+    public DefaultAccountEmail(AccountEmail source, String newEmail) {
+        this(source.getId(), source.getAccountId(), newEmail);
+    }
+
+    public DefaultAccountEmail(UUID id, UUID accountId, String email) {
+        super(id);
+        this.accountId = accountId;
+        this.email = email;
+    }
+
+    @Override
+    public UUID getAccountId() {
+        return accountId;
+    }
+
+    @Override
+    public String getEmail() {
+        return email;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        DefaultAccountEmail that = (DefaultAccountEmail) o;
+
+        if (!id.equals(that.id)) return false;
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        return id.hashCode();
+    }
+}
diff --git a/account/src/main/java/com/ning/billing/account/api/DefaultAccountService.java b/account/src/main/java/com/ning/billing/account/api/DefaultAccountService.java
index ca2e213..af3d2a7 100644
--- a/account/src/main/java/com/ning/billing/account/api/DefaultAccountService.java
+++ b/account/src/main/java/com/ning/billing/account/api/DefaultAccountService.java
@@ -16,32 +16,17 @@
 
 package com.ning.billing.account.api;
 
-import com.google.inject.Inject;
-import com.ning.billing.lifecycle.LifecycleHandlerType;
-import com.ning.billing.lifecycle.LifecycleHandlerType.LifecycleLevel;
 
 public class DefaultAccountService implements AccountService {
 
     private static final String ACCOUNT_SERVICE_NAME = "account-service";
 
-    private final AccountUserApi accountApi;
-
-    @Inject
-    public DefaultAccountService(AccountUserApi api) {
-        this.accountApi = api;
-    }
-
-    @Override
+   @Override
     public String getName() {
         return ACCOUNT_SERVICE_NAME;
     }
 
-    @Override
-    public AccountUserApi getAccountUserApi() {
-        return accountApi;
-    }
-
-    @LifecycleHandlerType(LifecycleLevel.INIT_SERVICE)
-    public void initialize() {
-    }
+//    @LifecycleHandlerType(LifecycleLevel.INIT_SERVICE)
+//    public void initialize() {
+//    }
 }
diff --git a/account/src/main/java/com/ning/billing/account/api/DefaultChangedField.java b/account/src/main/java/com/ning/billing/account/api/DefaultChangedField.java
index c97a894..c979fc6 100644
--- a/account/src/main/java/com/ning/billing/account/api/DefaultChangedField.java
+++ b/account/src/main/java/com/ning/billing/account/api/DefaultChangedField.java
@@ -16,22 +16,36 @@
 
 package com.ning.billing.account.api;
 
-import com.ning.billing.util.clock.DefaultClock;
+
+import org.codehaus.jackson.annotate.JsonCreator;
+import org.codehaus.jackson.annotate.JsonProperty;
 import org.joda.time.DateTime;
 
 public class DefaultChangedField implements ChangedField {
+    
     private final String fieldName;
     private final String oldValue;
     private final String newValue;
     private final DateTime changeDate;
 
-    public DefaultChangedField(String fieldName, String oldValue, String newValue) {
-        this.changeDate = new DefaultClock().getUTCNow();
+    @JsonCreator
+    public DefaultChangedField(@JsonProperty("fieldName") String fieldName,
+            @JsonProperty("oldValue") String oldValue,
+            @JsonProperty("newValue") String newValue,
+            @JsonProperty("changeDate") DateTime changeDate) {
+        this.changeDate = changeDate;
         this.fieldName = fieldName;
         this.oldValue = oldValue;
         this.newValue = newValue;
     }
 
+    public DefaultChangedField(String fieldName,
+            String oldValue,
+            String newValue) {
+        this(fieldName, oldValue, newValue, new DateTime());
+    }
+
+    
     @Override
     public String getFieldName() {
         return fieldName;
@@ -51,4 +65,52 @@ public class DefaultChangedField implements ChangedField {
     public DateTime getChangeDate() {
         return changeDate;
     }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result
+                + ((changeDate == null) ? 0 : changeDate.hashCode());
+        result = prime * result
+                + ((fieldName == null) ? 0 : fieldName.hashCode());
+        result = prime * result
+                + ((newValue == null) ? 0 : newValue.hashCode());
+        result = prime * result
+                + ((oldValue == null) ? 0 : oldValue.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        DefaultChangedField other = (DefaultChangedField) obj;
+        if (changeDate == null) {
+            if (other.changeDate != null)
+                return false;
+        } else if (changeDate.compareTo(other.changeDate) != 0)
+            return false;
+        if (fieldName == null) {
+            if (other.fieldName != null)
+                return false;
+        } else if (!fieldName.equals(other.fieldName))
+            return false;
+        if (newValue == null) {
+            if (other.newValue != null)
+                return false;
+        } else if (!newValue.equals(other.newValue))
+            return false;
+        if (oldValue == null) {
+            if (other.oldValue != null)
+                return false;
+        } else if (!oldValue.equals(other.oldValue))
+            return false;
+        return true;
+    }
+    
 }
diff --git a/account/src/main/java/com/ning/billing/account/api/DefaultMutableAccountData.java b/account/src/main/java/com/ning/billing/account/api/DefaultMutableAccountData.java
new file mode 100644
index 0000000..0f72d41
--- /dev/null
+++ b/account/src/main/java/com/ning/billing/account/api/DefaultMutableAccountData.java
@@ -0,0 +1,361 @@
+/*
+ * Copyright 2010-2011 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.account.api;
+
+import org.joda.time.DateTimeZone;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.util.tag.TagStore;
+
+public class DefaultMutableAccountData implements MutableAccountData {
+    private String externalKey;
+    private String email;
+    private String name;
+    private int firstNameLength;
+    private Currency currency;
+    private int billCycleDay;
+    private String paymentProviderName;
+    private DateTimeZone timeZone;
+    private String locale;
+    private String address1;
+    private String address2;
+    private String companyName;
+    private String city;
+    private String stateOrProvince;
+    private String country;
+    private String postalCode;
+    private String phone;
+    private boolean isMigrated;
+    private boolean isNotifiedForInvoices;
+    
+    public DefaultMutableAccountData(String externalKey, String email, String name,
+            int firstNameLength, Currency currency, int billCycleDay,
+            String paymentProviderName, TagStore tags, DateTimeZone timeZone,
+            String locale, String address1, String address2,
+            String companyName, String city, String stateOrProvince,
+            String country, String postalCode, String phone,
+            boolean isMigrated, boolean isNotifiedForInvoices) {
+        super();
+        this.externalKey = externalKey;
+        this.email = email;
+        this.name = name;
+        this.firstNameLength = firstNameLength;
+        this.currency = currency;
+        this.billCycleDay = billCycleDay;
+        this.paymentProviderName = paymentProviderName;
+        this.timeZone = timeZone;
+        this.locale = locale;
+        this.address1 = address1;
+        this.address2 = address2;
+        this.companyName = companyName;
+        this.city = city;
+        this.stateOrProvince = stateOrProvince;
+        this.country = country;
+        this.postalCode = postalCode;
+        this.phone = phone;
+        this.isMigrated = isMigrated;
+        this.isNotifiedForInvoices = isNotifiedForInvoices;
+    }
+    
+    public DefaultMutableAccountData(AccountData accountData) {
+        super();
+        this.externalKey = accountData.getExternalKey();
+        this.email = accountData.getEmail();
+        this.name = accountData.getName();
+        this.firstNameLength = accountData.getFirstNameLength();
+        this.currency = accountData.getCurrency();
+        this.billCycleDay = accountData.getBillCycleDay();
+        this.paymentProviderName = accountData.getPaymentProviderName();
+        this.timeZone = accountData.getTimeZone();
+        this.locale = accountData.getLocale();
+        this.address1 = accountData.getAddress1();
+        this.address2 = accountData.getAddress2();
+        this.companyName = accountData.getCompanyName();
+        this.city = accountData.getCity();
+        this.stateOrProvince = accountData.getStateOrProvince();
+        this.country = accountData.getCountry();
+        this.postalCode = accountData.getPostalCode();
+        this.phone = accountData.getPhone();
+        this.isMigrated = accountData.isMigrated();
+        this.isNotifiedForInvoices = accountData.isNotifiedForInvoices();
+    }
+
+    /* (non-Javadoc)
+     * @see com.ning.billing.account.api.MutableAccountData#getExternalKey()
+     */
+    @Override
+    public String getExternalKey() {
+        return externalKey;
+    }
+    /* (non-Javadoc)
+     * @see com.ning.billing.account.api.MutableAccountData#getEmail()
+     */
+    @Override
+    public String getEmail() {
+        return email;
+    }
+    /* (non-Javadoc)
+     * @see com.ning.billing.account.api.MutableAccountData#getName()
+     */
+    @Override
+    public String getName() {
+        return name;
+    }
+    /* (non-Javadoc)
+     * @see com.ning.billing.account.api.MutableAccountData#getFirstNameLength()
+     */
+    @Override
+    public int getFirstNameLength() {
+        return firstNameLength;
+    }
+    /* (non-Javadoc)
+     * @see com.ning.billing.account.api.MutableAccountData#getCurrency()
+     */
+    @Override
+    public Currency getCurrency() {
+        return currency;
+    }
+    /* (non-Javadoc)
+     * @see com.ning.billing.account.api.MutableAccountData#getBillCycleDay()
+     */
+    @Override
+    public int getBillCycleDay() {
+        return billCycleDay;
+    }
+    /* (non-Javadoc)
+     * @see com.ning.billing.account.api.MutableAccountData#getPaymentProviderName()
+     */
+    @Override
+    public String getPaymentProviderName() {
+        return paymentProviderName;
+    }
+    /* (non-Javadoc)
+     * @see com.ning.billing.account.api.MutableAccountData#getTimeZone()
+     */
+    @Override
+    public DateTimeZone getTimeZone() {
+        return timeZone;
+    }
+    /* (non-Javadoc)
+     * @see com.ning.billing.account.api.MutableAccountData#getLocale()
+     */
+    @Override
+    public String getLocale() {
+        return locale;
+    }
+    /* (non-Javadoc)
+     * @see com.ning.billing.account.api.MutableAccountData#getAddress1()
+     */
+    @Override
+    public String getAddress1() {
+        return address1;
+    }
+    /* (non-Javadoc)
+     * @see com.ning.billing.account.api.MutableAccountData#getAddress2()
+     */
+    @Override
+    public String getAddress2() {
+        return address2;
+    }
+    /* (non-Javadoc)
+     * @see com.ning.billing.account.api.MutableAccountData#getCompanyName()
+     */
+    @Override
+    public String getCompanyName() {
+        return companyName;
+    }
+    /* (non-Javadoc)
+     * @see com.ning.billing.account.api.MutableAccountData#getCity()
+     */
+    @Override
+    public String getCity() {
+        return city;
+    }
+    /* (non-Javadoc)
+     * @see com.ning.billing.account.api.MutableAccountData#getStateOrProvince()
+     */
+    @Override
+    public String getStateOrProvince() {
+        return stateOrProvince;
+    }
+    /* (non-Javadoc)
+     * @see com.ning.billing.account.api.MutableAccountData#getCountry()
+     */
+    @Override
+    public String getCountry() {
+        return country;
+    }
+    /* (non-Javadoc)
+     * @see com.ning.billing.account.api.MutableAccountData#getPostalCode()
+     */
+    @Override
+    public String getPostalCode() {
+        return postalCode;
+    }
+    /* (non-Javadoc)
+     * @see com.ning.billing.account.api.MutableAccountData#getPhone()
+     */
+    @Override
+    public String getPhone() {
+        return phone;
+    }
+    /* (non-Javadoc)
+     * @see com.ning.billing.account.api.MutableAccountData#isMigrated()
+     */
+    @Override
+    public boolean isMigrated() {
+        return isMigrated;
+    }
+    /* (non-Javadoc)
+     * @see com.ning.billing.account.api.MutableAccountData#getSendInvoiceEmails()
+     */
+    @Override
+    public boolean isNotifiedForInvoices() {
+        return isNotifiedForInvoices;
+    }
+    /* (non-Javadoc)
+     * @see com.ning.billing.account.api.MutableAccountData#setExternalKey(java.lang.String)
+     */
+    @Override
+    public void setExternalKey(String externalKey) {
+        this.externalKey = externalKey;
+    }
+    /* (non-Javadoc)
+     * @see com.ning.billing.account.api.MutableAccountData#setEmail(java.lang.String)
+     */
+    @Override
+    public void setEmail(String email) {
+        this.email = email;
+    }
+    /* (non-Javadoc)
+     * @see com.ning.billing.account.api.MutableAccountData#setName(java.lang.String)
+     */
+    @Override
+    public void setName(String name) {
+        this.name = name;
+    }
+    /* (non-Javadoc)
+     * @see com.ning.billing.account.api.MutableAccountData#setFirstNameLength(int)
+     */
+    @Override
+    public void setFirstNameLength(int firstNameLength) {
+        this.firstNameLength = firstNameLength;
+    }
+    /* (non-Javadoc)
+     * @see com.ning.billing.account.api.MutableAccountData#setCurrency(com.ning.billing.catalog.api.Currency)
+     */
+    @Override
+    public void setCurrency(Currency currency) {
+        this.currency = currency;
+    }
+    /* (non-Javadoc)
+     * @see com.ning.billing.account.api.MutableAccountData#setBillCycleDay(int)
+     */
+    @Override
+    public void setBillCycleDay(int billCycleDay) {
+        this.billCycleDay = billCycleDay;
+    }
+    /* (non-Javadoc)
+     * @see com.ning.billing.account.api.MutableAccountData#setPaymentProviderName(java.lang.String)
+     */
+    @Override
+    public void setPaymentProviderName(String paymentProviderName) {
+        this.paymentProviderName = paymentProviderName;
+    }
+    /* (non-Javadoc)
+     * @see com.ning.billing.account.api.MutableAccountData#setTimeZone(org.joda.time.DateTimeZone)
+     */
+    @Override
+    public void setTimeZone(DateTimeZone timeZone) {
+        this.timeZone = timeZone;
+    }
+    /* (non-Javadoc)
+     * @see com.ning.billing.account.api.MutableAccountData#setLocale(java.lang.String)
+     */
+    @Override
+    public void setLocale(String locale) {
+        this.locale = locale;
+    }
+    /* (non-Javadoc)
+     * @see com.ning.billing.account.api.MutableAccountData#setAddress1(java.lang.String)
+     */
+    @Override
+    public void setAddress1(String address1) {
+        this.address1 = address1;
+    }
+    /* (non-Javadoc)
+     * @see com.ning.billing.account.api.MutableAccountData#setAddress2(java.lang.String)
+     */
+    @Override
+    public void setAddress2(String address2) {
+        this.address2 = address2;
+    }
+    /* (non-Javadoc)
+     * @see com.ning.billing.account.api.MutableAccountData#setCompanyName(java.lang.String)
+     */
+    @Override
+    public void setCompanyName(String companyName) {
+        this.companyName = companyName;
+    }
+    /* (non-Javadoc)
+     * @see com.ning.billing.account.api.MutableAccountData#setCity(java.lang.String)
+     */
+    @Override
+    public void setCity(String city) {
+        this.city = city;
+    }
+    /* (non-Javadoc)
+     * @see com.ning.billing.account.api.MutableAccountData#setStateOrProvince(java.lang.String)
+     */
+    @Override
+    public void setStateOrProvince(String stateOrProvince) {
+        this.stateOrProvince = stateOrProvince;
+    }
+    /* (non-Javadoc)
+     * @see com.ning.billing.account.api.MutableAccountData#setCountry(java.lang.String)
+     */
+    @Override
+    public void setCountry(String country) {
+        this.country = country;
+    }
+    /* (non-Javadoc)
+     * @see com.ning.billing.account.api.MutableAccountData#setPostalCode(java.lang.String)
+     */
+    @Override
+    public void setPostalCode(String postalCode) {
+        this.postalCode = postalCode;
+    }
+    /* (non-Javadoc)
+     * @see com.ning.billing.account.api.MutableAccountData#setPhone(java.lang.String)
+     */
+    @Override
+    public void setPhone(String phone) {
+        this.phone = phone;
+    }
+
+    @Override
+    public void setIsMigrated(boolean isMigrated) {
+        this.isMigrated = isMigrated;
+    }
+
+    @Override
+    public void setIsNotifiedForInvoices(boolean isNotifiedForInvoices) {
+        this.isNotifiedForInvoices = isNotifiedForInvoices;
+    }
+
+
+}
diff --git a/account/src/main/java/com/ning/billing/account/api/user/DefaultAccountCreationEvent.java b/account/src/main/java/com/ning/billing/account/api/user/DefaultAccountCreationEvent.java
index 91d9b82..5dabd40 100644
--- a/account/src/main/java/com/ning/billing/account/api/user/DefaultAccountCreationEvent.java
+++ b/account/src/main/java/com/ning/billing/account/api/user/DefaultAccountCreationEvent.java
@@ -17,21 +17,50 @@
 package com.ning.billing.account.api.user;
 
 import com.ning.billing.account.api.Account;
-import com.ning.billing.account.api.AccountCreationNotification;
+import com.ning.billing.account.api.AccountCreationEvent;
 import com.ning.billing.account.api.AccountData;
+import com.ning.billing.account.api.DefaultAccount;
+import com.ning.billing.catalog.api.Currency;
 
 import java.util.UUID;
 
-public class DefaultAccountCreationEvent implements AccountCreationNotification {
+import org.codehaus.jackson.annotate.JsonCreator;
+import org.codehaus.jackson.annotate.JsonIgnore;
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.joda.time.DateTimeZone;
+
+public class DefaultAccountCreationEvent implements AccountCreationEvent {
+	
+	private final UUID userToken;	
     private final UUID id;
     private final AccountData data;
 
-    public DefaultAccountCreationEvent(Account data) {
-        this.id = data.getId();
+    @JsonCreator
+    public DefaultAccountCreationEvent(@JsonProperty("data") DefaultAccountData data,
+            @JsonProperty("userToken") UUID userToken,
+            @JsonProperty("id") UUID id) {
+        this.id = id;
+        this.userToken = userToken;
         this.data = data;
     }
+    
+    public DefaultAccountCreationEvent(Account data, UUID userToken) {
+        this.id = data.getId();
+        this.data = new DefaultAccountData(data);
+        this.userToken = userToken;
+    }
+
+    @JsonIgnore
+	@Override
+	public BusEventType getBusEventType() {
+		return BusEventType.ACCOUNT_CREATE;
+	}
 
     @Override
+    public UUID getUserToken() {
+    	return userToken;
+    }
+    @Override
     public UUID getId() {
         return id;
     }
@@ -40,4 +69,375 @@ public class DefaultAccountCreationEvent implements AccountCreationNotification 
     public AccountData getData() {
         return data;
     }
+    
+    
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((data == null) ? 0 : data.hashCode());
+        result = prime * result + ((id == null) ? 0 : id.hashCode());
+        result = prime * result
+                + ((userToken == null) ? 0 : userToken.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        DefaultAccountCreationEvent other = (DefaultAccountCreationEvent) obj;
+        if (data == null) {
+            if (other.data != null)
+                return false;
+        } else if (!data.equals(other.data))
+            return false;
+        if (id == null) {
+            if (other.id != null)
+                return false;
+        } else if (!id.equals(other.id))
+            return false;
+        if (userToken == null) {
+            if (other.userToken != null)
+                return false;
+        } else if (!userToken.equals(other.userToken))
+            return false;
+        return true;
+    }
+
+
+    public static class DefaultAccountData implements AccountData {
+
+        private final String externalKey;
+        private final String name;
+        private final Integer firstNameLength;
+        private final String email;
+        private final Integer billCycleDay;
+        private final String currency;
+        private final String paymentProviderName;
+        private final String timeZone;
+        private final String locale;
+        private final String address1;
+        private final String address2;
+        private final String companyName;
+        private final String city;
+        private final String stateOrProvince;
+        private final String postalCode;
+        private final String country;
+        private final String phone;
+        private final boolean isMigrated;
+        private final boolean isNotifiedForInvoices;
+        
+        
+        public DefaultAccountData(Account d) {
+            this(d.getExternalKey() != null ?  d.getExternalKey().toString() : null,
+                    d.getName(),
+                    d.getFirstNameLength(),
+                    d.getEmail(),
+                    d.getBillCycleDay(),
+                    d.getCurrency() != null ?  d.getCurrency().name() : null,
+                    d.getPaymentProviderName(), 
+                    d.getTimeZone() != null ?  d.getTimeZone().getID() : null,
+                    d.getLocale(),
+                    d.getAddress1(),
+                    d.getAddress2(),
+                    d.getCompanyName(),
+                    d.getCity(),
+                    d.getStateOrProvince(),
+                    d.getPostalCode(),
+                    d.getCountry(),
+                    d.getPhone(),
+                    d.isMigrated(),
+                    d.isNotifiedForInvoices());
+        }
+        
+        @JsonCreator
+        public DefaultAccountData(@JsonProperty("externalKey") String externalKey,
+                @JsonProperty("name") String name,
+                @JsonProperty("firstNameLength") Integer firstNameLength,
+                @JsonProperty("email") String email,
+                @JsonProperty("billCycleDay") Integer billCycleDay,
+                @JsonProperty("currency") String currency,
+                @JsonProperty("paymentProviderName") String paymentProviderName,
+                @JsonProperty("timeZone") String timeZone,
+                @JsonProperty("locale") String locale,
+                @JsonProperty("address1") String address1,
+                @JsonProperty("address2") String address2,
+                @JsonProperty("companyName") String companyName,
+                @JsonProperty("city") String city,
+                @JsonProperty("stateOrProvince") String stateOrProvince,
+                @JsonProperty("postalCode") String postalCode,
+                @JsonProperty("country") String country,
+                @JsonProperty("phone") String phone,
+                @JsonProperty("isMigrated") boolean isMigrated,
+                @JsonProperty("isNotifiedForInvoices") boolean isNotifiedForInvoices) {
+            super();
+            this.externalKey = externalKey;
+            this.name = name;
+            this.firstNameLength = firstNameLength;
+            this.email = email;
+            this.billCycleDay = billCycleDay;
+            this.currency = currency;
+            this.paymentProviderName = paymentProviderName;
+            this.timeZone = timeZone;
+            this.locale = locale;
+            this.address1 = address1;
+            this.address2 = address2;
+            this.companyName = companyName;
+            this.city = city;
+            this.stateOrProvince = stateOrProvince;
+            this.postalCode = postalCode;
+            this.country = country;
+            this.phone = phone;
+            this.isMigrated = isMigrated;
+            this.isNotifiedForInvoices = isNotifiedForInvoices;
+        }
+
+        @Override
+        public String getExternalKey() {
+            return externalKey;
+        }
+
+        @Override
+        public String getName() {
+            return name;
+        }
+
+        @Override
+        public int getFirstNameLength() {
+            return firstNameLength;
+        }
+
+        @Override
+        public String getEmail() {
+            return email;
+        }
+
+        @Override
+        public int getBillCycleDay() {
+            return billCycleDay;
+        }
+
+        @Override
+        public Currency getCurrency() {
+            return Currency.valueOf(currency);
+        }
+
+        @Override
+        public String getPaymentProviderName() {
+            return paymentProviderName;
+        }
+
+        @JsonIgnore
+        @Override
+        public DateTimeZone getTimeZone() {
+            return DateTimeZone.forID(timeZone);
+        }
+        
+        @JsonProperty("timeZone")
+        public String getTimeZoneString() {
+            return timeZone;
+        }
+
+        @Override
+        public String getLocale() {
+            return locale;
+        }
+
+        @Override
+        public String getAddress1() {
+            return address1;
+        }
+
+        @Override
+        public String getAddress2() {
+            return address2;
+        }
+
+        @Override
+        public String getCompanyName() {
+            return companyName;
+        }
+
+        @Override
+        public String getCity() {
+            return city;
+        }
+
+        @Override
+        public String getStateOrProvince() {
+            return stateOrProvince;
+        }
+
+        @Override
+        public String getPostalCode() {
+            return postalCode;
+        }
+
+        @Override
+        public String getCountry() {
+            return country;
+        }
+
+        @Override
+        public String getPhone() {
+            return phone;
+        }
+
+        @Override
+        @JsonIgnore
+        public boolean isMigrated() {
+            return isMigrated;
+        }
+
+        @Override
+        @JsonIgnore
+        public boolean isNotifiedForInvoices() {
+            return isNotifiedForInvoices;
+        }
+
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = 1;
+            result = prime * result
+                    + ((address1 == null) ? 0 : address1.hashCode());
+            result = prime * result
+                    + ((address2 == null) ? 0 : address2.hashCode());
+            result = prime * result
+                    + ((billCycleDay == null) ? 0 : billCycleDay.hashCode());
+            result = prime * result + ((city == null) ? 0 : city.hashCode());
+            result = prime * result
+                    + ((companyName == null) ? 0 : companyName.hashCode());
+            result = prime * result
+                    + ((country == null) ? 0 : country.hashCode());
+            result = prime * result
+                    + ((currency == null) ? 0 : currency.hashCode());
+            result = prime * result + ((email == null) ? 0 : email.hashCode());
+            result = prime * result
+                    + ((externalKey == null) ? 0 : externalKey.hashCode());
+            result = prime
+                    * result
+                    + ((firstNameLength == null) ? 0 : firstNameLength
+                            .hashCode());
+            result = prime * result
+                    + ((locale == null) ? 0 : locale.hashCode());
+            result = prime * result + ((name == null) ? 0 : name.hashCode());
+            result = prime
+                    * result
+                    + ((paymentProviderName == null) ? 0 : paymentProviderName
+                            .hashCode());
+            result = prime * result + ((phone == null) ? 0 : phone.hashCode());
+            result = prime * result
+                    + ((postalCode == null) ? 0 : postalCode.hashCode());
+            result = prime
+                    * result
+                    + ((stateOrProvince == null) ? 0 : stateOrProvince
+                            .hashCode());
+            result = prime * result
+                    + ((timeZone == null) ? 0 : timeZone.hashCode());
+            return result;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj)
+                return true;
+            if (obj == null)
+                return false;
+            if (getClass() != obj.getClass())
+                return false;
+            DefaultAccountData other = (DefaultAccountData) obj;
+            if (address1 == null) {
+                if (other.address1 != null)
+                    return false;
+            } else if (!address1.equals(other.address1))
+                return false;
+            if (address2 == null) {
+                if (other.address2 != null)
+                    return false;
+            } else if (!address2.equals(other.address2))
+                return false;
+            if (billCycleDay == null) {
+                if (other.billCycleDay != null)
+                    return false;
+            } else if (!billCycleDay.equals(other.billCycleDay))
+                return false;
+            if (city == null) {
+                if (other.city != null)
+                    return false;
+            } else if (!city.equals(other.city))
+                return false;
+            if (companyName == null) {
+                if (other.companyName != null)
+                    return false;
+            } else if (!companyName.equals(other.companyName))
+                return false;
+            if (country == null) {
+                if (other.country != null)
+                    return false;
+            } else if (!country.equals(other.country))
+                return false;
+            if (currency == null) {
+                if (other.currency != null)
+                    return false;
+            } else if (!currency.equals(other.currency))
+                return false;
+            if (email == null) {
+                if (other.email != null)
+                    return false;
+            } else if (!email.equals(other.email))
+                return false;
+            if (externalKey == null) {
+                if (other.externalKey != null)
+                    return false;
+            } else if (!externalKey.equals(other.externalKey))
+                return false;
+            if (firstNameLength == null) {
+                if (other.firstNameLength != null)
+                    return false;
+            } else if (!firstNameLength.equals(other.firstNameLength))
+                return false;
+            if (locale == null) {
+                if (other.locale != null)
+                    return false;
+            } else if (!locale.equals(other.locale))
+                return false;
+            if (name == null) {
+                if (other.name != null)
+                    return false;
+            } else if (!name.equals(other.name))
+                return false;
+            if (paymentProviderName == null) {
+                if (other.paymentProviderName != null)
+                    return false;
+            } else if (!paymentProviderName.equals(other.paymentProviderName))
+                return false;
+            if (phone == null) {
+                if (other.phone != null)
+                    return false;
+            } else if (!phone.equals(other.phone))
+                return false;
+            if (postalCode == null) {
+                if (other.postalCode != null)
+                    return false;
+            } else if (!postalCode.equals(other.postalCode))
+                return false;
+            if (stateOrProvince == null) {
+                if (other.stateOrProvince != null)
+                    return false;
+            } else if (!stateOrProvince.equals(other.stateOrProvince))
+                return false;
+            if (timeZone == null) {
+                if (other.timeZone != null)
+                    return false;
+            } else if (!timeZone.equals(other.timeZone))
+                return false;
+            return true;
+        }
+    }
 }
diff --git a/account/src/main/java/com/ning/billing/account/api/user/DefaultAccountUserApi.java b/account/src/main/java/com/ning/billing/account/api/user/DefaultAccountUserApi.java
index f1c58fb..c28e25b 100644
--- a/account/src/main/java/com/ning/billing/account/api/user/DefaultAccountUserApi.java
+++ b/account/src/main/java/com/ning/billing/account/api/user/DefaultAccountUserApi.java
@@ -19,41 +19,46 @@ package com.ning.billing.account.api.user;
 import java.util.List;
 import java.util.UUID;
 
+import com.ning.billing.account.dao.AccountEmailDao;
+import org.joda.time.DateTime;
+
 import com.google.inject.Inject;
 import com.ning.billing.ErrorCode;
 import com.ning.billing.account.api.Account;
 import com.ning.billing.account.api.AccountApiException;
 import com.ning.billing.account.api.AccountData;
+import com.ning.billing.account.api.AccountEmail;
+import com.ning.billing.account.api.AccountUserApi;
 import com.ning.billing.account.api.DefaultAccount;
 import com.ning.billing.account.api.MigrationAccountData;
-import com.ning.billing.account.api.MutableAccountData;
 import com.ning.billing.account.dao.AccountDao;
 import com.ning.billing.util.callcontext.CallContext;
 import com.ning.billing.util.callcontext.CallContextFactory;
 import com.ning.billing.util.customfield.CustomField;
 import com.ning.billing.util.entity.EntityPersistenceException;
-import com.ning.billing.util.tag.Tag;
-import org.joda.time.DateTime;
+import com.ning.billing.util.tag.TagDefinition;
 
-public class DefaultAccountUserApi implements com.ning.billing.account.api.AccountUserApi {
+public class DefaultAccountUserApi implements AccountUserApi {
     private final CallContextFactory factory;
-    private final AccountDao dao;
+    private final AccountDao accountDao;
+    private final AccountEmailDao accountEmailDao;
 
     @Inject
-    public DefaultAccountUserApi(final CallContextFactory factory, final AccountDao dao) {
+    public DefaultAccountUserApi(final CallContextFactory factory, final AccountDao accountDao, final AccountEmailDao accountEmailDao) {
         this.factory = factory;
-        this.dao = dao;
+        this.accountDao = accountDao;
+        this.accountEmailDao = accountEmailDao;
     }
 
     @Override
     public Account createAccount(final AccountData data, final List<CustomField> fields,
-                                 final List<Tag> tags, final CallContext context) throws AccountApiException {
+                                 final List<TagDefinition> tagDefinitions, final CallContext context) throws AccountApiException {
         Account account = new DefaultAccount(data);
         account.setFields(fields);
-        account.addTags(tags);
+        account.addTagsFromDefinitions(tagDefinitions);
 
         try {
-            dao.create(account, context);
+            accountDao.create(account, context);
         } catch (EntityPersistenceException e) {
             throw new AccountApiException(e, ErrorCode.ACCOUNT_CREATION_FAILED);
         }
@@ -62,29 +67,37 @@ public class DefaultAccountUserApi implements com.ning.billing.account.api.Accou
     }
 
     @Override
-    public Account getAccountByKey(final String key) {
-        return dao.getAccountByKey(key);
+    public Account getAccountByKey(final String key) throws AccountApiException {
+        Account account = accountDao.getAccountByKey(key);
+        if(account == null) {
+            throw new AccountApiException(ErrorCode.ACCOUNT_DOES_NOT_EXIST_FOR_KEY, key);
+        }
+        return account;
     }
 
     @Override
-    public Account getAccountById(final UUID id) {
-        return dao.getById(id.toString());
+    public Account getAccountById(final UUID id) throws AccountApiException {
+        Account account = accountDao.getById(id);
+        if(account == null) {
+            throw new AccountApiException(ErrorCode.ACCOUNT_DOES_NOT_EXIST_FOR_ID, id);
+        }
+        return account;
     }
 
     @Override
     public List<Account> getAccounts() {
-        return dao.get();
+        return accountDao.get();
     }
 
     @Override
     public UUID getIdFromKey(final String externalKey) throws AccountApiException {
-        return dao.getIdFromKey(externalKey);
+        return accountDao.getIdFromKey(externalKey);
     }
 
     @Override
     public void updateAccount(final Account account, final CallContext context) throws AccountApiException {
         try {
-            dao.update(account, context);
+            accountDao.update(account, context);
         } catch (EntityPersistenceException e) {
             throw new AccountApiException(e, ErrorCode.ACCOUNT_UPDATE_FAILED);
         }
@@ -96,36 +109,34 @@ public class DefaultAccountUserApi implements com.ning.billing.account.api.Accou
         Account account = new DefaultAccount(accountId, accountData);
 
         try {
-            dao.update(account, context);
+            accountDao.update(account, context);
         } catch (EntityPersistenceException e) {
-            throw new AccountApiException(e, ErrorCode.ACCOUNT_UPDATE_FAILED);
+            throw new AccountApiException(e, e.getCode(), e.getMessage());
         }
-  
     }
 
     @Override
     public void updateAccount(final String externalKey, final AccountData accountData, final CallContext context) throws AccountApiException {
     	UUID accountId = getIdFromKey(externalKey);
-    	if(accountId == null) {
+    	if (accountId == null) {
     		throw new AccountApiException(ErrorCode.ACCOUNT_DOES_NOT_EXIST_FOR_KEY, externalKey);
     	}
-
     	updateAccount(accountId, accountData, context);
      }
 
 	@Override
 	public Account migrateAccount(final MigrationAccountData data, final List<CustomField> fields,
-                                  final List<Tag> tags, final CallContext context)
+                                  final List<TagDefinition> tagDefinitions, final CallContext context)
             throws AccountApiException {
         DateTime createdDate = data.getCreatedDate() == null ? context.getCreatedDate() : data.getCreatedDate();
         DateTime updatedDate = data.getUpdatedDate() == null ? context.getUpdatedDate() : data.getUpdatedDate();
         CallContext migrationContext = factory.toMigrationCallContext(context, createdDate, updatedDate);
 		Account account = new DefaultAccount(data);
         account.setFields(fields);
-        account.addTags(tags);
+        account.addTagsFromDefinitions(tagDefinitions);
 
         try {
-            dao.create(account, migrationContext);
+            accountDao.create(account, migrationContext);
         } catch (EntityPersistenceException e) {
             throw new AccountApiException(e, ErrorCode.ACCOUNT_CREATION_FAILED);
         }
@@ -133,5 +144,13 @@ public class DefaultAccountUserApi implements com.ning.billing.account.api.Accou
         return account;
 	}
 
+    @Override
+    public List<AccountEmail> getEmails(final UUID accountId) {
+        return accountEmailDao.getEmails(accountId);
+    }
 
+    @Override
+    public void saveEmails(final UUID accountId, final List<AccountEmail> newEmails, final CallContext context) {
+        accountEmailDao.saveEmails(accountId, newEmails, context);
+    }
 }
diff --git a/account/src/main/java/com/ning/billing/account/dao/AccountBinder.java b/account/src/main/java/com/ning/billing/account/dao/AccountBinder.java
index a4a00a8..57b61c2 100644
--- a/account/src/main/java/com/ning/billing/account/dao/AccountBinder.java
+++ b/account/src/main/java/com/ning/billing/account/dao/AccountBinder.java
@@ -60,6 +60,8 @@ public @interface AccountBinder {
                     q.bind("country", account.getCountry());
                     q.bind("postalCode", account.getPostalCode());
                     q.bind("phone", account.getPhone());
+                    q.bind("migrated", account.isMigrated());
+                    q.bind("isNotifiedForInvoices", account.isNotifiedForInvoices());
                 }
             };
         }
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 4be1058..67474ab 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
@@ -16,11 +16,13 @@
 
 package com.ning.billing.account.dao;
 
+import java.util.List;
 import java.util.UUID;
 import com.ning.billing.account.api.Account;
 import com.ning.billing.account.api.AccountApiException;
+import com.ning.billing.account.api.AccountEmail;
 import com.ning.billing.util.callcontext.CallContext;
-import com.ning.billing.util.entity.UpdatableEntityDao;
+import com.ning.billing.util.entity.dao.UpdatableEntityDao;
 
 public interface AccountDao extends UpdatableEntityDao<Account> {
     public Account getAccountByKey(String key);
diff --git a/account/src/main/java/com/ning/billing/account/dao/AccountEmailBinder.java b/account/src/main/java/com/ning/billing/account/dao/AccountEmailBinder.java
new file mode 100644
index 0000000..bd71364
--- /dev/null
+++ b/account/src/main/java/com/ning/billing/account/dao/AccountEmailBinder.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2010-2011 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.account.dao;
+
+import com.ning.billing.account.api.AccountEmail;
+import org.skife.jdbi.v2.SQLStatement;
+import org.skife.jdbi.v2.sqlobject.Binder;
+import org.skife.jdbi.v2.sqlobject.BinderFactory;
+import org.skife.jdbi.v2.sqlobject.BindingAnnotation;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@BindingAnnotation(AccountEmailBinder.AccountEmailBinderFactory.class)
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.PARAMETER})
+public @interface AccountEmailBinder {
+    public static class AccountEmailBinderFactory implements BinderFactory {
+        @Override
+        public Binder<AccountEmailBinder, AccountEmail> build(Annotation annotation) {
+            return new Binder<AccountEmailBinder, AccountEmail>() {
+                @Override
+                public void bind(@SuppressWarnings("rawtypes") SQLStatement q, AccountEmailBinder bind, AccountEmail accountEmail) {
+                    q.bind("id", accountEmail.getId().toString());
+                    q.bind("accountId", accountEmail.getAccountId().toString());
+                    q.bind("email", accountEmail.getEmail());
+                }
+            };
+        }
+    }
+}
diff --git a/account/src/main/java/com/ning/billing/account/dao/AccountEmailHistoryBinder.java b/account/src/main/java/com/ning/billing/account/dao/AccountEmailHistoryBinder.java
new file mode 100644
index 0000000..01344b3
--- /dev/null
+++ b/account/src/main/java/com/ning/billing/account/dao/AccountEmailHistoryBinder.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2010-2011 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.account.dao;
+
+import com.ning.billing.account.api.AccountEmail;
+import com.ning.billing.util.dao.EntityHistory;
+import org.skife.jdbi.v2.SQLStatement;
+import org.skife.jdbi.v2.sqlobject.Binder;
+import org.skife.jdbi.v2.sqlobject.BinderFactory;
+import org.skife.jdbi.v2.sqlobject.BindingAnnotation;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@BindingAnnotation(AccountEmailHistoryBinder.AccountEmailHistoryBinderFactory.class)
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.PARAMETER})
+public @interface AccountEmailHistoryBinder {
+    public static class AccountEmailHistoryBinderFactory implements BinderFactory {
+        @Override
+        public Binder<AccountEmailHistoryBinder, EntityHistory<AccountEmail>> build(Annotation annotation) {
+            return new Binder<AccountEmailHistoryBinder, EntityHistory<AccountEmail>>() {
+                @Override
+                public void bind(SQLStatement q, AccountEmailHistoryBinder bind, EntityHistory<AccountEmail> history) {
+                    q.bind("recordId", history.getValue());
+                    q.bind("changeType", history.getChangeType().toString());
+
+                    AccountEmail accountEmail = history.getEntity();
+                    q.bind("id", accountEmail.getId().toString());
+                    q.bind("accountId", accountEmail.getAccountId().toString());
+                    q.bind("email", accountEmail.getEmail());
+                }
+            };
+        }
+    }
+}
diff --git a/account/src/main/java/com/ning/billing/account/dao/AccountEmailMapper.java b/account/src/main/java/com/ning/billing/account/dao/AccountEmailMapper.java
new file mode 100644
index 0000000..9a46344
--- /dev/null
+++ b/account/src/main/java/com/ning/billing/account/dao/AccountEmailMapper.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2010-2011 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.account.dao;
+
+import com.ning.billing.account.api.AccountEmail;
+import com.ning.billing.account.api.DefaultAccountEmail;
+import com.ning.billing.util.dao.MapperBase;
+import org.joda.time.DateTime;
+import org.skife.jdbi.v2.StatementContext;
+import org.skife.jdbi.v2.tweak.ResultSetMapper;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.UUID;
+
+public class AccountEmailMapper extends MapperBase implements ResultSetMapper<AccountEmail> {
+    @Override
+    public AccountEmail map(int index, ResultSet result, StatementContext context) throws SQLException {
+        UUID id = UUID.fromString(result.getString("id"));
+        UUID accountId = UUID.fromString(result.getString("account_id"));
+        String email = result.getString("email");
+
+        return new DefaultAccountEmail(id, accountId, email);
+    }
+}
diff --git a/account/src/main/java/com/ning/billing/account/dao/AccountEmailSqlDao.java b/account/src/main/java/com/ning/billing/account/dao/AccountEmailSqlDao.java
new file mode 100644
index 0000000..15f405e
--- /dev/null
+++ b/account/src/main/java/com/ning/billing/account/dao/AccountEmailSqlDao.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2010-2011 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.account.dao;
+
+import com.ning.billing.account.api.AccountEmail;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.callcontext.CallContextBinder;
+import com.ning.billing.util.dao.EntityHistory;
+import com.ning.billing.util.dao.ObjectType;
+import com.ning.billing.util.dao.ObjectTypeBinder;
+import com.ning.billing.util.entity.collection.dao.UpdatableEntityCollectionSqlDao;
+import org.skife.jdbi.v2.sqlobject.Bind;
+import org.skife.jdbi.v2.sqlobject.SqlBatch;
+import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
+import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
+import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
+import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
+
+import java.util.List;
+
+@ExternalizedSqlViaStringTemplate3
+@RegisterMapper(AccountEmailMapper.class)
+public interface AccountEmailSqlDao extends UpdatableEntityCollectionSqlDao<AccountEmail>, Transactional<AccountEmailSqlDao>, Transmogrifier {
+    @Override
+    @SqlBatch(transactional=false)
+    public void insertFromTransaction(@Bind("objectId") final String objectId,
+                                      @ObjectTypeBinder final ObjectType objectType,
+                                      @AccountEmailBinder final List<AccountEmail> entities,
+                                      @CallContextBinder final CallContext context);
+
+    @Override
+    @SqlBatch(transactional=false)
+    public void updateFromTransaction(@Bind("objectId") final String objectId,
+                                      @ObjectTypeBinder final ObjectType objectType,
+                                      @AccountEmailBinder final List<AccountEmail> entities,
+                                      @CallContextBinder final CallContext context);
+
+    @Override
+    @SqlBatch(transactional=false)
+    public void deleteFromTransaction(@Bind("objectId") final String objectId,
+                                      @ObjectTypeBinder final ObjectType objectType,
+                                      @AccountEmailBinder final List<AccountEmail> entities,
+                                      @CallContextBinder final CallContext context);
+
+    @Override
+    @SqlBatch(transactional=false)
+    public void addHistoryFromTransaction(@Bind("objectId") final String objectId,
+                                               @ObjectTypeBinder final ObjectType objectType,
+                                               @AccountEmailHistoryBinder final List<EntityHistory<AccountEmail>> entities,
+                                               @CallContextBinder final CallContext context);
+//    @Override
+//    @SqlUpdate
+//    public void create(@AccountEmailBinder final AccountEmail accountEmail,
+//                       @CallContextBinder final CallContext context);
+
+//    @SqlBatch(transactional = false)
+//    public void create(@AccountEmailBinder final List<AccountEmail> accountEmailList,
+//                       @CallContextBinder final CallContext context);
+
+//    @Override
+//    @SqlUpdate
+//    public void update(@AccountEmailBinder final AccountEmail accountEmail,
+//                       @CallContextBinder final CallContext context);
+//
+//    @SqlBatch(transactional = false)
+//    public void update(@AccountEmailBinder final List<AccountEmail> accountEmailList,
+//                       @CallContextBinder final CallContext context);
+//
+//    @SqlUpdate
+//    public void delete(@AccountEmailBinder final AccountEmail accountEmail,
+//                       @CallContextBinder final CallContext context);
+//
+//    @SqlBatch(transactional = false)
+//    public void delete(@AccountEmailBinder final List<AccountEmail> accountEmailList,
+//                       @CallContextBinder final CallContext context);
+//
+//    @SqlBatch(transactional=false)
+//    public void insertAccountEmailHistoryFromTransaction(@Bind("historyRecordId") final List<String> historyRecordIdList,
+//                                                         @AccountEmailBinder final List<AccountEmail> accountEmail,
+//                                                         @ChangeTypeBinder final ChangeType changeType,
+//                                                         @CallContextBinder final CallContext context);
+//
+//    @SqlQuery
+//    public List<AccountEmail> getByAccountId(@Bind("accountId") final String accountId);
+}
diff --git a/account/src/main/java/com/ning/billing/account/dao/AccountHistoryBinder.java b/account/src/main/java/com/ning/billing/account/dao/AccountHistoryBinder.java
new file mode 100644
index 0000000..66cba44
--- /dev/null
+++ b/account/src/main/java/com/ning/billing/account/dao/AccountHistoryBinder.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2010-2011 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.account.dao;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.util.dao.EntityHistory;
+import org.joda.time.DateTimeZone;
+import org.skife.jdbi.v2.SQLStatement;
+import org.skife.jdbi.v2.sqlobject.Binder;
+import org.skife.jdbi.v2.sqlobject.BinderFactory;
+import org.skife.jdbi.v2.sqlobject.BindingAnnotation;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@BindingAnnotation(AccountHistoryBinder.AccountHistoryBinderFactory.class)
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.PARAMETER})
+public @interface AccountHistoryBinder {
+    public static class AccountHistoryBinderFactory implements BinderFactory {
+        @Override
+        public Binder<AccountHistoryBinder, EntityHistory<Account>> build(Annotation annotation) {
+            return new Binder<AccountHistoryBinder, EntityHistory<Account>>() {
+                @Override
+                public void bind(SQLStatement q, AccountHistoryBinder bind, EntityHistory<Account> history) {
+                    q.bind("recordId", history.getValue());
+                    q.bind("changeType", history.getChangeType().toString());
+
+                    Account account = history.getEntity();
+                    q.bind("id", account.getId().toString());
+                    q.bind("externalKey", account.getExternalKey());
+                    q.bind("email", account.getEmail());
+                    q.bind("name", account.getName());
+                    q.bind("firstNameLength", account.getFirstNameLength());
+                    Currency currency = account.getCurrency();
+                    q.bind("currency", (currency == null) ? null : currency.toString());
+                    q.bind("billingCycleDay", account.getBillCycleDay());
+                    q.bind("paymentProviderName", account.getPaymentProviderName());
+                    DateTimeZone timeZone = account.getTimeZone();
+                    q.bind("timeZone", (timeZone == null) ? null : timeZone.toString());
+                    q.bind("locale", account.getLocale());
+                    q.bind("address1", account.getAddress1());
+                    q.bind("address2", account.getAddress2());
+                    q.bind("companyName", account.getCompanyName());
+                    q.bind("city", account.getCity());
+                    q.bind("stateOrProvince", account.getStateOrProvince());
+                    q.bind("country", account.getCountry());
+                    q.bind("postalCode", account.getPostalCode());
+                    q.bind("phone", account.getPhone());
+                    q.bind("migrated", account.isMigrated());
+                    q.bind("isNotifiedForInvoices", account.isNotifiedForInvoices());
+                }
+            };
+        }
+    }
+}
diff --git a/account/src/main/java/com/ning/billing/account/dao/AccountMapper.java b/account/src/main/java/com/ning/billing/account/dao/AccountMapper.java
new file mode 100644
index 0000000..1a62c19
--- /dev/null
+++ b/account/src/main/java/com/ning/billing/account/dao/AccountMapper.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2010-2011 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.account.dao;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.DefaultAccount;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.util.dao.MapperBase;
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.skife.jdbi.v2.StatementContext;
+import org.skife.jdbi.v2.tweak.ResultSetMapper;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.UUID;
+
+public class AccountMapper extends MapperBase implements ResultSetMapper<Account> {
+    @Override
+    public Account map(int index, ResultSet result, StatementContext context) throws SQLException {
+        UUID id = UUID.fromString(result.getString("id"));
+        String externalKey = result.getString("external_key");
+        String email = result.getString("email");
+        String name = result.getString("name");
+        int firstNameLength = result.getInt("first_name_length");
+        int billingCycleDay = result.getInt("billing_cycle_day");
+
+        String currencyString = result.getString("currency");
+        Currency currency = (currencyString == null) ? null : Currency.valueOf(currencyString);
+
+        String paymentProviderName = result.getString("payment_provider_name");
+
+        String timeZoneId = result.getString("time_zone");
+        DateTimeZone timeZone = (timeZoneId == null) ? null : DateTimeZone.forID(timeZoneId);
+
+        String locale = result.getString("locale");
+
+        String address1 = result.getString("address1");
+        String address2 = result.getString("address2");
+        String companyName = result.getString("company_name");
+        String city = result.getString("city");
+        String stateOrProvince = result.getString("state_or_province");
+        String postalCode = result.getString("postal_code");
+        String country = result.getString("country");
+        String phone = result.getString("phone");
+
+        Boolean isMigrated = result.getBoolean("migrated");
+        Boolean isNotifiedForInvoices = result.getBoolean("is_notified_for_invoices");
+
+        return new DefaultAccount(id, externalKey, email, name,firstNameLength, currency,
+                billingCycleDay, paymentProviderName, timeZone, locale,
+                address1, address2, companyName, city, stateOrProvince, country, postalCode, phone,
+                isMigrated, isNotifiedForInvoices);
+    }
+}
\ No newline at end of file
diff --git a/account/src/main/java/com/ning/billing/account/dao/AccountSqlDao.java b/account/src/main/java/com/ning/billing/account/dao/AccountSqlDao.java
index 8c09af4..8ed05ee 100644
--- a/account/src/main/java/com/ning/billing/account/dao/AccountSqlDao.java
+++ b/account/src/main/java/com/ning/billing/account/dao/AccountSqlDao.java
@@ -16,17 +16,17 @@
 
 package com.ning.billing.account.dao;
 
-import java.sql.ResultSet;
-import java.sql.SQLException;
 import java.util.UUID;
 
+import com.ning.billing.account.api.Account;
+import com.ning.billing.util.ChangeType;
+import com.ning.billing.util.dao.AuditSqlDao;
 import com.ning.billing.util.callcontext.CallContext;
 import com.ning.billing.util.callcontext.CallContextBinder;
-import com.ning.billing.util.dao.MapperBase;
-import com.ning.billing.util.entity.UpdatableEntityDao;
-import org.joda.time.DateTime;
-import org.joda.time.DateTimeZone;
-import org.skife.jdbi.v2.StatementContext;
+import com.ning.billing.util.dao.ChangeTypeBinder;
+import com.ning.billing.util.dao.EntityHistory;
+import com.ning.billing.util.dao.UuidMapper;
+import com.ning.billing.util.entity.dao.UpdatableEntitySqlDao;
 import org.skife.jdbi.v2.sqlobject.Bind;
 import org.skife.jdbi.v2.sqlobject.SqlQuery;
 import org.skife.jdbi.v2.sqlobject.SqlUpdate;
@@ -34,16 +34,10 @@ import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
 import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
 import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
 import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
-import org.skife.jdbi.v2.tweak.ResultSetMapper;
-
-import com.ning.billing.account.api.Account;
-import com.ning.billing.account.api.user.AccountBuilder;
-import com.ning.billing.catalog.api.Currency;
-import com.ning.billing.util.UuidMapper;
 
 @ExternalizedSqlViaStringTemplate3
-@RegisterMapper({UuidMapper.class, AccountSqlDao.AccountMapper.class})
-public interface AccountSqlDao extends UpdatableEntityDao<Account>, Transactional<AccountSqlDao>, Transmogrifier {
+@RegisterMapper({UuidMapper.class, AccountMapper.class})
+public interface AccountSqlDao extends UpdatableEntitySqlDao<Account>, Transactional<AccountSqlDao>, Transmogrifier {
     @SqlQuery
     public Account getAccountByKey(@Bind("externalKey") final String key);
 
@@ -58,53 +52,8 @@ public interface AccountSqlDao extends UpdatableEntityDao<Account>, Transactiona
     @SqlUpdate
     public void update(@AccountBinder Account account, @CallContextBinder final CallContext context);
 
-    public static class AccountMapper extends MapperBase implements ResultSetMapper<Account> {
-        @Override
-        public Account map(int index, ResultSet result, StatementContext context) throws SQLException {
-            UUID id = UUID.fromString(result.getString("id"));
-            String externalKey = result.getString("external_key");
-            String email = result.getString("email");
-            String name = result.getString("name");
-            int firstNameLength = result.getInt("first_name_length");
-            int billingCycleDay = result.getInt("billing_cycle_day");
-
-            String currencyString = result.getString("currency");
-            Currency currency = (currencyString == null) ? null : Currency.valueOf(currencyString);
-
-            String paymentProviderName = result.getString("payment_provider_name");
-
-            String timeZoneId = result.getString("time_zone");
-            DateTimeZone timeZone = (timeZoneId == null) ? null : DateTimeZone.forID(timeZoneId);
-
-            String locale = result.getString("locale");
-
-            String address1 = result.getString("address1");
-            String address2 = result.getString("address2");
-            String companyName = result.getString("company_name");
-            String city = result.getString("city");
-            String stateOrProvince = result.getString("state_or_province");
-            String postalCode = result.getString("postal_code");
-            String country = result.getString("country");
-            String phone = result.getString("phone");
-
-            String createdBy = result.getString("created_by");
-            DateTime createdDate = getDate(result, "created_date");
-            String updatedBy = result.getString("updated_by");
-            DateTime updatedDate = getDate(result, "updated_date");
-
-            return new AccountBuilder(id).externalKey(externalKey).email(email)
-                                         .name(name).firstNameLength(firstNameLength)
-                                         .phone(phone).currency(currency)
-                                         .billingCycleDay(billingCycleDay)
-                                         .paymentProviderName(paymentProviderName)
-                                         .timeZone(timeZone).locale(locale)
-                                         .address1(address1).address2(address2)
-                                         .companyName(companyName)
-                                         .city(city).stateOrProvince(stateOrProvince)
-                                         .postalCode(postalCode).country(country)
-                                         .createdBy(createdBy).createdDate(createdDate)
-                                         .updatedBy(updatedBy).updatedDate(updatedDate)
-                                         .build();
-        }
-    }
+    @Override
+    @SqlUpdate
+    public void insertHistoryFromTransaction(@AccountHistoryBinder final EntityHistory<Account> account,
+                                            @CallContextBinder final CallContext context);
 }
diff --git a/account/src/main/java/com/ning/billing/account/dao/AuditedAccountDao.java b/account/src/main/java/com/ning/billing/account/dao/AuditedAccountDao.java
index 2d06666..6d52d10 100644
--- a/account/src/main/java/com/ning/billing/account/dao/AuditedAccountDao.java
+++ b/account/src/main/java/com/ning/billing/account/dao/AuditedAccountDao.java
@@ -21,28 +21,37 @@ import java.util.List;
 import java.util.UUID;
 
 import com.ning.billing.util.ChangeType;
-import com.ning.billing.util.audit.dao.AuditSqlDao;
 import com.ning.billing.util.callcontext.CallContext;
 import com.ning.billing.util.customfield.dao.CustomFieldDao;
+import com.ning.billing.util.dao.EntityAudit;
+import com.ning.billing.util.dao.EntityHistory;
+import com.ning.billing.util.dao.ObjectType;
+import com.ning.billing.util.dao.TableName;
 import com.ning.billing.util.entity.EntityPersistenceException;
 import com.ning.billing.util.tag.dao.TagDao;
 import org.skife.jdbi.v2.IDBI;
 import org.skife.jdbi.v2.Transaction;
 import org.skife.jdbi.v2.TransactionStatus;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import com.google.inject.Inject;
 import com.ning.billing.ErrorCode;
 import com.ning.billing.account.api.Account;
 import com.ning.billing.account.api.AccountApiException;
-import com.ning.billing.account.api.AccountChangeNotification;
-import com.ning.billing.account.api.AccountCreationNotification;
-import com.ning.billing.account.api.user.DefaultAccountChangeNotification;
+import com.ning.billing.account.api.AccountChangeEvent;
+import com.ning.billing.account.api.AccountCreationEvent;
+import com.ning.billing.account.api.user.DefaultAccountChangeEvent;
 import com.ning.billing.account.api.user.DefaultAccountCreationEvent;
 import com.ning.billing.util.customfield.CustomField;
 import com.ning.billing.util.customfield.dao.CustomFieldSqlDao;
 import com.ning.billing.util.bus.Bus;
+import com.ning.billing.util.bus.Bus.EventBusException;
 import com.ning.billing.util.tag.Tag;
 
 public class AuditedAccountDao implements AccountDao {
+    private final static Logger log = LoggerFactory.getLogger(AuditedAccountDao.class);
+    
     private final AccountSqlDao accountSqlDao;
     private final TagDao tagDao;
     private final CustomFieldDao customFieldDao;
@@ -80,11 +89,11 @@ public class AuditedAccountDao implements AccountDao {
     }
 
     @Override
-    public Account getById(final String id) {
+    public Account getById(final UUID id) {
         return accountSqlDao.inTransaction(new Transaction<Account, AccountSqlDao>() {
             @Override
             public Account inTransaction(final AccountSqlDao accountSqlDao, final TransactionStatus status) throws Exception {
-                Account account = accountSqlDao.getById(id);
+                Account account = accountSqlDao.getById(id.toString());
                 if (account != null) {
                     setCustomFieldsFromWithinTransaction(account, accountSqlDao);
                     setTagsFromWithinTransaction(account, accountSqlDao);
@@ -122,20 +131,25 @@ public class AuditedAccountDao implements AccountDao {
                         throw new AccountApiException(ErrorCode.ACCOUNT_ALREADY_EXISTS, key);
                     }
                     transactionalDao.create(account, context);
-                    UUID historyId = UUID.randomUUID();
 
-                    AccountHistorySqlDao historyDao = accountSqlDao.become(AccountHistorySqlDao.class);
-                    historyDao.insertAccountHistoryFromTransaction(account, historyId.toString(),
-                            ChangeType.INSERT.toString(), context);
+                    // insert history
+                    Long recordId = accountSqlDao.getRecordId(account.getId().toString());
+                    EntityHistory<Account> history = new EntityHistory<Account>(account.getId(), recordId, account, ChangeType.INSERT);
+                    accountSqlDao.insertHistoryFromTransaction(history, context);
 
-                    AuditSqlDao auditDao = accountSqlDao.become(AuditSqlDao.class);
-                    auditDao.insertAuditFromTransaction("account_history", historyId.toString(),
-                                                         ChangeType.INSERT, context);
+                    // insert audit
+                    Long historyRecordId = accountSqlDao.getHistoryRecordId(recordId);
+                    EntityAudit audit = new EntityAudit(TableName.ACCOUNT_HISTORY, historyRecordId, ChangeType.INSERT);
+                    accountSqlDao.insertAuditFromTransaction(audit, context);
 
                     saveTagsFromWithinTransaction(account, transactionalDao, context);
                     saveCustomFieldsFromWithinTransaction(account, transactionalDao, context);
-                    AccountCreationNotification creationEvent = new DefaultAccountCreationEvent(account);
-                    eventBus.post(creationEvent);
+                    AccountCreationEvent creationEvent = new DefaultAccountCreationEvent(account, context.getUserToken());
+                    try {
+                        eventBus.postFromTransaction(creationEvent, transactionalDao);
+                    } catch (EventBusException e) {
+                        log.warn("Failed to post account creation event for account " + account.getId(), e);
+                    }
                     return null;
                 }
             });
@@ -155,9 +169,9 @@ public class AuditedAccountDao implements AccountDao {
         try {
             accountSqlDao.inTransaction(new Transaction<Void, AccountSqlDao>() {
                 @Override
-                public Void inTransaction(final AccountSqlDao accountSqlDao, final TransactionStatus status) throws EntityPersistenceException, Bus.EventBusException {
+                public Void inTransaction(final AccountSqlDao transactional, final TransactionStatus status) throws EntityPersistenceException, Bus.EventBusException {
                     String accountId = account.getId().toString();
-                    Account currentAccount = accountSqlDao.getById(accountId);
+                    Account currentAccount = transactional.getById(accountId);
                     if (currentAccount == null) {
                         throw new EntityPersistenceException(ErrorCode.ACCOUNT_DOES_NOT_EXIST_FOR_ID, accountId);
                     }
@@ -167,22 +181,26 @@ public class AuditedAccountDao implements AccountDao {
                         throw new EntityPersistenceException(ErrorCode.ACCOUNT_CANNOT_CHANGE_EXTERNAL_KEY, currentKey);
                     }
 
-                    accountSqlDao.update(account, context);
+                    transactional.update(account, context);
 
-                    UUID historyId = UUID.randomUUID();
-                    AccountHistorySqlDao historyDao = accountSqlDao.become(AccountHistorySqlDao.class);
-                    historyDao.insertAccountHistoryFromTransaction(account, historyId.toString(), ChangeType.UPDATE.toString(), context);
+                    Long recordId = accountSqlDao.getRecordId(account.getId().toString());
+                    EntityHistory<Account> history = new EntityHistory<Account>(account.getId(), recordId, account, ChangeType.INSERT);
+                    accountSqlDao.insertHistoryFromTransaction(history, context);
 
-                    AuditSqlDao auditDao = accountSqlDao.become(AuditSqlDao.class);
-                    auditDao.insertAuditFromTransaction("account_history" ,historyId.toString(),
-                                                        ChangeType.INSERT, context);
+                    Long historyRecordId = accountSqlDao.getHistoryRecordId(recordId);
+                    EntityAudit audit = new EntityAudit(TableName.ACCOUNT_HISTORY, historyRecordId, ChangeType.INSERT);
+                    accountSqlDao.insertAuditFromTransaction(audit, context);
 
-                    saveTagsFromWithinTransaction(account, accountSqlDao, context);
-                    saveCustomFieldsFromWithinTransaction(account, accountSqlDao, context);
+                    saveTagsFromWithinTransaction(account, transactional, context);
+                    saveCustomFieldsFromWithinTransaction(account, transactional, context);
 
-                    AccountChangeNotification changeEvent = new DefaultAccountChangeNotification(account.getId(), currentAccount, account);
+                    AccountChangeEvent changeEvent = new DefaultAccountChangeEvent(account.getId(), context.getUserToken(), currentAccount, account);
                     if (changeEvent.hasChanges()) {
-                        eventBus.post(changeEvent);
+                        try {
+                            eventBus.postFromTransaction(changeEvent, transactional);
+                        } catch (EventBusException e) {
+                            log.warn("Failed to post account change event for account " + account.getId(), e);
+                        }
                     }
                     return null;
                 }
@@ -203,7 +221,7 @@ public class AuditedAccountDao implements AccountDao {
 
     private void setCustomFieldsFromWithinTransaction(final Account account, final AccountSqlDao transactionalDao) {
         CustomFieldSqlDao customFieldSqlDao = transactionalDao.become(CustomFieldSqlDao.class);
-        List<CustomField> fields = customFieldSqlDao.load(account.getId().toString(), account.getObjectName());
+        List<CustomField> fields = customFieldSqlDao.load(account.getId().toString(), account.getObjectType());
 
         account.clearFields();
         if (fields != null) {
@@ -212,7 +230,7 @@ public class AuditedAccountDao implements AccountDao {
     }
 
     private void setTagsFromWithinTransaction(final Account account, final AccountSqlDao transactionalDao) {
-        List<Tag> tags = tagDao.loadTagsFromTransaction(transactionalDao, account.getId(), Account.ObjectType);
+        List<Tag> tags = tagDao.loadEntitiesFromTransaction(transactionalDao, account.getId(), ObjectType.ACCOUNT);
         account.clearTags();
 
         if (tags != null) {
@@ -222,11 +240,11 @@ public class AuditedAccountDao implements AccountDao {
 
     private void saveTagsFromWithinTransaction(final Account account, final AccountSqlDao transactionalDao,
                                                final CallContext context) {
-        tagDao.saveTagsFromTransaction(transactionalDao, account.getId(), account.getObjectName(), account.getTagList(), context);
+        tagDao.saveEntitiesFromTransaction(transactionalDao, account.getId(), account.getObjectType(), account.getTagList(), context);
     }
 
     private void saveCustomFieldsFromWithinTransaction(final Account account, final AccountSqlDao transactionalDao,
                                                        final CallContext context) {
-        customFieldDao.saveFields(transactionalDao, account.getId(), account.getObjectName(), account.getFieldList(), context);
+        customFieldDao.saveEntitiesFromTransaction(transactionalDao, account.getId(), account.getObjectType(), account.getFieldList(), context);
     }
 }
diff --git a/account/src/main/java/com/ning/billing/account/dao/AuditedAccountEmailDao.java b/account/src/main/java/com/ning/billing/account/dao/AuditedAccountEmailDao.java
new file mode 100644
index 0000000..e51c194
--- /dev/null
+++ b/account/src/main/java/com/ning/billing/account/dao/AuditedAccountEmailDao.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2010-2011 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.account.dao;
+
+import com.google.inject.Inject;
+import com.ning.billing.account.api.AccountEmail;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.dao.AuditedCollectionDaoBase;
+import com.ning.billing.util.dao.ObjectType;
+import com.ning.billing.util.dao.TableName;
+import com.ning.billing.util.entity.collection.dao.UpdatableEntityCollectionSqlDao;
+import org.skife.jdbi.v2.IDBI;
+import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
+
+import java.util.List;
+import java.util.UUID;
+
+public class AuditedAccountEmailDao extends AuditedCollectionDaoBase<AccountEmail> implements AccountEmailDao {
+    private final AccountEmailSqlDao accountEmailSqlDao;
+
+    @Inject
+    public AuditedAccountEmailDao(IDBI dbi) {
+        this.accountEmailSqlDao = dbi.onDemand(AccountEmailSqlDao.class);
+    }
+
+    @Override
+    public List<AccountEmail> getEmails(final UUID accountId) {
+        return super.loadEntities(accountId, ObjectType.ACCOUNT_EMAIL);
+    }
+
+    @Override
+    public void saveEmails(final UUID accountId, final List<AccountEmail> emails, final CallContext context) {
+        super.saveEntitiesFromTransaction(accountEmailSqlDao, accountId, ObjectType.ACCOUNT_EMAIL, emails, context);
+    }
+
+    public void test() {
+        accountEmailSqlDao.test();
+    }
+
+//    @Override
+//    public List<AccountEmail> getEmails(final UUID accountId) {
+//        return accountEmailSqlDao.load(accountId.toString(), null);
+//        //return accountEmailSqlDao.getByAccountId(accountId.toString());
+//    }
+
+//    @Override
+//    public void saveEmails(final UUID accountId, final List<AccountEmail> emails, final CallContext context) {
+//        final List<AccountEmail> existingEmails = accountEmailSqlDao.getByAccountId(accountId.toString());
+//        final List<AccountEmail> updatedEmails = new ArrayList<AccountEmail>();
+//
+//        Iterator<AccountEmail> existingEmailIterator = existingEmails.iterator();
+//        while (existingEmailIterator.hasNext()) {
+//            AccountEmail existingEmail = existingEmailIterator.next();
+//
+//            Iterator<AccountEmail> newEmailIterator = emails.iterator();
+//            while (newEmailIterator.hasNext()) {
+//                AccountEmail newEmail = newEmailIterator.next();
+//                if (newEmail.getId().equals(existingEmail.getId())) {
+//                    // check equality; if not equal, add to updated
+//                    if (!newEmail.equals(existingEmail)) {
+//                        updatedEmails.add(newEmail);
+//                    }
+//
+//                    // remove from both
+//                    newEmailIterator.remove();
+//                    existingEmailIterator.remove();
+//                }
+//            }
+//        }
+//
+//        // remaining emails in newEmail are inserts; remaining emails in existingEmail are deletes
+//        accountEmailSqlDao.inTransaction(new Transaction<Void, AccountEmailSqlDao>() {
+//            @Override
+//            public Void inTransaction(AccountEmailSqlDao dao, TransactionStatus transactionStatus) throws Exception {
+//                dao.create(emails, context);
+//                dao.update(updatedEmails, context);
+//                dao.delete(existingEmails, context);
+//
+//                List<String> insertHistoryIdList = getIdList(emails.size());
+//                List<String> updateHistoryIdList = getIdList(updatedEmails.size());
+//                List<String> deleteHistoryIdList = getIdList(existingEmails.size());
+//
+//                // insert histories
+//                dao.insertAccountEmailHistoryFromTransaction(insertHistoryIdList, emails, ChangeType.INSERT, context);
+//                dao.insertAccountEmailHistoryFromTransaction(updateHistoryIdList, updatedEmails, ChangeType.UPDATE, context);
+//                dao.insertAccountEmailHistoryFromTransaction(deleteHistoryIdList, existingEmails, ChangeType.DELETE, context);
+//
+//                // insert audits
+//                auditSqlDao.insertAuditFromTransaction(TableName.ACCOUNT_EMAIL_HISTORY, insertHistoryIdList, ChangeType.INSERT, context);
+//                auditSqlDao.insertAuditFromTransaction(TableName.ACCOUNT_EMAIL_HISTORY, updateHistoryIdList, ChangeType.UPDATE, context);
+//                auditSqlDao.insertAuditFromTransaction(TableName.ACCOUNT_EMAIL_HISTORY, deleteHistoryIdList, ChangeType.DELETE, context);
+//
+//                return null;
+//            }
+//        });
+//    }
+//
+//    private List<String> getIdList(int size) {
+//        List<String> results = new ArrayList<String>();
+//        for (int i = 0; i < size; i++) {
+//            results.add(UUID.randomUUID().toString());
+//        }
+//        return results;
+//    }
+
+    @Override
+    protected TableName getTableName() {
+        return TableName.ACCOUNT_EMAIL_HISTORY;
+    }
+
+    @Override
+    protected UpdatableEntityCollectionSqlDao<AccountEmail> transmogrifyDao(Transmogrifier transactionalDao) {
+        return transactionalDao.become(AccountEmailSqlDao.class);
+    }
+
+    @Override
+    protected UpdatableEntityCollectionSqlDao<AccountEmail> getSqlDao() {
+        return accountEmailSqlDao;
+    }
+}
diff --git a/account/src/main/java/com/ning/billing/account/glue/AccountModule.java b/account/src/main/java/com/ning/billing/account/glue/AccountModule.java
index 60e0a14..956d878 100644
--- a/account/src/main/java/com/ning/billing/account/glue/AccountModule.java
+++ b/account/src/main/java/com/ning/billing/account/glue/AccountModule.java
@@ -16,7 +16,6 @@
 
 package com.ning.billing.account.glue;
 
-import org.skife.config.ConfigurationObjectFactory;
 
 import com.google.inject.AbstractModule;
 import com.ning.billing.account.api.AccountService;
@@ -24,21 +23,22 @@ import com.ning.billing.account.api.AccountUserApi;
 import com.ning.billing.account.api.DefaultAccountService;
 import com.ning.billing.account.api.user.DefaultAccountUserApi;
 import com.ning.billing.account.dao.AccountDao;
+import com.ning.billing.account.dao.AccountEmailDao;
 import com.ning.billing.account.dao.AuditedAccountDao;
+import com.ning.billing.account.dao.AuditedAccountEmailDao;
+import com.ning.billing.util.glue.RealImplementation;
 
 public class AccountModule extends AbstractModule {
-
     private void installConfig() {
-        final AccountConfig config = new ConfigurationObjectFactory(System.getProperties()).build(AccountConfig.class);
-        bind(AccountConfig.class).toInstance(config);
     }
 
     protected void installAccountDao() {
+        bind(AccountEmailDao.class).to(AuditedAccountEmailDao.class).asEagerSingleton();
         bind(AccountDao.class).to(AuditedAccountDao.class).asEagerSingleton();
     }
 
     protected void installAccountUserApi() {
-        bind(AccountUserApi.class).to(DefaultAccountUserApi.class).asEagerSingleton();
+        bind(AccountUserApi.class).annotatedWith(RealImplementation.class).to(DefaultAccountUserApi.class).asEagerSingleton();
     }
 
     private void installAccountService() {
diff --git a/account/src/main/resources/com/ning/billing/account/dao/AccountEmailSqlDao.sql.stg b/account/src/main/resources/com/ning/billing/account/dao/AccountEmailSqlDao.sql.stg
new file mode 100644
index 0000000..3f981a4
--- /dev/null
+++ b/account/src/main/resources/com/ning/billing/account/dao/AccountEmailSqlDao.sql.stg
@@ -0,0 +1,85 @@
+group account_emails;
+
+fields(prefix) ::= <<
+    <prefix>id,
+    <prefix>account_id,
+    <prefix>email,
+    <prefix>created_by,
+    <prefix>created_date,
+    <prefix>updated_by,
+    <prefix>updated_date
+>>
+
+insertFromTransaction() ::= <<
+    INSERT INTO account_emails(<fields()>)
+    VALUES (:id, :accountId, :email, :userName, :createdDate, :userName, :updatedDate);
+>>
+
+updateFromTransaction() ::= <<
+    UPDATE account_emails
+    SET email = :email, updated_by = :userName, updated_date = :updatedDate
+    WHERE id = :id;
+>>
+
+deleteFromTransaction() ::= <<
+    DELETE FROM account_emails
+    WHERE id = :id;
+>>
+
+addHistoryFromTransaction() ::= <<
+    INSERT INTO account_email_history(record_id, id, account_id, email, change_type, updated_by, date)
+    VALUES (:recordId, :id, :accountId, :email, :changeType, :userName, :updatedDate);
+>>
+
+load() ::= <<
+    SELECT <fields()> FROM account_emails WHERE account_id = :objectId;
+>>
+
+getRecordIds() ::= <<
+    SELECT record_id, id
+    FROM account_emails
+    WHERE account_id = :objectId;
+>>
+
+getMaxHistoryRecordId() ::= <<
+    SELECT MAX(history_record_id)
+    FROM account_email_history;
+>>
+
+getHistoryRecordIds() ::= <<
+    SELECT history_record_id, record_id
+    FROM account_email_history
+    WHERE history_record_id > :maxHistoryRecordId;
+>>
+
+getById() ::= <<
+    SELECT <fields()> FROM account_emails WHERE id = :id;
+>>
+
+get() ::= <<
+    SELECT <fields()> FROM account_emails;
+>>
+
+getByAccountId() ::= <<
+    SELECT <fields()> FROM account_emails WHERE account_id = :accountId;
+>>
+
+auditFields(prefix) ::= <<
+    <prefix>table_name,
+    <prefix>record_id,
+    <prefix>change_type,
+    <prefix>change_date,
+    <prefix>changed_by,
+    <prefix>reason_code,
+    <prefix>comments,
+    <prefix>user_token
+>>
+
+insertAuditFromTransaction() ::= <<
+    INSERT INTO audit_log(<auditFields()>)
+    VALUES(:tableName, :recordId, :changeType, :createdDate, :userName, NULL, NULL, NULL);
+>>
+
+test() ::= <<
+    SELECT 1 FROM account_emails;
+>>
\ No newline at end of file
diff --git a/account/src/main/resources/com/ning/billing/account/dao/AccountSqlDao.sql.stg b/account/src/main/resources/com/ning/billing/account/dao/AccountSqlDao.sql.stg
index eda3949..fda1503 100644
--- a/account/src/main/resources/com/ning/billing/account/dao/AccountSqlDao.sql.stg
+++ b/account/src/main/resources/com/ning/billing/account/dao/AccountSqlDao.sql.stg
@@ -19,6 +19,8 @@ accountFields(prefix) ::= <<
     <prefix>country, 
     <prefix>postal_code,
     <prefix>phone,
+    <prefix>migrated,
+    <prefix>is_notified_for_invoices,
     <prefix>created_by,
     <prefix>created_date,
     <prefix>updated_by,
@@ -32,7 +34,7 @@ create() ::= <<
       (:id, :externalKey, :email, :name, :firstNameLength, :currency, :billingCycleDay,
       :paymentProviderName, :timeZone, :locale,
       :address1, :address2, :companyName, :city, :stateOrProvince, :country, :postalCode, :phone,
-      :userName, :createdDate, :userName, :updatedDate);
+      :migrated, :isNotifiedForInvoices, :userName, :createdDate, :userName, :updatedDate);
 >>
 
 update() ::= <<
@@ -42,10 +44,58 @@ update() ::= <<
         time_zone = :timeZone, locale = :locale,
         address1 = :address1, address2 = :address2, company_name = :companyName, city = :city, state_or_province = :stateOrProvince,
         country = :country, postal_code = :postalCode, phone = :phone,
-        updated_date = :updatedDate, updated_by = :userName
+        is_notified_for_invoices = :isNotifiedForInvoices, updated_date = :updatedDate, updated_by = :userName
     WHERE id = :id;
 >>
 
+historyFields() ::= <<
+    record_id,
+    id,
+    external_key,
+    email,
+    name,
+    first_name_length,
+    currency,
+    billing_cycle_day,
+    payment_provider_name,
+    time_zone,
+    locale,
+    address1,
+    address2,
+    company_name,
+    city,
+    state_or_province,
+    country,
+    postal_code,
+    phone,
+    migrated,
+    is_notified_for_invoices,
+    change_type,
+    updated_by,
+    date
+>>
+
+getRecordId() ::= <<
+    SELECT record_id
+    FROM accounts
+    WHERE id = :id;
+>>
+
+getHistoryRecordId() ::= <<
+    SELECT MAX(history_record_id)
+    FROM account_history
+    WHERE record_id = :recordId;
+>>
+
+insertHistoryFromTransaction() ::= <<
+    INSERT INTO account_history(<historyFields()>)
+    VALUES
+    (:recordId, :id, :externalKey, :email, :name, :firstNameLength, :currency,
+     :billingCycleDay, :paymentProviderName, :timeZone, :locale,
+     :address1, :address2, :companyName, :city, :stateOrProvince,
+     :country, :postalCode, :phone, :migrated, :isNotifiedForInvoices, :changeType, :userName, :createdDate);
+>>
+
 getAccountByKey() ::= <<
     select <accountFields()>
     from accounts
@@ -69,6 +119,22 @@ getIdFromKey() ::= <<
     WHERE external_key = :externalKey;
 >>
 
+auditFields(prefix) ::= <<
+    <prefix>table_name,
+    <prefix>record_id,
+    <prefix>change_type,
+    <prefix>change_date,
+    <prefix>changed_by,
+    <prefix>reason_code,
+    <prefix>comments,
+    <prefix>user_token
+>>
+
+insertAuditFromTransaction() ::= <<
+    INSERT INTO audit_log(<auditFields()>)
+    VALUES(:tableName, :recordId, :changeType, :createdDate, :userName, NULL, NULL, NULL);
+>>
+
 test() ::= <<
     SELECT 1 FROM accounts;
 >>
diff --git a/account/src/main/resources/com/ning/billing/account/ddl.sql b/account/src/main/resources/com/ning/billing/account/ddl.sql
index ff9a41c..5fe7c90 100644
--- a/account/src/main/resources/com/ning/billing/account/ddl.sql
+++ b/account/src/main/resources/com/ning/billing/account/ddl.sql
@@ -1,5 +1,6 @@
 DROP TABLE IF EXISTS accounts;
 CREATE TABLE accounts (
+    record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
     id char(36) NOT NULL,
     external_key varchar(128) NULL,
     email varchar(50) NOT NULL,
@@ -19,18 +20,21 @@ CREATE TABLE accounts (
     postal_code varchar(11) DEFAULT NULL,
     phone varchar(25) DEFAULT NULL,
     migrated bool DEFAULT false,
+    is_notified_for_invoices boolean NOT NULL,
     created_date datetime NOT NULL,
     created_by varchar(50) NOT NULL,
     updated_date datetime DEFAULT NULL,
     updated_by varchar(50) DEFAULT NULL,
-    PRIMARY KEY(id)
+    PRIMARY KEY(record_id)
 ) ENGINE=innodb;
+CREATE UNIQUE INDEX accounts_id ON accounts(id);
 CREATE UNIQUE INDEX accounts_external_key ON accounts(external_key);
 CREATE UNIQUE INDEX accounts_email ON accounts(email);
 
 DROP TABLE IF EXISTS account_history;
 CREATE TABLE account_history (
-    history_id char(36) NOT NULL,
+    history_record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
+    record_id int(11) unsigned NOT NULL,
     id char(36) NOT NULL,
     external_key varchar(128) NULL,
     email varchar(50) NOT NULL,
@@ -49,8 +53,41 @@ CREATE TABLE account_history (
     country varchar(50) DEFAULT NULL,
     postal_code varchar(11) DEFAULT NULL,
     phone varchar(25) DEFAULT NULL,
+    migrated bool DEFAULT false,
+    is_notified_for_invoices boolean NOT NULL,
+    change_type char(6) NOT NULL,
+    updated_by varchar(50) NOT NULL,
+    date datetime NOT NULL,
+    PRIMARY KEY(history_record_id)
+) ENGINE=innodb;
+CREATE INDEX account_history_record_id ON account_history(record_id);
+
+DROP TABLE IF EXISTS account_emails;
+CREATE TABLE account_emails (
+    record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
+    id char(36) NOT NULL,
+    account_id char(36) NOT NULL,
+    email varchar(50) NOT NULL,
+    created_by varchar(50) NOT NULL,
+    created_date datetime NOT NULL,
+    updated_by varchar(50) NOT NULL,
+    updated_date datetime NOT NULL,
+    PRIMARY KEY(record_id)
+) ENGINE=innodb;
+CREATE UNIQUE INDEX account_email_id ON account_emails(id);
+CREATE INDEX account_email_account_id ON account_emails(account_id);
+CREATE UNIQUE INDEX account_email_account_id_email ON account_emails(account_id, email);
+
+DROP TABLE IF EXISTS account_email_history;
+CREATE TABLE account_email_history (
+    history_record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
+    record_id int(11) unsigned NOT NULL,
+    id char(36) NOT NULL,
+    account_id char(36) NOT NULL,
+    email varchar(50) NOT NULL,
     change_type char(6) NOT NULL,
     updated_by varchar(50) NOT NULL,
-    date datetime
+    date datetime NOT NULL,
+    PRIMARY KEY(history_record_id)
 ) ENGINE=innodb;
-CREATE INDEX account_id ON account_history(id);
\ No newline at end of file
+CREATE INDEX account_email_record_id ON account_email_history(record_id);
\ No newline at end of file
diff --git a/account/src/test/java/com/ning/billing/account/api/MockAccountUserApi.java b/account/src/test/java/com/ning/billing/account/api/MockAccountUserApi.java
index d2fea9e..f916278 100644
--- a/account/src/test/java/com/ning/billing/account/api/MockAccountUserApi.java
+++ b/account/src/test/java/com/ning/billing/account/api/MockAccountUserApi.java
@@ -22,11 +22,11 @@ import java.util.UUID;
 import java.util.concurrent.CopyOnWriteArrayList;
 
 import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.tag.TagDefinition;
 import org.joda.time.DateTimeZone;
 
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.util.customfield.CustomField;
-import com.ning.billing.util.tag.Tag;
 
 public class MockAccountUserApi implements AccountUserApi {
     private final CopyOnWriteArrayList<Account> accounts = new CopyOnWriteArrayList<Account>();
@@ -51,16 +51,16 @@ public class MockAccountUserApi implements AccountUserApi {
                                  final String phone) {
 
 		Account result = new DefaultAccount(id, externalKey, email, name,
-				firstNameLength, currency, billCycleDay, paymentProviderName,
-				timeZone, locale, address1, address2, companyName, city,
-				stateOrProvince, country, postalCode, phone, null, null, null, null);
+                                firstNameLength, currency, billCycleDay, paymentProviderName,
+                                timeZone, locale, address1, address2, companyName, city,
+                                stateOrProvince, country, postalCode, phone, false, false);
 		accounts.add(result);
 		return result;
 	}
 
     @Override
     public Account createAccount(final AccountData data, final List<CustomField> fields,
-                                 final List<Tag> tags, final CallContext context) throws AccountApiException {
+                                 final List<TagDefinition> tagDefinitions, final CallContext context) throws AccountApiException {
         Account result = new DefaultAccount(data);
         accounts.add(result);
         return result;
@@ -102,13 +102,23 @@ public class MockAccountUserApi implements AccountUserApi {
     }
 
     @Override
+    public List<AccountEmail> getEmails(UUID accountId) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void saveEmails(UUID accountId, List<AccountEmail> emails, CallContext context) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
     public void updateAccount(final Account account, final CallContext context) {
         throw new UnsupportedOperationException();
     }
 
 	@Override
 	public Account migrateAccount(final MigrationAccountData data,
-			final List<CustomField> fields, final List<Tag> tags, final CallContext context)
+			final List<CustomField> fields, final List<TagDefinition> tagDefinitions, final CallContext context)
 			throws AccountApiException {
 		Account result = new DefaultAccount(data);
         accounts.add(result);
diff --git a/account/src/test/java/com/ning/billing/account/api/user/TestEventJson.java b/account/src/test/java/com/ning/billing/account/api/user/TestEventJson.java
new file mode 100644
index 0000000..6518355
--- /dev/null
+++ b/account/src/test/java/com/ning/billing/account/api/user/TestEventJson.java
@@ -0,0 +1,71 @@
+/* 
+ * Copyright 2010-2011 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.account.api.user;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import org.codehaus.jackson.map.ObjectMapper;
+import org.codehaus.jackson.map.SerializationConfig;
+import org.testng.Assert;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Test;
+
+import com.ning.billing.account.api.AccountChangeEvent;
+import com.ning.billing.account.api.ChangedField;
+import com.ning.billing.account.api.DefaultChangedField;
+import com.ning.billing.account.api.user.DefaultAccountCreationEvent.DefaultAccountData;
+
+public class TestEventJson {
+    
+    private ObjectMapper mapper = new ObjectMapper();
+    
+    @BeforeTest(groups= {"fast"})
+    public void setup() {
+        mapper = new ObjectMapper();
+        mapper.disable(SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS);
+    }
+
+    @Test(groups= {"fast"})
+    public void testDefaultAccountChangeEvent() throws Exception {
+
+        List<ChangedField> changes = new ArrayList<ChangedField>();
+        changes.add(new DefaultChangedField("fieldXX", "valueX", "valueXXX"));
+        changes.add(new DefaultChangedField("fieldYY", "valueY", "valueYYY"));        
+        AccountChangeEvent e = new DefaultAccountChangeEvent(UUID.randomUUID(), changes, UUID.randomUUID());
+        
+        String json = mapper.writeValueAsString(e);
+        
+        Class<?> claz = Class.forName("com.ning.billing.account.api.user.DefaultAccountChangeEvent");
+        Object obj =  mapper.readValue(json, claz);
+        Assert.assertTrue(obj.equals(e));
+    }
+    
+    @Test(groups= {"fast"})
+    public void testAccountCreationEvent() throws Exception {
+        
+        DefaultAccountData data = new DefaultAccountData("dsfdsf", "bobo", 3, "bobo@yahoo.com", 12, "USD", "paypal", 
+                "UTC", "US", "21 avenue", "", "Gling", "San Franciso", "CA", "94110", "USA", "4126789887", false, false);
+        DefaultAccountCreationEvent e = new DefaultAccountCreationEvent(data, UUID.randomUUID(), UUID.randomUUID());
+        
+        String json = mapper.writeValueAsString(e);
+        Class<?> claz = Class.forName(DefaultAccountCreationEvent.class.getName());
+        Object obj =  mapper.readValue(json, claz);
+        Assert.assertTrue(obj.equals(e));
+        
+    }
+}
diff --git a/account/src/test/java/com/ning/billing/account/dao/AccountDaoTestBase.java b/account/src/test/java/com/ning/billing/account/dao/AccountDaoTestBase.java
index 3a51bcc..9eb462f 100644
--- a/account/src/test/java/com/ning/billing/account/dao/AccountDaoTestBase.java
+++ b/account/src/test/java/com/ning/billing/account/dao/AccountDaoTestBase.java
@@ -20,11 +20,19 @@ import static org.testng.Assert.fail;
 
 import java.io.IOException;
 
+import com.ning.billing.dbi.MysqlTestingHelper;
+import com.ning.billing.util.bus.Bus;
+import com.ning.billing.util.bus.InMemoryBus;
 import com.ning.billing.util.callcontext.CallContext;
 import com.ning.billing.util.callcontext.CallOrigin;
 import com.ning.billing.util.callcontext.UserType;
 import com.ning.billing.util.callcontext.DefaultCallContextFactory;
 import com.ning.billing.util.clock.Clock;
+import com.ning.billing.util.clock.ClockMock;
+import com.ning.billing.util.customfield.dao.AuditedCustomFieldDao;
+import com.ning.billing.util.customfield.dao.CustomFieldDao;
+import com.ning.billing.util.tag.dao.AuditedTagDao;
+import com.ning.billing.util.tag.dao.TagDao;
 import org.apache.commons.io.IOUtils;
 import org.skife.jdbi.v2.Handle;
 import org.skife.jdbi.v2.IDBI;
@@ -33,17 +41,15 @@ import org.skife.jdbi.v2.TransactionStatus;
 import org.testng.annotations.AfterClass;
 import org.testng.annotations.BeforeClass;
 
-import com.google.inject.Guice;
-import com.google.inject.Injector;
-import com.google.inject.Stage;
-import com.ning.billing.account.glue.AccountModuleWithEmbeddedDb;
 import com.ning.billing.util.bus.DefaultBusService;
 import com.ning.billing.util.bus.BusService;
 import org.testng.annotations.BeforeMethod;
 
 public abstract class AccountDaoTestBase {
-    protected AccountModuleWithEmbeddedDb module;
+    private final MysqlTestingHelper helper = new MysqlTestingHelper();
+
     protected AccountDao accountDao;
+    protected AccountEmailDao accountEmailDao;
     protected IDBI dbi;
 
     protected CallContext context;
@@ -52,26 +58,30 @@ public abstract class AccountDaoTestBase {
     protected void setup() throws IOException {
         // Health check test to make sure MySQL is setup properly
         try {
-            module = new AccountModuleWithEmbeddedDb();
             final String accountDdl = IOUtils.toString(AccountSqlDao.class.getResourceAsStream("/com/ning/billing/account/ddl.sql"));
             final String utilDdl = IOUtils.toString(AccountSqlDao.class.getResourceAsStream("/com/ning/billing/util/ddl.sql"));
 
-            module.startDb();
-            module.initDb(accountDdl);
-            module.initDb(utilDdl);
+            helper.startMysql();
+            helper.initDb(accountDdl);
+            helper.initDb(utilDdl);
 
-            final Injector injector = Guice.createInjector(Stage.DEVELOPMENT, module);
-            dbi = injector.getInstance(IDBI.class);
+            dbi = helper.getDBI();
 
-            accountDao = injector.getInstance(AccountDao.class);
-            accountDao.test();
+            Bus bus = new InMemoryBus();
+            BusService busService = new DefaultBusService(bus);
+            ((DefaultBusService) busService).startBus();
 
-            Clock clock = injector.getInstance(Clock.class);
-            context = new DefaultCallContextFactory(clock).createCallContext("Vizzini", CallOrigin.TEST, UserType.TEST);
+            TagDao tagDao = new AuditedTagDao(dbi);
+            CustomFieldDao customFieldDao = new AuditedCustomFieldDao(dbi);
 
+            accountDao = new AuditedAccountDao(dbi, bus, tagDao, customFieldDao);
+            accountDao.test();
 
-            BusService busService = injector.getInstance(BusService.class);
-            ((DefaultBusService) busService).startBus();
+            accountEmailDao = new AuditedAccountEmailDao(dbi);
+            accountEmailDao.test();
+
+            Clock clock = new ClockMock();
+            context = new DefaultCallContextFactory(clock).createCallContext("Account Dao Tests", CallOrigin.TEST, UserType.TEST);
         }
         catch (Throwable t) {
             fail(t.toString());
@@ -81,7 +91,7 @@ public abstract class AccountDaoTestBase {
     @AfterClass(alwaysRun = true)
     public void stopMysql()
     {
-        module.stopDb();
+        helper.stopMysql();
     }
 
     @BeforeMethod(alwaysRun = true)
@@ -91,6 +101,8 @@ public abstract class AccountDaoTestBase {
             public Void inTransaction(Handle h, TransactionStatus status) throws Exception {
                 h.execute("truncate table accounts");
                 h.execute("truncate table notifications");
+                h.execute("truncate table bus_events");
+                h.execute("truncate table claimed_bus_events");                              
                 h.execute("truncate table claimed_notifications");
                 h.execute("truncate table tag_definitions");
                 h.execute("truncate table tags");
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 645124b..907447d 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
@@ -24,9 +24,8 @@ import java.util.concurrent.ConcurrentHashMap;
 
 import com.google.inject.Inject;
 import com.ning.billing.account.api.Account;
-import com.ning.billing.account.api.AccountApiException;
-import com.ning.billing.account.api.AccountChangeNotification;
-import com.ning.billing.account.api.user.DefaultAccountChangeNotification;
+import com.ning.billing.account.api.AccountChangeEvent;
+import com.ning.billing.account.api.user.DefaultAccountChangeEvent;
 import com.ning.billing.account.api.user.DefaultAccountCreationEvent;
 import com.ning.billing.util.callcontext.CallContext;
 import com.ning.billing.util.bus.Bus;
@@ -34,7 +33,7 @@ import com.ning.billing.util.bus.Bus.EventBusException;
 
 public class MockAccountDao implements AccountDao {
     private final Bus eventBus;
-    private final Map<String, Account> accounts = new ConcurrentHashMap<String, Account>();
+    private final Map<UUID, Account> accounts = new ConcurrentHashMap<UUID, Account>();
 
     @Inject
     public MockAccountDao(Bus eventBus) {
@@ -43,10 +42,10 @@ public class MockAccountDao implements AccountDao {
 
     @Override
     public void create(Account account, CallContext context) {
-        accounts.put(account.getId().toString(), account);
+        accounts.put(account.getId(), account);
 
         try {
-            eventBus.post(new DefaultAccountCreationEvent(account));
+            eventBus.post(new DefaultAccountCreationEvent(account, null));
         }
         catch (EventBusException ex) {
             throw new RuntimeException(ex);
@@ -54,7 +53,7 @@ public class MockAccountDao implements AccountDao {
     }
 
     @Override
-    public Account getById(String id) {
+    public Account getById(UUID id) {
         return accounts.get(id);
     }
 
@@ -85,9 +84,9 @@ public class MockAccountDao implements AccountDao {
 
     @Override
     public void update(Account account, CallContext context) {
-        Account currentAccount = accounts.put(account.getId().toString(), account);
+        Account currentAccount = accounts.put(account.getId(), account);
 
-        AccountChangeNotification changeEvent = new DefaultAccountChangeNotification(account.getId(), currentAccount, account);
+        AccountChangeEvent changeEvent = new DefaultAccountChangeEvent(account.getId(), null, currentAccount, account);
         if (changeEvent.hasChanges()) {
             try {
                 eventBus.post(changeEvent);
diff --git a/account/src/test/java/com/ning/billing/account/glue/AccountModuleWithMocks.java b/account/src/test/java/com/ning/billing/account/glue/AccountModuleWithMocks.java
index 74fe321..8700cfa 100644
--- a/account/src/test/java/com/ning/billing/account/glue/AccountModuleWithMocks.java
+++ b/account/src/test/java/com/ning/billing/account/glue/AccountModuleWithMocks.java
@@ -17,8 +17,12 @@
 package com.ning.billing.account.glue;
 
 import com.ning.billing.account.dao.AccountDao;
+import com.ning.billing.account.dao.AccountEmailDao;
 import com.ning.billing.account.dao.MockAccountDao;
+import com.ning.billing.mock.BrainDeadProxyFactory;
 import com.ning.billing.util.clock.MockClockModule;
+import com.ning.billing.util.customfield.CustomField;
+import com.ning.billing.util.customfield.dao.CustomFieldDao;
 import com.ning.billing.util.glue.CallContextModule;
 import com.ning.billing.util.glue.FieldStoreModule;
 import com.ning.billing.util.tag.MockTagStoreModuleMemory;
@@ -27,6 +31,8 @@ public class AccountModuleWithMocks extends AccountModule {
     @Override
     protected void installAccountDao() {
         bind(MockAccountDao.class).asEagerSingleton();
+        AccountEmailDao accountEmailDao = BrainDeadProxyFactory.createBrainDeadProxyFor(AccountEmailDao.class);
+        bind(AccountEmailDao.class).toInstance(accountEmailDao);
         bind(AccountDao.class).to(MockAccountDao.class);
     }
 
@@ -35,7 +41,5 @@ public class AccountModuleWithMocks extends AccountModule {
         super.configure();
         install(new MockClockModule());
         install(new CallContextModule());
-        install(new MockTagStoreModuleMemory());
-        install(new FieldStoreModule());
     }
 }

analytics/pom.xml 23(+23 -0)

diff --git a/analytics/pom.xml b/analytics/pom.xml
index b98db69..1a60b63 100644
--- a/analytics/pom.xml
+++ b/analytics/pom.xml
@@ -65,11 +65,34 @@
         <dependency>
             <groupId>com.ning.billing</groupId>
             <artifactId>killbill-catalog</artifactId>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+         <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-catalog</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-entitlement</artifactId>
             <scope>test</scope>
         </dependency>
         <dependency>
             <groupId>com.ning.billing</groupId>
             <artifactId>killbill-entitlement</artifactId>
+             <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-junction</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-junction</artifactId>
+             <type>test-jar</type>
             <scope>test</scope>
         </dependency>
         <dependency>
diff --git a/analytics/src/main/java/com/ning/billing/analytics/AnalyticsListener.java b/analytics/src/main/java/com/ning/billing/analytics/AnalyticsListener.java
index 3c3bab3..bf6f3eb 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/AnalyticsListener.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/AnalyticsListener.java
@@ -19,12 +19,13 @@ package com.ning.billing.analytics;
 import com.google.common.eventbus.Subscribe;
 import com.google.inject.Inject;
 import com.ning.billing.account.api.AccountApiException;
-import com.ning.billing.account.api.AccountChangeNotification;
-import com.ning.billing.account.api.AccountCreationNotification;
-import com.ning.billing.entitlement.api.user.SubscriptionTransition;
-import com.ning.billing.invoice.api.InvoiceCreationNotification;
-import com.ning.billing.payment.api.PaymentError;
-import com.ning.billing.payment.api.PaymentInfo;
+import com.ning.billing.account.api.AccountChangeEvent;
+import com.ning.billing.account.api.AccountCreationEvent;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
+import com.ning.billing.entitlement.api.user.SubscriptionEvent;
+import com.ning.billing.invoice.api.InvoiceCreationEvent;
+import com.ning.billing.payment.api.PaymentErrorEvent;
+import com.ning.billing.payment.api.PaymentInfoEvent;
 
 public class AnalyticsListener {
     private final BusinessSubscriptionTransitionRecorder bstRecorder;
@@ -37,7 +38,7 @@ public class AnalyticsListener {
     }
 
     @Subscribe
-    public void handleSubscriptionTransitionChange(final SubscriptionTransition event) throws AccountApiException {
+    public void handleSubscriptionTransitionChange(final SubscriptionEvent event) throws AccountApiException, EntitlementUserApiException {
         switch (event.getTransitionType()) {
             // A susbcription enters either through migration or as newly created subscription
             case MIGRATE_ENTITLEMENT:
@@ -66,12 +67,12 @@ public class AnalyticsListener {
     }
 
     @Subscribe
-    public void handleAccountCreation(final AccountCreationNotification event) {
+    public void handleAccountCreation(final AccountCreationEvent event) {
         bacRecorder.accountCreated(event.getData());
     }
 
     @Subscribe
-    public void handleAccountChange(final AccountChangeNotification event) {
+    public void handleAccountChange(final AccountChangeEvent event) {
         if (!event.hasChanges()) {
             return;
         }
@@ -80,17 +81,17 @@ public class AnalyticsListener {
     }
 
     @Subscribe
-    public void handleInvoice(final InvoiceCreationNotification event) {
+    public void handleInvoice(final InvoiceCreationEvent event) {
         bacRecorder.accountUpdated(event.getAccountId());
     }
 
     @Subscribe
-    public void handlePaymentInfo(final PaymentInfo paymentInfo) {
+    public void handlePaymentInfo(final PaymentInfoEvent paymentInfo) {
         bacRecorder.accountUpdated(paymentInfo);
     }
 
     @Subscribe
-    public void handlePaymentError(final PaymentError paymentError) {
+    public void handlePaymentError(final PaymentErrorEvent paymentError) {
         // TODO - we can't tie the error back to an account yet
     }
 }
diff --git a/analytics/src/main/java/com/ning/billing/analytics/BusinessAccountRecorder.java b/analytics/src/main/java/com/ning/billing/analytics/BusinessAccountRecorder.java
index 6832a2b..e692eb7 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/BusinessAccountRecorder.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/BusinessAccountRecorder.java
@@ -16,8 +16,18 @@
 
 package com.ning.billing.analytics;
 
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import com.google.inject.Inject;
 import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.AccountApiException;
 import com.ning.billing.account.api.AccountData;
 import com.ning.billing.account.api.AccountUserApi;
 import com.ning.billing.account.api.ChangedField;
@@ -25,17 +35,10 @@ import com.ning.billing.analytics.dao.BusinessAccountDao;
 import com.ning.billing.invoice.api.Invoice;
 import com.ning.billing.invoice.api.InvoiceUserApi;
 import com.ning.billing.payment.api.PaymentApi;
+import com.ning.billing.payment.api.PaymentApiException;
 import com.ning.billing.payment.api.PaymentAttempt;
-import com.ning.billing.payment.api.PaymentInfo;
+import com.ning.billing.payment.api.PaymentInfoEvent;
 import com.ning.billing.util.tag.Tag;
-import org.joda.time.DateTime;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.math.BigDecimal;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.UUID;
 
 public class BusinessAccountRecorder {
     private static final Logger log = LoggerFactory.getLogger(BusinessAccountRecorder.class);
@@ -54,11 +57,16 @@ public class BusinessAccountRecorder {
     }
 
     public void accountCreated(final AccountData data) {
-        final Account account = accountApi.getAccountByKey(data.getExternalKey());
-        final BusinessAccount bac = createBusinessAccountFromAccount(account);
+        Account account;
+        try {
+            account = accountApi.getAccountByKey(data.getExternalKey());
+            final BusinessAccount bac = createBusinessAccountFromAccount(account);
 
-        log.info("ACCOUNT CREATION " + bac);
-        dao.createAccount(bac);
+            log.info("ACCOUNT CREATION " + bac);
+            dao.createAccount(bac);
+        } catch (AccountApiException e) {
+            log.warn("Error encountered creating BusinessAccount",e);
+        }
     }
 
     /**
@@ -77,18 +85,20 @@ public class BusinessAccountRecorder {
      *
      * @param paymentInfo payment object (from the payment plugin)
      */
-    public void accountUpdated(final PaymentInfo paymentInfo) {
-        final PaymentAttempt paymentAttempt = paymentApi.getPaymentAttemptForPaymentId(paymentInfo.getPaymentId());
-        if (paymentAttempt == null) {
-            return;
-        }
+    public void accountUpdated(final PaymentInfoEvent paymentInfo) {
+        try {
+            final PaymentAttempt paymentAttempt = paymentApi.getPaymentAttemptForPaymentId(paymentInfo.getId());
+            if (paymentAttempt == null) {
+                return;
+            }
 
-        final Account account = accountApi.getAccountById(paymentAttempt.getAccountId());
-        if (account == null) {
-            return;
+            final Account account = accountApi.getAccountById(paymentAttempt.getAccountId());
+            accountUpdated(account.getId());
+        } catch (AccountApiException e) {
+            log.warn("Error encountered creating BusinessAccount",e);
+        } catch (PaymentApiException e) {
+            log.warn("Error encountered creating BusinessAccount",e);            
         }
-
-        accountUpdated(account.getId());
     }
 
     /**
@@ -97,23 +107,28 @@ public class BusinessAccountRecorder {
      * @param accountId account id associated with the created invoice
      */
     public void accountUpdated(final UUID accountId) {
-        final Account account = accountApi.getAccountById(accountId);
+        try {
+            final Account account = accountApi.getAccountById(accountId);
 
-        if (account == null) {
-            log.warn("Couldn't find account {}", accountId);
-            return;
-        }
+            if (account == null) {
+                log.warn("Couldn't find account {}", accountId);
+                return;
+            }
 
-        BusinessAccount bac = dao.getAccount(account.getExternalKey());
-        if (bac == null) {
-            bac = createBusinessAccountFromAccount(account);
-            log.info("ACCOUNT CREATION " + bac);
-            dao.createAccount(bac);
-        } else {
-            updateBusinessAccountFromAccount(account, bac);
-            log.info("ACCOUNT UPDATE " + bac);
-            dao.saveAccount(bac);
+            BusinessAccount bac = dao.getAccount(account.getExternalKey());
+            if (bac == null) {
+                bac = createBusinessAccountFromAccount(account);
+                log.info("ACCOUNT CREATION " + bac);
+                dao.createAccount(bac);
+            } else {
+                updateBusinessAccountFromAccount(account, bac);
+                log.info("ACCOUNT UPDATE " + bac);
+                dao.saveAccount(bac);
+            }
+        } catch (AccountApiException e) {
+            log.warn("Error encountered creating BusinessAccount",e);
         }
+
     }
 
     private BusinessAccount createBusinessAccountFromAccount(final Account account) {
@@ -161,30 +176,25 @@ public class BusinessAccountRecorder {
             }
 
             // Retrieve payments information for these invoices
-            DateTime lastPaymentDate = null;
-            final List<PaymentInfo> payments = paymentApi.getPaymentInfo(invoiceIds);
-            if (payments != null) {
-                for (final PaymentInfo payment : payments) {
-                    // Use the last payment method/type/country as the default one for the account
-                    if (lastPaymentDate == null || payment.getCreatedDate().isAfter(lastPaymentDate)) {
-                        lastPaymentDate = payment.getCreatedDate();
-
-                        lastPaymentStatus = payment.getStatus();
-                        paymentMethod = payment.getPaymentMethod();
-                        creditCardType = payment.getCardType();
-                        billingAddressCountry = payment.getCardCountry();
-                    }
-                }
+            try {
+                final PaymentInfoEvent payment = paymentApi.getLastPaymentInfo(invoiceIds);
+
+                lastPaymentStatus = payment.getStatus();
+                paymentMethod = payment.getPaymentMethod();
+                creditCardType = payment.getCardType();
+                billingAddressCountry = payment.getCardCountry();
+
+                bac.setLastPaymentStatus(lastPaymentStatus);
+                bac.setPaymentMethod(paymentMethod);
+                bac.setCreditCardType(creditCardType);
+                bac.setBillingAddressCountry(billingAddressCountry);
+                bac.setLastInvoiceDate(lastInvoiceDate);
+                bac.setTotalInvoiceBalance(totalInvoiceBalance);
+
+                bac.setBalance(invoiceUserApi.getAccountBalance(account.getId()));
+            } catch (PaymentApiException ex) {
+                // TODO: handle this exception
             }
         }
-
-        bac.setLastPaymentStatus(lastPaymentStatus);
-        bac.setPaymentMethod(paymentMethod);
-        bac.setCreditCardType(creditCardType);
-        bac.setBillingAddressCountry(billingAddressCountry);
-        bac.setLastInvoiceDate(lastInvoiceDate);
-        bac.setTotalInvoiceBalance(totalInvoiceBalance);
-
-        bac.setBalance(invoiceUserApi.getAccountBalance(account.getId()));
     }
 }
diff --git a/analytics/src/main/java/com/ning/billing/analytics/BusinessSubscription.java b/analytics/src/main/java/com/ning/billing/analytics/BusinessSubscription.java
index 71e3402..8b00dff 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/BusinessSubscription.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/BusinessSubscription.java
@@ -17,6 +17,7 @@
 package com.ning.billing.analytics;
 
 import com.ning.billing.analytics.utils.Rounder;
+import com.ning.billing.catalog.api.Catalog;
 import com.ning.billing.catalog.api.CatalogApiException;
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.catalog.api.Duration;
@@ -87,17 +88,98 @@ public class BusinessSubscription
      * want the phase start date).
      *
      * @param subscription Subscription to use as a model
-     * @param currency     Account currency
+     * @param currency     ACCOUNT currency
      */
-    BusinessSubscription(final Subscription subscription, final Currency currency)
+    BusinessSubscription(final Subscription subscription, final Currency currency, Catalog catalog)
     {
-        this(subscription.getCurrentPriceList(), subscription.getCurrentPlan(), subscription.getCurrentPhase(), currency, subscription.getStartDate(), subscription.getState(), subscription.getId(), subscription.getBundleId());
+        this(subscription.getCurrentPriceList() == null ? null : subscription.getCurrentPriceList().getName(), subscription.getCurrentPlan().getName(), subscription.getCurrentPhase().getName(), currency, subscription.getStartDate(), subscription.getState(), subscription.getId(), subscription.getBundleId(), catalog);
     }
 
+    public BusinessSubscription(final String priceList, final String currentPlan, final String currentPhase, final Currency currency, final DateTime startDate, final SubscriptionState state, final UUID subscriptionId, final UUID bundleId, Catalog catalog) {
+        Plan thePlan = null;
+        PlanPhase thePhase = null;
+        try {
+            thePlan = (currentPlan != null) ? catalog.findPlan(currentPlan, new DateTime(), startDate) : null;
+            thePhase = (currentPhase != null) ? catalog.findPhase(currentPhase, new DateTime(), startDate) : null;
+        } catch (CatalogApiException e) {
+            log.error(String.format("Failed to retrieve Plan from catalog for plan %s, phase ", currentPlan, currentPhase));
+        }
+        
+      this.priceList = priceList;
+        
+        // Record plan information
+        if (currentPlan != null && thePlan.getProduct() != null) {
+            final Product product = thePlan.getProduct();
+            productName = product.getName();
+            productCategory = product.getCategory();
+            // TODO - we should keep the product type
+            productType = product.getCatalogName();
+        }
+        else {
+            productName = null;
+            productCategory = null;
+            productType = null;
+        }
+
+        // Record phase information
+        if (currentPhase != null) {
+            slug = thePhase.getName();
+
+            if (thePhase.getPhaseType() != null) {
+                phase = thePhase.getPhaseType().toString();
+            }
+            else {
+                phase = null;
+            }
+
+            if (thePhase.getBillingPeriod() != null) {
+                billingPeriod = thePhase.getBillingPeriod().toString();
+            }
+            else {
+                billingPeriod = null;
+            }
+
+            if (thePhase.getRecurringPrice() != null) {
+                //TODO check if this is the right way to handle exception
+                BigDecimal tmpPrice = null;
+                try {
+                    tmpPrice = thePhase.getRecurringPrice().getPrice(USD);
+                } catch (CatalogApiException e) {
+                    tmpPrice = new BigDecimal(0);
+                }
+                price = tmpPrice;
+                mrr = getMrrFromISubscription(thePhase.getDuration(), price);
+            }
+            else {
+                price = BigDecimal.ZERO;
+                mrr = BigDecimal.ZERO;
+            }
+        }
+        else {
+            slug = null;
+            phase = null;
+            billingPeriod = null;
+            price = BigDecimal.ZERO;
+            mrr = BigDecimal.ZERO;
+        }
+
+        if (currency != null) {
+            this.currency = currency.toString();
+        }
+        else {
+            this.currency = null;
+        }
+
+        this.startDate = startDate;
+        this.state = state;
+        this.subscriptionId = subscriptionId;
+        this.bundleId = bundleId;
+    }
+    
     public BusinessSubscription(final String priceList, final Plan currentPlan, final PlanPhase currentPhase, final Currency currency, final DateTime startDate, final SubscriptionState state, final UUID subscriptionId, final UUID bundleId)
     {
         this.priceList = priceList;
-
+        
         // Record plan information
         if (currentPlan != null && currentPlan.getProduct() != null) {
             final Product product = currentPlan.getProduct();
diff --git a/analytics/src/main/java/com/ning/billing/analytics/BusinessSubscriptionEvent.java b/analytics/src/main/java/com/ning/billing/analytics/BusinessSubscriptionEvent.java
index 2d45fd5..b1668dc 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/BusinessSubscriptionEvent.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/BusinessSubscriptionEvent.java
@@ -16,6 +16,13 @@
 
 package com.ning.billing.analytics;
 
+import org.joda.time.DateTime;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.ning.billing.catalog.api.Catalog;
+import com.ning.billing.catalog.api.CatalogApiException;
+import com.ning.billing.catalog.api.CatalogService;
 import com.ning.billing.catalog.api.Plan;
 import com.ning.billing.catalog.api.Product;
 import com.ning.billing.catalog.api.ProductCategory;
@@ -27,6 +34,9 @@ import static com.ning.billing.entitlement.api.user.Subscription.SubscriptionSta
  */
 public class BusinessSubscriptionEvent
 {
+    
+    private static final Logger log = LoggerFactory.getLogger(BusinessSubscriptionEvent.class);
+    
     private static final String MISC = "MISC";
 
     public enum EventType
@@ -78,44 +88,52 @@ public class BusinessSubscriptionEvent
         return eventType;
     }
 
-    public static BusinessSubscriptionEvent subscriptionCreated(final Plan plan)
+    public static BusinessSubscriptionEvent subscriptionCreated(final String plan, Catalog catalog, DateTime eventTime, DateTime subscriptionCreationDate)
     {
-        return eventFromType(EventType.ADD, plan);
+        return eventFromType(EventType.ADD, plan, catalog, eventTime, subscriptionCreationDate);
     }
 
-    public static BusinessSubscriptionEvent subscriptionCancelled(final Plan plan)
+    public static BusinessSubscriptionEvent subscriptionCancelled(final String plan, Catalog catalog, DateTime eventTime, DateTime subscriptionCreationDate)
     {
-        return eventFromType(EventType.CANCEL, plan);
+        return eventFromType(EventType.CANCEL, plan, catalog, eventTime, subscriptionCreationDate);
     }
 
-    public static BusinessSubscriptionEvent subscriptionChanged(final Plan plan)
+    public static BusinessSubscriptionEvent subscriptionChanged(final String plan, Catalog catalog, DateTime eventTime, DateTime subscriptionCreationDate)
     {
-        return eventFromType(EventType.CHANGE, plan);
+        return eventFromType(EventType.CHANGE, plan, catalog, eventTime, subscriptionCreationDate);
     }
 
-    public static BusinessSubscriptionEvent subscriptionRecreated(final Plan plan)
+    public static BusinessSubscriptionEvent subscriptionRecreated(final String plan, Catalog catalog, DateTime eventTime, DateTime subscriptionCreationDate)
     {
-        return eventFromType(EventType.RE_ADD, plan);
+        return eventFromType(EventType.RE_ADD, plan, catalog, eventTime, subscriptionCreationDate);
     }
 
-    public static BusinessSubscriptionEvent subscriptionPhaseChanged(final Plan plan, final SubscriptionState state)
+    public static BusinessSubscriptionEvent subscriptionPhaseChanged(final String plan, final SubscriptionState state, Catalog catalog, DateTime eventTime, DateTime subscriptionCreationDate)
     {
         if (state != null && state.equals(SubscriptionState.CANCELLED)) {
-            return eventFromType(EventType.SYSTEM_CANCEL, plan);
+            return eventFromType(EventType.SYSTEM_CANCEL, plan, catalog, eventTime, subscriptionCreationDate);
         }
         else {
-            return eventFromType(EventType.SYSTEM_CHANGE, plan);
+            return eventFromType(EventType.SYSTEM_CHANGE, plan, catalog, eventTime, subscriptionCreationDate);
         }
     }
 
-    private static BusinessSubscriptionEvent eventFromType(final EventType eventType, final Plan plan)
+    private static BusinessSubscriptionEvent eventFromType(final EventType eventType, final String plan, Catalog catalog, DateTime eventTime, DateTime subscriptionCreationDate)
     {
-        final ProductCategory category = getTypeFromSubscription(plan);
+        Plan thePlan = null;
+        try {
+            thePlan = catalog.findPlan(plan, eventTime, subscriptionCreationDate);
+        } catch (CatalogApiException e) {
+            log.error(String.format("Failed to retrieve PLan from catalog for %s", plan));
+            
+        }
+        final ProductCategory category = getTypeFromSubscription(thePlan);
         return new BusinessSubscriptionEvent(eventType, category);
     }
 
     private static ProductCategory getTypeFromSubscription(final Plan plan)
     {
+        
         if (plan != null && plan.getProduct() != null) {
             final Product product = plan.getProduct();
             if (product.getCatalogName() != null && product.getCategory() != null) {
diff --git a/analytics/src/main/java/com/ning/billing/analytics/BusinessSubscriptionTransitionRecorder.java b/analytics/src/main/java/com/ning/billing/analytics/BusinessSubscriptionTransitionRecorder.java
index 09fbc96..0044758 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/BusinessSubscriptionTransitionRecorder.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/BusinessSubscriptionTransitionRecorder.java
@@ -21,10 +21,12 @@ 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.analytics.dao.BusinessSubscriptionTransitionDao;
+import com.ning.billing.catalog.api.CatalogService;
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.entitlement.api.user.EntitlementUserApi;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
 import com.ning.billing.entitlement.api.user.SubscriptionBundle;
-import com.ning.billing.entitlement.api.user.SubscriptionTransition;
+import com.ning.billing.entitlement.api.user.SubscriptionEvent;
 import org.joda.time.DateTime;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -39,48 +41,54 @@ public class BusinessSubscriptionTransitionRecorder
     private final BusinessSubscriptionTransitionDao dao;
     private final EntitlementUserApi entitlementApi;
     private final AccountUserApi accountApi;
+    private final CatalogService catalogService;
 
     @Inject
-    public BusinessSubscriptionTransitionRecorder(final BusinessSubscriptionTransitionDao dao, final EntitlementUserApi entitlementApi, final AccountUserApi accountApi)
+    public BusinessSubscriptionTransitionRecorder(final BusinessSubscriptionTransitionDao dao, final CatalogService catalogService, final EntitlementUserApi entitlementApi, final AccountUserApi accountApi)
     {
         this.dao = dao;
+        this.catalogService = catalogService;
         this.entitlementApi = entitlementApi;
         this.accountApi = accountApi;
     }
 
-    public void subscriptionCreated(final SubscriptionTransition created) throws AccountApiException
+
+    public void subscriptionCreated(final SubscriptionEvent created) throws AccountApiException, EntitlementUserApiException
     {
-        final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionCreated(created.getNextPlan());
+        final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionCreated(created.getNextPlan(), catalogService.getFullCatalog(), created.getEffectiveTransitionTime(), created.getSubscriptionStartDate());
         recordTransition(event, created);
     }
 
-    public void subscriptionRecreated(final SubscriptionTransition recreated) throws AccountApiException
+
+    public void subscriptionRecreated(final SubscriptionEvent recreated) throws AccountApiException, EntitlementUserApiException
     {
-        final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionRecreated(recreated.getNextPlan());
+        final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionRecreated(recreated.getNextPlan(), catalogService.getFullCatalog(), recreated.getEffectiveTransitionTime(), recreated.getSubscriptionStartDate());
         recordTransition(event, recreated);
     }
 
 
-    public void subscriptionCancelled(final SubscriptionTransition cancelled) throws AccountApiException
+    public void subscriptionCancelled(final SubscriptionEvent cancelled) throws AccountApiException, EntitlementUserApiException
     {
         // cancelled.getNextPlan() is null here - need to look at the previous one to create the correct event name
-        final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionCancelled(cancelled.getPreviousPlan());
+        final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionCancelled(cancelled.getPreviousPlan(), catalogService.getFullCatalog(), cancelled.getEffectiveTransitionTime(), cancelled.getSubscriptionStartDate());
         recordTransition(event, cancelled);
     }
 
-    public void subscriptionChanged(final SubscriptionTransition changed) throws AccountApiException
+
+    public void subscriptionChanged(final SubscriptionEvent changed) throws AccountApiException, EntitlementUserApiException
     {
-        final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionChanged(changed.getNextPlan());
+        final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionChanged(changed.getNextPlan(), catalogService.getFullCatalog(), changed.getEffectiveTransitionTime(), changed.getSubscriptionStartDate());
         recordTransition(event, changed);
     }
 
-    public void subscriptionPhaseChanged(final SubscriptionTransition phaseChanged) throws AccountApiException
+    public void subscriptionPhaseChanged(final SubscriptionEvent phaseChanged) throws AccountApiException, EntitlementUserApiException
     {
-        final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionPhaseChanged(phaseChanged.getNextPlan(), phaseChanged.getNextState());
+        final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionPhaseChanged(phaseChanged.getNextPlan(), phaseChanged.getNextState(), catalogService.getFullCatalog(), phaseChanged.getEffectiveTransitionTime(), phaseChanged.getSubscriptionStartDate());
         recordTransition(event, phaseChanged);
     }
 
-    public void recordTransition(final BusinessSubscriptionEvent event, final SubscriptionTransition transition) throws AccountApiException
+    public void recordTransition(final BusinessSubscriptionEvent event, final SubscriptionEvent transition)
+        throws AccountApiException, EntitlementUserApiException
     {
         Currency currency = null;
         String transitionKey = null;
@@ -90,13 +98,13 @@ public class BusinessSubscriptionTransitionRecorder
         final SubscriptionBundle bundle = entitlementApi.getBundleFromId(transition.getBundleId());
         if (bundle != null) {
             transitionKey = bundle.getKey();
-
+            
             final Account account = accountApi.getAccountById(bundle.getAccountId());
             if (account != null) {
                 accountKey = account.getExternalKey();
                 currency = account.getCurrency();
             }
-        }
+        }   
 
         // The ISubscriptionTransition interface gives us all the prev/next information we need but the start date
         // of the previous plan. We need to retrieve it from our own transitions table
@@ -115,7 +123,8 @@ public class BusinessSubscriptionTransitionRecorder
             prevSubscription = null;
         }
         else {
-            prevSubscription = new BusinessSubscription(transition.getPreviousPriceList(), transition.getPreviousPlan(), transition.getPreviousPhase(), currency, previousEffectiveTransitionTime, transition.getPreviousState(), transition.getSubscriptionId(), transition.getBundleId());
+            
+            prevSubscription = new BusinessSubscription(transition.getPreviousPriceList(), transition.getPreviousPlan(), transition.getPreviousPhase(), currency, previousEffectiveTransitionTime, transition.getPreviousState(), transition.getSubscriptionId(), transition.getBundleId(), catalogService.getFullCatalog());
         }
         final BusinessSubscription nextSubscription;
 
@@ -124,7 +133,7 @@ public class BusinessSubscriptionTransitionRecorder
             nextSubscription = null;
         }
         else {
-            nextSubscription = new BusinessSubscription(transition.getNextPriceList(), transition.getNextPlan(), transition.getNextPhase(), currency, transition.getEffectiveTransitionTime(), transition.getNextState(), transition.getSubscriptionId(), transition.getBundleId());
+            nextSubscription = new BusinessSubscription(transition.getNextPriceList(), transition.getNextPlan(), transition.getNextPhase(), currency, transition.getEffectiveTransitionTime(), transition.getNextState(), transition.getSubscriptionId(), transition.getBundleId(), catalogService.getFullCatalog());
         }
 
         record(transition.getId(), transitionKey, accountKey, transition.getRequestedTransitionTime(), event, prevSubscription, nextSubscription);
diff --git a/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessAccountBinder.java b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessAccountBinder.java
index 6478536..b9da3fa 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessAccountBinder.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessAccountBinder.java
@@ -50,12 +50,12 @@ public @interface BusinessAccountBinder
                     final DateTime dateTimeNow = new DateTime(DateTimeZone.UTC);
 
                     if (account.getCreatedDt() != null) {
-                        q.bind("created_dt", account.getCreatedDt().getMillis());
+                        q.bind("created_date", account.getCreatedDt().getMillis());
                     }
                     else {
-                        q.bind("created_dt", dateTimeNow.getMillis());
+                        q.bind("created_date", dateTimeNow.getMillis());
                     }
-                    q.bind("updated_dt", dateTimeNow.getMillis());
+                    q.bind("updated_date", dateTimeNow.getMillis());
 
                     q.bind("account_key", account.getKey());
                     q.bind("balance", account.getRoundedBalance());
diff --git a/analytics/src/main/resources/com/ning/billing/analytics/dao/BusinessAccountDao.sql.stg b/analytics/src/main/resources/com/ning/billing/analytics/dao/BusinessAccountDao.sql.stg
index 55f03e8..10f27b8 100644
--- a/analytics/src/main/resources/com/ning/billing/analytics/dao/BusinessAccountDao.sql.stg
+++ b/analytics/src/main/resources/com/ning/billing/analytics/dao/BusinessAccountDao.sql.stg
@@ -3,8 +3,8 @@ group BusinessAccount;
 getAccount(account_key) ::= <<
   select
     account_key
-  , created_dt
-  , updated_dt
+  , created_date
+  , updated_date
   , balance
   , tags
   , last_invoice_date
@@ -22,8 +22,8 @@ getAccount(account_key) ::= <<
 createAccount() ::= <<
   insert into bac(
     account_key
-  , created_dt
-  , updated_dt
+  , created_date
+  , updated_date
   , balance
   , tags
   , last_invoice_date
@@ -34,8 +34,8 @@ createAccount() ::= <<
   , billing_address_country
   ) values (
     :account_key
-  , :created_dt
-  , :updated_dt
+  , :created_date
+  , :updated_date
   , :balance
   , :tags
   , :last_invoice_date
@@ -49,7 +49,7 @@ createAccount() ::= <<
 
 saveAccount() ::= <<
   update bac set
-    updated_dt=:updated_dt
+    updated_date=:updated_date
   , balance=:balance
   , tags=:tags
   , last_invoice_date=:last_invoice_date
diff --git a/analytics/src/main/resources/com/ning/billing/analytics/ddl.sql b/analytics/src/main/resources/com/ning/billing/analytics/ddl.sql
index 6489b12..7240236 100644
--- a/analytics/src/main/resources/com/ning/billing/analytics/ddl.sql
+++ b/analytics/src/main/resources/com/ning/billing/analytics/ddl.sql
@@ -40,8 +40,8 @@ create index bst_key_index on bst (event_key, requested_timestamp asc);
 drop table if exists bac;
 create table bac (
   account_key varchar(50) not null
-, created_dt bigint not null
-, updated_dt bigint not null
+, created_date bigint not null
+, updated_date bigint not null
 , balance numeric(10, 4) default 0
 , tags varchar(500) default null
 , last_invoice_date bigint default null
diff --git a/analytics/src/test/java/com/ning/billing/analytics/AnalyticsTestModule.java b/analytics/src/test/java/com/ning/billing/analytics/AnalyticsTestModule.java
index 1aafa7c..e1df28b 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/AnalyticsTestModule.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/AnalyticsTestModule.java
@@ -16,25 +16,25 @@
 
 package com.ning.billing.analytics;
 
-import com.ning.billing.invoice.glue.InvoiceModule;
-import com.ning.billing.payment.setup.PaymentModule;
-import com.ning.billing.util.glue.CallContextModule;
-import com.ning.billing.util.glue.FieldStoreModule;
-import com.ning.billing.util.tag.dao.TagDefinitionSqlDao;
 import org.skife.jdbi.v2.IDBI;
+
 import com.ning.billing.account.glue.AccountModule;
 import com.ning.billing.analytics.setup.AnalyticsModule;
-import com.ning.billing.catalog.glue.CatalogModule;
 import com.ning.billing.dbi.MysqlTestingHelper;
-import com.ning.billing.entitlement.glue.EntitlementModule;
-
+import com.ning.billing.entitlement.glue.DefaultEntitlementModule;
+import com.ning.billing.invoice.glue.DefaultInvoiceModule;
+import com.ning.billing.junction.glue.DefaultJunctionModule;
+import com.ning.billing.payment.setup.PaymentModule;
+import com.ning.billing.util.email.EmailModule;
+import com.ning.billing.util.email.templates.TemplateModule;
 import com.ning.billing.util.glue.BusModule;
-
+import com.ning.billing.util.glue.CallContextModule;
 import com.ning.billing.util.glue.ClockModule;
+import com.ning.billing.util.glue.FieldStoreModule;
+import com.ning.billing.util.glue.GlobalLockerModule;
 import com.ning.billing.util.glue.NotificationQueueModule;
 import com.ning.billing.util.glue.TagStoreModule;
-
-import java.lang.reflect.Field;
+import com.ning.billing.util.tag.dao.TagDefinitionSqlDao;
 
 public class AnalyticsTestModule extends AnalyticsModule
 {
@@ -44,18 +44,21 @@ public class AnalyticsTestModule extends AnalyticsModule
         super.configure();
 
         // Need to configure a few more things for the EventBus
+        install(new EmailModule());
+        install(new GlobalLockerModule());
         install(new ClockModule());
         install(new CallContextModule());
         install(new FieldStoreModule());
         install(new TagStoreModule());
         install(new AccountModule());
-        install(new CatalogModule());
         install(new BusModule());
-        install(new EntitlementModule());
-        install(new InvoiceModule());
+        install(new DefaultEntitlementModule());
+        install(new DefaultInvoiceModule());
+        install(new TemplateModule());
         install(new PaymentModule());
         install(new TagStoreModule());
         install(new NotificationQueueModule());
+        install(new DefaultJunctionModule());
 
         // Install the Dao layer
         final MysqlTestingHelper helper = new MysqlTestingHelper();
diff --git a/analytics/src/test/java/com/ning/billing/analytics/api/TestAnalyticsService.java b/analytics/src/test/java/com/ning/billing/analytics/api/TestAnalyticsService.java
index 495e985..1477dfa 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/api/TestAnalyticsService.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/api/TestAnalyticsService.java
@@ -26,23 +26,19 @@ import java.util.Arrays;
 import java.util.List;
 import java.util.UUID;
 
-import com.ning.billing.invoice.api.Invoice;
-import com.ning.billing.util.callcontext.CallContext;
-import com.ning.billing.util.callcontext.CallOrigin;
-import com.ning.billing.util.callcontext.UserType;
-import com.ning.billing.util.callcontext.DefaultCallContextFactory;
+import com.ning.billing.payment.api.DefaultPaymentAttempt;
+import com.ning.billing.util.tag.TagDefinition;
 import org.apache.commons.io.IOUtils;
 import org.joda.time.DateTime;
 import org.testng.Assert;
 import org.testng.annotations.AfterClass;
 import org.testng.annotations.BeforeClass;
-import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Guice;
 import org.testng.annotations.Test;
 
 import com.google.inject.Inject;
 import com.ning.billing.account.api.Account;
-import com.ning.billing.account.api.AccountCreationNotification;
+import com.ning.billing.account.api.AccountCreationEvent;
 import com.ning.billing.account.api.AccountUserApi;
 import com.ning.billing.account.api.user.DefaultAccountCreationEvent;
 import com.ning.billing.analytics.AnalyticsTestModule;
@@ -56,45 +52,62 @@ import com.ning.billing.analytics.MockPlan;
 import com.ning.billing.analytics.MockProduct;
 import com.ning.billing.analytics.dao.BusinessAccountDao;
 import com.ning.billing.analytics.dao.BusinessSubscriptionTransitionDao;
+import com.ning.billing.catalog.MockCatalogModule;
+import com.ning.billing.catalog.MockPriceList;
+import com.ning.billing.catalog.api.Catalog;
+import com.ning.billing.catalog.api.CatalogService;
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.catalog.api.PhaseType;
 import com.ning.billing.catalog.api.Plan;
 import com.ning.billing.catalog.api.PlanPhase;
+import com.ning.billing.catalog.api.PriceList;
 import com.ning.billing.catalog.api.Product;
 import com.ning.billing.catalog.api.ProductCategory;
 import com.ning.billing.dbi.MysqlTestingHelper;
+import com.ning.billing.entitlement.api.user.DefaultSubscriptionEvent;
 import com.ning.billing.entitlement.api.user.EntitlementUserApi;
 import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
 import com.ning.billing.entitlement.api.user.Subscription;
 import com.ning.billing.entitlement.api.user.SubscriptionBundle;
-import com.ning.billing.entitlement.api.user.SubscriptionTransition;
+import com.ning.billing.entitlement.api.user.SubscriptionEvent;
 import com.ning.billing.entitlement.api.user.SubscriptionTransitionData;
 import com.ning.billing.entitlement.events.EntitlementEvent;
 import com.ning.billing.entitlement.events.user.ApiEventType;
-import com.ning.billing.invoice.api.InvoiceCreationNotification;
-import com.ning.billing.invoice.api.user.DefaultInvoiceCreationNotification;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceCreationEvent;
+import com.ning.billing.invoice.api.user.DefaultInvoiceCreationEvent;
 import com.ning.billing.invoice.dao.InvoiceDao;
 import com.ning.billing.invoice.model.DefaultInvoice;
 import com.ning.billing.invoice.model.FixedPriceInvoiceItem;
+import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
+import com.ning.billing.payment.api.DefaultPaymentInfoEvent;
 import com.ning.billing.payment.api.PaymentAttempt;
-import com.ning.billing.payment.api.PaymentInfo;
+import com.ning.billing.payment.api.PaymentInfoEvent;
 import com.ning.billing.payment.dao.PaymentDao;
 import com.ning.billing.util.bus.Bus;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.callcontext.CallOrigin;
+import com.ning.billing.util.callcontext.DefaultCallContextFactory;
+import com.ning.billing.util.callcontext.UserType;
 import com.ning.billing.util.clock.Clock;
 import com.ning.billing.util.clock.DefaultClock;
 import com.ning.billing.util.tag.DefaultTagDefinition;
-import com.ning.billing.util.tag.DescriptiveTag;
-import com.ning.billing.util.tag.Tag;
 import com.ning.billing.util.tag.dao.TagDefinitionSqlDao;
 
-@Guice(modules = AnalyticsTestModule.class)
+@Guice(modules = {AnalyticsTestModule.class, MockCatalogModule.class})
 public class TestAnalyticsService {
+    
+    final Product product = new MockProduct("platinum", "subscription", ProductCategory.BASE);
+    final Plan plan = new MockPlan("platinum-monthly", product);
+    final PlanPhase phase = new MockPhase(PhaseType.EVERGREEN, plan, MockDuration.UNLIMITED(), 25.95);
+
+
     private static final UUID ID = UUID.randomUUID();
     private static final String KEY = "12345";
     private static final String ACCOUNT_KEY = "pierre-12345";
     private static final Currency ACCOUNT_CURRENCY = Currency.EUR;
-    private static final DefaultTagDefinition TAG_ONE = new DefaultTagDefinition("batch20", "something");
-    private static final DefaultTagDefinition TAG_TWO = new DefaultTagDefinition("awesome", "something");
+    private static final DefaultTagDefinition TAG_ONE = new DefaultTagDefinition("batch20", "something", false);
+    private static final DefaultTagDefinition TAG_TWO = new DefaultTagDefinition("awesome", "something", false);
     private static final BigDecimal INVOICE_AMOUNT = BigDecimal.valueOf(1243.11);
     private static final String PAYMENT_METHOD = "Paypal";
     private static final String CARD_COUNTRY = "France";
@@ -132,36 +145,40 @@ public class TestAnalyticsService {
     @Inject
     private MysqlTestingHelper helper;
 
-    private SubscriptionTransition transition;
+    private SubscriptionEvent transition;
     private BusinessSubscriptionTransition expectedTransition;
 
-    private AccountCreationNotification accountCreationNotification;
-    private InvoiceCreationNotification invoiceCreationNotification;
-    private PaymentInfo paymentInfoNotification;
-
-    @BeforeMethod(groups = "slow")
-    public void cleanup() throws Exception
-    {
-        helper.cleanupTable("bst");
-        helper.cleanupTable("bac");
-    }
-
+    private AccountCreationEvent accountCreationNotification;
+    private InvoiceCreationEvent invoiceCreationNotification;
+    private PaymentInfoEvent paymentInfoNotification;
 
+    @Inject
+    private  CatalogService catalogService;
+    
+    private Catalog catalog;
+ 
     @BeforeClass(groups = "slow")
     public void startMysql() throws IOException, ClassNotFoundException, SQLException, EntitlementUserApiException {
+
+        catalog = catalogService.getFullCatalog();
+        ((ZombieControl) catalog).addResult("findPlan", plan);
+        ((ZombieControl) catalog).addResult("findPhase", phase);        
+
         // Killbill generic setup
         setupBusAndMySQL();
 
+        helper.cleanupAllTables();
+        
         tagDao.create(TAG_ONE, context);
         tagDao.create(TAG_TWO, context);
 
         final MockAccount account = new MockAccount(UUID.randomUUID(), ACCOUNT_KEY, ACCOUNT_CURRENCY);
         try {
-            final List<Tag> tags = new ArrayList<Tag>();
-            tags.add(new DescriptiveTag(TAG_ONE));
-            tags.add(new DescriptiveTag(TAG_TWO));
+            final List<TagDefinition> tagDefinitions = new ArrayList<TagDefinition>();
+            tagDefinitions.add(TAG_ONE);
+            tagDefinitions.add(TAG_TWO);
 
-            final Account storedAccount = accountApi.createAccount(account, null, tags, context);
+            final Account storedAccount = accountApi.createAccount(account, null, tagDefinitions, context);
 
             // Create events for the bus and expected results
             createSubscriptionTransitionEvent(storedAccount);
@@ -173,7 +190,6 @@ public class TestAnalyticsService {
     }
 
     private void setupBusAndMySQL() throws IOException {
-        bus.start();
 
         final String analyticsDdl = IOUtils.toString(BusinessSubscriptionTransitionDao.class.getResourceAsStream("/com/ning/billing/analytics/ddl.sql"));
         final String accountDdl = IOUtils.toString(BusinessSubscriptionTransitionDao.class.getResourceAsStream("/com/ning/billing/account/ddl.sql"));
@@ -181,6 +197,7 @@ public class TestAnalyticsService {
         final String invoiceDdl = IOUtils.toString(BusinessSubscriptionTransitionDao.class.getResourceAsStream("/com/ning/billing/invoice/ddl.sql"));
         final String paymentDdl = IOUtils.toString(BusinessSubscriptionTransitionDao.class.getResourceAsStream("/com/ning/billing/payment/ddl.sql"));
         final String utilDdl = IOUtils.toString(BusinessSubscriptionTransitionDao.class.getResourceAsStream("/com/ning/billing/util/ddl.sql"));
+        final String junctionDdl = IOUtils.toString(BusinessSubscriptionTransitionDao.class.getResourceAsStream("/com/ning/billing/junction/ddl.sql"));
 
         helper.startMysql();
         helper.initDb(analyticsDdl);
@@ -189,9 +206,11 @@ public class TestAnalyticsService {
         helper.initDb(invoiceDdl);
         helper.initDb(paymentDdl);
         helper.initDb(utilDdl);
+        helper.initDb(junctionDdl);
 
-        helper.cleanupTable("tag_definitions");
-        helper.cleanupTable("accounts");
+        helper.cleanupAllTables();
+    	
+        bus.start();
     }
 
     private void createSubscriptionTransitionEvent(final Account account) throws EntitlementUserApiException {
@@ -202,15 +221,13 @@ public class TestAnalyticsService {
         Assert.assertEquals(bundle.getKey(), KEY);
 
         // Create a subscription transition event
-        final Product product = new MockProduct("platinum", "subscription", ProductCategory.BASE);
-        final Plan plan = new MockPlan("platinum-monthly", product);
-        final PlanPhase phase = new MockPhase(PhaseType.EVERGREEN, plan, MockDuration.UNLIMITED(), 25.95);
         final UUID subscriptionId = UUID.randomUUID();
         final DateTime effectiveTransitionTime = clock.getUTCNow();
         final DateTime requestedTransitionTime = clock.getUTCNow();
-        final String priceList = "something";
+        final PriceList priceList = new MockPriceList().setName("something");
 
-        transition = new SubscriptionTransitionData(
+        
+        transition = new DefaultSubscriptionEvent(new SubscriptionTransitionData(
                 ID,
                 subscriptionId,
                 bundle.getId(),
@@ -227,28 +244,28 @@ public class TestAnalyticsService {
                 phase,
                 priceList,
                 1L,
-                true
-        );
+                null,
+                true), null);
         expectedTransition = new BusinessSubscriptionTransition(
                 ID,
                 KEY,
                 ACCOUNT_KEY,
                 requestedTransitionTime,
-                BusinessSubscriptionEvent.subscriptionCreated(plan),
+                BusinessSubscriptionEvent.subscriptionCreated(plan.getName(), catalog, new DateTime(), new DateTime()),
                 null,
-                new BusinessSubscription(priceList, plan, phase, ACCOUNT_CURRENCY, effectiveTransitionTime, Subscription.SubscriptionState.ACTIVE, subscriptionId, bundle.getId())
+                new BusinessSubscription(priceList.getName(), plan.getName(), phase.getName(), ACCOUNT_CURRENCY, effectiveTransitionTime, Subscription.SubscriptionState.ACTIVE, subscriptionId, bundle.getId(), catalog)
         );
     }
 
     private void createAccountCreationEvent(final Account account) {
-        accountCreationNotification = new DefaultAccountCreationEvent(account);
+        accountCreationNotification = new DefaultAccountCreationEvent(account, null);
     }
 
     private void createInvoiceAndPaymentCreationEvents(final Account account) {
         final DefaultInvoice invoice = new DefaultInvoice(account.getId(), clock.getUTCNow(), clock.getUTCNow(), ACCOUNT_CURRENCY);
         final FixedPriceInvoiceItem invoiceItem = new FixedPriceInvoiceItem(
-                UUID.randomUUID(), invoice.getId(), account.getId(), UUID.randomUUID(), "somePlan", "somePhase", clock.getUTCNow(), clock.getUTCNow().plusDays(1),
-                INVOICE_AMOUNT, ACCOUNT_CURRENCY, context.getUserName(), clock.getUTCNow());
+                UUID.randomUUID(), invoice.getId(), account.getId(), UUID.randomUUID(), UUID.randomUUID(), "somePlan", "somePhase", clock.getUTCNow(), clock.getUTCNow().plusDays(1),
+                INVOICE_AMOUNT, ACCOUNT_CURRENCY);
         invoice.addInvoiceItem(invoiceItem);
 
         invoiceDao.create(invoice, context);
@@ -257,15 +274,15 @@ public class TestAnalyticsService {
         Assert.assertEquals(invoices.get(0).getInvoiceItems().size(), 1);
 
         // It doesn't really matter what the events contain - the listener will go back to the db
-        invoiceCreationNotification = new DefaultInvoiceCreationNotification(invoice.getId(), account.getId(),
-                INVOICE_AMOUNT, ACCOUNT_CURRENCY, clock.getUTCNow());
+        invoiceCreationNotification = new DefaultInvoiceCreationEvent(invoice.getId(), account.getId(),
+                INVOICE_AMOUNT, ACCOUNT_CURRENCY, clock.getUTCNow(), null);
 
-        paymentInfoNotification = new PaymentInfo.Builder().setPaymentId(UUID.randomUUID().toString()).setPaymentMethod(PAYMENT_METHOD).setCardCountry(CARD_COUNTRY).build();
-        final PaymentAttempt paymentAttempt = new PaymentAttempt(UUID.randomUUID(), invoice.getId(), account.getId(), BigDecimal.TEN,
-                ACCOUNT_CURRENCY, clock.getUTCNow(), clock.getUTCNow(), paymentInfoNotification.getPaymentId(), 1);
+        paymentInfoNotification = new DefaultPaymentInfoEvent.Builder().setId(UUID.randomUUID()).setPaymentMethod(PAYMENT_METHOD).setCardCountry(CARD_COUNTRY).build();
+        final PaymentAttempt paymentAttempt = new DefaultPaymentAttempt(UUID.randomUUID(), invoice.getId(), account.getId(), BigDecimal.TEN,
+                ACCOUNT_CURRENCY, clock.getUTCNow(), clock.getUTCNow(), paymentInfoNotification.getId(), 1, null, null);
         paymentDao.createPaymentAttempt(paymentAttempt, context);
         paymentDao.savePaymentInfo(paymentInfoNotification, context);
-        Assert.assertEquals(paymentDao.getPaymentInfo(Arrays.asList(invoice.getId().toString())).size(), 1);
+        Assert.assertEquals(paymentDao.getPaymentInfoList(Arrays.asList(invoice.getId().toString())).size(), 1);
     }
 
     @AfterClass(groups = "slow")
@@ -273,7 +290,7 @@ public class TestAnalyticsService {
         helper.stopMysql();
     }
 
-    @Test(groups = "slow")
+    @Test(groups = "slow", enabled=true)
     public void testRegisterForNotifications() throws Exception {
         // Make sure the service has been instantiated
         Assert.assertEquals(service.getName(), "analytics-service");
@@ -290,7 +307,7 @@ public class TestAnalyticsService {
         // Send events and wait for the async part...
         bus.post(transition);
         bus.post(accountCreationNotification);
-        Thread.sleep(1000);
+        Thread.sleep(5000);
 
         Assert.assertEquals(subscriptionDao.getTransitions(KEY).size(), 1);
         Assert.assertEquals(subscriptionDao.getTransitions(KEY).get(0), expectedTransition);
@@ -305,12 +322,12 @@ public class TestAnalyticsService {
 
         // Post the same invoice event again - the invoice balance shouldn't change
         bus.post(invoiceCreationNotification);
-        Thread.sleep(1000);
+        Thread.sleep(5000);
         Assert.assertTrue(accountDao.getAccount(ACCOUNT_KEY).getTotalInvoiceBalance().compareTo(INVOICE_AMOUNT) == 0);
 
         // Test payment integration - the fields have already been populated, just make sure the code is exercised
         bus.post(paymentInfoNotification);
-        Thread.sleep(1000);
+        Thread.sleep(5000);
         Assert.assertEquals(accountDao.getAccount(ACCOUNT_KEY).getPaymentMethod(), PAYMENT_METHOD);
         Assert.assertEquals(accountDao.getAccount(ACCOUNT_KEY).getBillingAddressCountry(), CARD_COUNTRY);
 
diff --git a/analytics/src/test/java/com/ning/billing/analytics/dao/TestAnalyticsDao.java b/analytics/src/test/java/com/ning/billing/analytics/dao/TestAnalyticsDao.java
index 1504b15..d8c856d 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/dao/TestAnalyticsDao.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/dao/TestAnalyticsDao.java
@@ -25,6 +25,8 @@ import com.ning.billing.analytics.MockPhase;
 import com.ning.billing.analytics.MockPlan;
 import com.ning.billing.analytics.MockProduct;
 import com.ning.billing.analytics.utils.Rounder;
+import com.ning.billing.catalog.api.Catalog;
+import com.ning.billing.catalog.api.CatalogService;
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.catalog.api.PhaseType;
 import com.ning.billing.catalog.api.Plan;
@@ -33,6 +35,9 @@ import com.ning.billing.catalog.api.Product;
 import com.ning.billing.catalog.api.ProductCategory;
 import com.ning.billing.dbi.MysqlTestingHelper;
 import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.mock.BrainDeadProxyFactory;
+import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
+
 import org.apache.commons.io.IOUtils;
 import org.joda.time.DateTime;
 import org.joda.time.DateTimeZone;
@@ -66,9 +71,17 @@ public class TestAnalyticsDao
     private BusinessAccountDao businessAccountDao;
     private BusinessAccount account;
 
+    private final CatalogService catalogService = BrainDeadProxyFactory.createBrainDeadProxyFor(CatalogService.class);
+    private final Catalog catalog = BrainDeadProxyFactory.createBrainDeadProxyFor(Catalog.class);
+    
     @BeforeClass(alwaysRun = true)
     public void startMysql() throws IOException, ClassNotFoundException, SQLException
     {
+
+        ((ZombieControl) catalog).addResult("findPlan", plan);
+        ((ZombieControl) catalog).addResult("findPhase", phase);        
+        ((ZombieControl) catalogService).addResult("getFullCatalog", catalog); 
+        
         final String ddl = IOUtils.toString(BusinessSubscriptionTransitionDao.class.getResourceAsStream("/com/ning/billing/analytics/ddl.sql"));
 
         helper.startMysql();
@@ -80,10 +93,11 @@ public class TestAnalyticsDao
 
     private void setupBusinessSubscriptionTransition()
     {
-        final BusinessSubscription prevSubscription = new BusinessSubscription(null, plan, phase, Currency.USD, new DateTime(DateTimeZone.UTC), Subscription.SubscriptionState.ACTIVE, UUID.randomUUID(), UUID.randomUUID());
-        final BusinessSubscription nextSubscription = new BusinessSubscription(null, plan, phase, Currency.USD, new DateTime(DateTimeZone.UTC), Subscription.SubscriptionState.CANCELLED, UUID.randomUUID(), UUID.randomUUID());
-        final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionCancelled(plan);
         final DateTime requestedTimestamp = new DateTime(DateTimeZone.UTC);
+        final BusinessSubscription prevSubscription = new BusinessSubscription(null, plan.getName(), phase.getName(), Currency.USD, new DateTime(DateTimeZone.UTC), Subscription.SubscriptionState.ACTIVE, UUID.randomUUID(), UUID.randomUUID(), catalog);
+        final BusinessSubscription nextSubscription = new BusinessSubscription(null, plan.getName(), phase.getName(), Currency.USD, new DateTime(DateTimeZone.UTC), Subscription.SubscriptionState.CANCELLED, UUID.randomUUID(), UUID.randomUUID(), catalog);
+        final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionCancelled(plan.getName(), catalog, requestedTimestamp, requestedTimestamp);
+        
 
         transition = new BusinessSubscriptionTransition(EVENT_ID, EVENT_KEY, ACCOUNT_KEY, requestedTimestamp, event, prevSubscription, nextSubscription);
 
@@ -212,7 +226,7 @@ public class TestAnalyticsDao
     @Test(groups = "slow")
     public void testTransitionsWithNullFieldsInSubscription()
     {
-        final BusinessSubscription subscriptionWithNullFields = new BusinessSubscription(null, plan, phase, Currency.USD, null, null, null, null);
+        final BusinessSubscription subscriptionWithNullFields = new BusinessSubscription(null, plan.getName(), phase.getName(), Currency.USD, null, null, null, null, catalog);
         final BusinessSubscriptionTransition transitionWithNullFields = new BusinessSubscriptionTransition(
             transition.getId(),
             transition.getKey(),
@@ -232,7 +246,7 @@ public class TestAnalyticsDao
     @Test(groups = "slow")
     public void testTransitionsWithNullPlanAndPhase() throws Exception
     {
-        final BusinessSubscription subscriptionWithNullPlanAndPhase = new BusinessSubscription(null, null, null, Currency.USD, null, null, null, null);
+        final BusinessSubscription subscriptionWithNullPlanAndPhase = new BusinessSubscription(null, null, null, Currency.USD, null, null, null, null, catalog);
         final BusinessSubscriptionTransition transitionWithNullPlanAndPhase = new BusinessSubscriptionTransition(
             transition.getId(),
             transition.getKey(),
@@ -249,7 +263,6 @@ public class TestAnalyticsDao
         Assert.assertEquals(transitions.get(0).getKey(), transition.getKey());
         Assert.assertEquals(transitions.get(0).getRequestedTimestamp(), transition.getRequestedTimestamp());
         Assert.assertEquals(transitions.get(0).getEvent(), transition.getEvent());
-        // Null Plan and Phase doesn't make sense so we turn the subscription into a null
         Assert.assertNull(transitions.get(0).getPreviousSubscription());
         Assert.assertNull(transitions.get(0).getNextSubscription());
     }
@@ -257,7 +270,7 @@ public class TestAnalyticsDao
     @Test(groups = "slow")
     public void testTransitionsWithNullPlan() throws Exception
     {
-        final BusinessSubscription subscriptionWithNullPlan = new BusinessSubscription(null, null, phase, Currency.USD, null, null, null, null);
+        final BusinessSubscription subscriptionWithNullPlan = new BusinessSubscription(null, null, phase.getName(), Currency.USD, null, null, null, null, catalog);
         final BusinessSubscriptionTransition transitionWithNullPlan = new BusinessSubscriptionTransition(
             transition.getId(),
             transition.getKey(),
@@ -278,7 +291,7 @@ public class TestAnalyticsDao
     @Test(groups = "slow")
     public void testTransitionsWithNullPhase() throws Exception
     {
-        final BusinessSubscription subscriptionWithNullPhase = new BusinessSubscription(null, plan, null, Currency.USD, null, null, null, null);
+        final BusinessSubscription subscriptionWithNullPhase = new BusinessSubscription(null, plan.getName(), null, Currency.USD, null, null, null, null, catalog);
         final BusinessSubscriptionTransition transitionWithNullPhase = new BusinessSubscriptionTransition(
             transition.getId(),
             transition.getKey(),
@@ -297,7 +310,7 @@ public class TestAnalyticsDao
         Assert.assertEquals(transitions.get(0).getEvent(), transition.getEvent());
 
         // Null Phase but Plan - we don't turn the subscription into a null, however price and mrr are both set to 0 (not null)
-        final BusinessSubscription blankSubscription = new BusinessSubscription(null, plan, new MockPhase(null, null, null, 0.0), Currency.USD, null, null, null, null);
+        final BusinessSubscription blankSubscription = new BusinessSubscription(null, plan.getName(), new MockPhase(null, null, null, 0.0).getName(), Currency.USD, null, null, null, null, catalog);
         Assert.assertEquals(transitions.get(0).getPreviousSubscription(), blankSubscription);
         Assert.assertEquals(transitions.get(0).getNextSubscription(), blankSubscription);
     }
@@ -342,7 +355,7 @@ public class TestAnalyticsDao
         Assert.assertEquals("PayPal", account.getPaymentMethod());
         Assert.assertTrue(account.getUpdatedDt().compareTo(previousUpdatedDt) > 0);
 
-        // Account not found
+        // ACCOUNT not found
         Assert.assertNull(businessAccountDao.getAccount("Doesn't exist"));
     }
 }
diff --git a/analytics/src/test/java/com/ning/billing/analytics/MockAccount.java b/analytics/src/test/java/com/ning/billing/analytics/MockAccount.java
index 4942118..03a7767 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/MockAccount.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/MockAccount.java
@@ -19,13 +19,15 @@ package com.ning.billing.analytics;
 import java.util.List;
 import java.util.UUID;
 
-import com.ning.billing.util.callcontext.CallContext;
-import org.joda.time.DateTime;
+import com.ning.billing.util.dao.ObjectType;
+import com.ning.billing.util.tag.ControlTagType;
 import org.joda.time.DateTimeZone;
 
 import com.ning.billing.account.api.Account;
 import com.ning.billing.account.api.MutableAccountData;
 import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.junction.api.BlockingState;
+import com.ning.billing.util.callcontext.CallContext;
 import com.ning.billing.util.customfield.CustomField;
 import com.ning.billing.util.tag.Tag;
 import com.ning.billing.util.tag.TagDefinition;
@@ -61,6 +63,16 @@ public class MockAccount implements Account
     }
 
     @Override
+    public boolean isMigrated() {
+        return false;
+    }
+
+    @Override
+    public boolean isNotifiedForInvoices() {
+        return false;
+    }
+
+    @Override
     public String getExternalKey()
     {
         return accountKey;
@@ -140,16 +152,6 @@ public class MockAccount implements Account
     }
 
     @Override
-    public String getCreatedBy() {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public DateTime getCreatedDate() {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
     public String getFieldValue(String fieldName) {
         throw new UnsupportedOperationException();
     }
@@ -190,7 +192,7 @@ public class MockAccount implements Account
     }
 
     @Override
-    public String getObjectName() {
+    public ObjectType getObjectType() {
         throw new UnsupportedOperationException();
     }
 
@@ -200,7 +202,12 @@ public class MockAccount implements Account
     }
 
     @Override
-    public boolean hasTag(String tagName) {
+    public boolean hasTag(TagDefinition tagDefinition) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean hasTag(ControlTagType controlTagType) {
         throw new UnsupportedOperationException();
     }
 
@@ -215,37 +222,37 @@ public class MockAccount implements Account
     }
 
     @Override
-    public void clearTags() {
+    public void addTagsFromDefinitions(List<TagDefinition> tagDefinitions) {
         throw new UnsupportedOperationException();
     }
 
     @Override
-    public void removeTag(TagDefinition definition) {
+    public void clearTags() {
         throw new UnsupportedOperationException();
     }
 
     @Override
-    public boolean generateInvoice() {
+    public void removeTag(TagDefinition definition) {
         throw new UnsupportedOperationException();
     }
 
     @Override
-    public boolean processPayment() {
+    public boolean generateInvoice() {
         throw new UnsupportedOperationException();
     }
 
     @Override
-    public String getUpdatedBy() {
+    public boolean processPayment() {
         throw new UnsupportedOperationException();
     }
 
     @Override
-    public DateTime getUpdatedDate() {
+    public MutableAccountData toMutableAccountData() {
         throw new UnsupportedOperationException();
     }
 
     @Override
-    public MutableAccountData toMutableAccountData() {
+    public BlockingState getBlockingState() {
         throw new UnsupportedOperationException();
     }
 
diff --git a/analytics/src/test/java/com/ning/billing/analytics/MockDuration.java b/analytics/src/test/java/com/ning/billing/analytics/MockDuration.java
index f10ff2f..e2fa1a2 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/MockDuration.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/MockDuration.java
@@ -20,6 +20,7 @@ import com.ning.billing.catalog.api.Duration;
 import com.ning.billing.catalog.api.TimeUnit;
 import org.apache.commons.lang.NotImplementedException;
 import org.joda.time.DateTime;
+import org.joda.time.Period;
 
 public class MockDuration
 {
@@ -43,6 +44,10 @@ public class MockDuration
             public DateTime addToDateTime(DateTime dateTime) {
                 throw new NotImplementedException();
             }
+            @Override
+            public Period toJodaPeriod() {
+                throw new UnsupportedOperationException();
+            }
         };
     }
 
@@ -66,6 +71,10 @@ public class MockDuration
             public DateTime addToDateTime(DateTime dateTime) {
                 throw new NotImplementedException();
             }
+            @Override
+            public Period toJodaPeriod() {
+                throw new UnsupportedOperationException();
+            }
         };
     }
 
@@ -89,6 +98,10 @@ public class MockDuration
             public DateTime addToDateTime(DateTime dateTime) {
                 throw new NotImplementedException();
             }
+            @Override
+            public Period toJodaPeriod() {
+                throw new UnsupportedOperationException();
+            }
         };
     }
 }
diff --git a/analytics/src/test/java/com/ning/billing/analytics/MockEntitlementUserApi.java b/analytics/src/test/java/com/ning/billing/analytics/MockEntitlementUserApi.java
index 06e3d79..725c62b 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/MockEntitlementUserApi.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/MockEntitlementUserApi.java
@@ -21,14 +21,17 @@ import java.util.List;
 import java.util.Map;
 import java.util.UUID;
 
-import com.ning.billing.util.callcontext.CallContext;
 import org.joda.time.DateTime;
+
 import com.ning.billing.catalog.api.PlanPhaseSpecifier;
 import com.ning.billing.entitlement.api.user.EntitlementUserApi;
-
 import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
 import com.ning.billing.entitlement.api.user.Subscription;
 import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.entitlement.api.user.SubscriptionStatusDryRun;
+import com.ning.billing.junction.api.BlockingState;
+import com.ning.billing.overdue.OverdueState;
+import com.ning.billing.util.callcontext.CallContext;
 
 public class MockEntitlementUserApi implements EntitlementUserApi
 {
@@ -72,6 +75,16 @@ public class MockEntitlementUserApi implements EntitlementUserApi
             {
                 return key;
             }
+
+            @Override
+            public OverdueState<SubscriptionBundle> getOverdueState() {
+                throw new UnsupportedOperationException();
+            }
+
+            @Override
+            public BlockingState getBlockingState() {
+                throw new UnsupportedOperationException();
+            }
         };
     }
 
@@ -119,4 +132,16 @@ public class MockEntitlementUserApi implements EntitlementUserApi
 	public DateTime getNextBillingDate(UUID account) {
 		throw new UnsupportedOperationException();
 	}
+
+    @Override
+    public Subscription getBaseSubscription(UUID bundleId) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public List<SubscriptionStatusDryRun> getDryRunChangePlanStatus(
+            UUID subscriptionId, String productName, DateTime requestedDate)
+            throws EntitlementUserApiException {
+        throw new UnsupportedOperationException();
+    }
 }
diff --git a/analytics/src/test/java/com/ning/billing/analytics/MockSubscription.java b/analytics/src/test/java/com/ning/billing/analytics/MockSubscription.java
index 99e435f..1a72707 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/MockSubscription.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/MockSubscription.java
@@ -16,24 +16,28 @@
 
 package com.ning.billing.analytics;
 
+import java.util.List;
+import java.util.UUID;
+
+import com.ning.billing.util.dao.ObjectType;
+import com.ning.billing.util.tag.ControlTagType;
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+
 import com.ning.billing.catalog.api.BillingPeriod;
 import com.ning.billing.catalog.api.Plan;
 import com.ning.billing.catalog.api.PlanPhase;
 import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.catalog.api.PriceList;
 import com.ning.billing.catalog.api.ProductCategory;
 import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
 import com.ning.billing.entitlement.api.user.Subscription;
-import com.ning.billing.entitlement.api.user.SubscriptionTransition;
+import com.ning.billing.entitlement.api.user.SubscriptionEvent;
+import com.ning.billing.junction.api.BlockingState;
 import com.ning.billing.util.callcontext.CallContext;
 import com.ning.billing.util.customfield.CustomField;
-
 import com.ning.billing.util.tag.Tag;
 import com.ning.billing.util.tag.TagDefinition;
-import org.joda.time.DateTime;
-import org.joda.time.DateTimeZone;
-
-import java.util.List;
-import java.util.UUID;
 
 public class MockSubscription implements Subscription
 {
@@ -53,13 +57,13 @@ public class MockSubscription implements Subscription
     }
 
     @Override
-    public void cancel(DateTime requestedDate, boolean eot, CallContext context)
+    public boolean cancel(DateTime requestedDate, boolean eot, CallContext context)
     {
         throw new UnsupportedOperationException();
     }
 
     @Override
-    public void changePlan(final String productName, final BillingPeriod term, final String planSet, DateTime requestedDate, CallContext context)
+    public boolean changePlan(final String productName, final BillingPeriod term, final String planSet, DateTime requestedDate, CallContext context)
     {
         throw new UnsupportedOperationException();
     }
@@ -71,16 +75,6 @@ public class MockSubscription implements Subscription
     }
 
     @Override
-    public String getCreatedBy() {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public DateTime getCreatedDate() {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
     public UUID getBundleId()
     {
         return BUNDLE_ID;
@@ -112,13 +106,13 @@ public class MockSubscription implements Subscription
 
 
     @Override
-    public void uncancel(CallContext context) throws EntitlementUserApiException
+    public boolean uncancel(CallContext context) throws EntitlementUserApiException
     {
         throw new UnsupportedOperationException();
     }
 
     @Override
-    public String getCurrentPriceList()
+    public PriceList getCurrentPriceList()
     {
         return null;
     }
@@ -129,7 +123,7 @@ public class MockSubscription implements Subscription
     }
 
     @Override
-    public SubscriptionTransition getPendingTransition() {
+    public SubscriptionEvent getPendingTransition() {
         throw new UnsupportedOperationException();
     }
 
@@ -144,7 +138,7 @@ public class MockSubscription implements Subscription
 	}
 
     @Override
-    public SubscriptionTransition getPreviousTransition() {
+    public SubscriptionEvent getPreviousTransition() {
         return null;
     }
 
@@ -154,7 +148,7 @@ public class MockSubscription implements Subscription
     }
 
     @Override
-    public void recreate(PlanPhaseSpecifier spec, DateTime requestedDate, CallContext context)
+    public boolean recreate(PlanPhaseSpecifier spec, DateTime requestedDate, CallContext context)
             throws EntitlementUserApiException {
         throw new UnsupportedOperationException();
     }
@@ -200,7 +194,7 @@ public class MockSubscription implements Subscription
     }
 
     @Override
-    public String getObjectName() {
+    public ObjectType getObjectType() {
         throw new UnsupportedOperationException();
     }
 
@@ -210,7 +204,12 @@ public class MockSubscription implements Subscription
     }
 
     @Override
-    public boolean hasTag(String tagName) {
+    public boolean hasTag(TagDefinition tagDefinition) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean hasTag(ControlTagType controlTagType) {
         throw new UnsupportedOperationException();
     }
 
@@ -225,6 +224,11 @@ public class MockSubscription implements Subscription
     }
 
     @Override
+    public void addTagsFromDefinitions(List<TagDefinition> tagDefinitions) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
     public void clearTags() {
         throw new UnsupportedOperationException();
     }
@@ -243,4 +247,15 @@ public class MockSubscription implements Subscription
     public boolean processPayment() {
         throw new UnsupportedOperationException();
     }
+
+    @Override
+    public List<SubscriptionEvent> getBillingTransitions() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public BlockingState getBlockingState() {
+        throw new UnsupportedOperationException();
+    }
+
 }
diff --git a/analytics/src/test/java/com/ning/billing/analytics/TestAnalyticsListener.java b/analytics/src/test/java/com/ning/billing/analytics/TestAnalyticsListener.java
index 7254a5d..72aa146 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/TestAnalyticsListener.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/TestAnalyticsListener.java
@@ -16,24 +16,33 @@
 
 package com.ning.billing.analytics;
 
+import java.util.UUID;
+
+import javax.annotation.Nullable;
+
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.ning.billing.catalog.api.Catalog;
+import com.ning.billing.catalog.api.CatalogService;
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.catalog.api.PhaseType;
 import com.ning.billing.catalog.api.Plan;
 import com.ning.billing.catalog.api.PlanPhase;
+import com.ning.billing.catalog.api.PriceList;
 import com.ning.billing.catalog.api.Product;
 import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.entitlement.api.user.DefaultSubscriptionEvent;
 import com.ning.billing.entitlement.api.user.Subscription;
 import com.ning.billing.entitlement.api.user.SubscriptionTransitionData;
 import com.ning.billing.entitlement.events.EntitlementEvent;
 import com.ning.billing.entitlement.events.user.ApiEventType;
-import org.joda.time.DateTime;
-import org.joda.time.DateTimeZone;
-import org.testng.Assert;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-
-import javax.annotation.Nullable;
-import java.util.UUID;
+import com.ning.billing.mock.BrainDeadProxyFactory;
+import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
 
 public class TestAnalyticsListener
 {
@@ -47,14 +56,25 @@ public class TestAnalyticsListener
     private final Product product = new MockProduct("platinium", "subscription", ProductCategory.BASE);
     private final Plan plan = new MockPlan("platinum-monthly", product);
     private final PlanPhase phase = new MockPhase(PhaseType.EVERGREEN, plan, MockDuration.UNLIMITED(), 25.95);
-    private final String priceList = null;
+    private final PriceList priceList = null;
 
+    private final CatalogService catalogService = BrainDeadProxyFactory.createBrainDeadProxyFor(CatalogService.class);
+    private final Catalog catalog = BrainDeadProxyFactory.createBrainDeadProxyFor(Catalog.class);
+    
+    
     private AnalyticsListener listener;
 
+    @BeforeClass(alwaysRun = true) 
+    public void setupCatalog() {
+        ((ZombieControl) catalog).addResult("findPlan", plan);
+        ((ZombieControl) catalog).addResult("findPhase", phase);        
+        ((ZombieControl) catalogService).addResult("getFullCatalog", catalog);        
+        
+    }
     @BeforeMethod(alwaysRun = true)
     public void setUp() throws Exception
     {
-        final BusinessSubscriptionTransitionRecorder recorder = new BusinessSubscriptionTransitionRecorder(dao, new MockEntitlementUserApi(bundleUUID, KEY), new MockIAccountUserApi(ACCOUNT_KEY, CURRENCY));
+        final BusinessSubscriptionTransitionRecorder recorder = new BusinessSubscriptionTransitionRecorder(dao, catalogService, new MockEntitlementUserApi(bundleUUID, KEY), new MockAccountUserApi(ACCOUNT_KEY, CURRENCY));
         listener = new AnalyticsListener(recorder, null);
     }
 
@@ -63,28 +83,28 @@ public class TestAnalyticsListener
     {
         // Create a subscription
         final DateTime effectiveTransitionTime = new DateTime(DateTimeZone.UTC);
-        final DateTime requestedTransitionTime = new DateTime(DateTimeZone.UTC);
+        final DateTime requestedTransitionTime = effectiveTransitionTime;
         final SubscriptionTransitionData firstTransition = createFirstSubscriptionTransition(requestedTransitionTime, effectiveTransitionTime);
         final BusinessSubscriptionTransition firstBST = createExpectedFirstBST(firstTransition.getId(), requestedTransitionTime, effectiveTransitionTime);
-        listener.handleSubscriptionTransitionChange(firstTransition);
+        listener.handleSubscriptionTransitionChange(new DefaultSubscriptionEvent(firstTransition, effectiveTransitionTime));
         Assert.assertEquals(dao.getTransitions(KEY).size(), 1);
         Assert.assertEquals(dao.getTransitions(KEY).get(0), firstBST);
 
         // Cancel it
         final DateTime effectiveCancelTransitionTime = new DateTime(DateTimeZone.UTC);
-        final DateTime requestedCancelTransitionTime = new DateTime(DateTimeZone.UTC);
+        final DateTime requestedCancelTransitionTime = effectiveCancelTransitionTime;
         final SubscriptionTransitionData cancelledSubscriptionTransition = createCancelSubscriptionTransition(requestedCancelTransitionTime, effectiveCancelTransitionTime, firstTransition.getNextState());
         final BusinessSubscriptionTransition cancelledBST = createExpectedCancelledBST(cancelledSubscriptionTransition.getId(), requestedCancelTransitionTime, effectiveCancelTransitionTime, firstBST.getNextSubscription());
-        listener.handleSubscriptionTransitionChange(cancelledSubscriptionTransition);
+        listener.handleSubscriptionTransitionChange(new DefaultSubscriptionEvent(cancelledSubscriptionTransition, effectiveTransitionTime));
         Assert.assertEquals(dao.getTransitions(KEY).size(), 2);
         Assert.assertEquals(dao.getTransitions(KEY).get(1), cancelledBST);
 
        // Recreate it
         final DateTime effectiveRecreatedTransitionTime = new DateTime(DateTimeZone.UTC);
-        final DateTime requestedRecreatedTransitionTime = new DateTime(DateTimeZone.UTC);
+        final DateTime requestedRecreatedTransitionTime = effectiveRecreatedTransitionTime;
         final SubscriptionTransitionData recreatedSubscriptionTransition = createRecreatedSubscriptionTransition(requestedRecreatedTransitionTime, effectiveRecreatedTransitionTime, cancelledSubscriptionTransition.getNextState());
         final BusinessSubscriptionTransition recreatedBST = createExpectedRecreatedBST(recreatedSubscriptionTransition.getId(), requestedRecreatedTransitionTime, effectiveRecreatedTransitionTime, cancelledBST.getNextSubscription());
-        listener.handleSubscriptionTransitionChange(recreatedSubscriptionTransition);
+        listener.handleSubscriptionTransitionChange(new DefaultSubscriptionEvent(recreatedSubscriptionTransition, effectiveTransitionTime));
         Assert.assertEquals(dao.getTransitions(KEY).size(), 3);
         Assert.assertEquals(dao.getTransitions(KEY).get(2), recreatedBST);
 
@@ -92,20 +112,21 @@ public class TestAnalyticsListener
 
     private BusinessSubscriptionTransition createExpectedFirstBST(final UUID id, final DateTime requestedTransitionTime, final DateTime effectiveTransitionTime)
     {
-        final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionCreated(plan);
+        final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionCreated(plan.getName(), catalog, effectiveTransitionTime, effectiveTransitionTime);
+                
         final Subscription.SubscriptionState subscriptionState = Subscription.SubscriptionState.ACTIVE;
         return createExpectedBST(id, event, requestedTransitionTime, effectiveTransitionTime, null, subscriptionState);
     }
 
     private BusinessSubscriptionTransition createExpectedCancelledBST(final UUID id, final DateTime requestedTransitionTime, final DateTime effectiveTransitionTime, final BusinessSubscription lastSubscription)
     {
-        final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionCancelled(plan);
+        final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionCancelled(plan.getName(), catalog, effectiveTransitionTime, effectiveTransitionTime);
         return createExpectedBST(id, event, requestedTransitionTime, effectiveTransitionTime, lastSubscription, null);
     }
 
     private BusinessSubscriptionTransition createExpectedRecreatedBST(final UUID id, final DateTime requestedTransitionTime, final DateTime effectiveTransitionTime, final BusinessSubscription lastSubscription)
     {
-        final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionRecreated(plan);
+        final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionRecreated(plan.getName(), catalog, effectiveTransitionTime, effectiveTransitionTime);
         final Subscription.SubscriptionState subscriptionState = Subscription.SubscriptionState.ACTIVE;
         return createExpectedBST(id, event, requestedTransitionTime, effectiveTransitionTime, lastSubscription, subscriptionState);
     }
@@ -129,13 +150,13 @@ public class TestAnalyticsListener
             previousSubscription,
             nextState == null ? null : new BusinessSubscription(
                 null,
-                plan,
-                phase,
+                plan.getName(),
+                phase.getName(),
                 CURRENCY,
                 effectiveTransitionTime,
                 nextState,
                 subscriptionId,
-                bundleUUID
+                bundleUUID, catalog
             )
         );
     }
@@ -161,6 +182,7 @@ public class TestAnalyticsListener
             phase,
             priceList,
             1L,
+            null,
             true
         );
     }
@@ -187,6 +209,7 @@ public class TestAnalyticsListener
             null,
             null,
             1L,
+            null,
             true
         );
     }
@@ -212,6 +235,7 @@ public class TestAnalyticsListener
             phase,
             priceList,
             1L,
+            null,
             true
         );
     }
@@ -242,6 +266,7 @@ public class TestAnalyticsListener
             phase,
             priceList,
             1L,
+            null,
             true
         );
     }
diff --git a/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscription.java b/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscription.java
index 55c73ad..2df0aeb 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscription.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscription.java
@@ -16,6 +16,8 @@
 
 package com.ning.billing.analytics;
 
+import com.ning.billing.catalog.api.Catalog;
+import com.ning.billing.catalog.api.CatalogService;
 import com.ning.billing.catalog.api.Duration;
 import com.ning.billing.catalog.api.PhaseType;
 import com.ning.billing.catalog.api.Plan;
@@ -23,7 +25,11 @@ import com.ning.billing.catalog.api.PlanPhase;
 import com.ning.billing.catalog.api.Product;
 import com.ning.billing.catalog.api.ProductCategory;
 import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.mock.BrainDeadProxyFactory;
+import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
+
 import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
@@ -35,7 +41,7 @@ public class TestBusinessSubscription
 {
     private final Duration MONTHLY = MockDuration.MONHTLY();
     private final Duration YEARLY = MockDuration.YEARLY();
-    final Object[][] catalog = {
+    final Object[][] catalogMapping = {
         {MONTHLY, 229.0000, 229.0000},
         {MONTHLY, 19.9500, 19.9500},
         {MONTHLY, 14.9500, 14.9500},
@@ -53,21 +59,30 @@ public class TestBusinessSubscription
     private Subscription isubscription;
     private BusinessSubscription subscription;
 
+    private final CatalogService catalogService = BrainDeadProxyFactory.createBrainDeadProxyFor(CatalogService.class);
+    private final Catalog catalog = BrainDeadProxyFactory.createBrainDeadProxyFor(Catalog.class);
+    
+
     @BeforeMethod(alwaysRun = true)
     public void setUp() throws Exception
     {
         product = new MockProduct("platinium", "subscription", ProductCategory.BASE);
         plan = new MockPlan("platinum-monthly", product);
         phase = new MockPhase(PhaseType.EVERGREEN, plan, MockDuration.UNLIMITED(), 25.95);
+
+        ((ZombieControl) catalog).addResult("findPlan", plan);
+        ((ZombieControl) catalog).addResult("findPhase", phase);        
+        ((ZombieControl) catalogService).addResult("getFullCatalog", catalog);        
+
         isubscription = new MockSubscription(Subscription.SubscriptionState.ACTIVE, plan, phase);
-        subscription = new BusinessSubscription(isubscription, USD);
+        subscription = new BusinessSubscription(isubscription, USD, catalog);
     }
 
     @Test(groups = "fast")
     public void testMrrComputation() throws Exception
     {
         int i = 0;
-        for (final Object[] object : catalog) {
+        for (final Object[] object : catalogMapping) {
             final Duration duration = (Duration) object[0];
             final double price = (Double) object[1];
             final double expectedMrr = (Double) object[2];
@@ -100,6 +115,6 @@ public class TestBusinessSubscription
         Assert.assertTrue(subscription.equals(subscription));
 
         final Subscription otherSubscription = new MockSubscription(Subscription.SubscriptionState.CANCELLED, plan, phase);
-        Assert.assertTrue(!subscription.equals(new BusinessSubscription(otherSubscription, USD)));
+        Assert.assertTrue(!subscription.equals(new BusinessSubscription(otherSubscription, USD, catalog)));
     }
 }
diff --git a/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscriptionEvent.java b/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscriptionEvent.java
index 793feac..e42c72f 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscriptionEvent.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscriptionEvent.java
@@ -16,13 +16,20 @@
 
 package com.ning.billing.analytics;
 
+import com.ning.billing.catalog.api.Catalog;
+import com.ning.billing.catalog.api.CatalogService;
 import com.ning.billing.catalog.api.PhaseType;
 import com.ning.billing.catalog.api.Plan;
 import com.ning.billing.catalog.api.PlanPhase;
 import com.ning.billing.catalog.api.Product;
 import com.ning.billing.catalog.api.ProductCategory;
 import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.mock.BrainDeadProxyFactory;
+import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
+
+import org.joda.time.DateTime;
 import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
@@ -33,12 +40,20 @@ public class TestBusinessSubscriptionEvent
     private PlanPhase phase;
     private Subscription subscription;
 
+    private final CatalogService catalogService = BrainDeadProxyFactory.createBrainDeadProxyFor(CatalogService.class);
+    private final Catalog catalog = BrainDeadProxyFactory.createBrainDeadProxyFor(Catalog.class);
+    
     @BeforeMethod(alwaysRun = true)
     public void setUp() throws Exception
     {
         product = new MockProduct("platinium", "subscription", ProductCategory.BASE);
         plan = new MockPlan("platinum-monthly", product);
         phase = new MockPhase(PhaseType.EVERGREEN, plan, MockDuration.UNLIMITED(), 25.95);
+
+        ((ZombieControl) catalog).addResult("findPlan", plan);
+        ((ZombieControl) catalog).addResult("findPhase", phase);        
+        ((ZombieControl) catalogService).addResult("getFullCatalog", catalog);        
+
         subscription = new MockSubscription(Subscription.SubscriptionState.ACTIVE, plan, phase);
     }
 
@@ -65,29 +80,31 @@ public class TestBusinessSubscriptionEvent
     {
         BusinessSubscriptionEvent event;
 
-        event = BusinessSubscriptionEvent.subscriptionCreated(subscription.getCurrentPlan());
+        DateTime now = new DateTime();
+        
+        event = BusinessSubscriptionEvent.subscriptionCreated(subscription.getCurrentPlan().getName(), catalog, now, now);
         Assert.assertEquals(event.getEventType(), BusinessSubscriptionEvent.EventType.ADD);
         Assert.assertEquals(event.getCategory(), product.getCategory());
         Assert.assertEquals(event.toString(), "ADD_BASE");
 
-        event = BusinessSubscriptionEvent.subscriptionCancelled(subscription.getCurrentPlan());
+        event = BusinessSubscriptionEvent.subscriptionCancelled(subscription.getCurrentPlan().getName(), catalog, now, now);
         Assert.assertEquals(event.getEventType(), BusinessSubscriptionEvent.EventType.CANCEL);
         Assert.assertEquals(event.getCategory(), product.getCategory());
         Assert.assertEquals(event.toString(), "CANCEL_BASE");
 
-        event = BusinessSubscriptionEvent.subscriptionChanged(subscription.getCurrentPlan());
+        event = BusinessSubscriptionEvent.subscriptionChanged(subscription.getCurrentPlan().getName(), catalog, now, now);
         Assert.assertEquals(event.getEventType(), BusinessSubscriptionEvent.EventType.CHANGE);
         Assert.assertEquals(event.getCategory(), product.getCategory());
         Assert.assertEquals(event.toString(), "CHANGE_BASE");
 
-        event = BusinessSubscriptionEvent.subscriptionPhaseChanged(subscription.getCurrentPlan(), subscription.getState());
+        event = BusinessSubscriptionEvent.subscriptionPhaseChanged(subscription.getCurrentPlan().getName(), subscription.getState(), catalog, now, now);
         // The subscription is still active, it's a system change
         Assert.assertEquals(event.getEventType(), BusinessSubscriptionEvent.EventType.SYSTEM_CHANGE);
         Assert.assertEquals(event.getCategory(), product.getCategory());
         Assert.assertEquals(event.toString(), "SYSTEM_CHANGE_BASE");
 
         subscription = new MockSubscription(Subscription.SubscriptionState.CANCELLED, plan, phase);
-        event = BusinessSubscriptionEvent.subscriptionPhaseChanged(subscription.getCurrentPlan(), subscription.getState());
+        event = BusinessSubscriptionEvent.subscriptionPhaseChanged(subscription.getCurrentPlan().getName(), subscription.getState(), catalog, now, now);
         // The subscription is cancelled, it's a system cancellation
         Assert.assertEquals(event.getEventType(), BusinessSubscriptionEvent.EventType.SYSTEM_CANCEL);
         Assert.assertEquals(event.getCategory(), product.getCategory());
@@ -97,7 +114,8 @@ public class TestBusinessSubscriptionEvent
     @Test(groups = "fast")
     public void testEquals() throws Exception
     {
-        final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionChanged(subscription.getCurrentPlan());
+        DateTime now = new DateTime();
+        final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionChanged(subscription.getCurrentPlan().getName(), catalog, now, now);
         Assert.assertSame(event, event);
         Assert.assertEquals(event, event);
         Assert.assertTrue(event.equals(event));
diff --git a/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscriptionTransition.java b/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscriptionTransition.java
index eaa4c96..be27a91 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscriptionTransition.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscriptionTransition.java
@@ -16,12 +16,17 @@
 
 package com.ning.billing.analytics;
 
+import com.ning.billing.catalog.api.Catalog;
+import com.ning.billing.catalog.api.CatalogService;
 import com.ning.billing.catalog.api.PhaseType;
 import com.ning.billing.catalog.api.Plan;
 import com.ning.billing.catalog.api.PlanPhase;
 import com.ning.billing.catalog.api.Product;
 import com.ning.billing.catalog.api.ProductCategory;
 import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.mock.BrainDeadProxyFactory;
+import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
+
 import org.joda.time.DateTime;
 import org.joda.time.DateTimeZone;
 import org.testng.Assert;
@@ -43,6 +48,9 @@ public class TestBusinessSubscriptionTransition
     private String accountKey;
     private BusinessSubscriptionTransition transition;
 
+    private final CatalogService catalogService = BrainDeadProxyFactory.createBrainDeadProxyFor(CatalogService.class);
+    private final Catalog catalog = BrainDeadProxyFactory.createBrainDeadProxyFor(Catalog.class);
+    
     @BeforeMethod(alwaysRun = true)
     public void setUp() throws Exception
     {
@@ -52,9 +60,15 @@ public class TestBusinessSubscriptionTransition
         final Subscription prevISubscription = new MockSubscription(Subscription.SubscriptionState.ACTIVE, plan, phase);
         final Subscription nextISubscription = new MockSubscription(Subscription.SubscriptionState.CANCELLED, plan, phase);
 
-        prevSubscription = new BusinessSubscription(prevISubscription, USD);
-        nextSubscription = new BusinessSubscription(nextISubscription, USD);
-        event = BusinessSubscriptionEvent.subscriptionCancelled(prevISubscription.getCurrentPlan());
+        ((ZombieControl) catalog).addResult("findPlan", plan);
+        ((ZombieControl) catalog).addResult("findPhase", phase);        
+        ((ZombieControl) catalogService).addResult("getFullCatalog", catalog); 
+        
+        DateTime now = new DateTime();
+        
+        prevSubscription = new BusinessSubscription(prevISubscription, USD, catalog);
+        nextSubscription = new BusinessSubscription(nextISubscription, USD, catalog);
+        event = BusinessSubscriptionEvent.subscriptionCancelled(prevISubscription.getCurrentPlan().getName(), catalog, now, now);
         requestedTimestamp = new DateTime(DateTimeZone.UTC);
         id = UUID.randomUUID();
         key = "1234";
diff --git a/api/src/main/java/com/ning/billing/account/api/Account.java b/api/src/main/java/com/ning/billing/account/api/Account.java
index 3f1e383..1f7000d 100644
--- a/api/src/main/java/com/ning/billing/account/api/Account.java
+++ b/api/src/main/java/com/ning/billing/account/api/Account.java
@@ -16,13 +16,11 @@
 
 package com.ning.billing.account.api;
 
-import com.ning.billing.util.entity.UpdatableEntity;
-
+import com.ning.billing.junction.api.Blockable;
 import com.ning.billing.util.customfield.Customizable;
+import com.ning.billing.util.entity.UpdatableEntity;
 import com.ning.billing.util.tag.Taggable;
 
-public interface Account extends AccountData, Customizable, UpdatableEntity, Taggable {
-    public static String ObjectType = "account";
-    
-    public MutableAccountData toMutableAccountData();    
+public interface Account extends AccountData, Customizable, UpdatableEntity, Taggable, Blockable {
+    public MutableAccountData toMutableAccountData(); 
 }
diff --git a/api/src/main/java/com/ning/billing/account/api/AccountData.java b/api/src/main/java/com/ning/billing/account/api/AccountData.java
index 654400b..3220971 100644
--- a/api/src/main/java/com/ning/billing/account/api/AccountData.java
+++ b/api/src/main/java/com/ning/billing/account/api/AccountData.java
@@ -55,4 +55,8 @@ public interface AccountData {
     public String getCountry();
 
     public String getPhone();
+
+    public boolean isMigrated();
+
+    public boolean isNotifiedForInvoices();
 }
diff --git a/api/src/main/java/com/ning/billing/account/api/AccountEmail.java b/api/src/main/java/com/ning/billing/account/api/AccountEmail.java
new file mode 100644
index 0000000..88ec469
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/account/api/AccountEmail.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2010-2011 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.account.api;
+
+import com.ning.billing.util.entity.UpdatableEntity;
+
+import java.util.UUID;
+
+public interface AccountEmail extends UpdatableEntity {
+    UUID getAccountId();
+    String getEmail();
+}
diff --git a/api/src/main/java/com/ning/billing/account/api/AccountService.java b/api/src/main/java/com/ning/billing/account/api/AccountService.java
index 43175da..febe8dd 100644
--- a/api/src/main/java/com/ning/billing/account/api/AccountService.java
+++ b/api/src/main/java/com/ning/billing/account/api/AccountService.java
@@ -20,5 +20,4 @@ import com.ning.billing.lifecycle.KillbillService;
 
 public interface AccountService extends KillbillService {
 
-    public AccountUserApi getAccountUserApi();
 }
diff --git a/api/src/main/java/com/ning/billing/account/api/AccountUserApi.java b/api/src/main/java/com/ning/billing/account/api/AccountUserApi.java
index 98d6272..89e5f70 100644
--- a/api/src/main/java/com/ning/billing/account/api/AccountUserApi.java
+++ b/api/src/main/java/com/ning/billing/account/api/AccountUserApi.java
@@ -21,22 +21,23 @@ import java.util.UUID;
 
 import com.ning.billing.util.callcontext.CallContext;
 import com.ning.billing.util.customfield.CustomField;
-import com.ning.billing.util.tag.Tag;
+import com.ning.billing.util.tag.TagDefinition;
 
 import javax.annotation.Nullable;
 
 public interface AccountUserApi {
     public Account createAccount(AccountData data, @Nullable List<CustomField> fields,
-                                 @Nullable List<Tag> tags, CallContext context) throws AccountApiException;
+                                 @Nullable List<TagDefinition> tagDefinitions, CallContext context) throws AccountApiException;
 
     public Account migrateAccount(MigrationAccountData data, @Nullable List<CustomField> fields,
-                                  @Nullable List<Tag> tags, CallContext context) throws AccountApiException;
+                                  @Nullable List<TagDefinition> tagDefinitions, CallContext context) throws AccountApiException;
 
     /***
      *
      * Note: does not update the external key
-     * @param account
-     * @throws AccountApiException
+     * @param account account to be updated
+     * @param context contains specific information about the call
+     * @throws AccountApiException if a failure occurs
      */
     public void updateAccount(Account account, CallContext context) throws AccountApiException;
 
@@ -44,11 +45,15 @@ public interface AccountUserApi {
 
     public void updateAccount(UUID accountId, AccountData accountData, CallContext context) throws AccountApiException;
 
-    public Account getAccountByKey(String key);
+    public Account getAccountByKey(String key) throws AccountApiException;
 
-    public Account getAccountById(UUID accountId);
+    public Account getAccountById(UUID accountId) throws AccountApiException;
 
     public List<Account> getAccounts();
 
     public UUID getIdFromKey(String externalKey) throws AccountApiException;
+
+    public List<AccountEmail> getEmails(UUID accountId);
+
+    public void saveEmails(UUID accountId, List<AccountEmail> emails, CallContext context);
 }
diff --git a/api/src/main/java/com/ning/billing/account/api/MutableAccountData.java b/api/src/main/java/com/ning/billing/account/api/MutableAccountData.java
index 2909b2d..6ea638d 100644
--- a/api/src/main/java/com/ning/billing/account/api/MutableAccountData.java
+++ b/api/src/main/java/com/ning/billing/account/api/MutableAccountData.java
@@ -16,182 +16,47 @@
 
 package com.ning.billing.account.api;
 
-import org.joda.time.DateTime;
 import org.joda.time.DateTimeZone;
 
 import com.ning.billing.catalog.api.Currency;
-import com.ning.billing.util.tag.TagStore;
-
-public class MutableAccountData implements AccountData {
-    private String externalKey;
-    private String email;
-    private String name;
-    private int firstNameLength;
-    private Currency currency;
-    private int billCycleDay;
-    private String paymentProviderName;
-    private DateTimeZone timeZone;
-    private String locale;
-    private String address1;
-    private String address2;
-    private String companyName;
-    private String city;
-    private String stateOrProvince;
-    private String country;
-    private String postalCode;
-    private String phone;
-    
-    public MutableAccountData(String externalKey, String email, String name,
-            int firstNameLength, Currency currency, int billCycleDay,
-            String paymentProviderName, TagStore tags, DateTimeZone timeZone,
-            String locale, String address1, String address2,
-            String companyName, String city, String stateOrProvince,
-            String country, String postalCode, String phone,
-            DateTime createdDate, DateTime updatedDate) {
-        super();
-        this.externalKey = externalKey;
-        this.email = email;
-        this.name = name;
-        this.firstNameLength = firstNameLength;
-        this.currency = currency;
-        this.billCycleDay = billCycleDay;
-        this.paymentProviderName = paymentProviderName;
-        this.timeZone = timeZone;
-        this.locale = locale;
-        this.address1 = address1;
-        this.address2 = address2;
-        this.companyName = companyName;
-        this.city = city;
-        this.stateOrProvince = stateOrProvince;
-        this.country = country;
-        this.postalCode = postalCode;
-        this.phone = phone;
-    }
-    
-    public MutableAccountData(AccountData accountData) {
-        super();
-        this.externalKey = accountData.getExternalKey();
-        this.email = accountData.getEmail();
-        this.name = accountData.getName();
-        this.firstNameLength = accountData.getFirstNameLength();
-        this.currency = accountData.getCurrency();
-        this.billCycleDay = accountData.getBillCycleDay();
-        this.paymentProviderName = accountData.getPaymentProviderName();
-        this.timeZone = accountData.getTimeZone();
-        this.locale = accountData.getLocale();
-        this.address1 = accountData.getAddress1();
-        this.address2 = accountData.getAddress2();
-        this.companyName = accountData.getCompanyName();
-        this.city = accountData.getCity();
-        this.stateOrProvince = accountData.getStateOrProvince();
-        this.country = accountData.getCountry();
-        this.postalCode = accountData.getPostalCode();
-        this.phone = accountData.getPhone();
-    }
-
-    public String getExternalKey() {
-        return externalKey;
-    }
-    public String getEmail() {
-        return email;
-    }
-    public String getName() {
-        return name;
-    }
-    public int getFirstNameLength() {
-        return firstNameLength;
-    }
-    public Currency getCurrency() {
-        return currency;
-    }
-    public int getBillCycleDay() {
-        return billCycleDay;
-    }
-    public String getPaymentProviderName() {
-        return paymentProviderName;
-    }
-    public DateTimeZone getTimeZone() {
-        return timeZone;
-    }
-    public String getLocale() {
-        return locale;
-    }
-    public String getAddress1() {
-        return address1;
-    }
-    public String getAddress2() {
-        return address2;
-    }
-    public String getCompanyName() {
-        return companyName;
-    }
-    public String getCity() {
-        return city;
-    }
-    public String getStateOrProvince() {
-        return stateOrProvince;
-    }
-    public String getCountry() {
-        return country;
-    }
-    public String getPostalCode() {
-        return postalCode;
-    }
-    public String getPhone() {
-        return phone;
-    }
-    
-    public void setExternalKey(String externalKey) {
-        this.externalKey = externalKey;
-    }
-    public void setEmail(String email) {
-        this.email = email;
-    }
-    public void setName(String name) {
-        this.name = name;
-    }
-    public void setFirstNameLength(int firstNameLength) {
-        this.firstNameLength = firstNameLength;
-    }
-    public void setCurrency(Currency currency) {
-        this.currency = currency;
-    }
-    public void setBillCycleDay(int billCycleDay) {
-        this.billCycleDay = billCycleDay;
-    }
-    public void setPaymentProviderName(String paymentProviderName) {
-        this.paymentProviderName = paymentProviderName;
-    }
-    public void setTimeZone(DateTimeZone timeZone) {
-        this.timeZone = timeZone;
-    }
-    public void setLocale(String locale) {
-        this.locale = locale;
-    }
-    public void setAddress1(String address1) {
-        this.address1 = address1;
-    }
-    public void setAddress2(String address2) {
-        this.address2 = address2;
-    }
-    public void setCompanyName(String companyName) {
-        this.companyName = companyName;
-    }
-    public void setCity(String city) {
-        this.city = city;
-    }
-    public void setStateOrProvince(String stateOrProvince) {
-        this.stateOrProvince = stateOrProvince;
-    }
-    public void setCountry(String country) {
-        this.country = country;
-    }
-    public void setPostalCode(String postalCode) {
-        this.postalCode = postalCode;
-    }
-    public void setPhone(String phone) {
-        this.phone = phone;
-    }
-
-
-}
+
+public interface MutableAccountData extends AccountData {
+    public void setExternalKey(String externalKey);
+
+    public void setEmail(String email);
+
+    public void setName(String name);
+
+    public void setFirstNameLength(int firstNameLength);
+
+    public void setCurrency(Currency currency);
+
+    public void setBillCycleDay(int billCycleDay);
+
+    public void setPaymentProviderName(String paymentProviderName);
+
+    public void setTimeZone(DateTimeZone timeZone);
+
+    public void setLocale(String locale);
+
+    public void setAddress1(String address1);
+
+    public void setAddress2(String address2);
+
+    public void setCompanyName(String companyName);
+
+    public void setCity(String city);
+
+    public void setStateOrProvince(String stateOrProvince);
+
+    public void setCountry(String country);
+
+    public void setPostalCode(String postalCode);
+
+    public void setPhone(String phone);
+
+    public void setIsMigrated(boolean isMigrated);
+
+    public void setIsNotifiedForInvoices(boolean isNotifiedForInvoices);
+
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/ning/billing/BillingExceptionBase.java b/api/src/main/java/com/ning/billing/BillingExceptionBase.java
index 3a458fc..a4ff3cd 100644
--- a/api/src/main/java/com/ning/billing/BillingExceptionBase.java
+++ b/api/src/main/java/com/ning/billing/BillingExceptionBase.java
@@ -35,6 +35,13 @@ public class BillingExceptionBase extends Exception {
         this.code = code;
         this.cause = cause;
     }
+    
+    public BillingExceptionBase(BillingExceptionBase cause) {
+        this.formattedMsg = cause.getMessage();
+        this.code = cause.getCode();
+        this.cause = cause;
+    }
+
 
     public BillingExceptionBase(Throwable cause, ErrorCode code, final Object... args) {
         String tmp = null;
diff --git a/api/src/main/java/com/ning/billing/catalog/api/Catalog.java b/api/src/main/java/com/ning/billing/catalog/api/Catalog.java
index 2b3609a..924a5e4 100644
--- a/api/src/main/java/com/ning/billing/catalog/api/Catalog.java
+++ b/api/src/main/java/com/ning/billing/catalog/api/Catalog.java
@@ -56,6 +56,11 @@ public interface Catalog {
     public abstract PlanPhase findPhase(String name, DateTime requestedDate, DateTime subscriptionStartDate) throws CatalogApiException;
 
     //
+    // Find a priceList
+    //  
+    public abstract PriceList findPriceList(String name, DateTime requestedDate) throws CatalogApiException;
+
+    //
     // Rules
     //
 	public abstract ActionPolicy planChangePolicy(PlanPhaseSpecifier from,
@@ -74,7 +79,5 @@ public interface Catalog {
 			PlanSpecifier to, DateTime requestedDate) throws CatalogApiException;
 
     public abstract boolean canCreatePlan(PlanSpecifier specifier, DateTime requestedDate) throws CatalogApiException;
-	
-	
-	
+		
 }
diff --git a/api/src/main/java/com/ning/billing/catalog/api/Duration.java b/api/src/main/java/com/ning/billing/catalog/api/Duration.java
index 9025367..90ea5c2 100644
--- a/api/src/main/java/com/ning/billing/catalog/api/Duration.java
+++ b/api/src/main/java/com/ning/billing/catalog/api/Duration.java
@@ -17,6 +17,7 @@
 package com.ning.billing.catalog.api;
 
 import org.joda.time.DateTime;
+import org.joda.time.Period;
 
 public interface Duration {
 
@@ -25,4 +26,6 @@ public interface Duration {
 	public abstract int getNumber();
 
     public DateTime addToDateTime(DateTime dateTime);
+
+    public Period toJodaPeriod();
 }
\ No newline at end of file
diff --git a/api/src/main/java/com/ning/billing/catalog/api/OverdueActions.java b/api/src/main/java/com/ning/billing/catalog/api/OverdueActions.java
new file mode 100644
index 0000000..8525d91
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/catalog/api/OverdueActions.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2010-2011 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.catalog.api;
+
+public enum OverdueActions  {
+	CANCEL,
+	PAYMENT_RETRY
+}
diff --git a/api/src/main/java/com/ning/billing/catalog/api/StaticCatalog.java b/api/src/main/java/com/ning/billing/catalog/api/StaticCatalog.java
index c93fde8..17ea132 100644
--- a/api/src/main/java/com/ning/billing/catalog/api/StaticCatalog.java
+++ b/api/src/main/java/com/ning/billing/catalog/api/StaticCatalog.java
@@ -48,10 +48,15 @@ public interface StaticCatalog {
     //
     // Find a phase
     //
-    public abstract PlanPhase findCurrentPhase(String name) throws CatalogApiException;
+    public abstract  PlanPhase findCurrentPhase(String name) throws CatalogApiException;
     
     //
-    // Rules
+    // Find a pricelist
+    //
+    public abstract PriceList findCurrentPricelist(String name) throws CatalogApiException;
+    
+    //
+    //  
     //
 	public abstract ActionPolicy planChangePolicy(PlanPhaseSpecifier from,
 			PlanSpecifier to) throws CatalogApiException;
@@ -71,6 +76,4 @@ public interface StaticCatalog {
 
     public abstract boolean canCreatePlan(PlanSpecifier specifier) throws CatalogApiException;
 
-
-	
 }
\ No newline at end of file
diff --git a/api/src/main/java/com/ning/billing/config/CatalogConfig.java b/api/src/main/java/com/ning/billing/config/CatalogConfig.java
index 21be358..73ffd9a 100644
--- a/api/src/main/java/com/ning/billing/config/CatalogConfig.java
+++ b/api/src/main/java/com/ning/billing/config/CatalogConfig.java
@@ -19,10 +19,10 @@ package com.ning.billing.config;
 import org.skife.config.Config;
 import org.skife.config.Default;
 
-public interface CatalogConfig {
+public interface CatalogConfig extends KillbillConfig {
 
     @Config("killbill.catalog.uri")
-    @Default("jar:///com/ning/billing/irs/catalog/NingCatalog.xml")
+    @Default("jar:///com/ning/billing/irs/catalog/Catalog.xml")
     String getCatalogURI();
 
 }
diff --git a/api/src/main/java/com/ning/billing/config/EntitlementConfig.java b/api/src/main/java/com/ning/billing/config/EntitlementConfig.java
index 1b6f3e2..12418c6 100644
--- a/api/src/main/java/com/ning/billing/config/EntitlementConfig.java
+++ b/api/src/main/java/com/ning/billing/config/EntitlementConfig.java
@@ -19,21 +19,17 @@ package com.ning.billing.config;
 import org.skife.config.Config;
 import org.skife.config.Default;
 
-public interface EntitlementConfig {
+import com.google.common.annotations.VisibleForTesting;
 
-    @Config("killbill.entitlement.dao.claim.time")
-    @Default("60000")
-    public long getDaoClaimTimeMs();
-
-    @Config("killbill.entitlement.dao.ready.max")
-    @Default("10")
-    public int getDaoMaxReadyEvents();
+public interface EntitlementConfig extends NotificationConfig, KillbillConfig  {
 
+	@Override
     @Config("killbill.entitlement.engine.notifications.sleep")
     @Default("500")
-    public long getNotificationSleepTimeMs();
+    public long getSleepTimeMs();    
 
+	@Override
     @Config("killbill.notifications.off")
     @Default("false")
-    public boolean isEventProcessingOff();
+    public boolean isNotificationProcessingOff();
 }
diff --git a/api/src/main/java/com/ning/billing/config/InvoiceConfig.java b/api/src/main/java/com/ning/billing/config/InvoiceConfig.java
index f56d3c2..407f4d3 100644
--- a/api/src/main/java/com/ning/billing/config/InvoiceConfig.java
+++ b/api/src/main/java/com/ning/billing/config/InvoiceConfig.java
@@ -19,23 +19,17 @@ package com.ning.billing.config;
 import org.skife.config.Config;
 import org.skife.config.Default;
 
-public interface InvoiceConfig {
-
-    @Config("killbill.invoice.dao.claim.time")
-    @Default("60000")
-    public long getDaoClaimTimeMs();
-
-    @Config("killbill.invoice.dao.ready.max")
-    @Default("10")
-    public int getDaoMaxReadyEvents();
+public interface InvoiceConfig extends NotificationConfig, KillbillConfig  {
 
+    @Override    
     @Config("killbill.invoice.engine.notifications.sleep")
     @Default("500")
-    public long getNotificationSleepTimeMs();
+    public long getSleepTimeMs();
 
+    @Override
     @Config("killbill.notifications.off")
     @Default("false")
-    public boolean isEventProcessingOff();
+    public boolean isNotificationProcessingOff();
 
     @Config("killbill.invoice.maxNumberOfMonthsInFuture")
     @Default("36")
diff --git a/api/src/main/java/com/ning/billing/config/KillbillConfig.java b/api/src/main/java/com/ning/billing/config/KillbillConfig.java
new file mode 100644
index 0000000..336f806
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/config/KillbillConfig.java
@@ -0,0 +1,23 @@
+/* 
+ * Copyright 2010-2011 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.config;
+
+/*
+ * Marker interface for killbill config files
+ */
+public interface KillbillConfig {
+
+}
diff --git a/api/src/main/java/com/ning/billing/config/PersistentQueueConfig.java b/api/src/main/java/com/ning/billing/config/PersistentQueueConfig.java
new file mode 100644
index 0000000..c7e1515
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/config/PersistentQueueConfig.java
@@ -0,0 +1,20 @@
+/* 
+ * Copyright 2010-2011 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.config;
+
+public interface PersistentQueueConfig {
+    public long getSleepTimeMs();
+}
diff --git a/api/src/main/java/com/ning/billing/entitlement/api/billing/BillingEvent.java b/api/src/main/java/com/ning/billing/entitlement/api/billing/BillingEvent.java
index 6340560..eb8410f 100644
--- a/api/src/main/java/com/ning/billing/entitlement/api/billing/BillingEvent.java
+++ b/api/src/main/java/com/ning/billing/entitlement/api/billing/BillingEvent.java
@@ -16,21 +16,26 @@
 
 package com.ning.billing.entitlement.api.billing;
 
-import com.ning.billing.catalog.api.Currency;
+import java.math.BigDecimal;
+
 import org.joda.time.DateTime;
 
+import com.ning.billing.account.api.Account;
 import com.ning.billing.catalog.api.BillingPeriod;
-import com.ning.billing.catalog.api.InternationalPrice;
+import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.catalog.api.Plan;
 import com.ning.billing.catalog.api.PlanPhase;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
 import com.ning.billing.entitlement.api.user.Subscription;
-import com.ning.billing.entitlement.api.user.SubscriptionTransition.SubscriptionTransitionType;
-
-import java.math.BigDecimal;
 
 public interface BillingEvent extends Comparable<BillingEvent> {
 
     /**
+     * @return the account that this billing event is associated with
+     */
+    public Account getAccount();
+
+    /**
      *
      * @return the billCycleDay as seen for that subscription at that time
      *
@@ -108,5 +113,4 @@ public interface BillingEvent extends Comparable<BillingEvent> {
 	 * @return a unique long indicating the ordering on which events got inserted on disk-- used for sorting only
 	 */
 	public Long getTotalOrdering();
-
-}
+ }
diff --git a/api/src/main/java/com/ning/billing/entitlement/api/billing/ChargeThruApi.java b/api/src/main/java/com/ning/billing/entitlement/api/billing/ChargeThruApi.java
new file mode 100644
index 0000000..69eb454
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/entitlement/api/billing/ChargeThruApi.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2010-2011 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.entitlement.api.billing;
+
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
+
+import com.ning.billing.util.callcontext.CallContext;
+
+public interface ChargeThruApi {
+
+    /**
+     * @param subscriptionId
+     * @return UUID of
+     */
+    public UUID getAccountIdFromSubscriptionId(UUID subscriptionId) throws EntitlementBillingApiException;
+    
+    /**
+     * Sets the charged through date for the subscription with that Id.
+     * 
+     * @param subscriptionId
+     * @param ctd
+     * @param context
+     */
+    public void setChargedThroughDate(UUID subscriptionId, DateTime ctd, CallContext context);
+}
diff --git a/api/src/main/java/com/ning/billing/entitlement/api/EntitlementService.java b/api/src/main/java/com/ning/billing/entitlement/api/EntitlementService.java
index 28084b2..50d45a1 100644
--- a/api/src/main/java/com/ning/billing/entitlement/api/EntitlementService.java
+++ b/api/src/main/java/com/ning/billing/entitlement/api/EntitlementService.java
@@ -16,19 +16,7 @@
 
 package com.ning.billing.entitlement.api;
 
-import com.ning.billing.entitlement.api.billing.EntitlementBillingApi;
-import com.ning.billing.entitlement.api.migration.EntitlementMigrationApi;
-import com.ning.billing.entitlement.api.user.EntitlementUserApi;
 import com.ning.billing.lifecycle.KillbillService;
 
 public interface EntitlementService extends KillbillService {
-
-    @Override
-    public String getName();
-
-    public EntitlementUserApi getUserApi();
-
-    public EntitlementBillingApi getBillingApi();
-
-    public EntitlementMigrationApi getMigrationApi();
 }
diff --git a/api/src/main/java/com/ning/billing/entitlement/api/SubscriptionTransitionType.java b/api/src/main/java/com/ning/billing/entitlement/api/SubscriptionTransitionType.java
new file mode 100644
index 0000000..8e75675
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/entitlement/api/SubscriptionTransitionType.java
@@ -0,0 +1,27 @@
+/* 
+ * Copyright 2010-2011 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.entitlement.api;
+
+public enum SubscriptionTransitionType {
+    MIGRATE_ENTITLEMENT,
+    CREATE,
+    MIGRATE_BILLING,
+    CHANGE,
+    RE_CREATE,
+    CANCEL,
+    UNCANCEL,
+    PHASE
+}
diff --git a/api/src/main/java/com/ning/billing/entitlement/api/timeline/BundleTimeline.java b/api/src/main/java/com/ning/billing/entitlement/api/timeline/BundleTimeline.java
new file mode 100644
index 0000000..6b12f9d
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/entitlement/api/timeline/BundleTimeline.java
@@ -0,0 +1,30 @@
+/* 
+ * Copyright 2010-2011 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.entitlement.api.timeline;
+
+import java.util.List;
+import java.util.UUID;
+
+public interface BundleTimeline {
+
+    String getViewId();
+    
+    UUID getBundleId();
+    
+    String getExternalKey();
+
+    List<SubscriptionTimeline> getSubscriptions();
+}
diff --git a/api/src/main/java/com/ning/billing/entitlement/api/timeline/EntitlementRepairException.java b/api/src/main/java/com/ning/billing/entitlement/api/timeline/EntitlementRepairException.java
new file mode 100644
index 0000000..56cdd99
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/entitlement/api/timeline/EntitlementRepairException.java
@@ -0,0 +1,41 @@
+/* 
+ * Copyright 2010-2011 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.entitlement.api.timeline;
+
+import com.ning.billing.BillingExceptionBase;
+import com.ning.billing.ErrorCode;
+import com.ning.billing.catalog.api.CatalogApiException;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
+
+public class EntitlementRepairException extends BillingExceptionBase {
+
+    private static final long serialVersionUID = 19067233L;
+
+    public EntitlementRepairException(EntitlementUserApiException e) {
+        super(e, e.getCode(), e.getMessage());
+    }
+    
+    public EntitlementRepairException(CatalogApiException e) {
+        super(e, e.getCode(), e.getMessage());
+    }
+    public EntitlementRepairException(Throwable e, ErrorCode code, Object...args) {
+        super(e, code, args);
+    }
+
+    public EntitlementRepairException(ErrorCode code, Object...args) {
+        super(code, args);
+    }
+}
diff --git a/api/src/main/java/com/ning/billing/entitlement/api/timeline/EntitlementTimelineApi.java b/api/src/main/java/com/ning/billing/entitlement/api/timeline/EntitlementTimelineApi.java
new file mode 100644
index 0000000..b76f25a
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/entitlement/api/timeline/EntitlementTimelineApi.java
@@ -0,0 +1,27 @@
+/* 
+ * Copyright 2010-2011 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.entitlement.api.timeline;
+
+import java.util.UUID;
+
+import com.ning.billing.util.callcontext.CallContext;
+
+public interface EntitlementTimelineApi {
+    
+    public BundleTimeline getBundleRepair(final UUID bundleId) throws EntitlementRepairException;
+    
+    public BundleTimeline repairBundle(final BundleTimeline input, final boolean dryRun, final CallContext context) throws EntitlementRepairException;
+}
diff --git a/api/src/main/java/com/ning/billing/entitlement/api/timeline/RepairEntitlementEvent.java b/api/src/main/java/com/ning/billing/entitlement/api/timeline/RepairEntitlementEvent.java
new file mode 100644
index 0000000..0ab6720
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/entitlement/api/timeline/RepairEntitlementEvent.java
@@ -0,0 +1,31 @@
+/* 
+ * Copyright 2010-2011 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.entitlement.api.timeline;
+
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+
+import com.ning.billing.util.bus.BusEvent;
+
+public interface RepairEntitlementEvent extends BusEvent {
+
+    public UUID getAccountId();
+    
+    public UUID getBundleId();
+
+    public DateTime getEffectiveDate();
+}
diff --git a/api/src/main/java/com/ning/billing/entitlement/api/timeline/SubscriptionTimeline.java b/api/src/main/java/com/ning/billing/entitlement/api/timeline/SubscriptionTimeline.java
new file mode 100644
index 0000000..434cb39
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/entitlement/api/timeline/SubscriptionTimeline.java
@@ -0,0 +1,50 @@
+/* 
+ * Copyright 2010-2011 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.entitlement.api.timeline;
+
+import java.util.List;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
+
+public interface SubscriptionTimeline {
+
+    public UUID getId();
+    
+    public List<DeletedEvent> getDeletedEvents();
+
+    public List<NewEvent> getNewEvents();    
+   
+    public List<ExistingEvent> getExistingEvents();    
+    
+    public interface DeletedEvent {
+        public UUID getEventId();
+    }
+    
+    public interface NewEvent {
+        public PlanPhaseSpecifier getPlanPhaseSpecifier();
+        public DateTime getRequestedDate();
+        public SubscriptionTransitionType getSubscriptionTransitionType();
+        
+    }
+    
+    public interface ExistingEvent extends DeletedEvent, NewEvent {
+        public DateTime getEffectiveDate();
+    }
+}
diff --git a/api/src/main/java/com/ning/billing/entitlement/api/user/EntitlementUserApi.java b/api/src/main/java/com/ning/billing/entitlement/api/user/EntitlementUserApi.java
index 205f256..1e8d7fd 100644
--- a/api/src/main/java/com/ning/billing/entitlement/api/user/EntitlementUserApi.java
+++ b/api/src/main/java/com/ning/billing/entitlement/api/user/EntitlementUserApi.java
@@ -21,22 +21,26 @@ import java.util.UUID;
 
 import com.ning.billing.util.callcontext.CallContext;
 import org.joda.time.DateTime;
+
+import com.ning.billing.catalog.api.BillingPeriod;
 import com.ning.billing.catalog.api.PlanPhaseSpecifier;
 
 
 public interface EntitlementUserApi {
 
-    public SubscriptionBundle getBundleFromId(UUID id);
+    public SubscriptionBundle getBundleFromId(UUID id) throws EntitlementUserApiException;
 
-    public Subscription getSubscriptionFromId(UUID id);
+    public Subscription getSubscriptionFromId(UUID id) throws EntitlementUserApiException;
 
-    public SubscriptionBundle getBundleForKey(String bundleKey);
+    public SubscriptionBundle getBundleForKey(String bundleKey) throws EntitlementUserApiException;
 
     public List<SubscriptionBundle> getBundlesForAccount(UUID accountId);
 
     public List<Subscription> getSubscriptionsForBundle(UUID bundleId);
 
     public List<Subscription> getSubscriptionsForKey(String bundleKey);
+    
+    public Subscription getBaseSubscription(UUID bundleId) throws EntitlementUserApiException;
 
     public SubscriptionBundle createBundleForAccount(UUID accountId, String bundleKey, CallContext context)
         throws EntitlementUserApiException;
@@ -44,5 +48,8 @@ public interface EntitlementUserApi {
     public Subscription createSubscription(UUID bundleId, PlanPhaseSpecifier spec, DateTime requestedDate, CallContext context)
         throws EntitlementUserApiException;
 
+    public List<SubscriptionStatusDryRun> getDryRunChangePlanStatus(UUID subscriptionId, String productName, DateTime requestedDate)
+    throws EntitlementUserApiException;
+    
     public DateTime getNextBillingDate(UUID account);
 }
diff --git a/api/src/main/java/com/ning/billing/entitlement/api/user/EntitlementUserApiException.java b/api/src/main/java/com/ning/billing/entitlement/api/user/EntitlementUserApiException.java
index 693938b..11cf455 100644
--- a/api/src/main/java/com/ning/billing/entitlement/api/user/EntitlementUserApiException.java
+++ b/api/src/main/java/com/ning/billing/entitlement/api/user/EntitlementUserApiException.java
@@ -31,8 +31,11 @@ public class EntitlementUserApiException extends BillingExceptionBase {
         super(e, code, args);
     }
 
+    public EntitlementUserApiException(Throwable e, int code, String message) {
+        super(e, code, message);
+    }
+
     public EntitlementUserApiException(ErrorCode code, Object...args) {
         super(code, args);
     }
-
 }
diff --git a/api/src/main/java/com/ning/billing/entitlement/api/user/Subscription.java b/api/src/main/java/com/ning/billing/entitlement/api/user/Subscription.java
index 0854e2f..af8e237 100644
--- a/api/src/main/java/com/ning/billing/entitlement/api/user/Subscription.java
+++ b/api/src/main/java/com/ning/billing/entitlement/api/user/Subscription.java
@@ -16,31 +16,34 @@
 
 package com.ning.billing.entitlement.api.user;
 
+import java.util.List;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+
 import com.ning.billing.catalog.api.BillingPeriod;
 import com.ning.billing.catalog.api.Plan;
 import com.ning.billing.catalog.api.PlanPhase;
 import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.catalog.api.PriceList;
 import com.ning.billing.catalog.api.ProductCategory;
-
+import com.ning.billing.junction.api.Blockable;
 import com.ning.billing.util.callcontext.CallContext;
 import com.ning.billing.util.entity.ExtendedEntity;
-import org.joda.time.DateTime;
-
-import java.util.UUID;
 
 
-public interface Subscription extends ExtendedEntity {
+public interface Subscription extends ExtendedEntity, Blockable {
 
-    public void cancel(DateTime requestedDate, boolean eot, CallContext context)
+    public boolean cancel(DateTime requestedDate, boolean eot, CallContext context)
     throws EntitlementUserApiException;
 
-    public void uncancel(CallContext context)
+    public boolean uncancel(CallContext context)
     throws EntitlementUserApiException;
 
-    public void changePlan(String productName, BillingPeriod term, String planSet, DateTime requestedDate, CallContext context)
-        throws EntitlementUserApiException;
+    public boolean changePlan(String productName, BillingPeriod term, String planSet, DateTime requestedDate, CallContext context)
+    throws EntitlementUserApiException;
 
-    public void recreate(PlanPhaseSpecifier spec, DateTime requestedDate, CallContext context)
+    public boolean recreate(PlanPhaseSpecifier spec, DateTime requestedDate, CallContext context)
         throws EntitlementUserApiException;
 
     public enum SubscriptionState {
@@ -58,7 +61,7 @@ public interface Subscription extends ExtendedEntity {
 
     public Plan getCurrentPlan();
 
-    public String getCurrentPriceList();
+    public PriceList getCurrentPriceList();
 
     public PlanPhase getCurrentPhase();
 
@@ -68,7 +71,9 @@ public interface Subscription extends ExtendedEntity {
 
     public ProductCategory getCategory();
 
-    public SubscriptionTransition getPendingTransition();
+    public SubscriptionEvent getPendingTransition();
 
-    public SubscriptionTransition getPreviousTransition();
+    public SubscriptionEvent getPreviousTransition();
+    
+    public List<SubscriptionEvent> getBillingTransitions();
 }
diff --git a/api/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionBundle.java b/api/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionBundle.java
index f7c2e84..fddb5e9 100644
--- a/api/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionBundle.java
+++ b/api/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionBundle.java
@@ -16,17 +16,23 @@
 
 package com.ning.billing.entitlement.api.user;
 
+import java.util.UUID;
+
+import com.ning.billing.util.entity.Entity;
 import org.joda.time.DateTime;
 
-import java.util.UUID;
+import com.ning.billing.junction.api.Blockable;
+import com.ning.billing.junction.api.BlockingState;
+import com.ning.billing.overdue.OverdueState;
 
-public interface SubscriptionBundle {
+public interface SubscriptionBundle extends Blockable, Entity {
 
     public UUID getAccountId();
 
-    public UUID getId();
-
     public DateTime getStartDate();
 
     public String getKey();
+
+    public OverdueState<SubscriptionBundle> getOverdueState();
+    
 }
diff --git a/api/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionStatusDryRun.java b/api/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionStatusDryRun.java
new file mode 100644
index 0000000..36048fa
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionStatusDryRun.java
@@ -0,0 +1,42 @@
+/* 
+ * Copyright 2010-2011 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.entitlement.api.user;
+
+import java.util.UUID;
+
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.PhaseType;
+
+public interface SubscriptionStatusDryRun {
+
+    public UUID getId();
+    
+    public String getProductName();
+    
+    public BillingPeriod getBillingPeriod();
+    
+    public String getPriceList();
+    
+    public PhaseType getPhaseType();
+
+    public DryRunChangeReason getReason();
+    
+    public enum DryRunChangeReason {
+        AO_INCLUDED_IN_NEW_PLAN,
+        AO_NOT_AVAILABLE_IN_NEW_PLAN,
+        AO_AVAILABLE_IN_NEW_PLAN
+    }
+}
diff --git a/api/src/main/java/com/ning/billing/ErrorCode.java b/api/src/main/java/com/ning/billing/ErrorCode.java
index 57ea8d9..860ae6c 100644
--- a/api/src/main/java/com/ning/billing/ErrorCode.java
+++ b/api/src/main/java/com/ning/billing/ErrorCode.java
@@ -18,6 +18,8 @@ package com.ning.billing;
 
 public enum ErrorCode {
 
+
+    
     /*
      * Range 0 : COMMON EXCEPTIONS
      */
@@ -44,6 +46,8 @@ public enum ErrorCode {
     /* Change plan */
     ENT_CHANGE_NON_ACTIVE(1021, "Subscription %s is in state %s: Failed to change plan"),
     ENT_CHANGE_FUTURE_CANCELLED(1022, "Subscription %s is future cancelled: Failed to change plan"),
+    ENT_CHANGE_DRY_RUN_NOT_BP(1022, "Change DryRun API is only available for BP"),
+    
     /* Cancellation */
     ENT_CANCEL_BAD_STATE(1031, "Subscription %s is in state %s: Failed to cancel"),
     /* Recreation */
@@ -56,6 +60,31 @@ public enum ErrorCode {
     ENT_GET_NO_BUNDLE_FOR_SUBSCRIPTION(1080, "Could not find a bundle for subscription %s"),
     ENT_GET_INVALID_BUNDLE_ID(1081, "Could not find a bundle matching id %s"),
     ENT_INVALID_SUBSCRIPTION_ID(1082, "Unknown subscription %s"),
+    ENT_GET_INVALID_BUNDLE_KEY(1083, "Could not find a bundle matching key %s"),
+    ENT_GET_NO_SUCH_BASE_SUBSCRIPTION(1084, "Could not base subscription for bundle %s"),
+
+    /* Repair */
+    ENT_REPAIR_INVALID_DELETE_SET(1091, "Event %s is not deleted for subscription %s but prior events were"),
+    ENT_REPAIR_NON_EXISTENT_DELETE_EVENT(1092, "Event %s does not exist for subscription %s"),    
+    ENT_REPAIR_MISSING_AO_DELETE_EVENT(1093, "Event %s should be in deleted set for subscription %s because BP events got deleted earlier"),
+    ENT_REPAIR_NEW_EVENT_BEFORE_LAST_BP_REMAINING(1094, "New event %s for subscription %s is before last remaining event for BP"),
+    ENT_REPAIR_NEW_EVENT_BEFORE_LAST_AO_REMAINING(1095, "New event %s for subscription %s is before last remaining event"),
+    ENT_REPAIR_UNKNOWN_TYPE(1096, "Unknown new event type %s for subscription %s"),
+    ENT_REPAIR_UNKNOWN_BUNDLE(1097, "Unknown bundle %s"), 
+    ENT_REPAIR_UNKNOWN_SUBSCRIPTION(1098, "Unknown subscription %s"),     
+    ENT_REPAIR_NO_ACTIVE_SUBSCRIPTIONS(1099, "No active subscriptions on bundle %s"),         
+    ENT_REPAIR_VIEW_CHANGED(1100, "View for bundle %s has changed from %s to %s"),             
+    ENT_REPAIR_SUB_RECREATE_NOT_EMPTY(1101, "Subscription %s with recreation for bundle %s should specify all existing events to be deleted"),    
+    ENT_REPAIR_SUB_EMPTY(1102, "Subscription %s with recreation for bundle %s should specify all existing events to be deleted"),    
+    ENT_REPAIR_BP_RECREATE_MISSING_AO(1103, "BP recreation for bundle %s implies repair all subscriptions"),    
+    ENT_REPAIR_BP_RECREATE_MISSING_AO_CREATE(1104, "BP recreation for bundle %s implies that all AO should be start also with a CREATE"),        
+    ENT_REPAIR_AO_CREATE_BEFORE_BP_START(1105, "Can't recreate AO %s for bundle %s before BP starts"),    
+    
+    
+    ENT_BUNDLE_IS_OVERDUE_BLOCKED(1090, "Changes to this bundle are blocked by overdue enforcement (%s :  %s)"),
+    ENT_ACCOUNT_IS_OVERDUE_BLOCKED(1091, "Changes to this account are blocked by overdue enforcement (%s)"),
+ 
+    
     /*
     *
     * Range 2000 : CATALOG
@@ -107,7 +136,12 @@ public enum ErrorCode {
      * Billing Alignment
      */
     CAT_INVALID_BILLING_ALIGNMENT(2060, "Invalid billing alignment '%s'"),
-
+    /*
+     * Overdue
+     */
+    CAT_NO_SUCH_OVEDUE_STATE(2070, "No such overdue state '%s'"),
+    CAT_MISSING_CLEAR_STATE(2071, "Missing a clear state"),
+    CAT_NO_OVERDUEABLE_TYPE(2072, "No such overdueable type: "),
    /*
     *
     * Range 3000 : ACCOUNT
@@ -150,9 +184,53 @@ public enum ErrorCode {
     INVOICE_INVALID_TRANSITION(4002, "Transition did not contain a subscription id."),
     INVOICE_NO_ACCOUNT_ID_FOR_SUBSCRIPTION_ID(4003, "No account id was retrieved for subscription id %s"),
     INVOICE_INVALID_DATE_SEQUENCE(4004, "Date sequence was invalid. Start Date: %s; End Date: %s; Target Date: %s"),
-    INVOICE_TARGET_DATE_TOO_FAR_IN_THE_FUTURE(4005, "The target date was too far in the future. Target Date: %s")
-    ;
+    INVOICE_TARGET_DATE_TOO_FAR_IN_THE_FUTURE(4005, "The target date was too far in the future. Target Date: %s"),
+    
+    /*
+     * 
+     * Range 5000: Overdue system
+     * 
+     */
+    OVERDUE_CAT_ERROR_ENCOUNTERED(5001,"Catalog error encountered on Overdueable: id='%s', type='%s'"),  
+    OVERDUE_TYPE_NOT_SUPPORTED(5002,"Overdue of this type is not supported: id='%s', type='%s'"),  
+    OVERDUE_NO_REEVALUATION_INTERVAL(5003,"No valid reevaluation interval for state (name: %s)"),
+    /*
+     * 
+     * Range 6000: Blocking system
+     * 
+     */
+    BLOCK_BLOCKED_ACTION(6000, "The action %s is block on this %s with id=%s"),
+    BLOCK_TYPE_NOT_SUPPORTED(6001, "The Blockable type '%s' is not supported"),
+    
+    
+    /*
+     * Range 7000 : Payment
+     */
+    PAYMENT_NO_SUCH_PAYMENT_METHOD(7001, "Payment method for account %s, and paymentId %s does not exist"),
+    PAYMENT_NO_PAYMENT_METHODS(7002, "Payment methods for account %s don't exist"),
+    PAYMENT_UPD_GATEWAY_FAILED(7003, "Failed to update payment gateway for account %s : %s"),
+    PAYMENT_GET_PAYMENT_PROVIDER(7004, "Failed to retrieve payment provider for account %s : %s"),    
+    PAYMENT_ADD_PAYMENT_METHOD(7005, "Failed to add payment method for account %s : %s"),        
+    PAYMENT_DEL_PAYMENT_METHOD(7006, "Failed to delete payment method for account %s : %s"),        
+    PAYMENT_UPD_PAYMENT_METHOD(7007, "Failed to update payment method for account %s : %s"),            
+    PAYMENT_CREATE_PAYMENT(7008, "Failed to create payment for account %s : %s"),                
+    PAYMENT_CREATE_PAYMENT_FOR_ATTEMPT(7009, "Failed to create payment for account %s and attempt %s : %s"),                    
+    PAYMENT_CREATE_PAYMENT_FOR_ATTEMPT_WITH_NON_POSITIVE_INV(70010, "Got payment attempt with negative or null invoice for account %s"),                        
+    PAYMENT_CREATE_PAYMENT_FOR_ATTEMPT_BAD(7011, "Failed to create payment for attempts %s "),                    
+    PAYMENT_CREATE_PAYMENT_PROVIDER_ACCOUNT(7012, "Failed to create payment provider account for account %s : %s"),                
+    PAYMENT_UPD_PAYMENT_PROVIDER_ACCOUNT(7013, "Failed to update payment provider account for account %s : %s"),                    
+    PAYMENT_CREATE_REFUND(7014, "Failed to create refund for account %s : %s"),                
+    /*
+    *
+    * Range 9000: Miscellaneous
+    *
+    */
+    EMAIL_SENDING_FAILED(9000, "Sending email failed"),
+    EMAIL_PROPERTIES_FILE_MISSING(9001, "The properties file for email configuration could not be found."),
+    MISSING_TRANSLATION_RESOURCE(9010, "The resources for %s translation could not be found."),
+    MISSING_DEFAULT_TRANSLATION_RESOURCE(9011, "The default resource for %s translation could not be found.");
 
+    
     private int code;
     private String format;
 
diff --git a/api/src/main/java/com/ning/billing/glue/EntitlementModule.java b/api/src/main/java/com/ning/billing/glue/EntitlementModule.java
new file mode 100644
index 0000000..421e68d
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/glue/EntitlementModule.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2010-2011 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.glue;
+
+public interface EntitlementModule {
+
+    public abstract void installEntitlementService();
+
+    public abstract void installEntitlementUserApi();
+
+    public abstract void installEntitlementMigrationApi();
+
+    public abstract void installChargeThruApi();
+
+    public abstract void installEntitlementTimelineApi();
+
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/ning/billing/glue/InvoiceModule.java b/api/src/main/java/com/ning/billing/glue/InvoiceModule.java
new file mode 100644
index 0000000..7e55895
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/glue/InvoiceModule.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2010-2011 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.glue;
+
+public interface InvoiceModule {
+
+    public abstract void installInvoiceUserApi();
+
+    public abstract void installInvoicePaymentApi();
+
+    public abstract void installInvoiceMigrationApi();
+
+    public abstract void installInvoiceTestApi();
+
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/ning/billing/glue/JunctionModule.java b/api/src/main/java/com/ning/billing/glue/JunctionModule.java
new file mode 100644
index 0000000..413e99c
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/glue/JunctionModule.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2010-2011 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.glue;
+
+
+public interface JunctionModule {
+
+    public void installBillingApi();
+   
+    public void installAccountUserApi() ;
+    
+    public void installBlockingApi() ;
+
+    public void installEntitlementUserApi();
+
+}
diff --git a/api/src/main/java/com/ning/billing/invoice/api/EmptyInvoiceEvent.java b/api/src/main/java/com/ning/billing/invoice/api/EmptyInvoiceEvent.java
new file mode 100644
index 0000000..a0012a8
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/invoice/api/EmptyInvoiceEvent.java
@@ -0,0 +1,22 @@
+/* 
+ * Copyright 2010-2011 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.invoice.api;
+
+import com.ning.billing.util.bus.BusEvent;
+
+public interface EmptyInvoiceEvent extends BusEvent {
+
+}
diff --git a/api/src/main/java/com/ning/billing/invoice/api/formatters/InvoiceFormatterFactory.java b/api/src/main/java/com/ning/billing/invoice/api/formatters/InvoiceFormatterFactory.java
new file mode 100644
index 0000000..af4af92
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/invoice/api/formatters/InvoiceFormatterFactory.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2010-2011 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.invoice.api.formatters;
+
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.util.template.translation.TranslatorConfig;
+
+import java.util.Locale;
+
+public interface InvoiceFormatterFactory {
+    public InvoiceFormatter createInvoiceFormatter(TranslatorConfig config, Invoice invoice, Locale locale);
+}
diff --git a/api/src/main/java/com/ning/billing/invoice/api/formatters/InvoiceItemFormatter.java b/api/src/main/java/com/ning/billing/invoice/api/formatters/InvoiceItemFormatter.java
new file mode 100644
index 0000000..c14e621
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/invoice/api/formatters/InvoiceItemFormatter.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2010-2011 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.invoice.api.formatters;
+
+import com.ning.billing.invoice.api.InvoiceItem;
+
+public interface InvoiceItemFormatter extends InvoiceItem {
+    public String getFormattedStartDate();
+    public String getFormattedEndDate();
+}
diff --git a/api/src/main/java/com/ning/billing/invoice/api/Invoice.java b/api/src/main/java/com/ning/billing/invoice/api/Invoice.java
index 0cf30ab..cfa172a 100644
--- a/api/src/main/java/com/ning/billing/invoice/api/Invoice.java
+++ b/api/src/main/java/com/ning/billing/invoice/api/Invoice.java
@@ -25,8 +25,6 @@ import java.util.List;
 import java.util.UUID;
 
 public interface Invoice extends ExtendedEntity {
-    public static String ObjectType = "invoice";
-
     boolean addInvoiceItem(InvoiceItem item);
 
     boolean addInvoiceItems(List<InvoiceItem> items);
diff --git a/api/src/main/java/com/ning/billing/invoice/api/InvoiceItem.java b/api/src/main/java/com/ning/billing/invoice/api/InvoiceItem.java
index abbc3f1..cd7decd 100644
--- a/api/src/main/java/com/ning/billing/invoice/api/InvoiceItem.java
+++ b/api/src/main/java/com/ning/billing/invoice/api/InvoiceItem.java
@@ -28,6 +28,8 @@ public interface InvoiceItem extends Entity, Comparable<InvoiceItem> {
 
     UUID getAccountId();
 
+    UUID getBundleId();
+
     UUID getSubscriptionId();
 
     String getPlanName();
@@ -44,5 +46,5 @@ public interface InvoiceItem extends Entity, Comparable<InvoiceItem> {
 
     Currency getCurrency();
 
-    InvoiceItem asCredit();
+    InvoiceItem asReversingItem();
 }
diff --git a/api/src/main/java/com/ning/billing/invoice/api/InvoiceNotifier.java b/api/src/main/java/com/ning/billing/invoice/api/InvoiceNotifier.java
new file mode 100644
index 0000000..cd0b7b6
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/invoice/api/InvoiceNotifier.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2010-2011 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.invoice.api;
+
+import com.ning.billing.BillingExceptionBase;
+import com.ning.billing.account.api.Account;
+
+import java.io.IOException;
+
+public interface InvoiceNotifier {
+    public void notify(Account account, Invoice invoice) throws InvoiceApiException;
+}
diff --git a/api/src/main/java/com/ning/billing/invoice/api/InvoicePaymentApi.java b/api/src/main/java/com/ning/billing/invoice/api/InvoicePaymentApi.java
index 9fb0bb3..0a34604 100644
--- a/api/src/main/java/com/ning/billing/invoice/api/InvoicePaymentApi.java
+++ b/api/src/main/java/com/ning/billing/invoice/api/InvoicePaymentApi.java
@@ -42,7 +42,7 @@ public interface InvoicePaymentApi {
     public void notifyOfPaymentAttempt(InvoicePayment invoicePayment, CallContext context);
 
     public void notifyOfPaymentAttempt(UUID invoiceId, BigDecimal amountOutstanding, Currency currency, UUID paymentAttemptId, DateTime paymentAttemptDate, CallContext context);
-
+    
     public void notifyOfPaymentAttempt(UUID invoiceId, UUID paymentAttemptId, DateTime paymentAttemptDate, CallContext context);
 
 }
diff --git a/api/src/main/java/com/ning/billing/invoice/api/InvoiceService.java b/api/src/main/java/com/ning/billing/invoice/api/InvoiceService.java
index 7109223..c9f9f0e 100644
--- a/api/src/main/java/com/ning/billing/invoice/api/InvoiceService.java
+++ b/api/src/main/java/com/ning/billing/invoice/api/InvoiceService.java
@@ -19,6 +19,5 @@ package com.ning.billing.invoice.api;
 import com.ning.billing.lifecycle.KillbillService;
 
 public interface InvoiceService extends KillbillService {
-    public InvoiceUserApi getUserApi();
-    public InvoicePaymentApi getPaymentApi();
+
 }
diff --git a/api/src/main/java/com/ning/billing/junction/api/Blockable.java b/api/src/main/java/com/ning/billing/junction/api/Blockable.java
new file mode 100644
index 0000000..39900cc
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/junction/api/Blockable.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2010-2011 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.junction.api;
+
+import java.util.UUID;
+
+import com.ning.billing.ErrorCode;
+import com.ning.billing.account.api.Account;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+
+public interface Blockable {
+
+    public enum Type {
+        ACCOUNT,
+        SUBSCRIPTION_BUNDLE,
+        SUBSCRIPTION;
+        
+        public static Type get(Blockable o) throws BlockingApiException{
+            if (o instanceof Account){
+                return ACCOUNT;
+            } else if (o instanceof SubscriptionBundle){
+                return SUBSCRIPTION_BUNDLE;
+            } else if (o instanceof Subscription){
+                return SUBSCRIPTION;
+            }
+            throw new BlockingApiException(ErrorCode.BLOCK_TYPE_NOT_SUPPORTED , o.getClass().getName());
+        }
+        
+        public static Type get(String type) throws BlockingApiException {
+            if (type.equalsIgnoreCase(ACCOUNT.name())) {
+                return ACCOUNT;
+            } else if (type.equalsIgnoreCase(SUBSCRIPTION_BUNDLE.name())) {
+                return SUBSCRIPTION_BUNDLE;
+            } else if (type.equalsIgnoreCase(SUBSCRIPTION.name())) {
+                return SUBSCRIPTION;
+            }
+            throw new BlockingApiException(ErrorCode.BLOCK_TYPE_NOT_SUPPORTED , type);
+        }
+
+    }
+
+    public UUID getId();
+    
+    public BlockingState getBlockingState();
+}
diff --git a/api/src/main/java/com/ning/billing/junction/api/BlockingApi.java b/api/src/main/java/com/ning/billing/junction/api/BlockingApi.java
new file mode 100644
index 0000000..d7fc678
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/junction/api/BlockingApi.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2010-2011 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.junction.api;
+
+import java.util.SortedSet;
+import java.util.UUID;
+
+
+public interface BlockingApi {
+    public static final String CLEAR_STATE_NAME = "__KILLBILL__CLEAR__OVERDUE_STATE__";
+
+    public BlockingState getBlockingStateFor(Blockable overdueable);
+
+    public BlockingState getBlockingStateFor(UUID overdueableId);
+    
+    public SortedSet<BlockingState> getBlockingHistory(Blockable overdueable);
+
+    public SortedSet<BlockingState> getBlockingHistory(UUID overdueableId);
+    
+    public <T extends Blockable> void  setBlockingState(BlockingState state);
+    
+}
diff --git a/api/src/main/java/com/ning/billing/junction/api/BlockingApiException.java b/api/src/main/java/com/ning/billing/junction/api/BlockingApiException.java
new file mode 100644
index 0000000..3a7a6c5
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/junction/api/BlockingApiException.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2010-2011 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.junction.api;
+
+import com.ning.billing.BillingExceptionBase;
+import com.ning.billing.ErrorCode;
+
+public class BlockingApiException extends BillingExceptionBase {
+	private static final long serialVersionUID = 1L;
+
+	public BlockingApiException(Throwable cause, ErrorCode code, Object... args) {
+		super(cause, code, args);
+	}
+
+	public BlockingApiException(ErrorCode code, Object... args) {
+		super(code, args);
+	}
+
+}
diff --git a/api/src/main/java/com/ning/billing/junction/api/BlockingState.java b/api/src/main/java/com/ning/billing/junction/api/BlockingState.java
new file mode 100644
index 0000000..08202a2
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/junction/api/BlockingState.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2010-2011 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.junction.api;
+
+import org.joda.time.DateTime;
+
+public interface BlockingState extends Comparable<BlockingState> {
+
+    public abstract String getStateName();
+    
+    public abstract Blockable.Type getType();
+
+    public abstract DateTime getTimestamp();
+
+    public abstract boolean isBlockChange();
+
+    public abstract boolean isBlockEntitlement();
+
+    public abstract boolean isBlockBilling();
+
+    public abstract int compareTo(BlockingState arg0);
+
+    public abstract int hashCode();
+
+    public abstract String getDescription();
+
+    public abstract String toString();
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/ning/billing/junction/api/DefaultBlockingState.java b/api/src/main/java/com/ning/billing/junction/api/DefaultBlockingState.java
new file mode 100644
index 0000000..332cb85
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/junction/api/DefaultBlockingState.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright 2010-2011 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.junction.api;
+
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+
+
+public class DefaultBlockingState implements BlockingState{
+
+    private static BlockingState clearState= null;
+    
+    private final UUID blockingId;
+    private final Blockable.Type type;
+    private final String stateName;
+    private final String service;
+    private final boolean blockChange;
+    private final boolean blockEntitlement;
+    private final boolean blockBilling;
+    private final DateTime timestamp;
+    
+    public static BlockingState getClearState() {
+        if(clearState == null) {
+            clearState = new DefaultBlockingState(null, BlockingApi.CLEAR_STATE_NAME, null, null, false, false, false);
+        }
+        return clearState;
+    }    
+    
+    public DefaultBlockingState(UUID blockingId, 
+            String stateName, 
+            Blockable.Type type, 
+            String service,
+            boolean blockChange,
+            boolean blockEntitlement,
+            boolean blockBilling
+            ) {
+        this(   blockingId, 
+                 stateName, 
+                 type, 
+                 service,
+                 blockChange,
+                 blockEntitlement,
+                 blockBilling,
+                 null);
+    }    
+    
+    public DefaultBlockingState(UUID blockingId, 
+            String stateName, 
+            Blockable.Type type, 
+            String service,
+            boolean blockChange,
+            boolean blockEntitlement,
+            boolean blockBilling,
+            DateTime timestamp
+            ) {
+        super();
+        this.blockingId = blockingId;
+        this.stateName = stateName;
+        this.service = service;
+        this.blockChange = blockChange;
+        this.blockEntitlement = blockEntitlement;
+        this.blockBilling = blockBilling;
+        this.type = type;
+        this.timestamp = timestamp;
+    }
+    
+    public UUID getBlockedId() {
+        return blockingId;
+    }
+    /* (non-Javadoc)
+     * @see com.ning.billing.junction.api.blocking.BlockingState#getStateName()
+     */
+    @Override
+    public String getStateName() {
+        return stateName;
+    }
+
+    @Override
+    public Blockable.Type getType() {
+        return type;
+    }
+    /* (non-Javadoc)
+     * @see com.ning.billing.junction.api.blocking.BlockingState#getTimestamp()
+     */
+    @Override
+    public DateTime getTimestamp() {
+        return timestamp;
+    }
+
+    public String getService() {
+        return service;
+    }
+
+    /* (non-Javadoc)
+     * @see com.ning.billing.junction.api.blocking.BlockingState#isBlockChange()
+     */
+    @Override
+    public boolean isBlockChange() {
+        return blockChange;
+    }
+
+    /* (non-Javadoc)
+     * @see com.ning.billing.junction.api.blocking.BlockingState#isBlockEntitlement()
+     */
+    @Override
+    public boolean isBlockEntitlement() {
+        return blockEntitlement;
+    }
+
+    /* (non-Javadoc)
+     * @see com.ning.billing.junction.api.blocking.BlockingState#isBlockBilling()
+     */
+    @Override
+    public boolean isBlockBilling() {
+        return blockBilling;
+    }
+
+    /* (non-Javadoc)
+     * @see com.ning.billing.junction.api.blocking.BlockingState#compareTo(com.ning.billing.junction.api.blocking.DefaultBlockingState)
+     */
+    public int compareTo(BlockingState arg0) {
+        if (timestamp.compareTo(arg0.getTimestamp()) != 0) {
+            return timestamp.compareTo(arg0.getTimestamp());
+        } else {
+            return hashCode() - arg0.hashCode();
+        }
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + (blockBilling ? 1231 : 1237);
+        result = prime * result + (blockChange ? 1231 : 1237);
+        result = prime * result + (blockEntitlement ? 1231 : 1237);
+        result = prime * result + ((blockingId == null) ? 0 : blockingId.hashCode());
+        result = prime * result + ((service == null) ? 0 : service.hashCode());
+        result = prime * result + ((stateName == null) ? 0 : stateName.hashCode());
+        result = prime * result + ((timestamp == null) ? 0 : timestamp.hashCode());
+        result = prime * result + ((type == null) ? 0 : type.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        DefaultBlockingState other = (DefaultBlockingState) obj;
+        if (blockBilling != other.blockBilling)
+            return false;
+        if (blockChange != other.blockChange)
+            return false;
+        if (blockEntitlement != other.blockEntitlement)
+            return false;
+        if (blockingId == null) {
+            if (other.blockingId != null)
+                return false;
+        } else if (!blockingId.equals(other.blockingId))
+            return false;
+        if (service == null) {
+            if (other.service != null)
+                return false;
+        } else if (!service.equals(other.service))
+            return false;
+        if (stateName == null) {
+            if (other.stateName != null)
+                return false;
+        } else if (!stateName.equals(other.stateName))
+            return false;
+        if (timestamp == null) {
+            if (other.timestamp != null)
+                return false;
+        } else if (!timestamp.equals(other.timestamp))
+            return false;
+        if (type != other.type)
+            return false;
+        return true;
+    }
+    
+    /* (non-Javadoc)
+     * @see com.ning.billing.junction.api.blocking.BlockingState#getDescription()
+     */
+    @Override
+    public String getDescription() {
+        String entitlement = onOff(isBlockEntitlement());
+        String billing = onOff(isBlockBilling());
+        String change = onOff(isBlockChange());
+               
+        return String.format("(Change: %s, Entitlement: %s, Billing: %s)", change, entitlement, billing);
+    }
+    
+    private String onOff(boolean val) {
+        if(val) {
+            return "Off";
+        } else {
+            return "On";
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "BlockingState [blockingId=" + blockingId + ", type=" + type + ", stateName=" + stateName + ", service="
+                + service + ", blockChange=" + blockChange + ", blockEntitlement=" + blockEntitlement
+                + ", blockBilling=" + blockBilling + ", timestamp=" + timestamp + "]";
+    }
+
+
+    
+}
diff --git a/api/src/main/java/com/ning/billing/overdue/config/api/BillingState.java b/api/src/main/java/com/ning/billing/overdue/config/api/BillingState.java
new file mode 100644
index 0000000..965e402
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/overdue/config/api/BillingState.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2010-2011 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.overdue.config.api;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+
+import com.ning.billing.junction.api.Blockable;
+import com.ning.billing.util.tag.Tag;
+
+public class BillingState<T extends Blockable> {
+	private final UUID objectId;
+	private final int numberOfUnpaidInvoices;
+	private final BigDecimal balanceOfUnpaidInvoices;
+	private final DateTime dateOfEarliestUnpaidInvoice;
+	private final PaymentResponse responseForLastFailedPayment;
+	private final Tag[] tags;
+	
+	public BillingState(UUID id, int numberOfUnpaidInvoices, BigDecimal balanceOfUnpaidInvoices,
+			DateTime dateOfEarliestUnpaidInvoice,
+			PaymentResponse responseForLastFailedPayment,
+			Tag[] tags) {
+		super();
+		this.objectId = id;
+		this.numberOfUnpaidInvoices = numberOfUnpaidInvoices;
+		this.balanceOfUnpaidInvoices = balanceOfUnpaidInvoices;
+		this.dateOfEarliestUnpaidInvoice = dateOfEarliestUnpaidInvoice;
+		this.responseForLastFailedPayment = responseForLastFailedPayment;
+		this.tags = tags;
+	}
+
+	public UUID getObjectId() {
+		return objectId;
+	}
+	
+	public int getNumberOfUnpaidInvoices() {
+		return numberOfUnpaidInvoices;
+	}
+
+	public BigDecimal getBalanceOfUnpaidInvoices() {
+		return balanceOfUnpaidInvoices;
+	}
+
+	public DateTime getDateOfEarliestUnpaidInvoice() {
+		return dateOfEarliestUnpaidInvoice;
+	}
+	
+	public PaymentResponse getResponseForLastFailedPayment() {
+		return responseForLastFailedPayment;
+	}
+
+	public Tag[] getTags() {
+		return tags;
+	}
+
+}
diff --git a/api/src/main/java/com/ning/billing/overdue/config/api/BillingStateBundle.java b/api/src/main/java/com/ning/billing/overdue/config/api/BillingStateBundle.java
new file mode 100644
index 0000000..15eebc8
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/overdue/config/api/BillingStateBundle.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2010-2011 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.overdue.config.api;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.PhaseType;
+import com.ning.billing.catalog.api.PriceList;
+import com.ning.billing.catalog.api.Product;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.util.tag.Tag;
+
+public class BillingStateBundle extends BillingState<SubscriptionBundle> {
+    private final Product basePlanProduct;
+    private final BillingPeriod basePlanBillingPeriod;
+    private final PriceList basePlanPriceList;
+    private final PhaseType basePlanPhaseType;
+    
+	public BillingStateBundle(UUID id, int numberOfUnpaidInvoices, BigDecimal unpaidInvoiceBalance,
+			DateTime dateOfEarliestUnpaidInvoice,
+			PaymentResponse responseForLastFailedPayment,
+			Tag[] tags, 
+			Product basePlanProduct,
+			BillingPeriod basePlanBillingPeriod, 
+			PriceList basePlanPriceList, PhaseType basePlanPhaseType) {
+		super(id, numberOfUnpaidInvoices, unpaidInvoiceBalance, 
+				dateOfEarliestUnpaidInvoice, responseForLastFailedPayment, tags);
+		
+		this.basePlanProduct = basePlanProduct;
+		this.basePlanBillingPeriod = basePlanBillingPeriod;
+		this.basePlanPriceList = basePlanPriceList;
+		this.basePlanPhaseType = basePlanPhaseType;
+	}
+	
+	public Product getBasePlanProduct() {
+		return basePlanProduct;
+	}
+	
+	public BillingPeriod getBasePlanBillingPeriod() {
+		return basePlanBillingPeriod;
+	}
+	
+	public PriceList getBasePlanPriceList() {
+		return basePlanPriceList;
+	}
+
+    public PhaseType getBasePlanPhaseType() {
+        return basePlanPhaseType;
+    }
+}
diff --git a/api/src/main/java/com/ning/billing/overdue/config/api/OverdueError.java b/api/src/main/java/com/ning/billing/overdue/config/api/OverdueError.java
new file mode 100644
index 0000000..78f62c6
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/overdue/config/api/OverdueError.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2010-2011 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.overdue.config.api;
+
+import com.ning.billing.BillingExceptionBase;
+import com.ning.billing.ErrorCode;
+
+public class OverdueError extends BillingExceptionBase {
+
+    public OverdueError(BillingExceptionBase cause) {
+        super(cause);
+     }
+
+    public OverdueError(Throwable cause, int code, String msg) {
+        super(cause, code, msg);
+    }
+
+    private static final long serialVersionUID = 1L;
+
+    public OverdueError(Throwable cause, ErrorCode code, Object... args) {
+        super(cause, code, args);
+    }
+
+    public OverdueError(ErrorCode code, Object... args) {
+        super(code, args);
+    }
+
+}
diff --git a/api/src/main/java/com/ning/billing/overdue/config/api/OverdueStateSet.java b/api/src/main/java/com/ning/billing/overdue/config/api/OverdueStateSet.java
new file mode 100644
index 0000000..693fa8f
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/overdue/config/api/OverdueStateSet.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2010-2011 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.overdue.config.api;
+
+import org.joda.time.DateTime;
+
+import com.ning.billing.junction.api.Blockable;
+import com.ning.billing.overdue.OverdueApiException;
+import com.ning.billing.overdue.OverdueState;
+
+public interface OverdueStateSet<T extends Blockable> {
+
+    public abstract OverdueState<T> findClearState() throws OverdueApiException;
+
+    public abstract OverdueState<T> findState(String stateName) throws OverdueApiException;
+
+    public abstract OverdueState<T> calculateOverdueState(BillingState<T> billingState, DateTime now) throws OverdueApiException;
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/ning/billing/overdue/config/api/PaymentResponse.java b/api/src/main/java/com/ning/billing/overdue/config/api/PaymentResponse.java
new file mode 100644
index 0000000..fb82d7c
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/overdue/config/api/PaymentResponse.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2010-2011 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.overdue.config.api;
+
+public enum PaymentResponse {
+    // Card issues
+    INVALID_CARD("The card number, expiry date or cvc is invalid or incorrect"),
+	EXPIRED_CARD("The card has expired"),
+	LOST_OR_STOLEN_CARD("The card has been lost or stolen"),
+
+	// Account issues
+    DO_NOT_HONOR("Do not honor the card - usually a problem with account"),
+	INSUFFICIENT_FUNDS("The account had insufficient funds to fulfil the payment"),
+	DECLINE("Generic payment decline"),
+	
+	//Transaction
+	PROCESSING_ERROR("Error processing card"),
+	INVALID_AMOUNT("An invalid amount was entered"),
+	DUPLICATE_TRANSACTION("A transaction with identical amount and credit card information was submitted very recently."),
+
+	//Other
+	OTHER("Some other error");
+	
+	private String description;
+	
+	private PaymentResponse(String description) {
+	    this.description = description;
+	}
+	
+	public String getDescription() {
+	    return description;
+	}
+	
+//	 690118 | Approved
+//	 136956 | Do Not Honor
+//	 119640 | Insufficient Funds
+//	  68514 | Invalid Account Number
+//	  66824 | Declined: 10417-The transaction cannot complete successfully.  Instruct the customer to use an alternative payment
+//	  55473 | Declined: 10201-Agreement was canceled
+//	  30930 | Pick Up Card
+//	  29857 | Lost/Stolen Card
+//	  28197 | Declined
+//	  24830 | Declined: 10207-Transaction failed but user has alternate funding source
+//	  18445 | Generic Decline
+//	  18254 | Expired Card
+//	  16521 | Cardholder transaction not permitted
+//	  11576 | Restricted Card
+//	   7410 | Account Number Does Not Match Payment Type
+//	   7312 | Invalid merchant information: 10507-Payer's account is denied
+//	   6425 | Invalid Transaction
+//	   2825 | Declined: 10204-User's account is closed or restricted
+//	   2730 | Invalid account number
+//	   1331 | 
+//	   1240 | Field format error: 10561-There's an error with this transaction. Please enter a complete billing address.
+//	   1125 | Cardholder requested that recurring or installment payment be stopped
+//	   1060 | No such issuer
+//	   1047 | Issuer Unavailable
+//	    816 | Not signed up for this tender type
+//	    749 | Transaction not allowed at terminal
+//	    663 | Invalid expiration date: 0910
+//	    548 | Invalid expiration date: 1010
+//	    542 | Invalid expiration date:
+//	    500 | Invalid expiration date: 0810
+//	    492 | Invalid expiration date: 1110
+//	    410 | Invalid expiration date: 0710
+//	    388 | Exceeds Approval Amount Limit
+//	    362 | Generic processor error: 10001-Internal Error
+//	    313 | Exceeds per transaction limit: 10553-This transaction cannot be processed.
+//	    310 | Decline CVV2/CID Fail
+//	    309 | Generic processor error: 10201-Agreement was canceled
+//	    278 | Generic processor error: 10417-The transaction cannot complete successfully.  Instruct the customer to use an alte
+//	    246 | Call Issuer
+//	    237 | Generic processor error: 11091-The transaction was blocked as it would exceed the sending limit for this buyer.
+//	    202 | Failed to connect to host Input Server Uri = https://payflowpro.paypal.com:443
+//	    166 | Exceeds number of PIN entries
+//	    150 | Invalid Amount
+
+}
diff --git a/api/src/main/java/com/ning/billing/overdue/OverdueApiException.java b/api/src/main/java/com/ning/billing/overdue/OverdueApiException.java
new file mode 100644
index 0000000..7629abf
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/overdue/OverdueApiException.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2010-2011 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.overdue;
+
+import com.ning.billing.BillingExceptionBase;
+import com.ning.billing.ErrorCode;
+
+public class OverdueApiException extends BillingExceptionBase {
+	private static final long serialVersionUID = 1L;
+
+	public OverdueApiException(Throwable cause, ErrorCode code, Object... args) {
+		super(cause, code, args);
+	}
+
+	public OverdueApiException(ErrorCode code, Object... args) {
+		super(code, args);
+	}
+
+}
diff --git a/api/src/main/java/com/ning/billing/overdue/OverdueService.java b/api/src/main/java/com/ning/billing/overdue/OverdueService.java
new file mode 100644
index 0000000..06c1f15
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/overdue/OverdueService.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2010-2011 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.overdue;
+
+import com.ning.billing.lifecycle.KillbillService;
+
+public interface OverdueService extends KillbillService {
+    String OVERDUE_SERVICE_NAME = "overdue-service";
+
+    public String getName();
+
+    public OverdueUserApi getUserApi();
+    
+}
diff --git a/api/src/main/java/com/ning/billing/overdue/OverdueState.java b/api/src/main/java/com/ning/billing/overdue/OverdueState.java
new file mode 100644
index 0000000..3de2c94
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/overdue/OverdueState.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2010-2011 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.overdue;
+
+import org.joda.time.Period;
+
+import com.ning.billing.junction.api.Blockable;
+
+
+
+public interface OverdueState<T extends Blockable> {
+
+    public String getName();
+
+    public String getExternalMessage();
+    
+    public int getDaysBetweenPaymentRetries();
+
+    public boolean disableEntitlementAndChangesBlocked();
+
+    public boolean blockChanges();
+    
+    public boolean isClearState();
+
+    public Period getReevaluationInterval() throws OverdueApiException;
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/ning/billing/overdue/OverdueUserApi.java b/api/src/main/java/com/ning/billing/overdue/OverdueUserApi.java
new file mode 100644
index 0000000..3101c10
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/overdue/OverdueUserApi.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2010-2011 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.overdue;
+
+import com.ning.billing.junction.api.Blockable;
+import com.ning.billing.overdue.config.api.BillingState;
+import com.ning.billing.overdue.config.api.OverdueError;
+
+public interface OverdueUserApi {
+
+    public <T extends Blockable> OverdueState<T> refreshOverdueStateFor(T overdueable) throws OverdueError, OverdueApiException;
+
+    public <T extends Blockable> void setOverrideBillingStateForAccount(T overdueable, BillingState<T> state) throws OverdueError;
+
+    public <T extends Blockable> OverdueState<T> getOverdueStateFor(T overdueable) throws OverdueError;
+}
diff --git a/api/src/main/java/com/ning/billing/payment/api/Either.java b/api/src/main/java/com/ning/billing/payment/api/Either.java
index 25ce8f8..c71c5b1 100644
--- a/api/src/main/java/com/ning/billing/payment/api/Either.java
+++ b/api/src/main/java/com/ning/billing/payment/api/Either.java
@@ -16,6 +16,7 @@
 
 package com.ning.billing.payment.api;
 
+import org.codehaus.jackson.annotate.JsonIgnore;
 import org.codehaus.jackson.annotate.JsonValue;
 
 public abstract class Either<T, V> {
@@ -29,9 +30,12 @@ public abstract class Either<T, V> {
     private Either() {
     }
 
+    @JsonIgnore
     public boolean isLeft() {
         return false;
     }
+
+    @JsonIgnore
     public boolean isRight() {
         return false;
     }
@@ -49,6 +53,7 @@ public abstract class Either<T, V> {
             this.value = value;
         }
         @Override
+        @JsonIgnore
         public boolean isLeft() {
             return true;
         }
@@ -66,6 +71,7 @@ public abstract class Either<T, V> {
             this.value = value;
         }
         @Override
+        @JsonIgnore
         public boolean isRight() {
             return true;
         }
diff --git a/api/src/main/java/com/ning/billing/payment/api/PaymentApi.java b/api/src/main/java/com/ning/billing/payment/api/PaymentApi.java
index 9076256..b1bef23 100644
--- a/api/src/main/java/com/ning/billing/payment/api/PaymentApi.java
+++ b/api/src/main/java/com/ning/billing/payment/api/PaymentApi.java
@@ -26,36 +26,57 @@ import com.ning.billing.util.callcontext.CallContext;
 
 public interface PaymentApi {
 
-    Either<PaymentError, Void> updatePaymentGateway(String accountKey, CallContext context);
+    public void updatePaymentGateway(final String accountKey, final CallContext context)
+        throws PaymentApiException;
 
-    Either<PaymentError, PaymentMethodInfo> getPaymentMethod(@Nullable String accountKey, String paymentMethodId);
+    public PaymentMethodInfo getPaymentMethod(final String accountKey, final String paymentMethodId)
+        throws PaymentApiException;
 
-    Either<PaymentError, List<PaymentMethodInfo>> getPaymentMethods(String accountKey);
+    public List<PaymentMethodInfo> getPaymentMethods(final String accountKey)
+        throws PaymentApiException;
 
-    Either<PaymentError, String> addPaymentMethod(@Nullable String accountKey, PaymentMethodInfo paymentMethod, CallContext context);
+    public String addPaymentMethod(final String accountKey, final PaymentMethodInfo paymentMethod, final CallContext context)
+        throws PaymentApiException;
 
-    Either<PaymentError, PaymentMethodInfo> updatePaymentMethod(String accountKey, PaymentMethodInfo paymentMethodInfo, CallContext context);
+    public PaymentMethodInfo updatePaymentMethod(final String accountKey, final PaymentMethodInfo paymentMethodInfo, final CallContext context)
+        throws PaymentApiException;
 
-    Either<PaymentError, Void> deletePaymentMethod(String accountKey, String paymentMethodId, CallContext context);
+    public void deletePaymentMethod(final String accountKey, final String paymentMethodId, final CallContext context)
+        throws PaymentApiException;
 
-    List<Either<PaymentError, PaymentInfo>> createPayment(String accountKey, List<String> invoiceIds, CallContext context);
-    List<Either<PaymentError, PaymentInfo>> createPayment(Account account, List<String> invoiceIds, CallContext context);
-    Either<PaymentError, PaymentInfo> createPaymentForPaymentAttempt(UUID paymentAttemptId, CallContext context);
+    public List<PaymentInfoEvent> createPayment(final String accountKey, final List<String> invoiceIds, final CallContext context)
+        throws PaymentApiException;
+    
+    public List<PaymentInfoEvent> createPayment(final Account account, final List<String> invoiceIds, final CallContext context)
+        throws PaymentApiException;
+    
+    public PaymentInfoEvent createPaymentForPaymentAttempt(final UUID paymentAttemptId, final CallContext context)
+        throws PaymentApiException;
 
-    List<Either<PaymentError, PaymentInfo>> createRefund(Account account, List<String> invoiceIds, CallContext context); //TODO
+    public List<PaymentInfoEvent> createRefund(final Account account, final List<String> invoiceIds, final CallContext context)
+        throws PaymentApiException;
 
-    Either<PaymentError, PaymentProviderAccount> getPaymentProviderAccount(String accountKey);
+    public PaymentProviderAccount getPaymentProviderAccount(final String accountKey)
+        throws PaymentApiException;
 
-    Either<PaymentError, String> createPaymentProviderAccount(Account account, CallContext context);
+    public String createPaymentProviderAccount(final Account account, final CallContext context)
+        throws PaymentApiException;
 
-    Either<PaymentError, Void> updatePaymentProviderAccountContact(String accountKey, CallContext context);
+    public void updatePaymentProviderAccountContact(String accountKey, CallContext context)
+        throws PaymentApiException;
 
-    PaymentAttempt getPaymentAttemptForPaymentId(String id);
+    public PaymentAttempt getPaymentAttemptForPaymentId(final UUID id)
+        throws PaymentApiException;
 
-    List<PaymentInfo> getPaymentInfo(List<String> invoiceIds);
+    public List<PaymentInfoEvent> getPaymentInfoList(final List<String> invoiceIds)
+        throws PaymentApiException;
 
-    List<PaymentAttempt> getPaymentAttemptsForInvoiceId(String invoiceId);
+    public PaymentInfoEvent getLastPaymentInfo(final List<String> invoiceIds)
+        throws PaymentApiException;
 
-    PaymentInfo getPaymentInfoForPaymentAttemptId(String paymentAttemptId);
+    public List<PaymentAttempt> getPaymentAttemptsForInvoiceId(final String invoiceId)
+        throws PaymentApiException;
 
+    public PaymentInfoEvent getPaymentInfoForPaymentAttemptId(final String paymentAttemptId)
+        throws PaymentApiException;
 }
diff --git a/api/src/main/java/com/ning/billing/payment/api/PaymentApiException.java b/api/src/main/java/com/ning/billing/payment/api/PaymentApiException.java
new file mode 100644
index 0000000..ff81b16
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/payment/api/PaymentApiException.java
@@ -0,0 +1,49 @@
+/* 
+ * Copyright 2010-2011 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.payment.api;
+
+import com.ning.billing.BillingExceptionBase;
+import com.ning.billing.ErrorCode;
+import com.ning.billing.account.api.AccountApiException;
+import com.ning.billing.catalog.api.CatalogApiException;
+
+public class PaymentApiException extends BillingExceptionBase {
+    
+    private static final long serialVersionUID = 39445033L;
+
+
+    public PaymentApiException(AccountApiException e) {
+        super(e, e.getCode(), e.getMessage());
+    }
+
+    /*
+    public PaymentApiException(CatalogApiException e) {
+        super(e, e.getCode(), e.getMessage());
+    }
+    */
+    
+    public PaymentApiException(Throwable e, ErrorCode code, Object...args) {
+        super(e, code, args);
+    }
+
+    public PaymentApiException(Throwable e, int code, String message) {
+        super(e, code, message);
+    }
+
+    public PaymentApiException(ErrorCode code, Object...args) {
+        super(code, args);
+    }
+}
diff --git a/api/src/main/java/com/ning/billing/payment/api/PaymentAttempt.java b/api/src/main/java/com/ning/billing/payment/api/PaymentAttempt.java
index 15ac4ee..a424fc6 100644
--- a/api/src/main/java/com/ning/billing/payment/api/PaymentAttempt.java
+++ b/api/src/main/java/com/ning/billing/payment/api/PaymentAttempt.java
@@ -16,281 +16,31 @@
 
 package com.ning.billing.payment.api;
 
-import java.math.BigDecimal;
-import java.util.UUID;
-
-import org.joda.time.DateTime;
-import org.joda.time.DateTimeZone;
-
-import com.google.common.base.Objects;
 import com.ning.billing.catalog.api.Currency;
-import com.ning.billing.invoice.api.Invoice;
-
-public class PaymentAttempt {
-    private final UUID paymentAttemptId;
-    private final UUID invoiceId;
-    private final UUID accountId;
-    private final BigDecimal amount;
-    private final Currency currency;
-    private final String paymentId;
-    private final DateTime invoiceDate;
-    private final DateTime paymentAttemptDate;
-    private final Integer retryCount;
-    private final DateTime createdDate;
-    private final DateTime updatedDate;
-
-    public PaymentAttempt(UUID paymentAttemptId,
-                          UUID invoiceId,
-                          UUID accountId,
-                          BigDecimal amount,
-                          Currency currency,
-                          DateTime invoiceDate,
-                          DateTime paymentAttemptDate,
-                          String paymentId,
-                          Integer retryCount,
-                          DateTime createdDate,
-                          DateTime updatedDate) {
-        this.paymentAttemptId = paymentAttemptId;
-        this.invoiceId = invoiceId;
-        this.accountId = accountId;
-        this.amount = amount;
-        this.currency = currency;
-        this.invoiceDate = invoiceDate;
-        this.paymentAttemptDate = paymentAttemptDate == null ? new DateTime(DateTimeZone.UTC) : paymentAttemptDate;
-        this.paymentId = paymentId;
-        this.retryCount = retryCount == null ? 0 : retryCount;
-        this.createdDate = createdDate;
-        this.updatedDate = updatedDate;
-    }
-
-    public PaymentAttempt(UUID paymentAttemptId,
-                          UUID invoiceId,
-                          UUID accountId,
-                          BigDecimal amount,
-                          Currency currency,
-                          DateTime invoiceDate,
-                          DateTime paymentAttemptDate,
-                          String paymentId,
-                          Integer retryCount) {
-        this(paymentAttemptId,
-             invoiceId,
-             accountId,
-             amount,
-             currency,
-             invoiceDate,
-             paymentAttemptDate,
-             paymentId,
-             retryCount,
-             null,
-             null);
-    }
-
-    public PaymentAttempt(UUID paymentAttemptId, UUID invoiceId, UUID accountId, BigDecimal amount, Currency currency, DateTime invoiceDate, DateTime paymentAttemptDate) {
-        this(paymentAttemptId, invoiceId, accountId, amount, currency, invoiceDate, paymentAttemptDate, null, null);
-    }
-
-    public PaymentAttempt(UUID paymentAttemptId, UUID invoiceId, UUID accountId, DateTime invoiceDate, DateTime paymentAttemptDate) {
-        this(paymentAttemptId, invoiceId, accountId, null, null, invoiceDate, paymentAttemptDate, null, null);
-    }
-
-    public PaymentAttempt(UUID paymentAttemptId, Invoice invoice) {
-        this(paymentAttemptId, invoice.getId(), invoice.getAccountId(), invoice.getBalance(), invoice.getCurrency(), invoice.getInvoiceDate(), null, null, null);
-    }
-
-    public DateTime getInvoiceDate() {
-        return invoiceDate;
-    }
-
-    public UUID getPaymentAttemptId() {
-        return paymentAttemptId;
-    }
-
-    public String getPaymentId() {
-        return paymentId;
-    }
-
-    public DateTime getPaymentAttemptDate() {
-        return paymentAttemptDate;
-    }
-
-    public UUID getInvoiceId() {
-        return invoiceId;
-    }
-
-    public UUID getAccountId() {
-        return accountId;
-    }
-
-    public DateTime getCreatedDate() {
-        return createdDate;
-    }
-
-    public DateTime getUpdatedDate() {
-        return updatedDate;
-    }
-
-    public BigDecimal getAmount() {
-        return amount;
-    }
-
-    public Currency getCurrency() {
-        return currency;
-    }
-
-    public Integer getRetryCount() {
-        return retryCount;
-    }
-
-    @Override
-    public String toString() {
-        return "PaymentAttempt [paymentAttemptId=" + paymentAttemptId + ", invoiceId=" + invoiceId + ", accountId=" + accountId + ", amount=" + amount + ", currency=" + currency + ", paymentId=" + paymentId + ", invoiceDate=" + invoiceDate + ", paymentAttemptDate=" + paymentAttemptDate + ", retryCount=" + retryCount + ", createdDate=" + createdDate + ", updatedDate=" + updatedDate + "]";
-    }
-
-    public Builder cloner() {
-        return new Builder(this);
-    }
-
-    public static class Builder {
-        private UUID paymentAttemptId;
-        private UUID invoiceId;
-        private UUID accountId;
-        private BigDecimal amount;
-        private Currency currency;
-        private DateTime invoiceDate;
-        private DateTime paymentAttemptDate;
-        private String paymentId;
-        private Integer retryCount;
-        private DateTime createdDate;
-        private DateTime updatedDate;
-
-        public Builder() {
-        }
-
-        public Builder(PaymentAttempt src) {
-            this.paymentAttemptId = src.paymentAttemptId;
-            this.invoiceId = src.invoiceId;
-            this.accountId = src.accountId;
-            this.amount = src.amount;
-            this.currency = src.currency;
-            this.invoiceDate = src.invoiceDate;
-            this.paymentAttemptDate = src.paymentAttemptDate;
-            this.paymentId = src.paymentId;
-            this.retryCount = src.retryCount;
-            this.createdDate = src.createdDate;
-            this.updatedDate = src.updatedDate;
-        }
-
-        public Builder setPaymentAttemptId(UUID paymentAttemptId) {
-            this.paymentAttemptId = paymentAttemptId;
-            return this;
-        }
-
-        public Builder setInvoiceId(UUID invoiceId) {
-            this.invoiceId = invoiceId;
-            return this;
-        }
-
-        public Builder setAccountId(UUID accountId) {
-            this.accountId = accountId;
-            return this;
-        }
-
-        public Builder setAmount(BigDecimal amount) {
-            this.amount = amount;
-            return this;
-        }
-
-        public Builder setCurrency(Currency currency) {
-            this.currency = currency;
-            return this;
-        }
-
-        public Builder setCreatedDate(DateTime createdDate) {
-            this.createdDate = createdDate;
-            return this;
-        }
-
-        public Builder setUpdatedDate(DateTime updatedDate) {
-            this.updatedDate = updatedDate;
-            return this;
-        }
-
-        public Builder setInvoiceDate(DateTime invoiceDate) {
-            this.invoiceDate = invoiceDate;
-            return this;
-        }
+import com.ning.billing.util.entity.Entity;
+import org.joda.time.DateTime;
 
-        public Builder setPaymentAttemptDate(DateTime paymentAttemptDate) {
-            this.paymentAttemptDate = paymentAttemptDate;
-            return this;
-        }
+import java.math.BigDecimal;
+import java.util.UUID;
 
-        public Builder setPaymentId(String paymentId) {
-            this.paymentId = paymentId;
-            return this;
-        }
+public interface PaymentAttempt extends Entity {
+    DateTime getInvoiceDate();
 
-        public Builder setRetryCount(Integer retryCount) {
-            this.retryCount = retryCount;
-            return this;
-        }
+    UUID getPaymentId();
 
-        public PaymentAttempt build() {
-            return new PaymentAttempt(paymentAttemptId,
-                                      invoiceId,
-                                      accountId,
-                                      amount,
-                                      currency,
-                                      invoiceDate,
-                                      paymentAttemptDate,
-                                      paymentId,
-                                      retryCount,
-                                      createdDate,
-                                      updatedDate);
-        }
+    DateTime getPaymentAttemptDate();
 
-    }
+    UUID getInvoiceId();
 
-    @Override
-    public int hashCode() {
-        return Objects.hashCode(paymentAttemptId,
-                                invoiceId,
-                                accountId,
-                                amount,
-                                currency,
-                                invoiceDate,
-                                paymentAttemptDate,
-                                paymentId,
-                                retryCount,
-                                createdDate,
-                                updatedDate);
-    }
+    UUID getAccountId();
 
-    @Override
-    public boolean equals(final Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
+    BigDecimal getAmount();
 
-        final PaymentAttempt that = (PaymentAttempt) o;
+    Currency getCurrency();
 
-        if (accountId != null ? !accountId.equals(that.accountId) : that.accountId != null) return false;
-        if (amount != null ? !(amount.compareTo(that.amount) == 0) : that.amount != null) return false;
-        if (createdDate != null ? !(getUnixTimestamp(createdDate) == getUnixTimestamp(that.createdDate)) : that.createdDate != null) return false;
-        if (currency != that.currency) return false;
-        if (invoiceDate != null ? !(getUnixTimestamp(invoiceDate) == getUnixTimestamp(that.invoiceDate)) : that.invoiceDate != null) return false;
-        if (invoiceId != null ? !invoiceId.equals(that.invoiceId) : that.invoiceId != null) return false;
-        if (paymentAttemptDate != null ? !(getUnixTimestamp(paymentAttemptDate) == getUnixTimestamp(that.paymentAttemptDate)) : that.paymentAttemptDate != null)
-            return false;
-        if (paymentAttemptId != null ? !paymentAttemptId.equals(that.paymentAttemptId) : that.paymentAttemptId != null)
-            return false;
-        if (paymentId != null ? !paymentId.equals(that.paymentId) : that.paymentId != null) return false;
-        if (retryCount != null ? !retryCount.equals(that.retryCount) : that.retryCount != null) return false;
-        if (updatedDate != null ? !(getUnixTimestamp(updatedDate) == getUnixTimestamp(that.updatedDate)) : that.updatedDate != null) return false;
+    Integer getRetryCount();
 
-        return true;
-    }
+    DateTime getCreatedDate();
 
-    private static long getUnixTimestamp(final DateTime dateTime) {
-        return dateTime.getMillis() / 1000;
-    }
+    DateTime getUpdatedDate();
 }
diff --git a/api/src/main/java/com/ning/billing/payment/api/PaymentErrorEvent.java b/api/src/main/java/com/ning/billing/payment/api/PaymentErrorEvent.java
new file mode 100644
index 0000000..56cc879
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/payment/api/PaymentErrorEvent.java
@@ -0,0 +1,31 @@
+/* 
+ * Copyright 2010-2011 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.payment.api;
+
+import java.util.UUID;
+
+import com.ning.billing.util.bus.BusEvent;
+
+public interface PaymentErrorEvent extends BusEvent {
+
+    public String getType();
+
+    public String getMessage();
+
+    public UUID getInvoiceId();
+
+    public UUID getAccountId();
+}
diff --git a/api/src/main/java/com/ning/billing/payment/api/PaymentInfoEvent.java b/api/src/main/java/com/ning/billing/payment/api/PaymentInfoEvent.java
new file mode 100644
index 0000000..c4cabb9
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/payment/api/PaymentInfoEvent.java
@@ -0,0 +1,49 @@
+/* 
+ * Copyright 2010-2011 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.payment.api;
+
+import java.math.BigDecimal;
+
+import com.ning.billing.util.entity.Entity;
+import org.joda.time.DateTime;
+
+import com.ning.billing.util.bus.BusEvent;
+
+public interface PaymentInfoEvent extends Entity, BusEvent {
+    public BigDecimal getAmount();
+
+    public String getBankIdentificationNumber();
+
+    public DateTime getEffectiveDate();
+
+    public String getPaymentNumber();
+
+    public String getPaymentMethod();
+
+    public String getCardType();
+
+    public String getCardCountry();
+
+    public String getReferenceId();
+
+    public String getPaymentMethodId();
+
+    public BigDecimal getRefundAmount();
+
+    public String getStatus();
+
+    public String getType();
+}
diff --git a/api/src/main/java/com/ning/billing/util/api/TagUserApi.java b/api/src/main/java/com/ning/billing/util/api/TagUserApi.java
index d5cb8f2..2ecd93d 100644
--- a/api/src/main/java/com/ning/billing/util/api/TagUserApi.java
+++ b/api/src/main/java/com/ning/billing/util/api/TagUserApi.java
@@ -17,13 +17,15 @@
 package com.ning.billing.util.api;
 
 import java.util.List;
+import java.util.UUID;
 
 import com.ning.billing.util.callcontext.CallContext;
-import org.joda.time.DateTime;
 
+import com.ning.billing.util.dao.ObjectType;
 import com.ning.billing.util.tag.Tag;
 import com.ning.billing.util.tag.TagDefinition;
 
+// TODO: add ability to create, update and remove tags
 public interface TagUserApi {
     /***
      *
@@ -33,13 +35,13 @@ public interface TagUserApi {
 
     /***
      *
-     * @param name Identifies the definition.
+     * @param definitionName Identifies the definition.
      * @param description Describes the use of the definition.
      * @param context The call context, for auditing purposes
      * @return the newly created tag definition
      * @throws TagDefinitionApiException
      */
-    public TagDefinition create(String name, String description, CallContext context) throws TagDefinitionApiException;
+    public TagDefinition create(String definitionName, String description, CallContext context) throws TagDefinitionApiException;
 
     /***
      *
@@ -57,7 +59,6 @@ public interface TagUserApi {
      */
     public void deleteTagDefinition(String definitionName, CallContext context) throws TagDefinitionApiException;
 
-    
 	/**
 	 * 
 	 * @param name
@@ -65,18 +66,12 @@ public interface TagUserApi {
      * @throws TagDefinitionApiException
 	 */
 	public TagDefinition getTagDefinition(String name) throws TagDefinitionApiException;
-	
-	/**
-	 * @param controlTagName
-	 * @throws TagDefinitionApiException
-	 */
-	public Tag createControlTag(String controlTagName) throws TagDefinitionApiException;
-	
-	
-	/**
-	 * @param tagDefinitionName
-	 * @return
-	 */
-	public Tag createDescriptiveTag(String tagDefinitionName) throws TagDefinitionApiException;
-	
+
+	public List<Tag> createControlTags(UUID objectId, ObjectType objectType, List<TagDefinition> tagDefinitions) throws TagDefinitionApiException;
+
+    public Tag createControlTag(UUID objectId, ObjectType objectType, TagDefinition tagDefinition) throws TagDefinitionApiException;
+
+	public List<Tag> createDescriptiveTags(UUID objectId, ObjectType objectType, List<TagDefinition> tagDefinitions) throws TagDefinitionApiException;
+
+	public Tag createDescriptiveTag(UUID objectId, ObjectType objectType, TagDefinition tagDefinition) throws TagDefinitionApiException;
 }
diff --git a/api/src/main/java/com/ning/billing/util/bus/BusEvent.java b/api/src/main/java/com/ning/billing/util/bus/BusEvent.java
index 481cb74..d969440 100644
--- a/api/src/main/java/com/ning/billing/util/bus/BusEvent.java
+++ b/api/src/main/java/com/ning/billing/util/bus/BusEvent.java
@@ -16,6 +16,22 @@
 
 package com.ning.billing.util.bus;
 
+import java.util.UUID;
+
 public interface BusEvent {
+	
+	public enum BusEventType {
+		ACCOUNT_CREATE,
+		ACCOUNT_CHANGE,
+		SUBSCRIPTION_TRANSITION,
+		BUNDLE_REPAIR,
+		INVOICE_EMPTY,
+		INVOICE_CREATION,
+		PAYMENT_INFO,
+		PAYMENT_ERROR
+	}
 
+	public BusEventType getBusEventType();
+	
+	public UUID getUserToken();
 }
diff --git a/api/src/main/java/com/ning/billing/util/callcontext/CallContext.java b/api/src/main/java/com/ning/billing/util/callcontext/CallContext.java
index da36f10..8be4760 100644
--- a/api/src/main/java/com/ning/billing/util/callcontext/CallContext.java
+++ b/api/src/main/java/com/ning/billing/util/callcontext/CallContext.java
@@ -16,9 +16,12 @@
 
 package com.ning.billing.util.callcontext;
 
+import java.util.UUID;
+
 import org.joda.time.DateTime;
 
 public interface CallContext {
+	public UUID getUserToken();
     public String getUserName();
     public CallOrigin getCallOrigin();
     public UserType getUserType();
diff --git a/api/src/main/java/com/ning/billing/util/customfield/Customizable.java b/api/src/main/java/com/ning/billing/util/customfield/Customizable.java
index daf549c..e43ed82 100644
--- a/api/src/main/java/com/ning/billing/util/customfield/Customizable.java
+++ b/api/src/main/java/com/ning/billing/util/customfield/Customizable.java
@@ -19,6 +19,7 @@ package com.ning.billing.util.customfield;
 import java.util.List;
 
 import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.dao.ObjectType;
 import com.ning.billing.util.entity.Entity;
 
 public interface Customizable {
@@ -38,5 +39,5 @@ public interface Customizable {
 
     public void clearPersistedFields(CallContext context);
 
-    public String getObjectName();
+    public ObjectType getObjectType();
 }
diff --git a/api/src/main/java/com/ning/billing/util/dao/ObjectType.java b/api/src/main/java/com/ning/billing/util/dao/ObjectType.java
new file mode 100644
index 0000000..18d987c
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/util/dao/ObjectType.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2010-2011 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.util.dao;
+
+public enum ObjectType {
+    ACCOUNT("account"),
+    ACCOUNT_EMAIL("account email"),
+    BUNDLE("subscription bundle"),
+    INVOICE("invoice"),
+    RECURRING_INVOICE_ITEM("recurring_invoice_item"),
+    SUBSCRIPTION("subscription");
+
+    private final String objectName;
+    ObjectType(String objectName) {
+        this.objectName = objectName;
+    }
+
+    public String getObjectName() {
+        return objectName;
+    }
+}
diff --git a/api/src/main/java/com/ning/billing/util/email/EmailApiException.java b/api/src/main/java/com/ning/billing/util/email/EmailApiException.java
new file mode 100644
index 0000000..9b5d6ba
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/util/email/EmailApiException.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2010-2011 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.util.email;
+
+import com.ning.billing.BillingExceptionBase;
+import com.ning.billing.ErrorCode;
+
+public class EmailApiException extends BillingExceptionBase {
+    private static final long serialVersionUID = 1L;
+
+    public EmailApiException(Throwable cause, int code, final String msg) {
+        super(cause, code, msg);
+    }
+
+    public EmailApiException(Throwable cause, ErrorCode code, final Object... args) {
+        super(cause, code, args);
+    }
+
+    public EmailApiException(ErrorCode code, final Object... args) {
+        super(code, args);
+    }
+}
diff --git a/api/src/main/java/com/ning/billing/util/entity/Entity.java b/api/src/main/java/com/ning/billing/util/entity/Entity.java
index 3369538..f363534 100644
--- a/api/src/main/java/com/ning/billing/util/entity/Entity.java
+++ b/api/src/main/java/com/ning/billing/util/entity/Entity.java
@@ -16,12 +16,8 @@
 
 package com.ning.billing.util.entity;
 
-import org.joda.time.DateTime;
-
 import java.util.UUID;
 
-public interface Entity<T> {
+public interface Entity {
     public UUID getId();
-    public String getCreatedBy();
-    public DateTime getCreatedDate();
 }
diff --git a/api/src/main/java/com/ning/billing/util/entity/UpdatableEntity.java b/api/src/main/java/com/ning/billing/util/entity/UpdatableEntity.java
index c860ddb..758d7dc 100644
--- a/api/src/main/java/com/ning/billing/util/entity/UpdatableEntity.java
+++ b/api/src/main/java/com/ning/billing/util/entity/UpdatableEntity.java
@@ -16,9 +16,5 @@
 
 package com.ning.billing.util.entity;
 
-import org.joda.time.DateTime;
-
 public interface UpdatableEntity extends Entity {
-    public String getUpdatedBy();
-    public DateTime getUpdatedDate();
 }
diff --git a/api/src/main/java/com/ning/billing/util/queue/QueueLifecycle.java b/api/src/main/java/com/ning/billing/util/queue/QueueLifecycle.java
new file mode 100644
index 0000000..8db4ec7
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/util/queue/QueueLifecycle.java
@@ -0,0 +1,34 @@
+/* 
+ * Copyright 2010-2011 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.util.queue;
+
+public interface QueueLifecycle {
+    /**
+     * Starts the queue
+     */
+    public void startQueue();
+
+    /**
+     * Stop the queue
+     *
+     */
+    public void stopQueue();
+    
+    /**
+     *  Processes event from queue
+     */
+    public int doProcessEvents();
+}
diff --git a/api/src/main/java/com/ning/billing/util/tag/ControlTagType.java b/api/src/main/java/com/ning/billing/util/tag/ControlTagType.java
index 406ec54..9f4bb38 100644
--- a/api/src/main/java/com/ning/billing/util/tag/ControlTagType.java
+++ b/api/src/main/java/com/ning/billing/util/tag/ControlTagType.java
@@ -18,7 +18,8 @@ package com.ning.billing.util.tag;
 
 public enum ControlTagType {
     AUTO_PAY_OFF("Suspends payments until removed.", true, false),
-    AUTO_INVOICING_OFF("Suspends invoicing until removed.", false, true),
+    AUTO_INVOICING_OFF("Suspends invoicing until removed.", false, true), 
+    OVERDUE_ENFORCEMENT_OFF("Suspends overdue enforcement behaviour until removed.", false, false),
     WRITTEN_OFF("Indicated that an invoice is written off. No billing or payment effect.", false, false);
 
     private final String description;
diff --git a/api/src/main/java/com/ning/billing/util/tag/TagDefinition.java b/api/src/main/java/com/ning/billing/util/tag/TagDefinition.java
index 408e09d..d35e011 100644
--- a/api/src/main/java/com/ning/billing/util/tag/TagDefinition.java
+++ b/api/src/main/java/com/ning/billing/util/tag/TagDefinition.java
@@ -18,8 +18,11 @@ package com.ning.billing.util.tag;
 
 import com.ning.billing.util.entity.Entity;
 
+// TODO: needs to surface created date, created by, isControlTag
 public interface TagDefinition extends Entity {
     String getName();
 
     String getDescription();
+
+    Boolean isControlTag();
 }
diff --git a/api/src/main/java/com/ning/billing/util/tag/Taggable.java b/api/src/main/java/com/ning/billing/util/tag/Taggable.java
index 3b2e8b0..37d15bf 100644
--- a/api/src/main/java/com/ning/billing/util/tag/Taggable.java
+++ b/api/src/main/java/com/ning/billing/util/tag/Taggable.java
@@ -21,11 +21,16 @@ import org.joda.time.DateTime;
 
 public interface Taggable {
     public List<Tag> getTagList();
-    public boolean hasTag(String tagName);
+
+    public boolean hasTag(TagDefinition tagDefinition);
+    public boolean hasTag(ControlTagType controlTagType);
+
     public void addTag(TagDefinition definition);
     public void addTags(List<Tag> tags);
+    public void addTagsFromDefinitions(List<TagDefinition> tagDefinitions);
     public void clearTags();
     public void removeTag(TagDefinition definition);
+
     public boolean generateInvoice();
     public boolean processPayment();
 }
diff --git a/api/src/main/java/com/ning/billing/util/tag/TagStore.java b/api/src/main/java/com/ning/billing/util/tag/TagStore.java
index 705e203..22749b9 100644
--- a/api/src/main/java/com/ning/billing/util/tag/TagStore.java
+++ b/api/src/main/java/com/ning/billing/util/tag/TagStore.java
@@ -23,7 +23,9 @@ public interface TagStore extends EntityCollection<Tag> {
 
     public boolean generateInvoice();
 
-    public void remove(String tagName);
+    public boolean containsTagForDefinition(TagDefinition definition);
 
-    public boolean containsTag(String tagName);
+    public boolean containsTagForControlTagType(ControlTagType controlTagType);
+
+    public Tag remove(TagDefinition tagDefinition);
 }
diff --git a/api/src/main/java/com/ning/billing/util/template/translation/TranslatorConfig.java b/api/src/main/java/com/ning/billing/util/template/translation/TranslatorConfig.java
new file mode 100644
index 0000000..506c8bc
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/util/template/translation/TranslatorConfig.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2010-2011 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.util.template.translation;
+
+import org.skife.config.Config;
+import org.skife.config.Default;
+
+public interface TranslatorConfig {
+    @Config("email.default.locale")
+    @Default("en_US")
+    public String getDefaultLocale();
+}
diff --git a/api/src/main/java/com/ning/billing/util/userrequest/CompletionUserRequest.java b/api/src/main/java/com/ning/billing/util/userrequest/CompletionUserRequest.java
new file mode 100644
index 0000000..bf67736
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/util/userrequest/CompletionUserRequest.java
@@ -0,0 +1,19 @@
+/* 
+ * Copyright 2010-2011 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.util.userrequest;
+
+public interface CompletionUserRequest extends CompletionUserRequestNotifier, CompletionUserRequestWaiter{
+}
diff --git a/api/src/main/java/com/ning/billing/util/userrequest/CompletionUserRequestNotifier.java b/api/src/main/java/com/ning/billing/util/userrequest/CompletionUserRequestNotifier.java
new file mode 100644
index 0000000..62b9256
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/util/userrequest/CompletionUserRequestNotifier.java
@@ -0,0 +1,25 @@
+/* 
+ * Copyright 2010-2011 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.util.userrequest;
+
+import com.ning.billing.util.bus.BusEvent;
+
+public interface CompletionUserRequestNotifier {
+
+    public void notifyForCompletion();
+    
+    public void onBusEvent(BusEvent curEvent);
+}
diff --git a/api/src/main/java/com/ning/billing/util/userrequest/CompletionUserRequestWaiter.java b/api/src/main/java/com/ning/billing/util/userrequest/CompletionUserRequestWaiter.java
new file mode 100644
index 0000000..d1258c8
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/util/userrequest/CompletionUserRequestWaiter.java
@@ -0,0 +1,48 @@
+/* 
+ * Copyright 2010-2011 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.util.userrequest;
+
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.TimeoutException;
+
+import com.ning.billing.account.api.AccountChangeEvent;
+import com.ning.billing.account.api.AccountCreationEvent;
+import com.ning.billing.entitlement.api.user.SubscriptionEvent;
+import com.ning.billing.invoice.api.EmptyInvoiceEvent;
+import com.ning.billing.invoice.api.InvoiceCreationEvent;
+import com.ning.billing.payment.api.PaymentErrorEvent;
+import com.ning.billing.payment.api.PaymentInfoEvent;
+import com.ning.billing.util.bus.BusEvent;
+
+public interface CompletionUserRequestWaiter {
+
+    public List<BusEvent> waitForCompletion(final long timeoutMilliSec) throws InterruptedException, TimeoutException;
+    
+    public void onAccountCreation(final AccountCreationEvent curEvent);
+
+    public void onAccountChange(final AccountChangeEvent curEvent);
+
+    public void onSubscriptionTransition(final SubscriptionEvent curEvent);    
+
+    public void onInvoiceCreation(final InvoiceCreationEvent curEvent);    
+    
+    public void onEmptyInvoice(final EmptyInvoiceEvent curEvent);        
+
+    public void onPaymentInfo(final PaymentInfoEvent curEvent);    
+
+    public void onPaymentError(final PaymentErrorEvent curEvent);    
+}

beatrix/pom.xml 39(+39 -0)

diff --git a/beatrix/pom.xml b/beatrix/pom.xml
index 1ac3279..a742878 100644
--- a/beatrix/pom.xml
+++ b/beatrix/pom.xml
@@ -41,6 +41,10 @@
             <artifactId>killbill-catalog</artifactId>
         </dependency>
         <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-junction</artifactId>
+        </dependency>
+        <dependency>
             <groupId>com.google.guava</groupId>
             <artifactId>guava</artifactId>
         </dependency>
@@ -61,6 +65,19 @@
             <groupId>joda-time</groupId>
             <artifactId>joda-time</artifactId>
         </dependency>
+
+        <!-- TEST SCOPE -->
+        <dependency> 
+            <groupId>com.jayway.awaitility</groupId>
+            <artifactId>awaitility</artifactId>
+            <scope>test</scope>
+        </dependency>      
+         <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-junction</artifactId>
+             <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
         <dependency>
             <groupId>com.ning.billing</groupId>
             <artifactId>killbill-payment</artifactId>
@@ -68,6 +85,28 @@
             <scope>test</scope>
         </dependency>
         <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-invoice</artifactId>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-account</artifactId>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-account</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-overdue</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
             <groupId>org.jdbi</groupId>
             <artifactId>jdbi</artifactId>
             <scope>test</scope>
diff --git a/beatrix/src/main/java/com/ning/billing/beatrix/lifecycle/ServiceFinder.java b/beatrix/src/main/java/com/ning/billing/beatrix/lifecycle/ServiceFinder.java
index 83115de..e27dff9 100644
--- a/beatrix/src/main/java/com/ning/billing/beatrix/lifecycle/ServiceFinder.java
+++ b/beatrix/src/main/java/com/ning/billing/beatrix/lifecycle/ServiceFinder.java
@@ -60,7 +60,7 @@ public class ServiceFinder {
 		    final Set<String> packageFilter = new HashSet<String>();
 		    packageFilter.add("com.ning.billing");
 		    final String jarFilter = "killbill";
-			return findClasses(loader, KillbillService.class.getName().toString(), jarFilter, packageFilter);
+			return findClasses(loader, KillbillService.class.getName(), jarFilter, packageFilter);
 		} catch (ClassNotFoundException nfe) {
 			throw new RuntimeException("Failed to initialize ClassFinder", nfe);
 		}
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/overdue/TestOverdueIntegration.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/overdue/TestOverdueIntegration.java
new file mode 100644
index 0000000..e44ddc8
--- /dev/null
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/overdue/TestOverdueIntegration.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2010-2011 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 static org.testng.Assert.assertNotNull;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+
+import org.joda.time.DateTime;
+import org.testng.Assert;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Guice;
+import org.testng.annotations.Test;
+
+import com.google.inject.Inject;
+import com.ning.billing.account.api.Account;
+import com.ning.billing.beatrix.integration.BeatrixModule;
+import com.ning.billing.beatrix.integration.TestIntegrationBase;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.catalog.api.PriceListSet;
+import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.entitlement.api.user.SubscriptionData;
+import com.ning.billing.junction.api.BlockingApi;
+import com.ning.billing.junction.api.BlockingState;
+import com.ning.billing.overdue.config.OverdueConfig;
+import com.ning.billing.payment.provider.MockPaymentProviderPlugin;
+import com.ning.billing.util.clock.ClockMock;
+import com.ning.billing.util.config.XMLLoader;
+
+@Test(groups = "slow")
+@Guice(modules = {BeatrixModule.class})
+public class TestOverdueIntegration extends TestIntegrationBase {
+    private final String configXml =  
+            "<overdueConfig>" +
+                    "   <bundleOverdueStates>" +
+                    "       <state name=\"OD1\">" +
+                    "           <condition>" +
+                    "               <timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>" +
+                    "                   <unit>MONTHS</unit><number>1</number>" +
+                    "               </timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>" +
+                    "           </condition>" +
+                    "           <externalMessage>Reached OD1</externalMessage>" +
+                    "           <blockChanges>true</blockChanges>" +
+                    "           <disableEntitlementAndChangesBlocked>false</disableEntitlementAndChangesBlocked>" +
+                    "       </state>" +
+                    "       <state name=\"OD2\">" +
+                    "           <condition>" +
+                    "               <timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>" +
+                    "                   <unit>MONTHS</unit><number>2</number>" +
+                    "               </timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>" +
+                    "           </condition>" +
+                    "           <externalMessage>Reached OD1</externalMessage>" +
+                    "           <blockChanges>true</blockChanges>" +
+                    "           <disableEntitlementAndChangesBlocked>true</disableEntitlementAndChangesBlocked>" +
+                    "       </state>" +
+                    "   </bundleOverdueStates>" +
+                    "</overdueConfig>";
+    private OverdueConfig config; 
+    
+    @Inject
+    private ClockMock clock;
+    
+    @Inject
+    private MockPaymentProviderPlugin paymentPlugin;
+    
+    @Inject
+    private BlockingApi blockingApi;
+    
+    private Account account;
+    private SubscriptionBundle bundle;
+    private String productName;
+    private BillingPeriod term;
+    private String planSetName;
+    
+    @BeforeMethod(groups = {"slow"})
+    public void setupOverdue() throws Exception {
+        InputStream is = new ByteArrayInputStream(configXml.getBytes());
+        config = XMLLoader.getObjectFromStreamNoValidation(is,  OverdueConfig.class);
+        Account account = accountUserApi.createAccount(getAccountData(25), null, null, context);
+        assertNotNull(account);
+
+        bundle = entitlementUserApi.createBundleForAccount(account.getId(), "whatever", context);
+
+        productName = "Shotgun";
+        term = BillingPeriod.MONTHLY;
+        planSetName = PriceListSet.DEFAULT_PRICELIST_NAME;
+        
+        // create account
+        // set mock payments to fail
+        // reset clock
+        // configure basic OD state rules for 2 states OD1 1-2month, OD2 2-3 month
+    }
+    
+    @AfterMethod
+    public void cleanup(){
+        // Clear databases
+    }
+    
+    @Test(groups={"slow"}, enabled = true)
+    public void testBasicOverdueState() throws Exception {
+        clock.setTime(new DateTime(2012, 5, 1, 0, 3, 42, 0));
+               
+        // set next invoice to fail and create network 
+        paymentPlugin.makeNextInvoiceFail();
+        SubscriptionData baseSubscription = subscriptionDataFromSubscription(entitlementUserApi.createSubscription(bundle.getId(),
+                new PlanPhaseSpecifier(productName, ProductCategory.BASE, term, planSetName, null), null, context));
+        assertNotNull(baseSubscription);
+
+       // advance time 2weeks
+       clock.addWeeks(2);
+        
+       // should still be in clear state
+       BlockingState state = blockingApi.getBlockingStateFor(bundle);
+       Assert.assertEquals(state.getStateName(), BlockingApi.CLEAR_STATE_NAME);
+       // set next invoice to fail and advance time 1 month
+       clock.addWeeks(4);
+       
+       // should now be in OD1 state
+       // set next invoice to fail and advance time 1 month
+       // should now be in OD2 state
+       clock.addWeeks(4);
+        
+    }
+}
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/payment/PaymentTestModule.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/payment/PaymentTestModule.java
new file mode 100644
index 0000000..d444776
--- /dev/null
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/payment/PaymentTestModule.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2010-2011 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.payment;
+
+import com.ning.billing.account.dao.MockAccountDao;
+import com.ning.billing.invoice.dao.MockInvoiceDao;
+import org.apache.commons.collections.MapUtils;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.inject.Provider;
+import com.ning.billing.account.dao.AccountDao;
+import com.ning.billing.config.PaymentConfig;
+import com.ning.billing.invoice.dao.InvoiceDao;
+import com.ning.billing.junction.api.BillingApi;
+import com.ning.billing.mock.BrainDeadProxyFactory;
+import com.ning.billing.payment.dao.MockPaymentDao;
+import com.ning.billing.payment.dao.PaymentDao;
+import com.ning.billing.payment.provider.MockPaymentProviderPluginModule;
+import com.ning.billing.payment.setup.PaymentModule;
+import com.ning.billing.util.bus.Bus;
+import com.ning.billing.util.bus.InMemoryBus;
+import com.ning.billing.util.notificationq.MockNotificationQueueService;
+import com.ning.billing.util.notificationq.NotificationQueueService;
+
+public class PaymentTestModule extends PaymentModule {
+	public static class MockProvider implements Provider<BillingApi> {
+		@Override
+		public BillingApi get() {
+			return BrainDeadProxyFactory.createBrainDeadProxyFor(BillingApi.class);
+		}
+
+	}
+
+    public PaymentTestModule() {
+        super(MapUtils.toProperties(ImmutableMap.of("killbill.payment.provider.default", "my-mock",
+                "killbill.payment.engine.events.off", "false")));
+    }
+
+    @Override
+    protected void installPaymentDao() {
+        bind(PaymentDao.class).to(MockPaymentDao.class).asEagerSingleton();
+    }
+
+    @Override
+    protected void installPaymentProviderPlugins(PaymentConfig config) {
+        install(new MockPaymentProviderPluginModule("my-mock"));
+    }
+
+    @Override
+    protected void configure() {
+        super.configure();
+        bind(Bus.class).to(InMemoryBus.class).asEagerSingleton();
+        bind(MockAccountDao.class).asEagerSingleton();
+        bind(AccountDao.class).to(MockAccountDao.class);
+        bind(MockInvoiceDao.class).asEagerSingleton();
+        bind(InvoiceDao.class).to(MockInvoiceDao.class);
+        bind(NotificationQueueService.class).to(MockNotificationQueueService.class).asEagerSingleton();
+
+    }
+}
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/payment/TestHelper.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/payment/TestHelper.java
new file mode 100644
index 0000000..753b966
--- /dev/null
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/payment/TestHelper.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2010-2011 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.payment;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import com.ning.billing.mock.BrainDeadProxyFactory;
+import org.apache.commons.lang.RandomStringUtils;
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+
+import com.google.inject.Inject;
+import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.AccountUserApi;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.api.test.InvoiceTestApi;
+import com.ning.billing.invoice.model.DefaultInvoice;
+import com.ning.billing.invoice.model.RecurringInvoiceItem;
+import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.callcontext.CallContextFactory;
+import com.ning.billing.util.callcontext.CallOrigin;
+import com.ning.billing.util.callcontext.UserType;
+import com.ning.billing.util.entity.EntityPersistenceException;
+
+public class TestHelper {
+    protected final AccountUserApi accountUserApi;
+    protected final InvoiceTestApi invoiceTestApi;
+    private final CallContext context;
+
+    @Inject
+    public TestHelper(CallContextFactory factory, AccountUserApi accountUserApi, InvoiceTestApi invoiceTestApi) {
+        this.accountUserApi = accountUserApi;
+        this.invoiceTestApi = invoiceTestApi;
+        context = factory.createCallContext("Payment Test", CallOrigin.TEST, UserType.TEST);
+    }
+
+    // These helper methods can be overridden in a plugin implementation
+    public Account createTestCreditCardAccount() throws EntityPersistenceException {
+        String email = "ccuser" + RandomStringUtils.randomAlphanumeric(8) + "@example.com";
+        final Account account = createTestAccount(email);
+
+        ((ZombieControl)accountUserApi).addResult("getAccountById", account);
+        ((ZombieControl)accountUserApi).addResult("getAccountByKey", account);
+        return account;
+    }
+
+    public Account createTestPayPalAccount() throws EntityPersistenceException {
+        final Account account = createTestAccount("ppuser@example.com");
+        ((ZombieControl)accountUserApi).addResult("getAccountById", account);
+        ((ZombieControl)accountUserApi).addResult("getAccountByKey", account);
+        return account;
+    }
+
+    private Account createTestAccount(String email) {
+        Account account = BrainDeadProxyFactory.createBrainDeadProxyFor(Account.class);
+        ZombieControl zombie = (ZombieControl) account;
+        zombie.addResult("getId", UUID.randomUUID());
+        String name = "First" + RandomStringUtils.randomAlphanumeric(5) + " " + "Last" + RandomStringUtils.randomAlphanumeric(5);
+        zombie.addResult("getName", name);
+        zombie.addResult("getFirstNameLength", 10);
+        String externalKey = RandomStringUtils.randomAlphanumeric(10);
+        zombie.addResult("getExternalKey", externalKey);
+        zombie.addResult("getPhone", "123-456-7890");
+        zombie.addResult("getEmail", email);
+        zombie.addResult("getCurrency", Currency.USD);
+        zombie.addResult("getBillCycleDay", 1);
+        zombie.addResult("getPaymentProviderName", "");
+
+        return account;
+    }
+
+    public Invoice createTestInvoice(Account account,
+                                     DateTime targetDate,
+                                     Currency currency,
+                                     InvoiceItem... items) {
+        Invoice invoice = new DefaultInvoice(account.getId(), new DateTime(), targetDate, currency);
+
+        for (InvoiceItem item : items) {
+            if (item instanceof RecurringInvoiceItem) {
+                RecurringInvoiceItem recurringInvoiceItem = (RecurringInvoiceItem) item;
+                invoice.addInvoiceItem(new RecurringInvoiceItem(invoice.getId(),
+                                                               account.getId(),
+                                                               recurringInvoiceItem.getBundleId(),
+                                                               recurringInvoiceItem.getSubscriptionId(),
+                                                               recurringInvoiceItem.getPlanName(),
+                                                               recurringInvoiceItem.getPhaseName(),
+                                                               recurringInvoiceItem.getStartDate(),
+                                                               recurringInvoiceItem.getEndDate(),
+                                                               recurringInvoiceItem.getAmount(),
+                                                               recurringInvoiceItem.getRate(),
+                                                               recurringInvoiceItem.getCurrency()));
+            }
+        }
+
+        invoiceTestApi.create(invoice, context);
+        return invoice;
+    }
+
+    public Invoice createTestInvoice(Account account) {
+        final DateTime now = new DateTime(DateTimeZone.UTC);
+        final UUID subscriptionId = UUID.randomUUID();
+        final UUID bundleId = UUID.randomUUID();
+        final BigDecimal amount = new BigDecimal("10.00");
+        
+        final InvoiceItem item = new RecurringInvoiceItem(null, account.getId(), bundleId, subscriptionId, "test plan", "test phase", now, now.plusMonths(1),
+                amount, new BigDecimal("1.0"), Currency.USD);
+
+
+        return createTestInvoice(account, now, Currency.USD, item);
+    }
+}
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegration.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegration.java
index e334966..dae1175 100644
--- a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegration.java
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegration.java
@@ -19,272 +19,62 @@ package com.ning.billing.beatrix.integration;
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertNotNull;
 import static org.testng.Assert.assertTrue;
-import static org.testng.Assert.fail;
 
-import java.io.IOException;
 import java.math.BigDecimal;
-import java.util.ArrayList;
 import java.util.List;
 import java.util.UUID;
 
-import com.ning.billing.account.api.AccountApiException;
-import com.ning.billing.catalog.api.PhaseType;
-import com.ning.billing.dbi.MysqlTestingHelper;
-import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
-
-import com.ning.billing.invoice.api.Invoice;
-import com.ning.billing.invoice.api.InvoiceItem;
-import com.ning.billing.invoice.model.InvoicingConfiguration;
-import com.ning.billing.util.callcontext.CallContext;
-import com.ning.billing.util.callcontext.CallOrigin;
-import com.ning.billing.util.callcontext.UserType;
-import com.ning.billing.util.callcontext.DefaultCallContextFactory;
-import org.apache.commons.io.IOUtils;
-import org.apache.commons.lang.RandomStringUtils;
 import org.joda.time.DateTime;
-import org.joda.time.DateTimeZone;
 import org.joda.time.Interval;
-import org.skife.jdbi.v2.Handle;
-import org.skife.jdbi.v2.IDBI;
-import org.skife.jdbi.v2.TransactionCallback;
-import org.skife.jdbi.v2.TransactionStatus;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import org.testng.annotations.AfterMethod;
-import org.testng.annotations.AfterSuite;
-
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.BeforeSuite;
 import org.testng.annotations.Guice;
 import org.testng.annotations.Test;
 
-import com.google.inject.Inject;
 import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.AccountApiException;
 import com.ning.billing.account.api.AccountData;
-import com.ning.billing.account.api.AccountService;
-import com.ning.billing.account.api.AccountUserApi;
-import com.ning.billing.beatrix.integration.TestBusHandler.NextEvent;
-import com.ning.billing.beatrix.lifecycle.Lifecycle;
+import com.ning.billing.api.TestApiListener.NextEvent;
 import com.ning.billing.catalog.api.BillingPeriod;
 import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.catalog.api.PhaseType;
 import com.ning.billing.catalog.api.PlanPhaseSpecifier;
 import com.ning.billing.catalog.api.PriceListSet;
 import com.ning.billing.catalog.api.ProductCategory;
-import com.ning.billing.entitlement.api.EntitlementService;
-import com.ning.billing.entitlement.api.user.EntitlementUserApi;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
 import com.ning.billing.entitlement.api.user.SubscriptionBundle;
 import com.ning.billing.entitlement.api.user.SubscriptionData;
-import com.ning.billing.invoice.api.InvoiceService;
-import com.ning.billing.invoice.api.InvoiceUserApi;
-
-import com.ning.billing.util.clock.ClockMock;
-import com.ning.billing.util.bus.BusService;
+import com.ning.billing.invoice.api.Invoice;
 
 @Test(groups = "slow")
-@Guice(modules = {MockModule.class})
-public class TestIntegration {
-    private static final int NUMBER_OF_DECIMALS = InvoicingConfiguration.getNumberOfDecimals();
-    private static final int ROUNDING_METHOD = InvoicingConfiguration.getRoundingMode();
-
-    private static final BigDecimal ONE = new BigDecimal("1.0000").setScale(NUMBER_OF_DECIMALS);
-    private static final BigDecimal TWENTY_NINE = new BigDecimal("29.0000").setScale(NUMBER_OF_DECIMALS);
-    private static final BigDecimal THIRTY = new BigDecimal("30.0000").setScale(NUMBER_OF_DECIMALS);
-    private static final BigDecimal THIRTY_ONE = new BigDecimal("31.0000").setScale(NUMBER_OF_DECIMALS);
-
-    private static final Logger log = LoggerFactory.getLogger(TestIntegration.class);
-    private static long AT_LEAST_ONE_MONTH_MS =  31L * 24L * 3600L * 1000L;
-
-    private static final long DELAY = 5000;
-
-    @Inject IDBI dbi;
-
-    @Inject
-    private ClockMock clock;
-    private CallContext context;
-
-    @Inject
-    private Lifecycle lifecycle;
-
-    @Inject
-    private BusService busService;
-
-    @Inject
-    private EntitlementService entitlementService;
-
-    @Inject
-    private InvoiceService invoiceService;
-
-    @Inject
-    private AccountService accountService;
-
-    @Inject
-    private MysqlTestingHelper helper;
-
-    private EntitlementUserApi entitlementUserApi;
-
-    private InvoiceUserApi invoiceUserApi;
-
-    private AccountUserApi accountUserApi;
-
-    private TestBusHandler busHandler;
-
-    private void setupMySQL() throws IOException
-    {
-        final String accountDdl = IOUtils.toString(TestIntegration.class.getResourceAsStream("/com/ning/billing/account/ddl.sql"));
-        final String entitlementDdl = IOUtils.toString(TestIntegration.class.getResourceAsStream("/com/ning/billing/entitlement/ddl.sql"));
-        final String invoiceDdl = IOUtils.toString(TestIntegration.class.getResourceAsStream("/com/ning/billing/invoice/ddl.sql"));
-        final String paymentDdl = IOUtils.toString(TestIntegration.class.getResourceAsStream("/com/ning/billing/payment/ddl.sql"));
-        final String utilDdl = IOUtils.toString(TestIntegration.class.getResourceAsStream("/com/ning/billing/util/ddl.sql"));
-
-        helper.startMysql();
-
-        helper.initDb(accountDdl);
-        helper.initDb(entitlementDdl);
-        helper.initDb(invoiceDdl);
-        helper.initDb(paymentDdl);
-        helper.initDb(utilDdl);
-    }
-
-    @BeforeSuite(groups = "slow")
-    public void setup() throws Exception{
-
-        setupMySQL();
-
-        context = new DefaultCallContextFactory(clock).createCallContext("Integration Test", CallOrigin.TEST, UserType.TEST);
-
-        /**
-         * Initialize lifecyle for subset of services
-         */
-        busHandler = new TestBusHandler();
-        lifecycle.fireStartupSequencePriorEventRegistration();
-        busService.getBus().register(busHandler);
-        lifecycle.fireStartupSequencePostEventRegistration();
-
-
-
-        /**
-         * Retrieve APIs
-         */
-        entitlementUserApi = entitlementService.getUserApi();
-        invoiceUserApi = invoiceService.getUserApi();
-        accountUserApi = accountService.getAccountUserApi();
-    }
-
-    @AfterSuite(groups = "slow")
-    public void tearDown() throws Exception {
-        lifecycle.fireShutdownSequencePriorEventUnRegistration();
-        busService.getBus().unregister(busHandler);
-        lifecycle.fireShutdownSequencePostEventUnRegistration();
-        helper.stopMysql();
-    }
-
-
-    @BeforeMethod(groups = "slow")
-    public void setupTest() {
-
-        log.warn("\n");
-        log.warn("RESET TEST FRAMEWORK\n\n");
-        busHandler.reset();
-        clock.resetDeltaFromReality();
-        cleanupData();
-    }
-
-    @AfterMethod(groups = "slow")
-    public void cleanupTest() {
-        log.warn("DONE WITH TEST\n");
-    }
-
-    private void cleanupData() {
-        dbi.inTransaction(new TransactionCallback<Void>() {
-            @Override
-            public Void inTransaction(Handle h, TransactionStatus status)
-                    throws Exception {
-                h.execute("truncate table accounts");
-                h.execute("truncate table entitlement_events");
-                h.execute("truncate table subscriptions");
-                h.execute("truncate table bundles");
-                h.execute("truncate table notifications");
-                h.execute("truncate table claimed_notifications");
-                h.execute("truncate table invoices");
-                h.execute("truncate table fixed_invoice_items");
-                h.execute("truncate table recurring_invoice_items");
-                h.execute("truncate table tag_definitions");
-                h.execute("truncate table tags");
-                h.execute("truncate table custom_fields");
-                h.execute("truncate table invoice_payments");
-                h.execute("truncate table payment_attempts");
-                h.execute("truncate table payments");
-                return null;
-            }
-        });
-    }
-
-    private void verifyTestResult(UUID accountId, UUID subscriptionId,
-                                  DateTime startDate, DateTime endDate,
-                                  BigDecimal amount, DateTime chargeThroughDate,
-                                  int totalInvoiceItemCount) {
-        SubscriptionData subscription = (SubscriptionData) entitlementUserApi.getSubscriptionFromId(subscriptionId);
-
-        List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(accountId);
-        List<InvoiceItem> invoiceItems = new ArrayList<InvoiceItem>();
-        for (Invoice invoice : invoices) {
-            invoiceItems.addAll(invoice.getInvoiceItems());
-        }
-        assertEquals(invoiceItems.size(), totalInvoiceItemCount);
-
-        boolean wasFound = false;
-
-        for (InvoiceItem item : invoiceItems) {
-            if (item.getStartDate().compareTo(startDate) == 0) {
-                if (item.getEndDate().compareTo(endDate) == 0) {
-                    if (item.getAmount().compareTo(amount) == 0) {
-                        wasFound = true;
-                        break;
-                    }
-                }
-            }
-        }
-
-        if (!wasFound) {
-            fail();
-        }
-
-        DateTime ctd = subscription.getChargedThroughDate();
-        assertNotNull(ctd);
-        log.info("Checking CTD: " + ctd.toString() + "; clock is " + clock.getUTCNow().toString());
-        assertTrue(clock.getUTCNow().isBefore(ctd));
-        assertTrue(ctd.compareTo(chargeThroughDate) == 0);
-    }
-
+@Guice(modules = {BeatrixModule.class})
+public class TestIntegration extends TestIntegrationBase {
     @Test(groups = "slow", enabled = true)
     public void testBasePlanCompleteWithBillingDayInPast() throws Exception {
+        log.info("Starting testBasePlanCompleteWithBillingDayInPast");
         DateTime startDate = new DateTime(2012, 2, 1, 0, 3, 42, 0);
         testBasePlanComplete(startDate, 31, false);
     }
 
     @Test(groups = "slow", enabled = true)
     public void testBasePlanCompleteWithBillingDayPresent() throws Exception {
+        log.info("Starting testBasePlanCompleteWithBillingDayPresent");
         DateTime startDate = new DateTime(2012, 2, 1, 0, 3, 42, 0);
         testBasePlanComplete(startDate, 1, false);
     }
 
     @Test(groups = "slow", enabled = true)
     public void testBasePlanCompleteWithBillingDayAlignedWithTrial() throws Exception {
+        log.info("Starting testBasePlanCompleteWithBillingDayAlignedWithTrial");
         DateTime startDate = new DateTime(2012, 2, 1, 0, 3, 42, 0);
         testBasePlanComplete(startDate, 2, false);
     }
 
     @Test(groups = "slow", enabled = true)
     public void testBasePlanCompleteWithBillingDayInFuture() throws Exception {
+        log.info("Starting testBasePlanCompleteWithBillingDayInFuture");
         DateTime startDate = new DateTime(2012, 2, 1, 0, 3, 42, 0);
         testBasePlanComplete(startDate, 3, true);
     }
 
-    private void waitForDebug() throws Exception {
-        Thread.sleep(600000);
-    }
-
     @Test(groups = {"slow", "stress"}, enabled = false)
     public void stressTest() throws Exception {
         final int maxIterations = 7;
@@ -305,9 +95,101 @@ public class TestIntegration {
         }
     }
 
+    // STEPH set to disabled until test written properly and fixed
+    @Test(groups = "slow", enabled = false)
+    public void testRepairChangeBPWithAddonIncluded() throws Exception {
+        
+        log.info("Starting testRepairChangeBPWithAddonIncluded");
+        
+        DateTime initialDate = new DateTime(2012, 4, 25, 0, 3, 42, 0);
+        clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis());
+        
+        Account account = accountUserApi.createAccount(getAccountData(25), null, null, context);
+        assertNotNull(account);
+
+        SubscriptionBundle bundle = entitlementUserApi.createBundleForAccount(account.getId(), "whatever", context);
+
+        String productName = "Shotgun";
+        BillingPeriod term = BillingPeriod.MONTHLY;
+        String planSetName = PriceListSet.DEFAULT_PRICELIST_NAME;
+
+        busHandler.pushExpectedEvent(NextEvent.CREATE);
+        busHandler.pushExpectedEvent(NextEvent.INVOICE);
+        SubscriptionData baseSubscription = subscriptionDataFromSubscription(entitlementUserApi.createSubscription(bundle.getId(),
+                new PlanPhaseSpecifier(productName, ProductCategory.BASE, term, planSetName, null), null, context));
+        assertNotNull(baseSubscription);
+        assertTrue(busHandler.isCompleted(DELAY));
+   
+        // MOVE CLOCK A LITTLE BIT-- STILL IN TRIAL
+        Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(3));
+        clock.addDeltaFromReality(it.toDurationMillis());
+
+        busHandler.pushExpectedEvent(NextEvent.CREATE);
+        busHandler.pushExpectedEvent(NextEvent.INVOICE);
+        busHandler.pushExpectedEvent(NextEvent.PAYMENT);
+        SubscriptionData aoSubscription = subscriptionDataFromSubscription(entitlementUserApi.createSubscription(bundle.getId(),
+                new PlanPhaseSpecifier("Telescopic-Scope", ProductCategory.ADD_ON, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null), null, context));
+        assertTrue(busHandler.isCompleted(DELAY));
+        
+        busHandler.pushExpectedEvent(NextEvent.CREATE);
+        busHandler.pushExpectedEvent(NextEvent.INVOICE);
+        busHandler.pushExpectedEvent(NextEvent.PAYMENT);
+        SubscriptionData aoSubscription2 = subscriptionDataFromSubscription(entitlementUserApi.createSubscription(bundle.getId(),
+                new PlanPhaseSpecifier("Laser-Scope", ProductCategory.ADD_ON, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null), null, context)); 
+        assertTrue(busHandler.isCompleted(DELAY));
+        
+
+        // MOVE CLOCK A LITTLE BIT MORE -- EITHER STAY IN TRIAL OR GET OUT   
+        int duration = 35;
+        it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(duration));
+        busHandler.pushExpectedEvent(NextEvent.PHASE);
+        busHandler.pushExpectedEvent(NextEvent.PHASE);
+        busHandler.pushExpectedEvent(NextEvent.PHASE);            
+        busHandler.pushExpectedEvent(NextEvent.INVOICE);
+        busHandler.pushExpectedEvent(NextEvent.PAYMENT);
+        clock.addDeltaFromReality(it.toDurationMillis());
+        assertTrue(busHandler.isCompleted(DELAY));
+        
+        assertListenerStatus();
+    }
+   
+    @Test(groups = {"slow"})
+    public void testRepairForInvoicing() throws AccountApiException, EntitlementUserApiException {
+
+        log.info("Starting testRepairForInvoicing");
+
+        Account account = accountUserApi.createAccount(getAccountData(1), null, null, context);
+        UUID accountId = account.getId();
+        assertNotNull(account);
+
+        DateTime initialDate = new DateTime(2012, 4, 25, 0, 3, 42, 0);
+        clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis());
+
+        SubscriptionBundle bundle = entitlementUserApi.createBundleForAccount(account.getId(), "someBundle", context);
+        assertNotNull(bundle);
+
+        String productName = "Shotgun";
+        BillingPeriod term = BillingPeriod.MONTHLY;
+        String planSetName = PriceListSet.DEFAULT_PRICELIST_NAME;
+        entitlementUserApi.createSubscription(bundle.getId(),
+                new PlanPhaseSpecifier(productName, ProductCategory.BASE, term, planSetName, null), null, context);
+
+        busHandler.reset();
+        busHandler.pushExpectedEvent(NextEvent.CREATE);
+        busHandler.pushExpectedEvent(NextEvent.INVOICE);
+        assertTrue(busHandler.isCompleted(DELAY));
+
+        List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(accountId);
+        assertEquals(invoices.size(), 1);
+
+        // TODO: Jeff implement repair
+    }
+
     @Test(groups = "slow", enabled = true)
     public void testWithRecreatePlan() throws Exception {
 
+        log.info("Starting testWithRecreatePlan");
+
         DateTime initialDate = new DateTime(2012, 2, 1, 0, 3, 42, 0);
         int billingDay = 2;
 
@@ -329,8 +211,10 @@ public class TestIntegration {
         //
         busHandler.pushExpectedEvent(NextEvent.CREATE);
         busHandler.pushExpectedEvent(NextEvent.INVOICE);
-        SubscriptionData subscription = (SubscriptionData) entitlementUserApi.createSubscription(bundle.getId(),
-                new PlanPhaseSpecifier(productName, ProductCategory.BASE, term, planSetName, null), null, context);
+        
+        SubscriptionData subscription = subscriptionDataFromSubscription(entitlementUserApi.createSubscription(bundle.getId(),
+                new PlanPhaseSpecifier(productName, ProductCategory.BASE, term, planSetName, null), null, context));
+        
         assertNotNull(subscription);
         assertTrue(busHandler.isCompleted(DELAY));
 
@@ -352,7 +236,7 @@ public class TestIntegration {
         clock.addDeltaFromReality(AT_LEAST_ONE_MONTH_MS);
         assertTrue(busHandler.isCompleted(DELAY));
 
-        subscription = (SubscriptionData) entitlementUserApi.getSubscriptionFromId(subscription.getId());
+        subscription = subscriptionDataFromSubscription(entitlementUserApi.getSubscriptionFromId(subscription.getId()));
         subscription.cancel(clock.getUTCNow(), false, context);
 
         // MOVE AFTER CANCEL DATE AND EXPECT EVENT : NextEvent.CANCEL
@@ -366,19 +250,21 @@ public class TestIntegration {
         term = BillingPeriod.MONTHLY;
         planSetName = PriceListSet.DEFAULT_PRICELIST_NAME;
 
-        busHandler.pushExpectedEvent(NextEvent.CREATE);
+        busHandler.pushExpectedEvent(NextEvent.RE_CREATE);
         busHandler.pushExpectedEvent(NextEvent.INVOICE);
         busHandler.pushExpectedEvent(NextEvent.PAYMENT);
         subscription.recreate(new PlanPhaseSpecifier(productName, ProductCategory.BASE, term, planSetName, null), endDate, context);
         assertTrue(busHandler.isCompleted(DELAY));
 
-
+        assertListenerStatus();
     }
+     
     private void testBasePlanComplete(DateTime initialCreationDate, int billingDay,
                                       boolean proRationExpected) throws Exception {
 
         log.info("Beginning test with BCD of " + billingDay);
-        Account account = accountUserApi.createAccount(getAccountData(billingDay), null, null, context);
+        AccountData accountData = getAccountData(billingDay);
+        Account account = accountUserApi.createAccount(accountData, null, null, context);
         UUID accountId = account.getId();
         assertNotNull(account);
 
@@ -395,8 +281,9 @@ public class TestIntegration {
         //
         busHandler.pushExpectedEvent(NextEvent.CREATE);
         busHandler.pushExpectedEvent(NextEvent.INVOICE);
-        SubscriptionData subscription = (SubscriptionData) entitlementUserApi.createSubscription(bundle.getId(),
-                new PlanPhaseSpecifier(productName, ProductCategory.BASE, term, planSetName, null), null, context);
+        SubscriptionData subscription = subscriptionDataFromSubscription(entitlementUserApi.createSubscription(bundle.getId(),
+                new PlanPhaseSpecifier(productName, ProductCategory.BASE, term, planSetName, null), null, context));
+           
         assertNotNull(subscription);
 
         assertTrue(busHandler.isCompleted(DELAY));
@@ -493,7 +380,7 @@ public class TestIntegration {
         newTerm = BillingPeriod.MONTHLY;
         newPlanSetName = PriceListSet.DEFAULT_PRICELIST_NAME;
         newProductName = "Pistol";
-        subscription = (SubscriptionData) entitlementUserApi.getSubscriptionFromId(subscription.getId());
+        subscription = subscriptionDataFromSubscription(entitlementUserApi.getSubscriptionFromId(subscription.getId()));
         subscription.changePlan(newProductName, newTerm, newPlanSetName, clock.getUTCNow(), context);
 
         //
@@ -541,7 +428,7 @@ public class TestIntegration {
         //
         // FINALLY CANCEL SUBSCRIPTION EOT
         //
-        subscription = (SubscriptionData) entitlementUserApi.getSubscriptionFromId(subscription.getId());
+        subscription = subscriptionDataFromSubscription(entitlementUserApi.getSubscriptionFromId(subscription.getId()));
         subscription.cancel(clock.getUTCNow(), false, context);
 
         // MOVE AFTER CANCEL DATE AND EXPECT EVENT : NextEvent.CANCEL
@@ -557,7 +444,7 @@ public class TestIntegration {
         clock.addDeltaFromReality(AT_LEAST_ONE_MONTH_MS + 1000);
         assertTrue(busHandler.isCompleted(DELAY));
 
-        subscription = (SubscriptionData) entitlementUserApi.getSubscriptionFromId(subscription.getId());
+        subscription = subscriptionDataFromSubscription(entitlementUserApi.getSubscriptionFromId(subscription.getId()));
         DateTime lastCtd = subscription.getChargedThroughDate();
         assertNotNull(lastCtd);
         log.info("Checking CTD: " + lastCtd.toString() + "; clock is " + clock.getUTCNow().toString());
@@ -565,49 +452,22 @@ public class TestIntegration {
 
         // The invoice system is still working to verify there is nothing to do
         Thread.sleep(DELAY);
+        
+        assertListenerStatus();
+        
         log.info("TEST PASSED !");
     }
 
-    @Test(groups = "slow")
-    public void testHappyPath() throws AccountApiException, EntitlementUserApiException {
-        Account account = accountUserApi.createAccount(getAccountData(3), null, null, context);
-        assertNotNull(account);
-
-        SubscriptionBundle bundle = entitlementUserApi.createBundleForAccount(account.getId(), "whatever", context);
-
-        String productName = "Shotgun";
-        BillingPeriod term = BillingPeriod.MONTHLY;
-        String planSetName = PriceListSet.DEFAULT_PRICELIST_NAME;
-
-        busHandler.pushExpectedEvent(NextEvent.CREATE);
-        busHandler.pushExpectedEvent(NextEvent.INVOICE);
-        SubscriptionData subscription = (SubscriptionData) entitlementUserApi.createSubscription(bundle.getId(),
-                new PlanPhaseSpecifier(productName, ProductCategory.BASE, term, planSetName, null), null, context);
-        assertNotNull(subscription);
-
-        assertTrue(busHandler.isCompleted(DELAY));
-
-        busHandler.pushExpectedEvent(NextEvent.CHANGE);
-        busHandler.pushExpectedEvent(NextEvent.INVOICE);
-        BillingPeriod newTerm = BillingPeriod.MONTHLY;
-        String newPlanSetName = PriceListSet.DEFAULT_PRICELIST_NAME;
-        String newProductName = "Assault-Rifle";
-        subscription.changePlan(newProductName, newTerm, newPlanSetName, clock.getUTCNow(), context);
-
-        assertTrue(busHandler.isCompleted(DELAY));
-
-        busHandler.pushExpectedEvent(NextEvent.PHASE);
-        busHandler.pushExpectedEvent(NextEvent.INVOICE);
-        clock.setDeltaFromReality(AT_LEAST_ONE_MONTH_MS);
-        assertTrue(busHandler.isCompleted(DELAY));
-
-    }
 
     @Test(groups = "slow")
     public void testForMultipleRecurringPhases() throws AccountApiException, EntitlementUserApiException, InterruptedException {
-        clock.setDeltaFromReality(new DateTime().getMillis() - clock.getUTCNow().getMillis());
 
-        Account account = accountUserApi.createAccount(getAccountData(15), null, null, context);
+        log.info("Starting testForMultipleRecurringPhases");
+        
+        DateTime initialCreationDate = new DateTime(2012, 2, 1, 0, 3, 42, 0);
+        clock.setDeltaFromReality(initialCreationDate.getMillis() - clock.getUTCNow().getMillis());
+
+        Account account = accountUserApi.createAccount(getAccountData(2), null, null, context);
         UUID accountId = account.getId();
 
         String productName = "Blowdart";
@@ -616,114 +476,54 @@ public class TestIntegration {
         busHandler.pushExpectedEvent(NextEvent.CREATE);
         busHandler.pushExpectedEvent(NextEvent.INVOICE);
         SubscriptionBundle bundle = entitlementUserApi.createBundleForAccount(accountId, "testKey", context);
-        SubscriptionData subscription = (SubscriptionData) entitlementUserApi.createSubscription(bundle.getId(),
+        subscriptionDataFromSubscription(entitlementUserApi.createSubscription(bundle.getId(),
                                         new PlanPhaseSpecifier(productName, ProductCategory.BASE,
-                                        BillingPeriod.MONTHLY, planSetName, PhaseType.TRIAL), null, context);
+                                        BillingPeriod.MONTHLY, planSetName, PhaseType.TRIAL), null, context));
+
         assertTrue(busHandler.isCompleted(DELAY));
         List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(accountId);
         assertNotNull(invoices);
         assertTrue(invoices.size() == 1);
-
+        
         busHandler.pushExpectedEvent(NextEvent.PHASE);
         busHandler.pushExpectedEvent(NextEvent.INVOICE);
         busHandler.pushExpectedEvent(NextEvent.PAYMENT);
-        clock.addDeltaFromReality(6 * AT_LEAST_ONE_MONTH_MS);
-        assertTrue(busHandler.isCompleted(DELAY));
+        clock.addDeltaFromReality(AT_LEAST_ONE_MONTH_MS);
+        assertTrue(busHandler.isCompleted(DELAY));           
         invoices = invoiceUserApi.getInvoicesByAccount(accountId);
         assertNotNull(invoices);
-        assertTrue(invoices.size() == 2);
-
-        busHandler.pushExpectedEvent(NextEvent.PHASE);
+        assertEquals(invoices.size(),2);
+      
+        for (int i = 0; i < 5; i++) {
+            log.info("============== loop number " + i +"=======================");
+            busHandler.pushExpectedEvent(NextEvent.INVOICE);
+            busHandler.pushExpectedEvent(NextEvent.PAYMENT);
+            clock.addDeltaFromReality(AT_LEAST_ONE_MONTH_MS);
+            assertTrue(busHandler.isCompleted(DELAY));           
+        }
+        
         busHandler.pushExpectedEvent(NextEvent.INVOICE);
         busHandler.pushExpectedEvent(NextEvent.PAYMENT);
-        clock.addDeltaFromReality(6 * AT_LEAST_ONE_MONTH_MS);
-        assertTrue(busHandler.isCompleted(DELAY));
+        busHandler.pushExpectedEvent(NextEvent.PHASE);
+        clock.addDeltaFromReality(AT_LEAST_ONE_MONTH_MS);
+        assertTrue(busHandler.isCompleted(DELAY));           
+ 
         invoices = invoiceUserApi.getInvoicesByAccount(accountId);
         assertNotNull(invoices);
-        assertTrue(invoices.size() == 3);
-    }
-
-    protected AccountData getAccountData(final int billingDay) {
+        assertEquals(invoices.size(),8);
 
-        final String someRandomKey = RandomStringUtils.randomAlphanumeric(10);
-        return new AccountData() {
-            @Override
-            public String getName() {
-                return "firstName lastName";
-            }
-            @Override
-            public int getFirstNameLength() {
-                return "firstName".length();
-            }
-            @Override
-            public String getEmail() {
-                return  someRandomKey + "@laposte.fr";
-            }
-            @Override
-            public String getPhone() {
-                return "4152876341";
-            }
-            @Override
-            public String getExternalKey() {
-                return someRandomKey;
-            }
-            @Override
-            public int getBillCycleDay() {
-                return billingDay;
-            }
-            @Override
-            public Currency getCurrency() {
-                return Currency.USD;
-            }
-            @Override
-            public String getPaymentProviderName() {
-                return MockModule.PLUGIN_NAME;
-            }
-
-            @Override
-            public DateTimeZone getTimeZone() {
-                return null;
-            }
-
-            @Override
-            public String getLocale() {
-                return null;
-            }
-
-            @Override
-            public String getAddress1() {
-                return null;
-            }
-
-            @Override
-            public String getAddress2() {
-                return null;
-            }
-
-            @Override
-            public String getCompanyName() {
-                return null;
-            }
-
-            @Override
-            public String getCity() {
-                return null;
-            }
-
-            @Override
-            public String getStateOrProvince() {
-                return null;
-            }
-
-            @Override
-            public String getPostalCode() {
-                return null;
-            }
+        for (int i = 0; i <= 5; i++) {
+            log.info("============== second loop number " + i +"=======================");
+            busHandler.pushExpectedEvent(NextEvent.INVOICE);
+            busHandler.pushExpectedEvent(NextEvent.PAYMENT);
+            clock.addDeltaFromReality(AT_LEAST_ONE_MONTH_MS);
+            assertTrue(busHandler.isCompleted(DELAY));           
+        }
+               
+        invoices = invoiceUserApi.getInvoicesByAccount(accountId);
+        assertNotNull(invoices);
+        assertEquals(invoices.size(),14);
 
-            @Override
-            public String getCountry() {
-                return null;
-            }
-        };
+        assertListenerStatus();
     }
 }
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegrationBase.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegrationBase.java
new file mode 100644
index 0000000..78e37d0
--- /dev/null
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegrationBase.java
@@ -0,0 +1,348 @@
+/* 
+ * Copyright 2010-2011 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;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang.RandomStringUtils;
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.skife.jdbi.v2.IDBI;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+
+import com.google.inject.Inject;
+import com.ning.billing.account.api.AccountData;
+import com.ning.billing.account.api.AccountService;
+import com.ning.billing.account.api.AccountUserApi;
+import com.ning.billing.api.TestApiListener;
+import com.ning.billing.api.TestListenerStatus;
+import com.ning.billing.beatrix.lifecycle.Lifecycle;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.dbi.MysqlTestingHelper;
+import com.ning.billing.entitlement.api.EntitlementService;
+import com.ning.billing.entitlement.api.timeline.EntitlementTimelineApi;
+import com.ning.billing.entitlement.api.user.EntitlementUserApi;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.api.user.SubscriptionData;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.api.InvoiceService;
+import com.ning.billing.invoice.api.InvoiceUserApi;
+import com.ning.billing.invoice.model.InvoicingConfiguration;
+import com.ning.billing.junction.plumbing.api.BlockingSubscription;
+import com.ning.billing.util.bus.BusService;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.callcontext.CallOrigin;
+import com.ning.billing.util.callcontext.DefaultCallContextFactory;
+import com.ning.billing.util.callcontext.UserType;
+import com.ning.billing.util.clock.ClockMock;
+
+public class TestIntegrationBase implements TestListenerStatus {
+
+    protected static final int NUMBER_OF_DECIMALS = InvoicingConfiguration.getNumberOfDecimals();
+    protected static final int ROUNDING_METHOD = InvoicingConfiguration.getRoundingMode();
+
+    protected static final BigDecimal ONE = new BigDecimal("1.0000").setScale(NUMBER_OF_DECIMALS);
+    protected static final BigDecimal TWENTY_NINE = new BigDecimal("29.0000").setScale(NUMBER_OF_DECIMALS);
+    protected static final BigDecimal THIRTY = new BigDecimal("30.0000").setScale(NUMBER_OF_DECIMALS);
+    protected static final BigDecimal THIRTY_ONE = new BigDecimal("31.0000").setScale(NUMBER_OF_DECIMALS);
+
+    protected static final Logger log = LoggerFactory.getLogger(TestIntegration.class);
+    protected static long AT_LEAST_ONE_MONTH_MS =  31L * 24L * 3600L * 1000L;
+
+
+    protected static final long DELAY = 5000;
+
+    @Inject
+    protected IDBI dbi;
+
+    @Inject
+    protected ClockMock clock;
+    
+    protected CallContext context;
+
+    @Inject
+    protected Lifecycle lifecycle;
+
+    @Inject
+    protected BusService busService;
+
+    @Inject
+    protected EntitlementService entitlementService;
+
+    @Inject
+    protected InvoiceService invoiceService;
+
+    @Inject
+    protected AccountService accountService;
+
+    @Inject
+    protected MysqlTestingHelper helper;
+    @Inject
+    protected EntitlementUserApi entitlementUserApi;
+
+    @Inject
+    protected EntitlementTimelineApi repairApi;
+    
+    @Inject
+    protected InvoiceUserApi invoiceUserApi;
+
+    @Inject
+    protected AccountUserApi accountUserApi;
+
+    protected TestApiListener busHandler;
+
+    
+    private boolean isListenerFailed;
+    private String listenerFailedMsg;
+    
+    @Override
+    public void failed(String msg) {
+        isListenerFailed = true;
+        listenerFailedMsg = msg;
+    }
+
+    @Override
+    public void resetTestListenerStatus() {
+        isListenerFailed = false;
+        listenerFailedMsg = null;
+    }
+
+    
+    protected void assertListenerStatus() {
+        if (isListenerFailed) {
+            log.error(listenerFailedMsg);
+            Assert.fail(listenerFailedMsg);
+        }
+    }
+
+    protected void setupMySQL() throws IOException
+    {
+        final String accountDdl = IOUtils.toString(TestIntegration.class.getResourceAsStream("/com/ning/billing/account/ddl.sql"));
+        final String entitlementDdl = IOUtils.toString(TestIntegration.class.getResourceAsStream("/com/ning/billing/entitlement/ddl.sql"));
+        final String invoiceDdl = IOUtils.toString(TestIntegration.class.getResourceAsStream("/com/ning/billing/invoice/ddl.sql"));
+        final String paymentDdl = IOUtils.toString(TestIntegration.class.getResourceAsStream("/com/ning/billing/payment/ddl.sql"));
+        final String utilDdl = IOUtils.toString(TestIntegration.class.getResourceAsStream("/com/ning/billing/util/ddl.sql"));
+        final String junctionDb = IOUtils.toString(TestIntegration.class.getResourceAsStream("/com/ning/billing/junction/ddl.sql"));
+
+        helper.startMysql();
+
+        helper.initDb(accountDdl);
+        helper.initDb(entitlementDdl);
+        helper.initDb(invoiceDdl);
+        helper.initDb(paymentDdl);
+        helper.initDb(utilDdl);
+        helper.initDb(junctionDb);
+    }
+
+  
+    @BeforeClass(groups = "slow")
+    public void setup() throws Exception{
+
+        setupMySQL();
+        
+        context = new DefaultCallContextFactory(clock).createCallContext("Integration Test", CallOrigin.TEST, UserType.TEST);
+        busHandler = new TestApiListener(this);
+        
+    }
+
+    @AfterClass(groups = "slow")
+    public void tearDown() throws Exception {
+        helper.stopMysql();
+    }
+
+
+    @BeforeMethod(groups = "slow")
+    public void setupTest() throws Exception {
+
+        log.warn("\n");
+        log.warn("RESET TEST FRAMEWORK\n\n");
+        
+        // Pre test cleanup
+        helper.cleanupAllTables();
+
+        clock.resetDeltaFromReality();
+        resetTestListenerStatus();
+        
+        // Start services
+        lifecycle.fireStartupSequencePriorEventRegistration();
+        busService.getBus().register(busHandler);
+        lifecycle.fireStartupSequencePostEventRegistration();
+    }
+
+    @AfterMethod(groups = "slow")
+    public void cleanupTest() throws Exception {
+        lifecycle.fireShutdownSequencePriorEventUnRegistration();
+        busService.getBus().unregister(busHandler);
+        lifecycle.fireShutdownSequencePostEventUnRegistration();
+
+        log.warn("DONE WITH TEST\n");
+    }
+    
+
+    protected void verifyTestResult(UUID accountId, UUID subscriptionId,
+                                  DateTime startDate, DateTime endDate,
+                                  BigDecimal amount, DateTime chargeThroughDate,
+                                  int totalInvoiceItemCount) throws EntitlementUserApiException {
+        SubscriptionData subscription = subscriptionDataFromSubscription(entitlementUserApi.getSubscriptionFromId(subscriptionId));
+
+        List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(accountId);
+        List<InvoiceItem> invoiceItems = new ArrayList<InvoiceItem>();
+        for (Invoice invoice : invoices) {
+            invoiceItems.addAll(invoice.getInvoiceItems());
+        }
+        assertEquals(invoiceItems.size(), totalInvoiceItemCount);
+
+        boolean wasFound = false;
+
+        for (InvoiceItem item : invoiceItems) {
+            if (item.getStartDate().compareTo(startDate) == 0) {
+                if (item.getEndDate().compareTo(endDate) == 0) {
+                    if (item.getAmount().compareTo(amount) == 0) {
+                        wasFound = true;
+                        break;
+                    }
+                }
+            }
+        }
+
+        if (!wasFound) {
+            fail();
+        }
+
+        DateTime ctd = subscription.getChargedThroughDate();
+        assertNotNull(ctd);
+        log.info("Checking CTD: " + ctd.toString() + "; clock is " + clock.getUTCNow().toString());
+        assertTrue(clock.getUTCNow().isBefore(ctd));
+        assertTrue(ctd.compareTo(chargeThroughDate) == 0);
+    }
+       
+    protected SubscriptionData subscriptionDataFromSubscription(Subscription sub) {
+        return (SubscriptionData)((BlockingSubscription)sub).getDelegateSubscription();
+    }
+    
+    protected AccountData getAccountData(final int billingDay) {
+
+        final String someRandomKey = RandomStringUtils.randomAlphanumeric(10);
+        return new AccountData() {
+            @Override
+            public String getName() {
+                return "firstName lastName";
+            }
+            @Override
+            public int getFirstNameLength() {
+                return "firstName".length();
+            }
+            @Override
+            public String getEmail() {
+                return  someRandomKey + "@laposte.fr";
+            }
+            @Override
+            public String getPhone() {
+                return "4152876341";
+            }
+
+            @Override
+            public boolean isMigrated() {
+                return false;
+            }
+
+            @Override
+            public boolean isNotifiedForInvoices() {
+                return false;
+            }
+
+            @Override
+            public String getExternalKey() {
+                return someRandomKey;
+            }
+            @Override
+            public int getBillCycleDay() {
+                return billingDay;
+            }
+            @Override
+            public Currency getCurrency() {
+                return Currency.USD;
+            }
+            @Override
+            public String getPaymentProviderName() {
+                return BeatrixModule.PLUGIN_NAME;
+            }
+
+            @Override
+            public DateTimeZone getTimeZone() {
+                return null;
+            }
+
+            @Override
+            public String getLocale() {
+                return null;
+            }
+
+            @Override
+            public String getAddress1() {
+                return null;
+            }
+
+            @Override
+            public String getAddress2() {
+                return null;
+            }
+
+            @Override
+            public String getCompanyName() {
+                return null;
+            }
+
+            @Override
+            public String getCity() {
+                return null;
+            }
+
+            @Override
+            public String getStateOrProvince() {
+                return null;
+            }
+
+            @Override
+            public String getPostalCode() {
+                return null;
+            }
+
+            @Override
+            public String getCountry() {
+                return null;
+            }
+        };
+    }
+}
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestRepairIntegration.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestRepairIntegration.java
new file mode 100644
index 0000000..fe6f8a8
--- /dev/null
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestRepairIntegration.java
@@ -0,0 +1,332 @@
+/* 
+ * Copyright 2010-2011 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;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.joda.time.Interval;
+import org.testng.Assert;
+import org.testng.annotations.Guice;
+import org.testng.annotations.Test;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.api.TestApiListener.NextEvent;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.PhaseType;
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.catalog.api.PriceListSet;
+import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
+import com.ning.billing.entitlement.api.timeline.BundleTimeline;
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline;
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline.DeletedEvent;
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline.ExistingEvent;
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline.NewEvent;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.entitlement.api.user.SubscriptionData;
+import com.ning.billing.entitlement.api.user.SubscriptionEvents;
+import com.ning.billing.entitlement.api.user.Subscription.SubscriptionState;
+
+@Test(groups = "slow")
+@Guice(modules = {BeatrixModule.class})
+public class TestRepairIntegration extends TestIntegrationBase {
+
+    
+    @Test(groups={"slow"}, enabled=false)
+    public void testRepairChangeBPWithAddonIncludedIntrial() throws Exception {
+        log.info("Starting testRepairChangeBPWithAddonIncludedIntrial");
+        testRepairChangeBPWithAddonIncluded(true);
+    }
+    
+    @Test(groups={"slow"}, enabled=false)
+    public void testRepairChangeBPWithAddonIncludedOutOfTrial() throws Exception {
+        log.info("Starting testRepairChangeBPWithAddonIncludedOutOfTrial");
+        testRepairChangeBPWithAddonIncluded(false);
+    }
+    
+    private void testRepairChangeBPWithAddonIncluded(boolean inTrial) throws Exception {
+        
+        DateTime initialDate = new DateTime(2012, 4, 25, 0, 3, 42, 0);
+        clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis());
+        
+        Account account = accountUserApi.createAccount(getAccountData(25), null, null, context);
+        assertNotNull(account);
+
+        SubscriptionBundle bundle = entitlementUserApi.createBundleForAccount(account.getId(), "whatever", context);
+
+        String productName = "Shotgun";
+        BillingPeriod term = BillingPeriod.MONTHLY;
+        String planSetName = PriceListSet.DEFAULT_PRICELIST_NAME;
+
+        busHandler.pushExpectedEvent(NextEvent.CREATE);
+        busHandler.pushExpectedEvent(NextEvent.INVOICE);
+        SubscriptionData baseSubscription = subscriptionDataFromSubscription(entitlementUserApi.createSubscription(bundle.getId(),
+                new PlanPhaseSpecifier(productName, ProductCategory.BASE, term, planSetName, null), null, context));
+        assertNotNull(baseSubscription);
+        assertTrue(busHandler.isCompleted(DELAY));
+   
+        // MOVE CLOCK A LITTLE BIT-- STILL IN TRIAL
+        Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(3));
+        clock.addDeltaFromReality(it.toDurationMillis());
+
+        busHandler.pushExpectedEvent(NextEvent.CREATE);
+        busHandler.pushExpectedEvent(NextEvent.INVOICE);
+        busHandler.pushExpectedEvent(NextEvent.PAYMENT);
+        SubscriptionData aoSubscription = subscriptionDataFromSubscription(entitlementUserApi.createSubscription(bundle.getId(),
+                new PlanPhaseSpecifier("Telescopic-Scope", ProductCategory.ADD_ON, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null), null, context));
+        assertTrue(busHandler.isCompleted(DELAY));
+        
+        busHandler.pushExpectedEvent(NextEvent.CREATE);
+        busHandler.pushExpectedEvent(NextEvent.INVOICE);
+        busHandler.pushExpectedEvent(NextEvent.PAYMENT);
+        SubscriptionData aoSubscription2 = subscriptionDataFromSubscription( entitlementUserApi.createSubscription(bundle.getId(),
+                new PlanPhaseSpecifier("Laser-Scope", ProductCategory.ADD_ON, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null), null, context)); 
+        assertTrue(busHandler.isCompleted(DELAY));
+        
+
+        // MOVE CLOCK A LITTLE BIT MORE -- EITHER STAY IN TRIAL OR GET OUT   
+        int duration = inTrial ? 3 : 35;
+        it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(duration));
+        if (!inTrial) {
+            busHandler.pushExpectedEvent(NextEvent.PHASE);
+            busHandler.pushExpectedEvent(NextEvent.PHASE);
+            busHandler.pushExpectedEvent(NextEvent.PHASE);            
+            busHandler.pushExpectedEvent(NextEvent.INVOICE);
+            busHandler.pushExpectedEvent(NextEvent.PAYMENT);
+        }
+        clock.addDeltaFromReality(it.toDurationMillis());
+        if (!inTrial) {
+            assertTrue(busHandler.isCompleted(DELAY));
+        }
+        boolean ifRepair = false;
+        if (ifRepair) {
+            BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
+            sortEventsOnBundle(bundleRepair);
+
+            // Quick check
+            SubscriptionTimeline bpRepair = getSubscriptionRepair(baseSubscription.getId(), bundleRepair);
+            assertEquals(bpRepair.getExistingEvents().size(), 2);
+
+            SubscriptionTimeline aoRepair = getSubscriptionRepair(aoSubscription.getId(), bundleRepair);
+            assertEquals(aoRepair.getExistingEvents().size(), 2);
+
+            SubscriptionTimeline aoRepair2 = getSubscriptionRepair(aoSubscription2.getId(), bundleRepair);
+            assertEquals(aoRepair2.getExistingEvents().size(), 2);
+
+            DateTime bpChangeDate = clock.getUTCNow().minusDays(1);
+
+            List<DeletedEvent> des = new LinkedList<SubscriptionTimeline.DeletedEvent>();
+            des.add(createDeletedEvent(bpRepair.getExistingEvents().get(1).getEventId()));        
+
+            PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Assault-Rifle", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.TRIAL);
+            NewEvent ne = createNewEvent(SubscriptionTransitionType.CHANGE, bpChangeDate, spec);
+
+            bpRepair = createSubscriptionReapir(baseSubscription.getId(), des, Collections.singletonList(ne));
+
+            bundleRepair =  createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(bpRepair));
+
+            // TIME TO  REPAIR
+            busHandler.pushExpectedEvent(NextEvent.INVOICE);
+            busHandler.pushExpectedEvent(NextEvent.PAYMENT);
+            busHandler.pushExpectedEvent(NextEvent.REPAIR_BUNDLE);
+            repairApi.repairBundle(bundleRepair, false, context);
+            assertTrue(busHandler.isCompleted(DELAY));
+
+            SubscriptionData newAoSubscription = subscriptionDataFromSubscription(  entitlementUserApi.getSubscriptionFromId(aoSubscription.getId()));
+            assertEquals(newAoSubscription.getState(), SubscriptionState.CANCELLED);
+            assertEquals(newAoSubscription.getAllTransitions().size(), 2);
+            assertEquals(newAoSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1);
+
+            SubscriptionData newAoSubscription2 = subscriptionDataFromSubscription(  entitlementUserApi.getSubscriptionFromId(aoSubscription2.getId()));
+            assertEquals(newAoSubscription2.getState(), SubscriptionState.ACTIVE);
+            assertEquals(newAoSubscription2.getAllTransitions().size(), 2);
+            assertEquals(newAoSubscription2.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1);
+
+
+            SubscriptionData newBaseSubscription = subscriptionDataFromSubscription(  entitlementUserApi.getSubscriptionFromId(baseSubscription.getId()));
+            assertEquals(newBaseSubscription.getState(), SubscriptionState.ACTIVE);
+            assertEquals(newBaseSubscription.getAllTransitions().size(), 3);
+            assertEquals(newBaseSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1);
+
+            assertListenerStatus();
+        }
+     }
+    
+    protected SubscriptionTimeline createSubscriptionReapir(final UUID id, final List<DeletedEvent> deletedEvents, final List<NewEvent> newEvents) {
+        return new SubscriptionTimeline() {
+            @Override
+            public UUID getId() {
+                return id;
+            }
+            @Override
+            public List<NewEvent> getNewEvents() {
+                return newEvents;
+            }
+            @Override
+            public List<ExistingEvent> getExistingEvents() {
+                return null;
+            }
+            @Override
+            public List<DeletedEvent> getDeletedEvents() {
+                return deletedEvents;
+            }
+        };
+    }
+
+    
+    protected BundleTimeline createBundleRepair(final UUID bundleId, final String viewId, final List<SubscriptionTimeline> subscriptionRepair) {
+        return new BundleTimeline() {
+            @Override
+            public String getViewId() {
+                return viewId;
+            }
+            @Override
+            public List<SubscriptionTimeline> getSubscriptions() {
+                return subscriptionRepair;
+            }
+            @Override
+            public UUID getBundleId() {
+                return bundleId;
+            }
+            @Override
+            public String getExternalKey() {
+                return null;
+            }
+        };
+    }
+
+    protected ExistingEvent createExistingEventForAssertion(final SubscriptionTransitionType type, 
+            final String productName, final PhaseType phaseType, final ProductCategory category, final String priceListName, final BillingPeriod billingPeriod,
+            final DateTime effectiveDateTime) {
+
+        final PlanPhaseSpecifier spec = new PlanPhaseSpecifier(productName, category, billingPeriod, priceListName, phaseType);
+        ExistingEvent ev = new ExistingEvent() {
+            @Override
+            public SubscriptionTransitionType getSubscriptionTransitionType() {
+                return type;
+            }
+             @Override
+            public DateTime getRequestedDate() {
+                 return null;
+            }
+            @Override
+            public PlanPhaseSpecifier getPlanPhaseSpecifier() {
+                return spec;
+            }
+            @Override
+            public UUID getEventId() {
+                return null;
+            }
+            @Override
+            public DateTime getEffectiveDate() {
+                return effectiveDateTime;
+            }
+        };
+        return ev;
+    }
+    
+    protected SubscriptionTimeline getSubscriptionRepair(final UUID id, final BundleTimeline bundleRepair) {
+        for (SubscriptionTimeline cur : bundleRepair.getSubscriptions()) {
+            if (cur.getId().equals(id)) {
+                return cur;
+            }
+        }
+        Assert.fail("Failed to find SubscriptionReapir " + id);
+        return null;
+    }
+    protected void validateExistingEventForAssertion(final ExistingEvent expected, final ExistingEvent input) {
+        
+        log.info(String.format("Got %s -> Expected %s", input.getPlanPhaseSpecifier().getProductName(), expected.getPlanPhaseSpecifier().getProductName()));
+        assertEquals(input.getPlanPhaseSpecifier().getProductName(), expected.getPlanPhaseSpecifier().getProductName());
+        log.info(String.format("Got %s -> Expected %s", input.getPlanPhaseSpecifier().getPhaseType(), expected.getPlanPhaseSpecifier().getPhaseType()));
+        assertEquals(input.getPlanPhaseSpecifier().getPhaseType(), expected.getPlanPhaseSpecifier().getPhaseType());
+        log.info(String.format("Got %s -> Expected %s", input.getPlanPhaseSpecifier().getProductCategory(), expected.getPlanPhaseSpecifier().getProductCategory()));
+        assertEquals(input.getPlanPhaseSpecifier().getProductCategory(), expected.getPlanPhaseSpecifier().getProductCategory());                    
+        log.info(String.format("Got %s -> Expected %s", input.getPlanPhaseSpecifier().getPriceListName(), expected.getPlanPhaseSpecifier().getPriceListName()));
+        assertEquals(input.getPlanPhaseSpecifier().getPriceListName(), expected.getPlanPhaseSpecifier().getPriceListName());                    
+        log.info(String.format("Got %s -> Expected %s", input.getPlanPhaseSpecifier().getBillingPeriod(), expected.getPlanPhaseSpecifier().getBillingPeriod()));
+        assertEquals(input.getPlanPhaseSpecifier().getBillingPeriod(), expected.getPlanPhaseSpecifier().getBillingPeriod());
+        log.info(String.format("Got %s -> Expected %s", input.getEffectiveDate(), expected.getEffectiveDate()));
+        assertEquals(input.getEffectiveDate(), expected.getEffectiveDate());        
+    }
+    
+    protected DeletedEvent createDeletedEvent(final UUID eventId) {
+        return new DeletedEvent() {
+            @Override
+            public UUID getEventId() {
+                return eventId;
+            }
+        };
+    }
+
+    protected NewEvent createNewEvent(final SubscriptionTransitionType type, final DateTime requestedDate, final PlanPhaseSpecifier spec) {
+
+        return new NewEvent() {
+            @Override
+            public SubscriptionTransitionType getSubscriptionTransitionType() {
+                return type;
+            }
+            @Override
+            public DateTime getRequestedDate() {
+                return requestedDate;
+            }
+            @Override
+            public PlanPhaseSpecifier getPlanPhaseSpecifier() {
+                return spec;
+            }
+        };
+    }
+
+    protected void sortEventsOnBundle(final BundleTimeline bundle) {
+        if (bundle.getSubscriptions() == null) {
+            return;
+        }
+        for (SubscriptionTimeline cur : bundle.getSubscriptions()) {
+            if (cur.getExistingEvents() != null) {
+                sortExistingEvent(cur.getExistingEvents());
+            }
+            if (cur.getNewEvents() != null) {
+                sortNewEvent(cur.getNewEvents());
+            }
+        }
+    }
+
+    protected void sortExistingEvent(final List<ExistingEvent> events) {
+        Collections.sort(events, new Comparator<ExistingEvent>() {
+            @Override
+            public int compare(ExistingEvent arg0, ExistingEvent arg1) {
+                return arg0.getEffectiveDate().compareTo(arg1.getEffectiveDate());
+            }
+        });
+    }
+    protected void sortNewEvent(final List<NewEvent> events) {
+        Collections.sort(events, new Comparator<NewEvent>() {
+            @Override
+            public int compare(NewEvent arg0, NewEvent arg1) {
+                return arg0.getRequestedDate().compareTo(arg1.getRequestedDate());
+            }
+        });
+    }
+}
diff --git a/beatrix/src/test/resources/catalogSample.xml b/beatrix/src/test/resources/catalogSample.xml
index 5b7aeaf..8c13707 100644
--- a/beatrix/src/test/resources/catalogSample.xml
+++ b/beatrix/src/test/resources/catalogSample.xml
@@ -1,43 +1,23 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<!--
-  ~ Copyright 2010-2011 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.
-  -->
+<!-- ~ Copyright 2010-2011 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. -->
 
-<!-- 
-Use cases covered so far:
-	Tiered Product (Pistol/Shotgun/Assault-Rifle)
-	Multiple changeEvent plan policies
-	Multiple PlanAlignment (see below, trial add-on alignments and rescue discount package)
-	Product transition rules
-	Add on (Scopes, Holster)
-	Multi-pack addon (Extra-Ammo)
-	Addon Trial aligned to base plan (holster-monthly-regular)
-	Addon Trial aligned to creation (holster-monthly-special)
-	Rescue discount package (assault-rifle-annual-rescue)
-	Plan phase with a recurring and a one off (refurbish-maintenance)
-	Plan with more than 2 phase (gunclub discount plans)
-		
-Use Cases to do:
-	Tiered Add On
-	Riskfree period
-	
-
-
- -->
+<!-- Use cases covered so far: Tiered Product (Pistol/Shotgun/Assault-Rifle) 
+	Multiple changeEvent plan policies Multiple PlanAlignment (see below, trial 
+	add-on alignments and rescue discount package) Product transition rules Add 
+	on (Scopes, Hoster) Multi-pack addon (Extra-Ammo) Addon Trial aligned to 
+	base plan (holster-monthly-regular) Addon Trial aligned to creation (holster-monthly-special) 
+	Rescue discount package (assault-rifle-annual-rescue) Plan phase with a reccurring 
+	and a one off (refurbish-maintenance) Phan with more than 2 phase (gunclub 
+	discount plans) Use Cases to do: Tiered Add On Riskfree period -->
 <catalog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	xsi:noNamespaceSchemaLocation="CatalogSchema.xsd">
+	xsi:noNamespaceSchemaLocation="CatalogSchema.xsd ">
 
 	<effectiveDate>2011-01-01T00:00:00+00:00</effectiveDate>
 	<catalogName>Firearms</catalogName>
@@ -47,24 +27,24 @@ Use Cases to do:
 		<currency>EUR</currency>
 		<currency>GBP</currency>
 	</currencies>
-	
+
 	<products>
+		<product name="Blowdart">
+			<category>BASE</category>
+		</product>
 		<product name="Pistol">
 			<category>BASE</category>
+		</product>
+		<product name="Shotgun">
+			<category>BASE</category>
 			<available>
 				<addonProduct>Telescopic-Scope</addonProduct>
 				<addonProduct>Laser-Scope</addonProduct>
 			</available>
 		</product>
-        <product name="Blowdart">
-            <category>BASE</category>
-        </product>
-		<product name="Shotgun">
-			<category>BASE</category>
-		</product>
 		<product name="Assault-Rifle">
 			<category>BASE</category>
-			<included> 
+			<included>
 				<addonProduct>Telescopic-Scope</addonProduct>
 			</included>
 			<available>
@@ -87,60 +67,42 @@ Use Cases to do:
 			<category>ADD_ON</category>
 		</product>
 	</products>
-	 
+
 	<rules>
 		<changePolicy>
-			<changePolicyCase> 
+			<changePolicyCase>
 				<phaseType>TRIAL</phaseType>
 				<policy>IMMEDIATE</policy>
 			</changePolicyCase>
-			<changePolicyCase> 
-				<toProduct>Pistol</toProduct>
-				<policy>END_OF_TERM</policy>
+			<changePolicyCase>
+				<toProduct>Assault-Rifle</toProduct>
+				<policy>IMMEDIATE</policy>
 			</changePolicyCase>
-			<changePolicyCase> 
-				<toPriceList>rescue</toPriceList>
-				<policy>END_OF_TERM</policy>
-			</changePolicyCase>		
-			<changePolicyCase> 
+			<changePolicyCase>
 				<fromProduct>Pistol</fromProduct>
 				<toProduct>Shotgun</toProduct>
 				<policy>IMMEDIATE</policy>
 			</changePolicyCase>
-			<changePolicyCase> 
-				<fromProduct>Assault-Rifle</fromProduct>
-				<toProduct>Shotgun</toProduct>
-				<policy>END_OF_TERM</policy>
-			</changePolicyCase>
-			<changePolicyCase> 
-				<fromBillingPeriod>MONTHLY</fromBillingPeriod>
-				<toProduct>Assault-Rifle</toProduct>
-				<toBillingPeriod>MONTHLY</toBillingPeriod>
+			<changePolicyCase>
+				<toPriceList>rescue</toPriceList>
 				<policy>END_OF_TERM</policy>
 			</changePolicyCase>
-			<changePolicyCase> 
-				<toProduct>Assault-Rifle</toProduct>
-				<policy>IMMEDIATE</policy>
-			</changePolicyCase>
-			<changePolicyCase> 
+			<changePolicyCase>
 				<fromBillingPeriod>MONTHLY</fromBillingPeriod>
 				<toBillingPeriod>ANNUAL</toBillingPeriod>
 				<policy>IMMEDIATE</policy>
 			</changePolicyCase>
-			<changePolicyCase> 
+			<changePolicyCase>
 				<fromBillingPeriod>ANNUAL</fromBillingPeriod>
 				<toBillingPeriod>MONTHLY</toBillingPeriod>
 				<policy>END_OF_TERM</policy>
 			</changePolicyCase>
-			<changePolicyCase> 
+			<changePolicyCase>
 				<policy>END_OF_TERM</policy>
 			</changePolicyCase>
 		</changePolicy>
 		<changeAlignment>
 			<changeAlignmentCase>
-				<alignment>START_OF_SUBSCRIPTION</alignment>
-			</changeAlignmentCase>
-			<changeAlignmentCase>
 				<toPriceList>rescue</toPriceList>
 				<alignment>CHANGE_OF_PLAN</alignment>
 			</changeAlignmentCase>
@@ -149,18 +111,25 @@ Use Cases to do:
 				<toPriceList>rescue</toPriceList>
 				<alignment>CHANGE_OF_PRICELIST</alignment>
 			</changeAlignmentCase>
+			<changeAlignmentCase>
+				<alignment>START_OF_SUBSCRIPTION</alignment>
+			</changeAlignmentCase>
 		</changeAlignment>
 		<cancelPolicy>
 			<cancelPolicyCase>
-				<policy>END_OF_TERM</policy>
-			</cancelPolicyCase>
-			<cancelPolicyCase>
 				<phaseType>TRIAL</phaseType>
 				<policy>IMMEDIATE</policy>
 			</cancelPolicyCase>
+			<cancelPolicyCase>
+				<policy>END_OF_TERM</policy>
+			</cancelPolicyCase>
 		</cancelPolicy>
 		<createAlignment>
 			<createAlignmentCase>
+				<product>Laser-Scope</product>
+				<alignment>START_OF_SUBSCRIPTION</alignment>
+			</createAlignmentCase>
+			<createAlignmentCase>
 				<alignment>START_OF_BUNDLE</alignment>
 			</createAlignmentCase>
 		</createAlignment>
@@ -186,21 +155,7 @@ Use Cases to do:
 	</rules>
 
 	<plans>
-		<plan name="pistol-monthly-no-trial">
-			<product>Pistol</product>
-			<finalPhase type="EVERGREEN">
-				<duration>
-					<unit>UNLIMITED</unit>
-				</duration>
-				<billingPeriod>MONTHLY</billingPeriod>
-				<recurringPrice>
-					<price><currency>GBP</currency><value>29.95</value></price>
-					<price><currency>EUR</currency><value>29.95</value></price> 
-					<price><currency>USD</currency><value>29.95</value></price>								
-				</recurringPrice>
-			</finalPhase>
-		</plan>
-        <plan name="blowdart-monthly">
+		<plan name="blowdart-monthly">
 			<product>Blowdart</product>
 			<initialPhases>
 				<phase type="TRIAL">
@@ -219,9 +174,18 @@ Use Cases to do:
 					</duration>
 					<billingPeriod>MONTHLY</billingPeriod>
 					<recurringPrice>
-						<price><currency>USD</currency><value>9.95</value></price>
-						<price><currency>EUR</currency><value>9.95</value></price>
-						<price><currency>GBP</currency><value>9.95</value></price>
+						<price>
+							<currency>USD</currency>
+							<value>9.95</value>
+						</price>
+						<price>
+							<currency>EUR</currency>
+							<value>9.95</value>
+						</price>
+						<price>
+							<currency>GBP</currency>
+							<value>9.95</value>
+						</price>
 					</recurringPrice>
 				</phase>
 			</initialPhases>
@@ -231,25 +195,34 @@ Use Cases to do:
 				</duration>
 				<billingPeriod>MONTHLY</billingPeriod>
 				<recurringPrice>
-					<price><currency>USD</currency><value>29.95</value></price>
-					<price><currency>EUR</currency><value>29.95</value></price>
-					<price><currency>GBP</currency><value>29.95</value></price>
+					<price>
+						<currency>USD</currency>
+						<value>29.95</value>
+					</price>
+					<price>
+						<currency>EUR</currency>
+						<value>29.95</value>
+					</price>
+					<price>
+						<currency>GBP</currency>
+						<value>29.95</value>
+					</price>
 				</recurringPrice>
 			</finalPhase>
 		</plan>
+
 		<plan name="pistol-monthly">
 			<product>Pistol</product>
 			<initialPhases>
-                <phase type="TRIAL">
-                    <duration>
-                        <unit>DAYS</unit>
-                        <number>30</number>
-                    </duration>
-                    <billingPeriod>NO_BILLING_PERIOD</billingPeriod>
-                    <fixedPrice>
-                    </fixedPrice>
-                    <!-- no price implies $0 -->
-                </phase>
+				<phase type="TRIAL">
+					<duration>
+						<unit>DAYS</unit>
+						<number>30</number>
+					</duration>
+					<billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+					<fixedPrice> <!-- empty price implies $0 -->
+					</fixedPrice>
+				</phase>
 			</initialPhases>
 			<finalPhase type="EVERGREEN">
 				<duration>
@@ -257,9 +230,18 @@ Use Cases to do:
 				</duration>
 				<billingPeriod>MONTHLY</billingPeriod>
 				<recurringPrice>
-					<price><currency>GBP</currency><value>29.95</value></price>
-					<price><currency>EUR</currency><value>29.95</value></price> 
-					<price><currency>USD</currency><value>29.95</value></price>								
+					<price>
+						<currency>GBP</currency>
+						<value>29.95</value>
+					</price>
+					<price>
+						<currency>EUR</currency>
+						<value>29.95</value>
+					</price>
+					<price>
+						<currency>USD</currency>
+						<value>29.95</value>
+					</price>
 				</recurringPrice>
 			</finalPhase>
 		</plan>
@@ -271,10 +253,9 @@ Use Cases to do:
 						<unit>DAYS</unit>
 						<number>30</number>
 					</duration>
-                    <billingPeriod>NO_BILLING_PERIOD</billingPeriod>
-                    <fixedPrice>
-                    </fixedPrice>
-				    <!-- no price implies $0 -->
+					<billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+					<fixedPrice></fixedPrice>
+					<!-- no price implies $0 -->
 				</phase>
 			</initialPhases>
 			<finalPhase type="EVERGREEN">
@@ -284,25 +265,33 @@ Use Cases to do:
 				</duration>
 				<billingPeriod>MONTHLY</billingPeriod>
 				<recurringPrice>
-					<price><currency>USD</currency><value>249.95</value></price>								
-					<price><currency>EUR</currency><value>149.95</value></price>
-					<price><currency>GBP</currency><value>169.95</value></price>
+					<price>
+						<currency>USD</currency>
+						<value>249.95</value>
+					</price>
+					<price>
+						<currency>EUR</currency>
+						<value>149.95</value>
+					</price>
+					<price>
+						<currency>GBP</currency>
+						<value>169.95</value>
+					</price>
 				</recurringPrice>
 			</finalPhase>
 		</plan>
 		<plan name="assault-rifle-monthly">
 			<product>Assault-Rifle</product>
 			<initialPhases>
-                                <phase type="TRIAL">
-                                        <duration>
-                                                <unit>DAYS</unit>
-                                                <number>30</number>
-                                        </duration>
-                                        <billingPeriod>NO_BILLING_PERIOD</billingPeriod>
-                                        <fixedPrice>
-                                        </fixedPrice>
-                                    <!-- no price implies $0 -->
-                                </phase>
+				<phase type="TRIAL">
+					<duration>
+						<unit>DAYS</unit>
+						<number>30</number>
+					</duration>
+					<billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+					<fixedPrice>
+					</fixedPrice>
+				</phase>
 			</initialPhases>
 			<finalPhase type="EVERGREEN">
 				<duration>
@@ -310,9 +299,18 @@ Use Cases to do:
 				</duration>
 				<billingPeriod>MONTHLY</billingPeriod>
 				<recurringPrice>
-					<price><currency>USD</currency><value>599.95</value></price>								
-					<price><currency>EUR</currency><value>349.95</value></price>
-					<price><currency>GBP</currency><value>399.95</value></price>
+					<price>
+						<currency>USD</currency>
+						<value>599.95</value>
+					</price>
+					<price>
+						<currency>EUR</currency>
+						<value>349.95</value>
+					</price>
+					<price>
+						<currency>GBP</currency>
+						<value>399.95</value>
+					</price>
 				</recurringPrice>
 			</finalPhase>
 		</plan>
@@ -335,9 +333,18 @@ Use Cases to do:
 				</duration>
 				<billingPeriod>ANNUAL</billingPeriod>
 				<recurringPrice>
-					<price><currency>USD</currency><value>199.95</value></price>								
-					<price><currency>EUR</currency><value>199.95</value></price>
-					<price><currency>GBP</currency><value>199.95</value></price>
+					<price>
+						<currency>USD</currency>
+						<value>199.95</value>
+					</price>
+					<price>
+						<currency>EUR</currency>
+						<value>199.95</value>
+					</price>
+					<price>
+						<currency>GBP</currency>
+						<value>199.95</value>
+					</price>
 				</recurringPrice>
 			</finalPhase>
 		</plan>
@@ -360,9 +367,18 @@ Use Cases to do:
 				</duration>
 				<billingPeriod>ANNUAL</billingPeriod>
 				<recurringPrice>
-					<price><currency>USD</currency><value>2399.95</value></price>								
-					<price><currency>EUR</currency><value>1499.95</value></price>
-					<price><currency>GBP</currency><value>1699.95</value></price>
+					<price>
+						<currency>USD</currency>
+						<value>2399.95</value>
+					</price>
+					<price>
+						<currency>EUR</currency>
+						<value>1499.95</value>
+					</price>
+					<price>
+						<currency>GBP</currency>
+						<value>1699.95</value>
+					</price>
 				</recurringPrice>
 			</finalPhase>
 		</plan>
@@ -385,9 +401,18 @@ Use Cases to do:
 				</duration>
 				<billingPeriod>ANNUAL</billingPeriod>
 				<recurringPrice>
-					<price><currency>USD</currency><value>5999.95</value></price>								
-					<price><currency>EUR</currency><value>3499.95</value></price>
-					<price><currency>GBP</currency><value>3999.95</value></price>
+					<price>
+						<currency>USD</currency>
+						<value>5999.95</value>
+					</price>
+					<price>
+						<currency>EUR</currency>
+						<value>3499.95</value>
+					</price>
+					<price>
+						<currency>GBP</currency>
+						<value>3999.95</value>
+					</price>
 				</recurringPrice>
 			</finalPhase>
 		</plan>
@@ -410,9 +435,18 @@ Use Cases to do:
 					</duration>
 					<billingPeriod>MONTHLY</billingPeriod>
 					<recurringPrice>
-						<price><currency>USD</currency><value>9.95</value></price>								
-						<price><currency>EUR</currency><value>9.95</value></price>
-						<price><currency>GBP</currency><value>9.95</value></price>
+						<price>
+							<currency>USD</currency>
+							<value>9.95</value>
+						</price>
+						<price>
+							<currency>EUR</currency>
+							<value>9.95</value>
+						</price>
+						<price>
+							<currency>GBP</currency>
+							<value>9.95</value>
+						</price>
 					</recurringPrice>
 				</phase>
 			</initialPhases>
@@ -422,9 +456,18 @@ Use Cases to do:
 				</duration>
 				<billingPeriod>ANNUAL</billingPeriod>
 				<recurringPrice>
-					<price><currency>USD</currency><value>199.95</value></price>								
-					<price><currency>EUR</currency><value>199.95</value></price>
-					<price><currency>GBP</currency><value>199.95</value></price>
+					<price>
+						<currency>USD</currency>
+						<value>199.95</value>
+					</price>
+					<price>
+						<currency>EUR</currency>
+						<value>199.95</value>
+					</price>
+					<price>
+						<currency>GBP</currency>
+						<value>199.95</value>
+					</price>
 				</recurringPrice>
 			</finalPhase>
 		</plan>
@@ -447,9 +490,18 @@ Use Cases to do:
 					</duration>
 					<billingPeriod>MONTHLY</billingPeriod>
 					<recurringPrice>
-						<price><currency>USD</currency><value>19.95</value></price>								
-						<price><currency>EUR</currency><value>49.95</value></price>
-						<price><currency>GBP</currency><value>69.95</value></price>
+						<price>
+							<currency>USD</currency>
+							<value>19.95</value>
+						</price>
+						<price>
+							<currency>EUR</currency>
+							<value>49.95</value>
+						</price>
+						<price>
+							<currency>GBP</currency>
+							<value>69.95</value>
+						</price>
 					</recurringPrice>
 				</phase>
 			</initialPhases>
@@ -459,9 +511,18 @@ Use Cases to do:
 				</duration>
 				<billingPeriod>ANNUAL</billingPeriod>
 				<recurringPrice>
-					<price><currency>USD</currency><value>2399.95</value></price>								
-					<price><currency>EUR</currency><value>1499.95</value></price>
-					<price><currency>GBP</currency><value>1699.95</value></price>
+					<price>
+						<currency>USD</currency>
+						<value>2399.95</value>
+					</price>
+					<price>
+						<currency>EUR</currency>
+						<value>1499.95</value>
+					</price>
+					<price>
+						<currency>GBP</currency>
+						<value>1699.95</value>
+					</price>
 				</recurringPrice>
 			</finalPhase>
 		</plan>
@@ -484,10 +545,19 @@ Use Cases to do:
 					</duration>
 					<billingPeriod>MONTHLY</billingPeriod>
 					<recurringPrice>
-						<price><currency>USD</currency><value>99.95</value></price>								
-						<price><currency>EUR</currency><value>99.95</value></price>
-						<price><currency>GBP</currency><value>99.95</value></price>
-						</recurringPrice>
+						<price>
+							<currency>USD</currency>
+							<value>99.95</value>
+						</price>
+						<price>
+							<currency>EUR</currency>
+							<value>99.95</value>
+						</price>
+						<price>
+							<currency>GBP</currency>
+							<value>99.95</value>
+						</price>
+					</recurringPrice>
 				</phase>
 			</initialPhases>
 			<finalPhase type="EVERGREEN">
@@ -496,37 +566,110 @@ Use Cases to do:
 				</duration>
 				<billingPeriod>ANNUAL</billingPeriod>
 				<recurringPrice>
-					<price><currency>USD</currency><value>5999.95</value></price>								
-					<price><currency>EUR</currency><value>3499.95</value></price>
-					<price><currency>GBP</currency><value>3999.95</value></price>
+					<price>
+						<currency>USD</currency>
+						<value>5999.95</value>
+					</price>
+					<price>
+						<currency>EUR</currency>
+						<value>3499.95</value>
+					</price>
+					<price>
+						<currency>GBP</currency>
+						<value>3999.95</value>
+					</price>
 				</recurringPrice>
 			</finalPhase>
 		</plan>
 		<plan name="laser-scope-monthly">
-		<product>Laser-Scope</product>
+			<product>Laser-Scope</product>
+			<initialPhases>
+				<phase type="DISCOUNT">
+					<duration>
+						<unit>MONTHS</unit>
+						<number>1</number>
+					</duration>
+					<billingPeriod>MONTHLY</billingPeriod>
+					<recurringPrice>
+						<price>
+							<currency>USD</currency>
+							<value>999.95</value>
+						</price>
+						<price>
+							<currency>EUR</currency>
+							<value>499.95</value>
+						</price>
+						<price>
+							<currency>GBP</currency>
+							<value>999.95</value>
+						</price>
+					</recurringPrice>
+				</phase>
+			</initialPhases>
 			<finalPhase type="EVERGREEN">
 				<duration>
 					<unit>UNLIMITED</unit>
 				</duration>
 				<billingPeriod>MONTHLY</billingPeriod>
 				<recurringPrice>
-					<price><currency>USD</currency><value>1999.95</value></price>								
-					<price><currency>EUR</currency><value>1499.95</value></price>
-					<price><currency>GBP</currency><value>1999.95</value></price>
+					<price>
+						<currency>USD</currency>
+						<value>1999.95</value>
+					</price>
+					<price>
+						<currency>EUR</currency>
+						<value>1499.95</value>
+					</price>
+					<price>
+						<currency>GBP</currency>
+						<value>1999.95</value>
+					</price>
 				</recurringPrice>
 			</finalPhase>
 		</plan>
 		<plan name="telescopic-scope-monthly">
 			<product>Telescopic-Scope</product>
+			<initialPhases>
+				<phase type="DISCOUNT">
+					<duration>
+						<unit>MONTHS</unit>
+						<number>1</number>
+					</duration>
+					<billingPeriod>MONTHLY</billingPeriod>
+					<recurringPrice>
+						<price>
+							<currency>USD</currency>
+							<value>399.95</value>
+						</price>
+						<price>
+							<currency>EUR</currency>
+							<value>299.95</value>
+						</price>
+						<price>
+							<currency>GBP</currency>
+							<value>399.95</value>
+						</price>
+					</recurringPrice>
+				</phase>
+			</initialPhases>
 			<finalPhase type="EVERGREEN">
 				<duration>
 					<unit>UNLIMITED</unit>
 				</duration>
 				<billingPeriod>MONTHLY</billingPeriod>
 				<recurringPrice>
-					<price><currency>USD</currency><value>999.95</value></price>								
-					<price><currency>EUR</currency><value>499.95</value></price>
-					<price><currency>GBP</currency><value>999.95</value></price>
+					<price>
+						<currency>USD</currency>
+						<value>999.95</value>
+					</price>
+					<price>
+						<currency>EUR</currency>
+						<value>499.95</value>
+					</price>
+					<price>
+						<currency>GBP</currency>
+						<value>999.95</value>
+					</price>
 				</recurringPrice>
 			</finalPhase>
 		</plan>
@@ -538,9 +681,18 @@ Use Cases to do:
 				</duration>
 				<billingPeriod>MONTHLY</billingPeriod>
 				<recurringPrice>
-					<price><currency>USD</currency><value>999.95</value></price>								
-					<price><currency>EUR</currency><value>499.95</value></price>
-					<price><currency>GBP</currency><value>999.95</value></price>
+					<price>
+						<currency>USD</currency>
+						<value>999.95</value>
+					</price>
+					<price>
+						<currency>EUR</currency>
+						<value>499.95</value>
+					</price>
+					<price>
+						<currency>GBP</currency>
+						<value>999.95</value>
+					</price>
 				</recurringPrice>
 			</finalPhase>
 			<plansAllowedInBundle>-1</plansAllowedInBundle> <!-- arbitrary number of these (multipack) -->
@@ -564,9 +716,18 @@ Use Cases to do:
 				</duration>
 				<billingPeriod>ANNUAL</billingPeriod>
 				<recurringPrice>
-					<price><currency>USD</currency><value>199.95</value></price>								
-					<price><currency>EUR</currency><value>199.95</value></price>
-					<price><currency>GBP</currency><value>199.95</value></price>
+					<price>
+						<currency>USD</currency>
+						<value>199.95</value>
+					</price>
+					<price>
+						<currency>EUR</currency>
+						<value>199.95</value>
+					</price>
+					<price>
+						<currency>GBP</currency>
+						<value>199.95</value>
+					</price>
 				</recurringPrice>
 			</finalPhase>
 		</plan>
@@ -589,9 +750,18 @@ Use Cases to do:
 				</duration>
 				<billingPeriod>ANNUAL</billingPeriod>
 				<recurringPrice>
-					<price><currency>USD</currency><value>199.95</value></price>								
-					<price><currency>EUR</currency><value>199.95</value></price>
-					<price><currency>GBP</currency><value>199.95</value></price>
+					<price>
+						<currency>USD</currency>
+						<value>199.95</value>
+					</price>
+					<price>
+						<currency>EUR</currency>
+						<value>199.95</value>
+					</price>
+					<price>
+						<currency>GBP</currency>
+						<value>199.95</value>
+					</price>
 				</recurringPrice>
 			</finalPhase>
 		</plan>
@@ -605,9 +775,18 @@ Use Cases to do:
 					</duration>
 					<billingPeriod>ANNUAL</billingPeriod>
 					<recurringPrice>
-						<price><currency>USD</currency><value>5999.95</value></price>								
-						<price><currency>EUR</currency><value>3499.95</value></price>
-						<price><currency>GBP</currency><value>3999.95</value></price>
+						<price>
+							<currency>USD</currency>
+							<value>5999.95</value>
+						</price>
+						<price>
+							<currency>EUR</currency>
+							<value>3499.95</value>
+						</price>
+						<price>
+							<currency>GBP</currency>
+							<value>3999.95</value>
+						</price>
 					</recurringPrice>
 				</phase>
 			</initialPhases>
@@ -617,9 +796,18 @@ Use Cases to do:
 				</duration>
 				<billingPeriod>ANNUAL</billingPeriod>
 				<recurringPrice>
-					<price><currency>USD</currency><value>5999.95</value></price>								
-					<price><currency>EUR</currency><value>3499.95</value></price>
-					<price><currency>GBP</currency><value>3999.95</value></price>
+					<price>
+						<currency>USD</currency>
+						<value>5999.95</value>
+					</price>
+					<price>
+						<currency>EUR</currency>
+						<value>3499.95</value>
+					</price>
+					<price>
+						<currency>GBP</currency>
+						<value>3999.95</value>
+					</price>
 				</recurringPrice>
 			</finalPhase>
 		</plan>
@@ -632,23 +820,40 @@ Use Cases to do:
 				</duration>
 				<billingPeriod>MONTHLY</billingPeriod>
 				<recurringPrice>
-					<price><currency>USD</currency><value>199.95</value></price>								
-					<price><currency>EUR</currency><value>199.95</value></price>
-					<price><currency>GBP</currency><value>199.95</value></price>
+					<price>
+						<currency>USD</currency>
+						<value>199.95</value>
+					</price>
+					<price>
+						<currency>EUR</currency>
+						<value>199.95</value>
+					</price>
+					<price>
+						<currency>GBP</currency>
+						<value>199.95</value>
+					</price>
 				</recurringPrice>
 				<fixedPrice>
-					<price><currency>USD</currency><value>599.95</value></price>								
-					<price><currency>EUR</currency><value>599.95</value></price>
-					<price><currency>GBP</currency><value>599.95</value></price>
+					<price>
+						<currency>USD</currency>
+						<value>599.95</value>
+					</price>
+					<price>
+						<currency>EUR</currency>
+						<value>599.95</value>
+					</price>
+					<price>
+						<currency>GBP</currency>
+						<value>599.95</value>
+					</price>
 				</fixedPrice>
 			</finalPhase>
 		</plan>
 	</plans>
-
 	<priceLists>
-		<defaultPriceList name="DEFAULT"> 
+		<defaultPriceList name="DEFAULT">
 			<plans>
-                <plan>blowdart-monthly</plan>
+			    <plan>blowdart-monthly</plan>
 				<plan>pistol-monthly</plan>
 				<plan>shotgun-monthly</plan>
 				<plan>assault-rifle-monthly</plan>
diff --git a/beatrix/src/test/resources/resource.properties b/beatrix/src/test/resources/resource.properties
index d63334b..a2c4ec1 100644
--- a/beatrix/src/test/resources/resource.properties
+++ b/beatrix/src/test/resources/resource.properties
@@ -1,7 +1,11 @@
 killbill.catalog.uri=file:src/test/resources/catalogSample.xml
 killbill.entitlement.dao.claim.time=60000
 killbill.entitlement.dao.ready.max=1
-killbill.entitlement.engine.notifications.sleep=500
+killbill.payment.engine.notifications.sleep=100
+killbill.invoice.engine.notifications.sleep=100
+killbill.entitlement.engine.notifications.sleep=100
+killbill.billing.util.persistent.bus.sleep=100
+killbill.billing.util.persistent.bus.nbThreads=1
 user.timezone=UTC
 
 
diff --git a/bin/clean-and-install b/bin/clean-and-install
new file mode 100755
index 0000000..c6c3ac4
--- /dev/null
+++ b/bin/clean-and-install
@@ -0,0 +1,22 @@
+#! /usr/bin/env bash
+
+###################################################################################
+#                                                                                 #
+#                   Copyright 2010-2011 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.                                                         #
+#                                                                                 #
+###################################################################################
+
+bin/db-helper -a clean -d killbill; 
+mvn -Dcom.ning.billing.dbi.test.useLocalDb=true clean install 

bin/cleanAndInstall 23(+23 -0)

diff --git a/bin/cleanAndInstall b/bin/cleanAndInstall
new file mode 100755
index 0000000..fca07ad
--- /dev/null
+++ b/bin/cleanAndInstall
@@ -0,0 +1,23 @@
+#! /usr/bin/env bash
+
+###################################################################################
+#                                                                                 #
+#                   Copyright 2010-2011 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.                                                         #
+#                                                                                 #
+###################################################################################
+
+bin/db-helper -a clean -d killbill; 
+bin/db-helper -a clean -d test_killbill; 
+mvn -Dcom.ning.billing.dbi.test.useLocalDb=true clean install 

bin/db-helper 156(+156 -0)

diff --git a/bin/db-helper b/bin/db-helper
new file mode 100755
index 0000000..cc8b746
--- /dev/null
+++ b/bin/db-helper
@@ -0,0 +1,156 @@
+#! /usr/bin/env bash
+
+
+###################################################################################
+#                                                                                 #
+#                   Copyright 2010-2011 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.                                                         #
+#                                                                                 #
+###################################################################################
+
+#set -x 
+
+HERE=`cd \`dirname $0\`; pwd`
+TOP=$HERE/..
+
+POM="$TOP/pom.xml"
+
+ACTION=
+DATABASE="killbill"
+USER="root"
+PWD="root"
+TEST_ALSO=
+
+DDL_FILE=
+CLEAN_FILE=
+
+function usage() {
+    echo -n "./db_helper "
+    echo -n " -a <create|clean|dump>"
+    echo -n " -d database_name (default = killbill)"    
+    echo -n " -u user_name (default = root)"
+    echo -n " -p password (default = root)"
+    echo -n " -t (also include test ddl)"
+    echo -n "-h this message"
+    echo
+    exit 1
+}
+
+function get_modules() {
+    local modules=`grep module $POM  | grep -v modules | cut -d '>' -f 2 | cut -d '<' -f 1`
+    echo $modules
+}
+
+function find_test_ddl() {
+    local modules=`get_modules`
+    local ddl_test=
+    
+    local cur_ddl=
+    for m in $modules; do
+        cur_ddl=`find $m/src/test/resources/ -name ddl_test.sql 2>/dev/null`
+        ddl_test="$ddl_test $cur_ddl"
+    done
+    echo "$ddl_test"
+    
+}
+function find_src_ddl() {
+    
+    local modules=`get_modules`
+    local ddl_src=
+    
+    local cur_ddl=
+    for m in $modules; do
+        cur_ddl=`find $m/src/main/resources/ -name ddl.sql 2>/dev/null`
+        ddl_src="$ddl_src $cur_ddl"
+    done
+    echo "$ddl_src"
+}
+
+
+function create_clean_file() {
+    local ddl_file=$1
+    local tables=`cat $ddl_file | grep -i "create table" | awk ' { print $3 } '` 
+
+    local tmp="/tmp/clean-$DATABASE.$$" 
+    echo "use $DATABASE;" >> $tmp
+    echo "" >> $tmp
+    for t in $tables; do
+        echo "truncate $t;" >> $tmp
+    done
+    echo $tmp
+}
+
+function create_ddl_file() {
+    local ddls=`find_src_ddl`
+    local test_ddls=
+    if [ ! -z $TEST_ALSO ]; then
+        test_ddls=`find_test_ddl`
+        ddls="$ddls $test_ddls"
+    fi
+
+    local tmp="/tmp/ddl-$DATABASE.$$"
+    touch $tmp
+    echo "use $DATABASE;" >> $tmp
+    echo "" >> $tmp
+    for d in $ddls; do
+        cat $d >> $tmp
+        echo "" >> $tmp
+    done
+    echo $tmp
+}
+
+function cleanup() {
+    rm -f "/tmp/*.$$"
+}
+
+
+while getopts ":a:d:u:pt" options; do
+  case $options in
+    a ) ACTION=$OPTARG;;
+	d ) DATABASE=$OPTARG;;
+	u ) USER=$OPTARG;;
+	p ) PWD=$OPTARG;;
+	t ) TEST_ALSO=1;;
+    h ) usage;;
+    * ) usage;;
+  esac
+done
+
+
+
+if [ -z $ACTION ]; then
+    echo "Need to specify an action <CREATE|CLEAN>"
+    usage
+fi
+
+
+if [ $ACTION == "dump" ]; then
+     DDL_FILE=`create_ddl_file`
+    cat $DDL_FILE
+fi
+
+if [ $ACTION == "create" ]; then
+    DDL_FILE=`create_ddl_file`
+    echo "Applying new schema $tmp to database $DATABASE"    
+    mysql -u $USER --password=$PWD < $DDL_FILE
+fi
+
+if [ $ACTION == "clean" ]; then
+    DDL_FILE=`create_ddl_file` 
+    CLEAN_FILE=`create_clean_file $DDL_FILE`   
+    echo "Cleaning db tables on database $DATABASE"
+    mysql -u $USER --password=$PWD < $DDL_FILE
+fi
+
+cleanup

bin/start-server 99(+99 -0)

diff --git a/bin/start-server b/bin/start-server
new file mode 100755
index 0000000..ce254da
--- /dev/null
+++ b/bin/start-server
@@ -0,0 +1,99 @@
+#! /usr/bin/env bash
+
+###################################################################################
+#                                                                                 #
+#                   Copyright 2010-2011 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.                                                         #
+#                                                                                 #
+###################################################################################
+
+
+HERE=`cd \`dirname $0\`; pwd`
+TOP=$HERE/..
+SERVER=$TOP/server
+
+PROPERTIES="$SERVER/src/main/resources/killbill-server.properties"
+
+DEBUG_OPTS_ECLIPSE=" -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=12345 "
+DEBUG_OPTS_ECLIPSE_WAIT=" -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=12345 "
+
+OPTS_ECLIPSE=" -Xmx2048m  -XX:+UseConcMarkSweepGC -XX:MaxPermSize=128m  "
+
+LOG="$SERVER/src/main/resources/log4j.xml"
+
+# From Argument Options
+PORT=8080
+START=
+DEBUG=
+WAIT_DEBUGGER=
+
+
+function usage() {
+    echo -n "./start-server "
+    echo -n " -s (start server)"
+    echo -n " -d (debugger turned on)"    
+    echo -n " -w (along with -d, wait for debugger before starting)"
+    echo -n " -p <port_number> default 8080"
+    echo -n "-h this message"        
+    exit 1
+}
+
+function build_properties() {
+    local opts=
+    local prop= 
+    for prop in `cat  $PROPERTIES | grep =`; do
+        local k=`echo $prop | awk '  BEGIN {FS="="} { print $1 }'`
+        local v=`echo $prop | awk 'BEGIN {FS="="} { print $2 }'`
+        opts="$opts -D$k=$v"
+    done
+    echo $opts
+}
+
+function start() {
+    local opts=`build_properties`
+    local start_cmd="mvn $opts -Dlog4j.configuration=file://$LOG -Dning.jmx.http.port=$PORT -Dxn.host.external.port=$PORT -DjettyPort=$PORT -Dxn.server.port=$PORT jetty:run"    
+
+    local debug_opts_eclipse= 
+    if [ ! -z $DEBUG ]; then
+        if  [ ! -z $WAIT_DEBUGGER ]; then
+            debug_opts_eclipse=$DEBUG_OPTS_ECLIPSE_WAIT
+        else
+            debug_opts_eclipse=$DEBUG_OPTS_ECLIPSE
+        fi
+    fi
+    export MAVEN_OPTS=" -Duser.timezone=UTC $OPTS_ECLIPSE $debug_opts_eclipse"
+    
+    echo "Starting IRS MAVEN_OPTS = $MAVEN_OPTS"
+    echo "$start_cmd"
+    cd $SERVER
+    $start_cmd
+}
+
+
+while getopts ":pswdh" options; do
+  case $options in
+	s ) START=1;;
+	d ) DEBUG=1;;
+	w ) WAIT_DEBUGGER=1;;
+	p ) PORT=$OPTARG;;
+    h ) usage;;
+    * ) usage;;
+  esac
+done
+
+if [ ! -z $START ]; then
+    start
+else
+    usage
+fi

catalog/pom.xml 6(+6 -0)

diff --git a/catalog/pom.xml b/catalog/pom.xml
index ecabef8..0e181ae 100644
--- a/catalog/pom.xml
+++ b/catalog/pom.xml
@@ -33,6 +33,12 @@
             <artifactId>killbill-util</artifactId>
         </dependency>
         <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-util</artifactId>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
             <groupId>org.testng</groupId>
             <artifactId>testng</artifactId>
             <scope>test</scope>
diff --git a/catalog/src/main/java/com/ning/billing/catalog/DefaultCatalogService.java b/catalog/src/main/java/com/ning/billing/catalog/DefaultCatalogService.java
index b824617..2ed1cfa 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/DefaultCatalogService.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/DefaultCatalogService.java
@@ -43,7 +43,6 @@ public class DefaultCatalogService implements KillbillService, Provider<Catalog>
     @Inject
     public DefaultCatalogService(CatalogConfig config, VersionedCatalogLoader loader) {
         this.config = config;
-        System.out.println(config.getCatalogURI());
         this.isInitialized = false;
         this.loader = loader;
     }
@@ -52,7 +51,6 @@ public class DefaultCatalogService implements KillbillService, Provider<Catalog>
     public synchronized void loadCatalog() throws ServiceException {
         if (!isInitialized) {
             try {
-            	System.out.println("Really really::" + config.getCatalogURI());
             	String url = config.getCatalogURI();
             	catalog = loader.load(url);
 
diff --git a/catalog/src/main/java/com/ning/billing/catalog/DefaultDuration.java b/catalog/src/main/java/com/ning/billing/catalog/DefaultDuration.java
index 0e821ef..71bfe50 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/DefaultDuration.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/DefaultDuration.java
@@ -21,6 +21,7 @@ import com.ning.billing.catalog.api.TimeUnit;
 import com.ning.billing.util.config.ValidatingConfig;
 import com.ning.billing.util.config.ValidationErrors;
 import org.joda.time.DateTime;
+import org.joda.time.Period;
 
 import javax.xml.bind.annotation.XmlAccessType;
 import javax.xml.bind.annotation.XmlAccessorType;
@@ -52,7 +53,7 @@ public class DefaultDuration extends ValidatingConfig<StandaloneCatalog> impleme
 
     @Override
     public DateTime addToDateTime(DateTime dateTime) {
-        if (number == null) {return dateTime;}
+        if ((number == null) && (unit != TimeUnit.UNLIMITED)) {return dateTime;}
 
         switch (unit) {
             case DAYS:
@@ -84,5 +85,21 @@ public class DefaultDuration extends ValidatingConfig<StandaloneCatalog> impleme
 		return this;
 	}
 	
-	
+    @Override
+    public Period toJodaPeriod() {
+        if ((number == null) && (unit != TimeUnit.UNLIMITED)) {return new Period();}
+
+        switch (unit) {
+            case DAYS:
+                return new Period().withDays(number);
+            case MONTHS:
+                return new Period().withMonths(number);
+            case YEARS:
+                return new Period().withYears(number);
+            case UNLIMITED:
+                return new Period().withYears(100);
+            default:
+                return new Period();
+        }
+    }
 }
diff --git a/catalog/src/main/java/com/ning/billing/catalog/DefaultPriceList.java b/catalog/src/main/java/com/ning/billing/catalog/DefaultPriceList.java
index aba447d..c597dc0 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/DefaultPriceList.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/DefaultPriceList.java
@@ -107,10 +107,20 @@ public class DefaultPriceList extends ValidatingConfig<StandaloneCatalog> implem
         return count;
     }
 	
-	public DefaultPriceList setRetired(boolean retired) {
+	protected DefaultPriceList setRetired(boolean retired) {
 		this.retired = retired;
 		return this;
 	}
 
+	public DefaultPriceList setName(String name) {
+		this.name = name;
+		return this;
+	}
+
+	public DefaultPriceList setPlans(DefaultPlan[] plans) {
+		this.plans = plans;
+		return this;
+	}
+
 
 }
diff --git a/catalog/src/main/java/com/ning/billing/catalog/DefaultPriceListSet.java b/catalog/src/main/java/com/ning/billing/catalog/DefaultPriceListSet.java
index f0636dd..83d810b 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/DefaultPriceListSet.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/DefaultPriceListSet.java
@@ -48,7 +48,7 @@ public class DefaultPriceListSet extends ValidatingConfig<StandaloneCatalog> {
 		this.childPriceLists = childPriceLists;
 	}
 
-	public DefaultPlan getPlanListFrom(String priceListName, Product product,
+	public DefaultPlan getPlanFrom(String priceListName, Product product,
 			BillingPeriod period) throws CatalogApiException {
 		DefaultPlan result = null;
 		DefaultPriceList pl = findPriceListFrom(priceListName);
diff --git a/catalog/src/main/java/com/ning/billing/catalog/DefaultProduct.java b/catalog/src/main/java/com/ning/billing/catalog/DefaultProduct.java
index ff3868c..6687c75 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/DefaultProduct.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/DefaultProduct.java
@@ -55,7 +55,6 @@ public class DefaultProduct extends ValidatingConfig<StandaloneCatalog> implemen
 	//Not included in XML
 	private String catalogName;
 	
-	
 	@Override
 	public String getCatalogName() {
 		return catalogName;
diff --git a/catalog/src/main/java/com/ning/billing/catalog/StandaloneCatalog.java b/catalog/src/main/java/com/ning/billing/catalog/StandaloneCatalog.java
index 029cdcd..a63645c 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/StandaloneCatalog.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/StandaloneCatalog.java
@@ -17,7 +17,6 @@
 package com.ning.billing.catalog;
 
 import java.net.URI;
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.Date;
 
@@ -40,6 +39,7 @@ import com.ning.billing.catalog.api.PlanChangeResult;
 import com.ning.billing.catalog.api.PlanPhase;
 import com.ning.billing.catalog.api.PlanPhaseSpecifier;
 import com.ning.billing.catalog.api.PlanSpecifier;
+import com.ning.billing.catalog.api.PriceList;
 import com.ning.billing.catalog.api.Product;
 import com.ning.billing.catalog.api.StaticCatalog;
 import com.ning.billing.catalog.rules.PlanRules;
@@ -70,11 +70,11 @@ public class StandaloneCatalog extends ValidatingConfig<StandaloneCatalog> imple
 	private PlanRules planRules;
 
 	@XmlElementWrapper(name="plans", required=true)
-	@XmlElement(name="plan", required=true)
+	@XmlElement(name="plan", required=true) 
 	private DefaultPlan[] plans;
 
-	@XmlElement(name="priceLists", required=true)
-	private DefaultPriceListSet priceLists;
+    @XmlElement(name="priceLists", required=true)
+    private DefaultPriceListSet priceLists;
 
 	public StandaloneCatalog() {}
 
@@ -142,7 +142,7 @@ public class StandaloneCatalog extends ValidatingConfig<StandaloneCatalog> imple
 			throw new CatalogApiException(ErrorCode.CAT_PRICE_LIST_NOT_FOUND,priceListName);
 		}
 		Product product = findCurrentProduct(productName);
-		DefaultPlan result = priceLists.getPlanListFrom(priceListName, product, period);
+		DefaultPlan result = priceLists.getPlanFrom(priceListName, product, period);
 		if ( result == null) {
 			String periodString = (period == null) ? "NULL" :  period.toString();
 			throw new CatalogApiException(ErrorCode.CAT_PLAN_NOT_FOUND, productName, periodString, priceListName);
@@ -187,6 +187,16 @@ public class StandaloneCatalog extends ValidatingConfig<StandaloneCatalog> imple
 		return plan.findPhase(name);
 	}
 
+    @Override
+    public PriceList findCurrentPricelist(String name)
+            throws CatalogApiException {
+        if (name == null || priceLists == null) {
+            throw new CatalogApiException(ErrorCode.CAT_PRICE_LIST_NOT_FOUND, name);
+        }
+        
+        return priceLists.findPriceListFrom(name);
+    }
+
 
 
 	//////////////////////////////////////////////////////////////////////////////
@@ -288,10 +298,10 @@ public class StandaloneCatalog extends ValidatingConfig<StandaloneCatalog> imple
 		return this;
 	}
 
-	protected StandaloneCatalog setPriceLists(DefaultPriceListSet priceLists) {
-		this.priceLists = priceLists;
-		return this;
-	}
+    protected StandaloneCatalog setPriceLists(DefaultPriceListSet priceLists) {
+        this.priceLists = priceLists;
+        return this;
+    }
 
 	@Override
 	public boolean canCreatePlan(PlanSpecifier specifier) throws CatalogApiException {
@@ -303,4 +313,5 @@ public class StandaloneCatalog extends ValidatingConfig<StandaloneCatalog> imple
 				(!plan.isRetired()) &&
 				(!priceList.isRetired());
 	}
+
 }
diff --git a/catalog/src/main/java/com/ning/billing/catalog/VersionedCatalog.java b/catalog/src/main/java/com/ning/billing/catalog/VersionedCatalog.java
index ef9b125..215b142 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/VersionedCatalog.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/VersionedCatalog.java
@@ -46,6 +46,7 @@ import com.ning.billing.catalog.api.PlanChangeResult;
 import com.ning.billing.catalog.api.PlanPhase;
 import com.ning.billing.catalog.api.PlanPhaseSpecifier;
 import com.ning.billing.catalog.api.PlanSpecifier;
+import com.ning.billing.catalog.api.PriceList;
 import com.ning.billing.catalog.api.Product;
 import com.ning.billing.catalog.api.StaticCatalog;
 import com.ning.billing.util.clock.Clock;
@@ -160,6 +161,8 @@ public class VersionedCatalog extends ValidatingConfig<StandaloneCatalog> implem
 		throw new CatalogApiException(ErrorCode.CAT_NO_CATALOG_FOR_GIVEN_DATE, requestedDate.toDate().toString());
 	}
 	
+	
+	
 	//
 	// Public methods not exposed in interface
 	//
@@ -270,6 +273,17 @@ public class VersionedCatalog extends ValidatingConfig<StandaloneCatalog> implem
 		return plan.findPhase(phaseName);
 	}
 	
+	
+	//
+    // Find a price list
+    //
+    @Override
+    public PriceList findPriceList(String name, DateTime requestedDate)
+            throws CatalogApiException {
+        return versionForDate(requestedDate).findCurrentPriceList(name);
+    }
+
+ 
     //
     // Rules
     //
@@ -381,6 +395,13 @@ public class VersionedCatalog extends ValidatingConfig<StandaloneCatalog> implem
 	public PlanPhase findCurrentPhase(String name) throws CatalogApiException {
 		return versionForDate(clock.getUTCNow()).findCurrentPhase(name);
 	}
+	
+
+    @Override
+    public PriceList findCurrentPricelist(String name)
+            throws CatalogApiException {
+        return versionForDate(clock.getUTCNow()).findCurrentPriceList(name);
+    }
 
 	@Override
 	public ActionPolicy planChangePolicy(PlanPhaseSpecifier from,
@@ -424,8 +445,4 @@ public class VersionedCatalog extends ValidatingConfig<StandaloneCatalog> implem
 		return versionForDate(clock.getUTCNow()).canCreatePlan(specifier);
 	}
 
-	
-
-
- 
 }
diff --git a/catalog/src/test/java/com/ning/billing/catalog/MockCatalog.java b/catalog/src/test/java/com/ning/billing/catalog/MockCatalog.java
index 244ca49..40dc146 100644
--- a/catalog/src/test/java/com/ning/billing/catalog/MockCatalog.java
+++ b/catalog/src/test/java/com/ning/billing/catalog/MockCatalog.java
@@ -18,14 +18,35 @@ package com.ning.billing.catalog;
 
 import java.util.Date;
 
+import org.joda.time.DateTime;
+
+import com.ning.billing.catalog.api.ActionPolicy;
+import com.ning.billing.catalog.api.BillingAlignment;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.Catalog;
+import com.ning.billing.catalog.api.CatalogApiException;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.catalog.api.PlanAlignmentChange;
+import com.ning.billing.catalog.api.PlanAlignmentCreate;
+import com.ning.billing.catalog.api.PlanChangeResult;
+import com.ning.billing.catalog.api.PlanPhase;
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.catalog.api.PlanSpecifier;
+import com.ning.billing.catalog.api.PriceList;
+import com.ning.billing.catalog.api.Product;
 import com.ning.billing.catalog.rules.CaseCancelPolicy;
 import com.ning.billing.catalog.rules.CaseChangePlanAlignment;
 import com.ning.billing.catalog.rules.CaseChangePlanPolicy;
 import com.ning.billing.catalog.rules.CaseCreateAlignment;
 import com.ning.billing.catalog.rules.PlanRules;
 
-public class MockCatalog extends StandaloneCatalog {
+public class MockCatalog extends StandaloneCatalog implements Catalog {
 	private static final String[] PRODUCT_NAMES = new String[]{ "TestProduct1", "TestProduct2", "TestProduct3"};
+    private boolean canCreatePlan;
+    private PlanChangeResult planChange;
+    private BillingAlignment billingAlignment;
+    private PlanAlignmentCreate planCreateAlignment;
 	
 	public MockCatalog() {
 		setEffectiveDate(new Date());
@@ -34,8 +55,8 @@ public class MockCatalog extends StandaloneCatalog {
 		populateRules();
 		populatePriceLists();
 	}
-	
-	public void populateRules(){
+
+    public void populateRules(){
 		setPlanRules(new PlanRules());
 	}
 
@@ -47,6 +68,8 @@ public class MockCatalog extends StandaloneCatalog {
 			){
 		
 	}
+	
+	
 
 	public void populatePriceLists() {
 		DefaultPlan[] plans = getCurrentPlans();
@@ -64,6 +87,156 @@ public class MockCatalog extends StandaloneCatalog {
 		return PRODUCT_NAMES;
 	}
 
+    @Override
+    public Currency[] getSupportedCurrencies(DateTime requestedDate) throws CatalogApiException {
+        return getCurrentSupportedCurrencies();
+    }
+
+    @Override
+    public Product[] getProducts(DateTime requestedDate) throws CatalogApiException {
+       return getCurrentProducts();
+    }
+
+    @Override
+    public Plan[] getPlans(DateTime requestedDate) throws CatalogApiException {
+        return getCurrentPlans();
+    }
+
+    @Override
+    public Plan findPlan(String name, DateTime requestedDate) throws CatalogApiException {
+        return findCurrentPlan(name);
+    }
+
+    @Override
+    public Plan findPlan(String productName, BillingPeriod term, String priceListName, DateTime requestedDate)
+            throws CatalogApiException {
+        return findCurrentPlan(productName, term, priceListName);
+    }
+
+    @Override
+    public Plan findPlan(String name, DateTime effectiveDate, DateTime subscriptionStartDate)
+            throws CatalogApiException {
+        return findCurrentPlan(name);
+    }
+
+    @Override
+    public Plan findPlan(String productName, BillingPeriod term, String priceListName, DateTime requestedDate,
+            DateTime subscriptionStartDate) throws CatalogApiException {
+       return findCurrentPlan(productName, term, priceListName);
+    }
+    
+    @Override
+    public Product findProduct(String name, DateTime requestedDate) throws CatalogApiException {
+        return findCurrentProduct(name);
+    }
+
+    @Override
+    public PlanPhase findPhase(String name, DateTime requestedDate, DateTime subscriptionStartDate)
+            throws CatalogApiException {
+        return findCurrentPhase(name);
+    }
+
+    @Override
+    public PriceList findPriceList(String name, DateTime requestedDate) throws CatalogApiException {
+        return findCurrentPricelist(name);
+    }
+
+    @Override
+    public ActionPolicy planChangePolicy(PlanPhaseSpecifier from, PlanSpecifier to, DateTime requestedDate)
+            throws CatalogApiException {
+        return planChangePolicy(from, to);
+    }
+
+    @Override
+    public PlanChangeResult planChange(PlanPhaseSpecifier from, PlanSpecifier to, DateTime requestedDate)
+            throws CatalogApiException {
+        return planChange(from, to);
+    }
+
+    @Override
+    public ActionPolicy planCancelPolicy(PlanPhaseSpecifier planPhase, DateTime requestedDate)
+            throws CatalogApiException {
+        return planCancelPolicy(planPhase);
+    }
+
+    @Override
+    public PlanAlignmentCreate planCreateAlignment(PlanSpecifier specifier, DateTime requestedDate)
+            throws CatalogApiException {
+        return planCreateAlignment(specifier);  
+    }
+
+    @Override
+    public BillingAlignment billingAlignment(PlanPhaseSpecifier planPhase, DateTime requestedDate)
+            throws CatalogApiException {
+        return billingAlignment(planPhase);
+    }
+
+    @Override
+    public PlanAlignmentChange planChangeAlignment(PlanPhaseSpecifier from, PlanSpecifier to, DateTime requestedDate)
+            throws CatalogApiException {
+        return planChangeAlignment(from, to);
+    }
+
+    @Override
+    public boolean canCreatePlan(PlanSpecifier specifier, DateTime requestedDate) throws CatalogApiException {
+        return canCreatePlan(specifier);
+    }
+
+    @Override
+    public ActionPolicy planChangePolicy(PlanPhaseSpecifier from, PlanSpecifier to) throws CatalogApiException {
+        // TODO Auto-generated method stub
+        return super.planChangePolicy(from, to);
+    }
+
+    @Override
+    public PlanAlignmentChange planChangeAlignment(PlanPhaseSpecifier from, PlanSpecifier to)
+            throws CatalogApiException {
+        // TODO Auto-generated method stub
+        return super.planChangeAlignment(from, to);
+    }
+
+    @Override
+    public ActionPolicy planCancelPolicy(PlanPhaseSpecifier planPhase) throws CatalogApiException {
+        // TODO Auto-generated method stub
+        return super.planCancelPolicy(planPhase);
+    }
+
+    @Override
+    public PlanAlignmentCreate planCreateAlignment(PlanSpecifier specifier) throws CatalogApiException {
+        return planCreateAlignment;
+    }
+
+    @Override
+    public BillingAlignment billingAlignment(PlanPhaseSpecifier planPhase) throws CatalogApiException {
+        // TODO Auto-generated method stub
+        return billingAlignment;
+    }
+
+    @Override
+    public PlanChangeResult planChange(PlanPhaseSpecifier from, PlanSpecifier to) throws CatalogApiException {
+        // TODO Auto-generated method stub
+        return planChange;
+    }
+
+    @Override
+    public boolean canCreatePlan(PlanSpecifier specifier) throws CatalogApiException {
+        return canCreatePlan;
+    }
+
+    public void setCanCreatePlan(boolean canCreatePlan) {
+        this.canCreatePlan = canCreatePlan;
+    }
+
+    public void setPlanChange(PlanChangeResult planChange) {
+        this.planChange = planChange;
+    }
+
+    public void setBillingAlignment(BillingAlignment billingAlignment) {
+        this.billingAlignment = billingAlignment;
+    }
+
+    public void setPlanCreateAlignment(PlanAlignmentCreate planCreateAlignment) {
+        this.planCreateAlignment = planCreateAlignment;
+    }
 
-	
 }
diff --git a/catalog/src/test/java/com/ning/billing/catalog/MockCatalogModule.java b/catalog/src/test/java/com/ning/billing/catalog/MockCatalogModule.java
new file mode 100644
index 0000000..d61d6ae
--- /dev/null
+++ b/catalog/src/test/java/com/ning/billing/catalog/MockCatalogModule.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2010-2011 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.catalog;
+
+import com.google.inject.AbstractModule;
+import com.ning.billing.catalog.api.Catalog;
+import com.ning.billing.catalog.api.CatalogService;
+import com.ning.billing.mock.BrainDeadProxyFactory;
+import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
+
+public class MockCatalogModule extends AbstractModule {
+
+    @Override
+    protected void configure() {
+        CatalogService catalogService = BrainDeadProxyFactory.createBrainDeadProxyFor(CatalogService.class);
+        ((ZombieControl) catalogService).addResult("getCurrentCatalog", new MockCatalog());
+        
+        catalogService = BrainDeadProxyFactory.createBrainDeadProxyFor(CatalogService.class);
+        Catalog catalog = BrainDeadProxyFactory.createBrainDeadProxyFor(Catalog.class);
+
+        ((ZombieControl) catalogService).addResult("getFullCatalog", catalog); 
+        
+        bind(CatalogService.class).toInstance(catalogService);
+    }
+}
diff --git a/catalog/src/test/java/com/ning/billing/catalog/MockPlan.java b/catalog/src/test/java/com/ning/billing/catalog/MockPlan.java
index deececb..f4dfab1 100644
--- a/catalog/src/test/java/com/ning/billing/catalog/MockPlan.java
+++ b/catalog/src/test/java/com/ning/billing/catalog/MockPlan.java
@@ -60,13 +60,21 @@ public class MockPlan extends DefaultPlan {
 				-1);
 	}
 
-	public static MockPlan createJetTrialFixedTermEvergreen1000USD() {
-		return new MockPlan("JetTrialEvergreen1000USD",
-				MockProduct.createJet(),
-				new DefaultPlanPhase[]{ MockPlanPhase.create30DayTrial(), MockPlanPhase.createUSDMonthlyFixedTerm("500.00", null, 6) },
-				MockPlanPhase.create1USDMonthlyEvergreen(),
-				-1);
-	}
+    public static MockPlan createJetTrialFixedTermEvergreen1000USD() {
+        return new MockPlan("JetTrialEvergreen1000USD",
+                MockProduct.createJet(),
+                new DefaultPlanPhase[]{ MockPlanPhase.create30DayTrial(), MockPlanPhase.createUSDMonthlyFixedTerm("500.00", null, 6) },
+                MockPlanPhase.create1USDMonthlyEvergreen(),
+                -1);
+    }
+
+    public static MockPlan createHornMonthlyNoTrial1USD() {
+        return new MockPlan("Horn1USD",
+                MockProduct.createHorn(),
+                new DefaultPlanPhase[]{ },
+                MockPlanPhase.create1USDMonthlyEvergreen(),
+                -1);
+    }
 
 	public MockPlan() {
 		this("BicycleTrialEvergreen1USD",
@@ -123,7 +131,8 @@ public class MockPlan extends DefaultPlan {
 				createPickupTrialEvergreen10USD(),
 				createSportsCarTrialEvergreen100USD(),
 				createJetTrialEvergreen1000USD(),
-				createJetTrialFixedTermEvergreen1000USD()
+				createJetTrialFixedTermEvergreen1000USD(),
+				createHornMonthlyNoTrial1USD()
 		};
 	}
 	
diff --git a/catalog/src/test/java/com/ning/billing/catalog/MockPriceList.java b/catalog/src/test/java/com/ning/billing/catalog/MockPriceList.java
new file mode 100644
index 0000000..9b672ba
--- /dev/null
+++ b/catalog/src/test/java/com/ning/billing/catalog/MockPriceList.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2010-2011 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.catalog;
+
+import com.ning.billing.catalog.api.PriceListSet;
+
+public class MockPriceList extends DefaultPriceList {
+
+	public MockPriceList() {
+		setName(PriceListSet.DEFAULT_PRICELIST_NAME);
+		setRetired(false);
+		setPlans(MockPlan.createAll());
+	}
+}
diff --git a/catalog/src/test/java/com/ning/billing/catalog/TestPlanPhase.java b/catalog/src/test/java/com/ning/billing/catalog/TestPlanPhase.java
index e25ac82..bee4e3d 100644
--- a/catalog/src/test/java/com/ning/billing/catalog/TestPlanPhase.java
+++ b/catalog/src/test/java/com/ning/billing/catalog/TestPlanPhase.java
@@ -58,23 +58,23 @@ public class TestPlanPhase {
 		DefaultPlanPhase ppDiscount = MockPlanPhase.create1USDMonthlyEvergreen().setPhaseType(PhaseType.DISCOUNT).setPlan(p);
 		DefaultPlanPhase ppTrial = MockPlanPhase.create30DayTrial().setPhaseType(PhaseType.TRIAL).setPlan(p);
 		DefaultPlanPhase ppEvergreen = MockPlanPhase.create1USDMonthlyEvergreen().setPhaseType(PhaseType.EVERGREEN).setPlan(p);
-		DefaultPlanPhase ppFixedterm = MockPlanPhase.create1USDMonthlyEvergreen().setPhaseType(PhaseType.FIXEDTERM).setPlan(p);
+		DefaultPlanPhase ppFixedTerm = MockPlanPhase.create1USDMonthlyEvergreen().setPhaseType(PhaseType.FIXEDTERM).setPlan(p);
 		
 		String ppnDiscount = DefaultPlanPhase.phaseName(p.getName(), ppDiscount.getPhaseType());
 		String ppnTrial = DefaultPlanPhase.phaseName(p.getName(), ppTrial.getPhaseType());
 		String ppnEvergreen = DefaultPlanPhase.phaseName(p.getName(), ppEvergreen.getPhaseType());
-		String ppnFixedterm = DefaultPlanPhase.phaseName(p.getName(), ppFixedterm.getPhaseType());
+		String ppnFixedTerm = DefaultPlanPhase.phaseName(p.getName(), ppFixedTerm.getPhaseType());
 		
 		Assert.assertEquals(ppnTrial, planNameExt + "trial");
 		Assert.assertEquals(ppnEvergreen, planNameExt + "evergreen");
-		Assert.assertEquals(ppnFixedterm, planNameExt + "fixedterm");
+		Assert.assertEquals(ppnFixedTerm, planNameExt + "fixedterm");
 		Assert.assertEquals(ppnDiscount, planNameExt + "discount");
 		
 		
 		Assert.assertEquals(DefaultPlanPhase.planName(ppnDiscount),planName);
 		Assert.assertEquals(DefaultPlanPhase.planName(ppnTrial),planName);
 		Assert.assertEquals(DefaultPlanPhase.planName(ppnEvergreen), planName);
-		Assert.assertEquals(DefaultPlanPhase.planName(ppnFixedterm), planName);
+		Assert.assertEquals(DefaultPlanPhase.planName(ppnFixedTerm), planName);
 		
 		
 		
diff --git a/catalog/src/test/java/com/ning/billing/catalog/TestPriceListSet.java b/catalog/src/test/java/com/ning/billing/catalog/TestPriceListSet.java
index 3cb8d2f..a26ce10 100644
--- a/catalog/src/test/java/com/ning/billing/catalog/TestPriceListSet.java
+++ b/catalog/src/test/java/com/ning/billing/catalog/TestPriceListSet.java
@@ -50,10 +50,10 @@ public class TestPriceListSet {
 		};
 		DefaultPriceListSet set = new DefaultPriceListSet(defaultPriceList, childPriceLists);
 		
-		Assert.assertEquals(set.getPlanListFrom(PriceListSet.DEFAULT_PRICELIST_NAME, foo, BillingPeriod.ANNUAL).getFinalPhase().getPhaseType(), PhaseType.EVERGREEN);
-		Assert.assertEquals(set.getPlanListFrom(PriceListSet.DEFAULT_PRICELIST_NAME, foo, BillingPeriod.MONTHLY).getFinalPhase().getPhaseType(), PhaseType.EVERGREEN);
-		Assert.assertEquals(set.getPlanListFrom("child", foo, BillingPeriod.ANNUAL).getFinalPhase().getPhaseType(), PhaseType.DISCOUNT);
-		Assert.assertEquals(set.getPlanListFrom("child", foo, BillingPeriod.MONTHLY).getFinalPhase().getPhaseType(), PhaseType.EVERGREEN);
+		Assert.assertEquals(set.getPlanFrom(PriceListSet.DEFAULT_PRICELIST_NAME, foo, BillingPeriod.ANNUAL).getFinalPhase().getPhaseType(), PhaseType.EVERGREEN);
+		Assert.assertEquals(set.getPlanFrom(PriceListSet.DEFAULT_PRICELIST_NAME, foo, BillingPeriod.MONTHLY).getFinalPhase().getPhaseType(), PhaseType.EVERGREEN);
+		Assert.assertEquals(set.getPlanFrom("child", foo, BillingPeriod.ANNUAL).getFinalPhase().getPhaseType(), PhaseType.DISCOUNT);
+		Assert.assertEquals(set.getPlanFrom("child", foo, BillingPeriod.MONTHLY).getFinalPhase().getPhaseType(), PhaseType.EVERGREEN);
 	}
 	
 	public void testForNullBillingPeriod() throws CatalogApiException {
@@ -76,10 +76,10 @@ public class TestPriceListSet {
 		};
 		DefaultPriceListSet set = new DefaultPriceListSet(defaultPriceList, childPriceLists);
 		
-		Assert.assertEquals(set.getPlanListFrom("child", foo, BillingPeriod.ANNUAL).getFinalPhase().getPhaseType(), PhaseType.DISCOUNT);
-		Assert.assertEquals(set.getPlanListFrom("child", foo, BillingPeriod.MONTHLY).getFinalPhase().getPhaseType(), PhaseType.EVERGREEN);
-		Assert.assertEquals(set.getPlanListFrom(PriceListSet.DEFAULT_PRICELIST_NAME, foo, BillingPeriod.ANNUAL).getFinalPhase().getPhaseType(), PhaseType.EVERGREEN);
-		Assert.assertEquals(set.getPlanListFrom(PriceListSet.DEFAULT_PRICELIST_NAME, foo, BillingPeriod.MONTHLY).getFinalPhase().getPhaseType(), PhaseType.EVERGREEN);
+		Assert.assertEquals(set.getPlanFrom("child", foo, BillingPeriod.ANNUAL).getFinalPhase().getPhaseType(), PhaseType.DISCOUNT);
+		Assert.assertEquals(set.getPlanFrom("child", foo, BillingPeriod.MONTHLY).getFinalPhase().getPhaseType(), PhaseType.EVERGREEN);
+		Assert.assertEquals(set.getPlanFrom(PriceListSet.DEFAULT_PRICELIST_NAME, foo, BillingPeriod.ANNUAL).getFinalPhase().getPhaseType(), PhaseType.EVERGREEN);
+		Assert.assertEquals(set.getPlanFrom(PriceListSet.DEFAULT_PRICELIST_NAME, foo, BillingPeriod.MONTHLY).getFinalPhase().getPhaseType(), PhaseType.EVERGREEN);
 	}
 
 }
diff --git a/catalog/src/test/resources/versionedCatalog/WeaponsHireSmall-1.xml b/catalog/src/test/resources/versionedCatalog/WeaponsHireSmall-1.xml
index c21aac1..ca17050 100644
--- a/catalog/src/test/resources/versionedCatalog/WeaponsHireSmall-1.xml
+++ b/catalog/src/test/resources/versionedCatalog/WeaponsHireSmall-1.xml
@@ -67,8 +67,7 @@
             </createAlignmentCase>
         </createAlignment>
 	</rules>
-
-
+ 
 	<plans>
 		<plan name="pistol-monthly">
 			<product>Pistol</product>
diff --git a/catalog/src/test/resources/versionedCatalog/WeaponsHireSmall-3.xml b/catalog/src/test/resources/versionedCatalog/WeaponsHireSmall-3.xml
index f7ae066..ccbd964 100644
--- a/catalog/src/test/resources/versionedCatalog/WeaponsHireSmall-3.xml
+++ b/catalog/src/test/resources/versionedCatalog/WeaponsHireSmall-3.xml
@@ -68,7 +68,6 @@
         </createAlignment>
 	</rules>
 
-
 	<plans>
 		<plan name="pistol-monthly">
 			<product>Pistol</product>
diff --git a/catalog/src/test/resources/WeaponsHire.xml b/catalog/src/test/resources/WeaponsHire.xml
index 3d36b9d..4a895b0 100644
--- a/catalog/src/test/resources/WeaponsHire.xml
+++ b/catalog/src/test/resources/WeaponsHire.xml
@@ -169,8 +169,8 @@ Use Cases to do:
 				<toPriceList>DEFAULT</toPriceList>
 			</priceListCase>
 		</priceList>
-	</rules>
-
+	</rules> 
+	
 	<plans>
 		<plan name="pistol-monthly">
 			<product>Pistol</product>

doc/api.html 91(+91 -0)

diff --git a/doc/api.html b/doc/api.html
new file mode 100644
index 0000000..f44a165
--- /dev/null
+++ b/doc/api.html
@@ -0,0 +1,91 @@
+<!-- ~ Copyright 2010-2011 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. -->
+
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="utf-8">
+    <title>Collector core | Dwarf</title>
+    <meta name="user guide" content="">
+    <meta name="author" content="Stephane Brossier">
+
+    <!--[if lt IE 9]>
+    <script src="http://html5shim.googlecode.com/svn/trunk/html5.js" type="text/javascript"></script>
+    <![endif]-->
+
+    <link rel=stylesheet type="text/css" href="css/bootstrap.min.css">
+    <link rel=stylesheet type="text/css" href="css/prettify.css">
+    <link rel=stylesheet type="text/css" href="css/killbill.css">
+    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
+    <script src="js/prettify.js"></script>
+    <script src="js/bootstrap-dropdown.js"></script>
+
+    <script type="text/javascript">
+      var _gaq = _gaq || [];
+      _gaq.push(['_setAccount', 'UA-19297396-1']);
+      _gaq.push(['_trackPageview']);
+      (function() {
+        var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
+        ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
+        var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
+      })();
+    </script>
+</head>
+
+<body onload="prettyPrint();">
+
+    <div class="topbar" data-dropdown="dropdown">
+        <div class="topbar-inner">
+            <div class="container-fluid">
+                <a class="brand" href="../index.html">Killbill</a>
+                <ul class="nav">
+                    <li><a href="user.html">User Guide</a></li>
+                    <li><a href="setup.html">Setup/Installation</a></li>   
+                    <li class="active"><a href="user.html">APIs</a></li>
+                    <li><a href="design.html">Design</a></li>                                                
+                    <li><a href="http://groups.google.com/group/killbill-users">Mailing List</a></li>
+                </ul>
+                <ul class="nav secondary-nav">
+                    <li class="dropdown">
+                        <a href="#" class="dropdown-toggle">Maven sites</a>
+                        <ul class="dropdown-menu">
+                            <li><a href="http://sbrossie.github.com/killbill" target="_blank">Killbill</a></li>
+                        </ul>
+                    </li>
+                </ul>
+                <ul class="nav secondary-nav">
+                    <li class="dropdown">
+                        <a href="#" class="dropdown-toggle">Github projects</a>
+                        <ul class="dropdown-menu">
+                            <li><a href="http://github.com/ning/killbill" target="_blank">Killbill</a></li>
+                        </ul>
+                    </li>
+                </ul>
+            </div>
+        </div>
+    </div>
+    <div class="container-fluid">
+        <div class="sidebar">
+            <div class="well">
+                <h5>Setup</h5>
+                <ul>
+                    <li><a href="#overview">Prerequisite</a></li>
+                    <li><a href="#Details">Installation</a></li>
+                </ul>
+            </div>
+        </div>
+        <div class="content">
+            <h2 class="overview">Overview</h2>
+                Bla bla get me prerequisite
+            <h2 class="details">Details</h2>
+                    details  
+        </div>
+    </div>
+</body>
+ 
diff --git a/doc/css/bootstrap.min.css b/doc/css/bootstrap.min.css
new file mode 100644
index 0000000..75e85d3
--- /dev/null
+++ b/doc/css/bootstrap.min.css
@@ -0,0 +1,356 @@
+html,body{margin:0;padding:0;}
+h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,cite,code,del,dfn,em,img,q,s,samp,small,strike,strong,sub,sup,tt,var,dd,dl,dt,li,ol,ul,fieldset,form,label,legend,button,table,caption,tbody,tfoot,thead,tr,th,td{margin:0;padding:0;border:0;font-weight:normal;font-style:normal;font-size:100%;line-height:1;font-family:inherit;}
+table{border-collapse:collapse;border-spacing:0;}
+ol,ul{list-style:none;}
+q:before,q:after,blockquote:before,blockquote:after{content:"";}
+html{overflow-y:scroll;font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;}
+a:focus{outline:thin dotted;}
+a:hover,a:active{outline:0;}
+article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block;}
+audio,canvas,video{display:inline-block;*display:inline;*zoom:1;}
+audio:not([controls]){display:none;}
+sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline;}
+sup{top:-0.5em;}
+sub{bottom:-0.25em;}
+img{border:0;-ms-interpolation-mode:bicubic;}
+button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle;}
+button,input{line-height:normal;*overflow:visible;}
+button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0;}
+button,input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button;}
+input[type="search"]{-webkit-appearance:textfield;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;}
+input[type="search"]::-webkit-search-decoration{-webkit-appearance:none;}
+textarea{overflow:auto;vertical-align:top;}
+body{background-color:#ffffff;margin:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:18px;color:#404040;}
+.container{width:940px;margin-left:auto;margin-right:auto;zoom:1;}.container:before,.container:after{display:table;content:"";zoom:1;}
+.container:after{clear:both;}
+.container-fluid{position:relative;min-width:940px;padding-left:20px;padding-right:20px;zoom:1;}.container-fluid:before,.container-fluid:after{display:table;content:"";zoom:1;}
+.container-fluid:after{clear:both;}
+.container-fluid>.sidebar{position:absolute;top:0;left:20px;width:220px;}
+.container-fluid>.content{margin-left:240px;}
+a{color:#0069d6;text-decoration:none;line-height:inherit;font-weight:inherit;}a:hover{color:#00438a;text-decoration:underline;}
+.pull-right{float:right;}
+.pull-left{float:left;}
+.hide{display:none;}
+.show{display:block;}
+.row{zoom:1;margin-left:-20px;}.row:before,.row:after{display:table;content:"";zoom:1;}
+.row:after{clear:both;}
+.row>[class*="span"]{display:inline;float:left;margin-left:20px;}
+.span1{width:40px;}
+.span2{width:100px;}
+.span3{width:160px;}
+.span4{width:220px;}
+.span5{width:280px;}
+.span6{width:340px;}
+.span7{width:400px;}
+.span8{width:460px;}
+.span9{width:520px;}
+.span10{width:580px;}
+.span11{width:640px;}
+.span12{width:700px;}
+.span13{width:760px;}
+.span14{width:820px;}
+.span15{width:880px;}
+.span16{width:940px;}
+.span17{width:1000px;}
+.span18{width:1060px;}
+.span19{width:1120px;}
+.span20{width:1180px;}
+.span21{width:1240px;}
+.span22{width:1300px;}
+.span23{width:1360px;}
+.span24{width:1420px;}
+.row >.offset1{margin-left:80px;}
+.row >.offset2{margin-left:140px;}
+.row >.offset3{margin-left:200px;}
+.row >.offset4{margin-left:260px;}
+.row >.offset5{margin-left:320px;}
+.row >.offset6{margin-left:380px;}
+.row >.offset7{margin-left:440px;}
+.row >.offset8{margin-left:500px;}
+.row >.offset9{margin-left:560px;}
+.row >.offset10{margin-left:620px;}
+.row >.offset11{margin-left:680px;}
+.row >.offset12{margin-left:740px;}
+.span-one-third{width:300px;}
+.span-two-thirds{width:620px;}
+.offset-one-third{margin-left:340px;}
+.offset-two-thirds{margin-left:660px;}
+p{font-size:13px;font-weight:normal;line-height:18px;margin-bottom:9px;}p small{font-size:11px;color:#bfbfbf;}
+h1,h2,h3,h4,h5,h6{font-weight:bold;color:#404040;}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{color:#bfbfbf;}
+h1{margin-bottom:18px;font-size:30px;line-height:36px;}h1 small{font-size:18px;}
+h2{font-size:24px;line-height:36px;}h2 small{font-size:14px;}
+h3,h4,h5,h6{line-height:36px;}
+h3{font-size:18px;}h3 small{font-size:14px;}
+h4{font-size:16px;}h4 small{font-size:12px;}
+h5{font-size:14px;}
+h6{font-size:13px;color:#bfbfbf;text-transform:uppercase;}
+ul,ol{margin:0 0 18px 25px;}
+ul ul,ul ol,ol ol,ol ul{margin-bottom:0;}
+ul{list-style:disc;}
+ol{list-style:decimal;}
+li{line-height:18px;color:#808080;}
+ul.unstyled{list-style:none;margin-left:0;}
+dl{margin-bottom:18px;}dl dt,dl dd{line-height:18px;}
+dl dt{font-weight:bold;}
+dl dd{margin-left:9px;}
+hr{margin:20px 0 19px;border:0;border-bottom:1px solid #eee;}
+strong{font-style:inherit;font-weight:bold;}
+em{font-style:italic;font-weight:inherit;line-height:inherit;}
+.muted{color:#bfbfbf;}
+blockquote{margin-bottom:18px;border-left:5px solid #eee;padding-left:15px;}blockquote p{font-size:14px;font-weight:300;line-height:18px;margin-bottom:0;}
+blockquote small{display:block;font-size:12px;font-weight:300;line-height:18px;color:#bfbfbf;}blockquote small:before{content:'\2014 \00A0';}
+address{display:block;line-height:18px;margin-bottom:18px;}
+code,pre{padding:0 3px 2px;font-family:Monaco, Andale Mono, Courier New, monospace;font-size:12px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;}
+code{background-color:#fee9cc;color:rgba(0, 0, 0, 0.75);padding:1px 3px;}
+pre{background-color:#f5f5f5;display:block;padding:8.5px;margin:0 0 18px;line-height:18px;font-size:12px;border:1px solid #ccc;border:1px solid rgba(0, 0, 0, 0.15);-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;white-space:pre;white-space:pre-wrap;word-wrap:break-word;}
+form{margin-bottom:18px;}
+fieldset{margin-bottom:18px;padding-top:18px;}fieldset legend{display:block;padding-left:150px;font-size:19.5px;line-height:1;color:#404040;*padding:0 0 5px 145px;*line-height:1.5;}
+form .clearfix{margin-bottom:18px;zoom:1;}form .clearfix:before,form .clearfix:after{display:table;content:"";zoom:1;}
+form .clearfix:after{clear:both;}
+label,input,select,textarea{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:normal;}
+label{padding-top:6px;font-size:13px;line-height:18px;float:left;width:130px;text-align:right;color:#404040;}
+form .input{margin-left:150px;}
+input[type=checkbox],input[type=radio]{cursor:pointer;}
+input,textarea,select,.uneditable-input{display:inline-block;width:210px;height:18px;padding:4px;font-size:13px;line-height:18px;color:#808080;border:1px solid #ccc;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;}
+select{padding:initial;}
+input[type=checkbox],input[type=radio]{width:auto;height:auto;padding:0;margin:3px 0;*margin-top:0;line-height:normal;border:none;}
+input[type=file]{background-color:#ffffff;padding:initial;border:initial;line-height:initial;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;}
+input[type=button],input[type=reset],input[type=submit]{width:auto;height:auto;}
+select,input[type=file]{height:27px;*height:auto;line-height:27px;*margin-top:4px;}
+select[multiple]{height:inherit;background-color:#ffffff;}
+textarea{height:auto;}
+.uneditable-input{background-color:#ffffff;display:block;border-color:#eee;-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);-moz-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);cursor:not-allowed;}
+:-moz-placeholder{color:#bfbfbf;}
+::-webkit-input-placeholder{color:#bfbfbf;}
+input,textarea{-webkit-transform-style:preserve-3d;-webkit-transition:border linear 0.2s,box-shadow linear 0.2s;-moz-transition:border linear 0.2s,box-shadow linear 0.2s;-ms-transition:border linear 0.2s,box-shadow linear 0.2s;-o-transition:border linear 0.2s,box-shadow linear 0.2s;transition:border linear 0.2s,box-shadow linear 0.2s;-webkit-box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.1);-moz-box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.1);box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.1);}
+input:focus,textarea:focus{outline:0;border-color:rgba(82, 168, 236, 0.8);-webkit-box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.1),0 0 8px rgba(82, 168, 236, 0.6);-moz-box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.1),0 0 8px rgba(82, 168, 236, 0.6);box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.1),0 0 8px rgba(82, 168, 236, 0.6);}
+input[type=file]:focus,input[type=checkbox]:focus,select:focus{-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;outline:1px dotted #666;}
+form .clearfix.error>label,form .clearfix.error .help-block,form .clearfix.error .help-inline{color:#b94a48;}
+form .clearfix.error input,form .clearfix.error textarea{color:#b94a48;border-color:#ee5f5b;}form .clearfix.error input:focus,form .clearfix.error textarea:focus{border-color:#e9322d;-webkit-box-shadow:0 0 6px #f8b9b7;-moz-box-shadow:0 0 6px #f8b9b7;box-shadow:0 0 6px #f8b9b7;}
+form .clearfix.error .input-prepend .add-on,form .clearfix.error .input-append .add-on{color:#b94a48;background-color:#fce6e6;border-color:#b94a48;}
+form .clearfix.warning>label,form .clearfix.warning .help-block,form .clearfix.warning .help-inline{color:#c09853;}
+form .clearfix.warning input,form .clearfix.warning textarea{color:#c09853;border-color:#ccae64;}form .clearfix.warning input:focus,form .clearfix.warning textarea:focus{border-color:#be9a3f;-webkit-box-shadow:0 0 6px #e5d6b1;-moz-box-shadow:0 0 6px #e5d6b1;box-shadow:0 0 6px #e5d6b1;}
+form .clearfix.warning .input-prepend .add-on,form .clearfix.warning .input-append .add-on{color:#c09853;background-color:#d2b877;border-color:#c09853;}
+form .clearfix.success>label,form .clearfix.success .help-block,form .clearfix.success .help-inline{color:#468847;}
+form .clearfix.success input,form .clearfix.success textarea{color:#468847;border-color:#57a957;}form .clearfix.success input:focus,form .clearfix.success textarea:focus{border-color:#458845;-webkit-box-shadow:0 0 6px #9acc9a;-moz-box-shadow:0 0 6px #9acc9a;box-shadow:0 0 6px #9acc9a;}
+form .clearfix.success .input-prepend .add-on,form .clearfix.success .input-append .add-on{color:#468847;background-color:#bcddbc;border-color:#468847;}
+.input-mini,input.mini,textarea.mini,select.mini{width:60px;}
+.input-small,input.small,textarea.small,select.small{width:90px;}
+.input-medium,input.medium,textarea.medium,select.medium{width:150px;}
+.input-large,input.large,textarea.large,select.large{width:210px;}
+.input-xlarge,input.xlarge,textarea.xlarge,select.xlarge{width:270px;}
+.input-xxlarge,input.xxlarge,textarea.xxlarge,select.xxlarge{width:530px;}
+textarea.xxlarge{overflow-y:auto;}
+input.span1,textarea.span1{display:inline-block;float:none;width:30px;margin-left:0;}
+input.span2,textarea.span2{display:inline-block;float:none;width:90px;margin-left:0;}
+input.span3,textarea.span3{display:inline-block;float:none;width:150px;margin-left:0;}
+input.span4,textarea.span4{display:inline-block;float:none;width:210px;margin-left:0;}
+input.span5,textarea.span5{display:inline-block;float:none;width:270px;margin-left:0;}
+input.span6,textarea.span6{display:inline-block;float:none;width:330px;margin-left:0;}
+input.span7,textarea.span7{display:inline-block;float:none;width:390px;margin-left:0;}
+input.span8,textarea.span8{display:inline-block;float:none;width:450px;margin-left:0;}
+input.span9,textarea.span9{display:inline-block;float:none;width:510px;margin-left:0;}
+input.span10,textarea.span10{display:inline-block;float:none;width:570px;margin-left:0;}
+input.span11,textarea.span11{display:inline-block;float:none;width:630px;margin-left:0;}
+input.span12,textarea.span12{display:inline-block;float:none;width:690px;margin-left:0;}
+input.span13,textarea.span13{display:inline-block;float:none;width:750px;margin-left:0;}
+input.span14,textarea.span14{display:inline-block;float:none;width:810px;margin-left:0;}
+input.span15,textarea.span15{display:inline-block;float:none;width:870px;margin-left:0;}
+input.span16,textarea.span16{display:inline-block;float:none;width:930px;margin-left:0;}
+input[disabled],select[disabled],textarea[disabled],input[readonly],select[readonly],textarea[readonly]{background-color:#f5f5f5;border-color:#ddd;cursor:not-allowed;}
+.actions{background:#f5f5f5;margin-top:18px;margin-bottom:18px;padding:17px 20px 18px 150px;border-top:1px solid #ddd;-webkit-border-radius:0 0 3px 3px;-moz-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px;}.actions .secondary-action{float:right;}.actions .secondary-action a{line-height:30px;}.actions .secondary-action a:hover{text-decoration:underline;}
+.help-inline,.help-block{font-size:13px;line-height:18px;color:#bfbfbf;}
+.help-inline{padding-left:5px;*position:relative;*top:-5px;}
+.help-block{display:block;max-width:600px;}
+.inline-inputs{color:#808080;}.inline-inputs span{padding:0 2px 0 1px;}
+.input-prepend input,.input-append input{-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0;}
+.input-prepend .add-on,.input-append .add-on{position:relative;background:#f5f5f5;border:1px solid #ccc;z-index:2;float:left;display:block;width:auto;min-width:16px;height:18px;padding:4px 4px 4px 5px;margin-right:-1px;font-weight:normal;line-height:18px;color:#bfbfbf;text-align:center;text-shadow:0 1px 0 #ffffff;-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;}
+.input-prepend .active,.input-append .active{background:#a9dba9;border-color:#46a546;}
+.input-prepend .add-on{*margin-top:1px;}
+.input-append input{float:left;-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;}
+.input-append .add-on{-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0;margin-right:0;margin-left:-1px;}
+.inputs-list{margin:0 0 5px;width:100%;}.inputs-list li{display:block;padding:0;width:100%;}
+.inputs-list label{display:block;float:none;width:auto;padding:0;margin-left:20px;line-height:18px;text-align:left;white-space:normal;}.inputs-list label strong{color:#808080;}
+.inputs-list label small{font-size:11px;font-weight:normal;}
+.inputs-list .inputs-list{margin-left:25px;margin-bottom:10px;padding-top:0;}
+.inputs-list:first-child{padding-top:6px;}
+.inputs-list li+li{padding-top:2px;}
+.inputs-list input[type=radio],.inputs-list input[type=checkbox]{margin-bottom:0;margin-left:-20px;float:left;}
+.form-stacked{padding-left:20px;}.form-stacked fieldset{padding-top:9px;}
+.form-stacked legend{padding-left:0;}
+.form-stacked label{display:block;float:none;width:auto;font-weight:bold;text-align:left;line-height:20px;padding-top:0;}
+.form-stacked .clearfix{margin-bottom:9px;}.form-stacked .clearfix div.input{margin-left:0;}
+.form-stacked .inputs-list{margin-bottom:0;}.form-stacked .inputs-list li{padding-top:0;}.form-stacked .inputs-list li label{font-weight:normal;padding-top:0;}
+.form-stacked div.clearfix.error{padding-top:10px;padding-bottom:10px;padding-left:10px;margin-top:0;margin-left:-10px;}
+.form-stacked .actions{margin-left:-20px;padding-left:20px;}
+table{width:100%;margin-bottom:18px;padding:0;font-size:13px;border-collapse:collapse;}table th,table td{padding:10px 10px 9px;line-height:18px;text-align:left;}
+table th{padding-top:9px;font-weight:bold;vertical-align:middle;}
+table td{vertical-align:top;border-top:1px solid #ddd;}
+table tbody th{border-top:1px solid #ddd;vertical-align:top;}
+.condensed-table th,.condensed-table td{padding:5px 5px 4px;}
+.bordered-table{border:1px solid #ddd;border-collapse:separate;*border-collapse:collapse;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}.bordered-table th+th,.bordered-table td+td,.bordered-table th+td{border-left:1px solid #ddd;}
+.bordered-table thead tr:first-child th:first-child,.bordered-table tbody tr:first-child td:first-child{-webkit-border-radius:4px 0 0 0;-moz-border-radius:4px 0 0 0;border-radius:4px 0 0 0;}
+.bordered-table thead tr:first-child th:last-child,.bordered-table tbody tr:first-child td:last-child{-webkit-border-radius:0 4px 0 0;-moz-border-radius:0 4px 0 0;border-radius:0 4px 0 0;}
+.bordered-table tbody tr:last-child td:first-child{-webkit-border-radius:0 0 0 4px;-moz-border-radius:0 0 0 4px;border-radius:0 0 0 4px;}
+.bordered-table tbody tr:last-child td:last-child{-webkit-border-radius:0 0 4px 0;-moz-border-radius:0 0 4px 0;border-radius:0 0 4px 0;}
+table .span1{width:20px;}
+table .span2{width:60px;}
+table .span3{width:100px;}
+table .span4{width:140px;}
+table .span5{width:180px;}
+table .span6{width:220px;}
+table .span7{width:260px;}
+table .span8{width:300px;}
+table .span9{width:340px;}
+table .span10{width:380px;}
+table .span11{width:420px;}
+table .span12{width:460px;}
+table .span13{width:500px;}
+table .span14{width:540px;}
+table .span15{width:580px;}
+table .span16{width:620px;}
+.zebra-striped tbody tr:nth-child(odd) td,.zebra-striped tbody tr:nth-child(odd) th{background-color:#f9f9f9;}
+.zebra-striped tbody tr:hover td,.zebra-striped tbody tr:hover th{background-color:#f5f5f5;}
+table .header{cursor:pointer;}table .header:after{content:"";float:right;margin-top:7px;border-width:0 4px 4px;border-style:solid;border-color:#000 transparent;visibility:hidden;}
+table .headerSortUp,table .headerSortDown{background-color:rgba(141, 192, 219, 0.25);text-shadow:0 1px 1px rgba(255, 255, 255, 0.75);}
+table .header:hover:after{visibility:visible;}
+table .headerSortDown:after,table .headerSortDown:hover:after{visibility:visible;filter:alpha(opacity=60);-khtml-opacity:0.6;-moz-opacity:0.6;opacity:0.6;}
+table .headerSortUp:after{border-bottom:none;border-left:4px solid transparent;border-right:4px solid transparent;border-top:4px solid #000;visibility:visible;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;filter:alpha(opacity=60);-khtml-opacity:0.6;-moz-opacity:0.6;opacity:0.6;}
+table .blue{color:#049cdb;border-bottom-color:#049cdb;}
+table .headerSortUp.blue,table .headerSortDown.blue{background-color:#ade6fe;}
+table .green{color:#46a546;border-bottom-color:#46a546;}
+table .headerSortUp.green,table .headerSortDown.green{background-color:#cdeacd;}
+table .red{color:#9d261d;border-bottom-color:#9d261d;}
+table .headerSortUp.red,table .headerSortDown.red{background-color:#f4c8c5;}
+table .yellow{color:#ffc40d;border-bottom-color:#ffc40d;}
+table .headerSortUp.yellow,table .headerSortDown.yellow{background-color:#fff6d9;}
+table .orange{color:#f89406;border-bottom-color:#f89406;}
+table .headerSortUp.orange,table .headerSortDown.orange{background-color:#fee9cc;}
+table .purple{color:#7a43b6;border-bottom-color:#7a43b6;}
+table .headerSortUp.purple,table .headerSortDown.purple{background-color:#e2d5f0;}
+.topbar{height:40px;position:fixed;top:0;left:0;right:0;z-index:10000;overflow:visible;}.topbar a{color:#bfbfbf;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);}
+.topbar h3 a:hover,.topbar .brand:hover,.topbar ul .active>a{background-color:#333;background-color:rgba(255, 255, 255, 0.05);color:#ffffff;text-decoration:none;}
+.topbar h3{position:relative;}
+.topbar h3 a,.topbar .brand{float:left;display:block;padding:8px 20px 12px;margin-left:-20px;color:#ffffff;font-size:20px;font-weight:200;line-height:1;}
+.topbar p{margin:0;line-height:40px;}.topbar p a:hover{background-color:transparent;color:#ffffff;}
+.topbar form{float:left;margin:5px 0 0 0;position:relative;filter:alpha(opacity=100);-khtml-opacity:1;-moz-opacity:1;opacity:1;}
+.topbar form.pull-right{float:right;}
+.topbar input{background-color:#444;background-color:rgba(255, 255, 255, 0.3);font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:normal;font-weight:13px;line-height:1;padding:4px 9px;color:#ffffff;color:rgba(255, 255, 255, 0.75);border:1px solid #111;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1),0 1px 0px rgba(255, 255, 255, 0.25);-moz-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1),0 1px 0px rgba(255, 255, 255, 0.25);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1),0 1px 0px rgba(255, 255, 255, 0.25);-webkit-transform-style:preserve-3d;-webkit-transition:none;-moz-transition:none;-ms-transition:none;-o-transition:none;transition:none;}.topbar input:-moz-placeholder{color:#e6e6e6;}
+.topbar input::-webkit-input-placeholder{color:#e6e6e6;}
+.topbar input:hover{background-color:#bfbfbf;background-color:rgba(255, 255, 255, 0.5);color:#ffffff;}
+.topbar input:focus,.topbar input.focused{outline:0;background-color:#ffffff;color:#404040;text-shadow:0 1px 0 #ffffff;border:0;padding:5px 10px;-webkit-box-shadow:0 0 3px rgba(0, 0, 0, 0.15);-moz-box-shadow:0 0 3px rgba(0, 0, 0, 0.15);box-shadow:0 0 3px rgba(0, 0, 0, 0.15);}
+.topbar-inner,.topbar .fill{background-color:#222;background-color:#222222;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#333333), to(#222222));background-image:-moz-linear-gradient(top, #333333, #222222);background-image:-ms-linear-gradient(top, #333333, #222222);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #333333), color-stop(100%, #222222));background-image:-webkit-linear-gradient(top, #333333, #222222);background-image:-o-linear-gradient(top, #333333, #222222);background-image:linear-gradient(top, #333333, #222222);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#333333', endColorstr='#222222', GradientType=0);-webkit-box-shadow:0 1px 3px rgba(0, 0, 0, 0.25),inset 0 -1px 0 rgba(0, 0, 0, 0.1);-moz-box-shadow:0 1px 3px rgba(0, 0, 0, 0.25),inset 0 -1px 0 rgba(0, 0, 0, 0.1);box-shadow:0 1px 3px rgba(0, 0, 0, 0.25),inset 0 -1px 0 rgba(0, 0, 0, 0.1);}
+.topbar div>ul,.nav{display:block;float:left;margin:0 10px 0 0;position:relative;left:0;}.topbar div>ul>li,.nav>li{display:block;float:left;}
+.topbar div>ul a,.nav a{display:block;float:none;padding:10px 10px 11px;line-height:19px;text-decoration:none;}.topbar div>ul a:hover,.nav a:hover{color:#ffffff;text-decoration:none;}
+.topbar div>ul .active>a,.nav .active>a{background-color:#222;background-color:rgba(0, 0, 0, 0.5);}
+.topbar div>ul.secondary-nav,.nav.secondary-nav{float:right;margin-left:10px;margin-right:0;}.topbar div>ul.secondary-nav .menu-dropdown,.nav.secondary-nav .menu-dropdown,.topbar div>ul.secondary-nav .dropdown-menu,.nav.secondary-nav .dropdown-menu{right:0;border:0;}
+.topbar div>ul a.menu:hover,.nav a.menu:hover,.topbar div>ul li.open .menu,.nav li.open .menu,.topbar div>ul .dropdown-toggle:hover,.nav .dropdown-toggle:hover,.topbar div>ul .dropdown.open .dropdown-toggle,.nav .dropdown.open .dropdown-toggle{background:#444;background:rgba(255, 255, 255, 0.05);}
+.topbar div>ul .menu-dropdown,.nav .menu-dropdown,.topbar div>ul .dropdown-menu,.nav .dropdown-menu{background-color:#333;}.topbar div>ul .menu-dropdown a.menu,.nav .menu-dropdown a.menu,.topbar div>ul .dropdown-menu a.menu,.nav .dropdown-menu a.menu,.topbar div>ul .menu-dropdown .dropdown-toggle,.nav .menu-dropdown .dropdown-toggle,.topbar div>ul .dropdown-menu .dropdown-toggle,.nav .dropdown-menu .dropdown-toggle{color:#ffffff;}.topbar div>ul .menu-dropdown a.menu.open,.nav .menu-dropdown a.menu.open,.topbar div>ul .dropdown-menu a.menu.open,.nav .dropdown-menu a.menu.open,.topbar div>ul .menu-dropdown .dropdown-toggle.open,.nav .menu-dropdown .dropdown-toggle.open,.topbar div>ul .dropdown-menu .dropdown-toggle.open,.nav .dropdown-menu .dropdown-toggle.open{background:#444;background:rgba(255, 255, 255, 0.05);}
+.topbar div>ul .menu-dropdown li a,.nav .menu-dropdown li a,.topbar div>ul .dropdown-menu li a,.nav .dropdown-menu li a{color:#999;text-shadow:0 1px 0 rgba(0, 0, 0, 0.5);}.topbar div>ul .menu-dropdown li a:hover,.nav .menu-dropdown li a:hover,.topbar div>ul .dropdown-menu li a:hover,.nav .dropdown-menu li a:hover{background-color:#191919;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#292929), to(#191919));background-image:-moz-linear-gradient(top, #292929, #191919);background-image:-ms-linear-gradient(top, #292929, #191919);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #292929), color-stop(100%, #191919));background-image:-webkit-linear-gradient(top, #292929, #191919);background-image:-o-linear-gradient(top, #292929, #191919);background-image:linear-gradient(top, #292929, #191919);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#292929', endColorstr='#191919', GradientType=0);color:#ffffff;}
+.topbar div>ul .menu-dropdown .active a,.nav .menu-dropdown .active a,.topbar div>ul .dropdown-menu .active a,.nav .dropdown-menu .active a{color:#ffffff;}
+.topbar div>ul .menu-dropdown .divider,.nav .menu-dropdown .divider,.topbar div>ul .dropdown-menu .divider,.nav .dropdown-menu .divider{background-color:#222;border-color:#444;}
+.topbar ul .menu-dropdown li a,.topbar ul .dropdown-menu li a{padding:4px 15px;}
+li.menu,.dropdown{position:relative;}
+a.menu:after,.dropdown-toggle:after{width:0;height:0;display:inline-block;content:"&darr;";text-indent:-99999px;vertical-align:top;margin-top:8px;margin-left:4px;border-left:4px solid transparent;border-right:4px solid transparent;border-top:4px solid #ffffff;filter:alpha(opacity=50);-khtml-opacity:0.5;-moz-opacity:0.5;opacity:0.5;}
+.menu-dropdown,.dropdown-menu{background-color:#ffffff;float:left;display:none;position:absolute;top:40px;z-index:900;min-width:160px;max-width:220px;_width:160px;margin-left:0;margin-right:0;padding:6px 0;zoom:1;border-color:#999;border-color:rgba(0, 0, 0, 0.2);border-style:solid;border-width:0 1px 1px;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;-webkit-box-shadow:0 2px 4px rgba(0, 0, 0, 0.2);-moz-box-shadow:0 2px 4px rgba(0, 0, 0, 0.2);box-shadow:0 2px 4px rgba(0, 0, 0, 0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box;}.menu-dropdown li,.dropdown-menu li{float:none;display:block;background-color:none;}
+.menu-dropdown .divider,.dropdown-menu .divider{height:1px;margin:5px 0;overflow:hidden;background-color:#eee;border-bottom:1px solid #ffffff;}
+.topbar .dropdown-menu a,.dropdown-menu a{display:block;padding:4px 15px;clear:both;font-weight:normal;line-height:18px;color:#808080;text-shadow:0 1px 0 #ffffff;}.topbar .dropdown-menu a:hover,.dropdown-menu a:hover,.topbar .dropdown-menu a.hover,.dropdown-menu a.hover{background-color:#dddddd;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#eeeeee), to(#dddddd));background-image:-moz-linear-gradient(top, #eeeeee, #dddddd);background-image:-ms-linear-gradient(top, #eeeeee, #dddddd);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #eeeeee), color-stop(100%, #dddddd));background-image:-webkit-linear-gradient(top, #eeeeee, #dddddd);background-image:-o-linear-gradient(top, #eeeeee, #dddddd);background-image:linear-gradient(top, #eeeeee, #dddddd);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#eeeeee', endColorstr='#dddddd', GradientType=0);color:#404040;text-decoration:none;-webkit-box-shadow:inset 0 1px 0 rgba(0, 0, 0, 0.025),inset 0 -1px rgba(0, 0, 0, 0.025);-moz-box-shadow:inset 0 1px 0 rgba(0, 0, 0, 0.025),inset 0 -1px rgba(0, 0, 0, 0.025);box-shadow:inset 0 1px 0 rgba(0, 0, 0, 0.025),inset 0 -1px rgba(0, 0, 0, 0.025);}
+.open .menu,.dropdown.open .menu,.open .dropdown-toggle,.dropdown.open .dropdown-toggle{color:#ffffff;background:#ccc;background:rgba(0, 0, 0, 0.3);}
+.open .menu-dropdown,.dropdown.open .menu-dropdown,.open .dropdown-menu,.dropdown.open .dropdown-menu{display:block;}
+.tabs,.pills{margin:0 0 18px;padding:0;list-style:none;zoom:1;}.tabs:before,.pills:before,.tabs:after,.pills:after{display:table;content:"";zoom:1;}
+.tabs:after,.pills:after{clear:both;}
+.tabs>li,.pills>li{float:left;}.tabs>li>a,.pills>li>a{display:block;}
+.tabs{border-color:#ddd;border-style:solid;border-width:0 0 1px;}.tabs>li{position:relative;margin-bottom:-1px;}.tabs>li>a{padding:0 15px;margin-right:2px;line-height:34px;border:1px solid transparent;-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0;}.tabs>li>a:hover{text-decoration:none;background-color:#eee;border-color:#eee #eee #ddd;}
+.tabs .active>a,.tabs .active>a:hover{color:#808080;background-color:#ffffff;border:1px solid #ddd;border-bottom-color:transparent;cursor:default;}
+.tabs .menu-dropdown,.tabs .dropdown-menu{top:35px;border-width:1px;-webkit-border-radius:0 6px 6px 6px;-moz-border-radius:0 6px 6px 6px;border-radius:0 6px 6px 6px;}
+.tabs a.menu:after,.tabs .dropdown-toggle:after{border-top-color:#999;margin-top:15px;margin-left:5px;}
+.tabs li.open.menu .menu,.tabs .open.dropdown .dropdown-toggle{border-color:#999;}
+.tabs li.open a.menu:after,.tabs .dropdown.open .dropdown-toggle:after{border-top-color:#555;}
+.pills a{margin:5px 3px 5px 0;padding:0 15px;line-height:30px;text-shadow:0 1px 1px #ffffff;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px;}.pills a:hover{color:#ffffff;text-decoration:none;text-shadow:0 1px 1px rgba(0, 0, 0, 0.25);background-color:#00438a;}
+.pills .active a{color:#ffffff;text-shadow:0 1px 1px rgba(0, 0, 0, 0.25);background-color:#0069d6;}
+.pills-vertical>li{float:none;}
+.tab-content>.tab-pane,.pill-content>.pill-pane{display:none;}
+.tab-content>.active,.pill-content>.active{display:block;}
+.breadcrumb{padding:7px 14px;margin:0 0 18px;background-color:#f5f5f5;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#ffffff), to(#f5f5f5));background-image:-moz-linear-gradient(top, #ffffff, #f5f5f5);background-image:-ms-linear-gradient(top, #ffffff, #f5f5f5);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #ffffff), color-stop(100%, #f5f5f5));background-image:-webkit-linear-gradient(top, #ffffff, #f5f5f5);background-image:-o-linear-gradient(top, #ffffff, #f5f5f5);background-image:linear-gradient(top, #ffffff, #f5f5f5);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#f5f5f5', GradientType=0);border:1px solid #ddd;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:inset 0 1px 0 #ffffff;-moz-box-shadow:inset 0 1px 0 #ffffff;box-shadow:inset 0 1px 0 #ffffff;}.breadcrumb li{display:inline;text-shadow:0 1px 0 #ffffff;}
+.breadcrumb .divider{padding:0 5px;color:#bfbfbf;}
+.breadcrumb .active a{color:#404040;}
+.hero-unit{background-color:#f5f5f5;margin-bottom:30px;padding:60px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;}.hero-unit h1{margin-bottom:0;font-size:60px;line-height:1;letter-spacing:-1px;}
+.hero-unit p{font-size:18px;font-weight:200;line-height:27px;}
+footer{margin-top:17px;padding-top:17px;border-top:1px solid #eee;}
+.page-header{margin-bottom:17px;border-bottom:1px solid #ddd;-webkit-box-shadow:0 1px 0 rgba(255, 255, 255, 0.5);-moz-box-shadow:0 1px 0 rgba(255, 255, 255, 0.5);box-shadow:0 1px 0 rgba(255, 255, 255, 0.5);}.page-header h1{margin-bottom:8px;}
+.btn.danger,.alert-message.danger,.btn.danger:hover,.alert-message.danger:hover,.btn.error,.alert-message.error,.btn.error:hover,.alert-message.error:hover,.btn.success,.alert-message.success,.btn.success:hover,.alert-message.success:hover,.btn.info,.alert-message.info,.btn.info:hover,.alert-message.info:hover{color:#ffffff;}
+.btn .close,.alert-message .close{font-family:Arial,sans-serif;line-height:18px;}
+.btn.danger,.alert-message.danger,.btn.error,.alert-message.error{background-color:#c43c35;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#ee5f5b), to(#c43c35));background-image:-moz-linear-gradient(top, #ee5f5b, #c43c35);background-image:-ms-linear-gradient(top, #ee5f5b, #c43c35);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #ee5f5b), color-stop(100%, #c43c35));background-image:-webkit-linear-gradient(top, #ee5f5b, #c43c35);background-image:-o-linear-gradient(top, #ee5f5b, #c43c35);background-image:linear-gradient(top, #ee5f5b, #c43c35);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ee5f5b', endColorstr='#c43c35', GradientType=0);text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);border-color:#c43c35 #c43c35 #882a25;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);}
+.btn.success,.alert-message.success{background-color:#57a957;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#62c462), to(#57a957));background-image:-moz-linear-gradient(top, #62c462, #57a957);background-image:-ms-linear-gradient(top, #62c462, #57a957);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #62c462), color-stop(100%, #57a957));background-image:-webkit-linear-gradient(top, #62c462, #57a957);background-image:-o-linear-gradient(top, #62c462, #57a957);background-image:linear-gradient(top, #62c462, #57a957);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#62c462', endColorstr='#57a957', GradientType=0);text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);border-color:#57a957 #57a957 #3d773d;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);}
+.btn.info,.alert-message.info{background-color:#339bb9;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#5bc0de), to(#339bb9));background-image:-moz-linear-gradient(top, #5bc0de, #339bb9);background-image:-ms-linear-gradient(top, #5bc0de, #339bb9);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #5bc0de), color-stop(100%, #339bb9));background-image:-webkit-linear-gradient(top, #5bc0de, #339bb9);background-image:-o-linear-gradient(top, #5bc0de, #339bb9);background-image:linear-gradient(top, #5bc0de, #339bb9);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#5bc0de', endColorstr='#339bb9', GradientType=0);text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);border-color:#339bb9 #339bb9 #22697d;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);}
+.btn{cursor:pointer;display:inline-block;background-color:#e6e6e6;background-repeat:no-repeat;background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), color-stop(25%, #ffffff), to(#e6e6e6));background-image:-webkit-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6);background-image:-moz-linear-gradient(top, #ffffff, #ffffff 25%, #e6e6e6);background-image:-ms-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6);background-image:-o-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6);background-image:linear-gradient(#ffffff, #ffffff 25%, #e6e6e6);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#e6e6e6', GradientType=0);padding:5px 14px 6px;text-shadow:0 1px 1px rgba(255, 255, 255, 0.75);color:#333;font-size:13px;line-height:normal;border:1px solid #ccc;border-bottom-color:#bbb;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);-webkit-transform-style:preserve-3d;-webkit-transition:0.1s linear all;-moz-transition:0.1s linear all;-ms-transition:0.1s linear all;-o-transition:0.1s linear all;transition:0.1s linear all;}.btn:hover{background-position:0 -15px;color:#333;text-decoration:none;}
+.btn:focus{outline:1px dotted #666;}
+.btn.primary{color:#ffffff;background-color:#0064cd;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#049cdb), to(#0064cd));background-image:-moz-linear-gradient(top, #049cdb, #0064cd);background-image:-ms-linear-gradient(top, #049cdb, #0064cd);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #049cdb), color-stop(100%, #0064cd));background-image:-webkit-linear-gradient(top, #049cdb, #0064cd);background-image:-o-linear-gradient(top, #049cdb, #0064cd);background-image:linear-gradient(top, #049cdb, #0064cd);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#049cdb', endColorstr='#0064cd', GradientType=0);text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);border-color:#0064cd #0064cd #003f81;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);}
+.btn.active,.btn :active{-webkit-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.25),0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.25),0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.25),0 1px 2px rgba(0, 0, 0, 0.05);}
+.btn.disabled{cursor:default;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=65);-khtml-opacity:0.65;-moz-opacity:0.65;opacity:0.65;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;}
+.btn[disabled]{cursor:default;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=65);-khtml-opacity:0.65;-moz-opacity:0.65;opacity:0.65;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;}
+.btn.large{font-size:15px;line-height:normal;padding:9px 14px 9px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;}
+.btn.small{padding:7px 9px 7px;font-size:11px;}
+:root .alert-message,:root .btn{border-radius:0 \0;}
+button.btn::-moz-focus-inner,input[type=submit].btn::-moz-focus-inner{padding:0;border:0;}
+.close{float:right;color:#000000;font-size:20px;font-weight:bold;line-height:13.5px;text-shadow:0 1px 0 #ffffff;filter:alpha(opacity=25);-khtml-opacity:0.25;-moz-opacity:0.25;opacity:0.25;}.close:hover{color:#000000;text-decoration:none;filter:alpha(opacity=40);-khtml-opacity:0.4;-moz-opacity:0.4;opacity:0.4;}
+.alert-message{position:relative;padding:7px 15px;margin-bottom:18px;color:#404040;background-color:#eedc94;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#fceec1), to(#eedc94));background-image:-moz-linear-gradient(top, #fceec1, #eedc94);background-image:-ms-linear-gradient(top, #fceec1, #eedc94);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #fceec1), color-stop(100%, #eedc94));background-image:-webkit-linear-gradient(top, #fceec1, #eedc94);background-image:-o-linear-gradient(top, #fceec1, #eedc94);background-image:linear-gradient(top, #fceec1, #eedc94);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fceec1', endColorstr='#eedc94', GradientType=0);text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);border-color:#eedc94 #eedc94 #e4c652;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);border-width:1px;border-style:solid;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.25);-moz-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.25);box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.25);}.alert-message .close{margin-top:1px;*margin-top:0;}
+.alert-message a{font-weight:bold;color:#404040;}
+.alert-message.danger p a,.alert-message.error p a,.alert-message.success p a,.alert-message.info p a{color:#ffffff;}
+.alert-message h5{line-height:18px;}
+.alert-message p{margin-bottom:0;}
+.alert-message div{margin-top:5px;margin-bottom:2px;line-height:28px;}
+.alert-message .btn{-webkit-box-shadow:0 1px 0 rgba(255, 255, 255, 0.25);-moz-box-shadow:0 1px 0 rgba(255, 255, 255, 0.25);box-shadow:0 1px 0 rgba(255, 255, 255, 0.25);}
+.alert-message.block-message{background-image:none;background-color:#fdf5d9;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);padding:14px;border-color:#fceec1;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;}.alert-message.block-message ul,.alert-message.block-message p{margin-right:30px;}
+.alert-message.block-message ul{margin-bottom:0;}
+.alert-message.block-message li{color:#404040;}
+.alert-message.block-message .alert-actions{margin-top:5px;}
+.alert-message.block-message.error,.alert-message.block-message.success,.alert-message.block-message.info{color:#404040;text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);}
+.alert-message.block-message.error{background-color:#fddfde;border-color:#fbc7c6;}
+.alert-message.block-message.success{background-color:#d1eed1;border-color:#bfe7bf;}
+.alert-message.block-message.info{background-color:#ddf4fb;border-color:#c6edf9;}
+.alert-message.block-message.danger p a,.alert-message.block-message.error p a,.alert-message.block-message.success p a,.alert-message.block-message.info p a{color:#404040;}
+.pagination{height:36px;margin:18px 0;}.pagination ul{float:left;margin:0;border:1px solid #ddd;border:1px solid rgba(0, 0, 0, 0.15);-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);}
+.pagination li{display:inline;}
+.pagination a{float:left;padding:0 14px;line-height:34px;border-right:1px solid;border-right-color:#ddd;border-right-color:rgba(0, 0, 0, 0.15);*border-right-color:#ddd;text-decoration:none;}
+.pagination a:hover,.pagination .active a{background-color:#c7eefe;}
+.pagination .disabled a,.pagination .disabled a:hover{background-color:transparent;color:#bfbfbf;}
+.pagination .next a{border:0;}
+.well{background-color:#f5f5f5;margin-bottom:20px;padding:19px;min-height:20px;border:1px solid #eee;border:1px solid rgba(0, 0, 0, 0.05);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);}.well blockquote{border-color:#ddd;border-color:rgba(0, 0, 0, 0.15);}
+.modal-backdrop{background-color:#000000;position:fixed;top:0;left:0;right:0;bottom:0;z-index:10000;}.modal-backdrop.fade{opacity:0;}
+.modal-backdrop,.modal-backdrop.fade.in{filter:alpha(opacity=80);-khtml-opacity:0.8;-moz-opacity:0.8;opacity:0.8;}
+.modal{position:fixed;top:50%;left:50%;z-index:11000;width:560px;margin:-250px 0 0 -280px;background-color:#ffffff;border:1px solid #999;border:1px solid rgba(0, 0, 0, 0.3);*border:1px solid #999;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-moz-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box;}.modal .close{margin-top:7px;}
+.modal.fade{-webkit-transform-style:preserve-3d;-webkit-transition:opacity .3s linear, top .3s ease-out;-moz-transition:opacity .3s linear, top .3s ease-out;-ms-transition:opacity .3s linear, top .3s ease-out;-o-transition:opacity .3s linear, top .3s ease-out;transition:opacity .3s linear, top .3s ease-out;top:-25%;}
+.modal.fade.in{top:50%;}
+.modal-header{border-bottom:1px solid #eee;padding:5px 15px;}
+.modal-body{padding:15px;}
+.modal-body form{margin-bottom:0;}
+.modal-footer{background-color:#f5f5f5;padding:14px 15px 15px;border-top:1px solid #ddd;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;-webkit-box-shadow:inset 0 1px 0 #ffffff;-moz-box-shadow:inset 0 1px 0 #ffffff;box-shadow:inset 0 1px 0 #ffffff;zoom:1;margin-bottom:0;}.modal-footer:before,.modal-footer:after{display:table;content:"";zoom:1;}
+.modal-footer:after{clear:both;}
+.modal-footer .btn{float:right;margin-left:5px;}
+.modal .popover,.modal .twipsy{z-index:12000;}
+.twipsy{display:block;position:absolute;visibility:visible;padding:5px;font-size:11px;z-index:1000;filter:alpha(opacity=80);-khtml-opacity:0.8;-moz-opacity:0.8;opacity:0.8;}.twipsy.fade.in{filter:alpha(opacity=80);-khtml-opacity:0.8;-moz-opacity:0.8;opacity:0.8;}
+.twipsy.above .twipsy-arrow{bottom:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-top:5px solid #000000;}
+.twipsy.left .twipsy-arrow{top:50%;right:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-left:5px solid #000000;}
+.twipsy.below .twipsy-arrow{top:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-bottom:5px solid #000000;}
+.twipsy.right .twipsy-arrow{top:50%;left:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-right:5px solid #000000;}
+.twipsy-inner{padding:3px 8px;background-color:#000000;color:white;text-align:center;max-width:200px;text-decoration:none;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}
+.twipsy-arrow{position:absolute;width:0;height:0;}
+.popover{position:absolute;top:0;left:0;z-index:1000;padding:5px;display:none;}.popover.above .arrow{bottom:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-top:5px solid #000000;}
+.popover.right .arrow{top:50%;left:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-right:5px solid #000000;}
+.popover.below .arrow{top:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-bottom:5px solid #000000;}
+.popover.left .arrow{top:50%;right:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-left:5px solid #000000;}
+.popover .arrow{position:absolute;width:0;height:0;}
+.popover .inner{background:#000000;background:rgba(0, 0, 0, 0.8);padding:3px;overflow:hidden;width:280px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-moz-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);}
+.popover .title{background-color:#f5f5f5;padding:9px 15px;line-height:1;-webkit-border-radius:3px 3px 0 0;-moz-border-radius:3px 3px 0 0;border-radius:3px 3px 0 0;border-bottom:1px solid #eee;}
+.popover .content{background-color:#ffffff;padding:14px;-webkit-border-radius:0 0 3px 3px;-moz-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px;-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box;}.popover .content p,.popover .content ul,.popover .content ol{margin-bottom:0;}
+.fade{-webkit-transform-style:preserve-3d;-webkit-transition:opacity 0.15s linear;-moz-transition:opacity 0.15s linear;-ms-transition:opacity 0.15s linear;-o-transition:opacity 0.15s linear;transition:opacity 0.15s linear;opacity:0;}.fade.in{opacity:1;}
+.label{padding:1px 3px 2px;font-size:9.75px;font-weight:bold;color:#ffffff;text-transform:uppercase;white-space:nowrap;background-color:#bfbfbf;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;}.label.important{background-color:#c43c35;}
+.label.warning{background-color:#f89406;}
+.label.success{background-color:#46a546;}
+.label.notice{background-color:#62cffc;}
+.media-grid{margin-left:-20px;margin-bottom:0;zoom:1;}.media-grid:before,.media-grid:after{display:table;content:"";zoom:1;}
+.media-grid:after{clear:both;}
+.media-grid li{display:inline;}
+.media-grid a{float:left;padding:4px;margin:0 0 18px 20px;border:1px solid #ddd;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow:0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:0 1px 1px rgba(0, 0, 0, 0.075);}.media-grid a img{display:block;}
+.media-grid a:hover{border-color:#0069d6;-webkit-box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);-moz-box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);}
diff --git a/doc/css/killbill.css b/doc/css/killbill.css
new file mode 100644
index 0000000..cf8ccee
--- /dev/null
+++ b/doc/css/killbill.css
@@ -0,0 +1,3 @@
+body {
+    padding-top: 60px;
+}
\ No newline at end of file

doc/css/prettify.css 118(+118 -0)

diff --git a/doc/css/prettify.css b/doc/css/prettify.css
new file mode 100644
index 0000000..3b38616
--- /dev/null
+++ b/doc/css/prettify.css
@@ -0,0 +1,118 @@
+
+/*
+ * Derived from einaros's Sons of Obsidian theme at
+ * http://studiostyl.es/schemes/son-of-obsidian by
+ * Alex Ford of CodeTunnel:
+ * http://CodeTunnel.com/blog/post/71/google-code-prettify-obsidian-theme
+ */
+
+.str
+{
+    color: #EC7600;
+}
+.kwd
+{
+    color: #93C763;
+}
+.com
+{
+    color: #66747B;
+}
+.typ
+{
+    color: #678CB1;
+}
+.lit
+{
+    color: #FACD22;
+}
+.pun
+{
+    color: #F1F2F3;
+}
+.pln
+{
+    color: #F1F2F3;
+}
+.tag
+{
+    color: #8AC763;
+}
+.atn
+{
+    color: #E0E2E4;
+}
+.atv
+{
+    color: #EC7600;
+}
+.dec
+{
+    color: purple;
+}
+pre.prettyprint
+{
+    border: 0px solid #888;
+}
+ol.linenums
+{
+    margin-top: 0;
+    margin-bottom: 0;
+}
+.prettyprint {
+    background: #000;
+}
+li.L0, li.L1, li.L2, li.L3, li.L4, li.L5, li.L6, li.L7, li.L8, li.L9
+{
+    color: #555;
+}
+li.L1, li.L3, li.L5, li.L7, li.L9 {
+    background: #111;
+}
+@media print
+{
+    .str
+    {
+        color: #060;
+    }
+    .kwd
+    {
+        color: #006;
+        font-weight: bold;
+    }
+    .com
+    {
+        color: #600;
+        font-style: italic;
+    }
+    .typ
+    {
+        color: #404;
+        font-weight: bold;
+    }
+    .lit
+    {
+        color: #044;
+    }
+    .pun
+    {
+        color: #440;
+    }
+    .pln
+    {
+        color: #000;
+    }
+    .tag
+    {
+        color: #006;
+        font-weight: bold;
+    }
+    .atn
+    {
+        color: #404;
+    }
+    .atv
+    {
+        color: #060;
+    }
+}
\ No newline at end of file

doc/design.html 100(+100 -0)

diff --git a/doc/design.html b/doc/design.html
new file mode 100644
index 0000000..0ff23be
--- /dev/null
+++ b/doc/design.html
@@ -0,0 +1,100 @@
+<!-- ~ Copyright 2010-2011 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. -->
+
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="utf-8">
+    <title>Collector core | Dwarf</title>
+    <meta name="user guide" content="">
+    <meta name="author" content="Stephane Brossier">
+
+    <!--[if lt IE 9]>
+    <script src="http://html5shim.googlecode.com/svn/trunk/html5.js" type="text/javascript"></script>
+    <![endif]-->
+
+    <link rel=stylesheet type="text/css" href="css/bootstrap.min.css">
+    <link rel=stylesheet type="text/css" href="css/prettify.css">
+    <link rel=stylesheet type="text/css" href="css/killbill.css">
+    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
+    <script src="js/prettify.js"></script>
+    <script src="js/bootstrap-dropdown.js"></script>
+
+    <script type="text/javascript">
+      var _gaq = _gaq || [];
+      _gaq.push(['_setAccount', 'UA-19297396-1']);
+      _gaq.push(['_trackPageview']);
+      (function() {
+        var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
+        ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
+        var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
+      })();
+    </script>
+</head>
+
+<body onload="prettyPrint();">
+
+    <div class="topbar" data-dropdown="dropdown">
+        <div class="topbar-inner">
+            <div class="container-fluid">
+                <a class="brand" href="../index.html">Killbill</a>
+                <ul class="nav">
+                    <li><a href="user.html">User Guide</a></li>
+                    <li><a href="setup.html">Setup/Installation</a></li>
+                    <li><a href="api.html">APIs</a></li>
+                    <li class="active"><a href="user.html">Setup/Installation</a></li>
+                    <li><a href="http://groups.google.com/group/killbill-users">Mailing List</a></li>
+                </ul>
+                <ul class="nav secondary-nav">
+                    <li class="dropdown">
+                        <a href="#" class="dropdown-toggle">Maven sites</a>
+                        <ul class="dropdown-menu">
+                            <li><a href="http://sbrossie.github.com/killbill" target="_blank">Killbill</a></li>
+                        </ul>
+                    </li>
+                </ul>
+                <ul class="nav secondary-nav">
+                    <li class="dropdown">
+                        <a href="#" class="dropdown-toggle">Github projects</a>
+                        <ul class="dropdown-menu">
+                            <li><a href="http://github.com/ning/killbill" target="_blank">Killbill</a></li>
+                        </ul>
+                    </li>
+                </ul>
+            </div>
+        </div>
+    </div>
+    <div class="container-fluid">
+        <div class="sidebar">
+            <div class="well">
+                <h5>Setup</h5>
+                <ul>
+                    <li><a href="#modularity">Modularity</a></li>                    
+                    <li><a href="#big-picture">Big Picture</a></li>
+                    <li><a href="#lifecycle">Lifecycle</a></li>
+                    <li><a href="#event-bus">Event Bus</a></li>                    
+                    <li><a href="#entitlement">Entitlement</a></li>
+                </ul>
+            </div>
+        </div>
+        <div class="content">
+            <h2 class="modularity">Modularity</h2>
+                Bla bla 
+            <h2 class="big-picture">Big Picture</h2>
+                Bla bla  
+            <h2 class="lifecycle">Lifecycle</h2>
+                Bla bla 
+            <h2 class="event-bus">Event Bus</h2>
+                Bla bla  
+            <h2 class="entitlement">Entitlement</h2>
+                Bla bla  
+        </div>
+    </div>
+</body>
+ 
diff --git a/doc/js/bootstrap-dropdown.js b/doc/js/bootstrap-dropdown.js
new file mode 100644
index 0000000..fda6da5
--- /dev/null
+++ b/doc/js/bootstrap-dropdown.js
@@ -0,0 +1,55 @@
+/* ============================================================
+ * bootstrap-dropdown.js v1.4.0
+ * http://twitter.github.com/bootstrap/javascript.html#dropdown
+ * ============================================================
+ * Copyright 2011 Twitter, Inc.
+ *
+ * Licensed 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.
+ * ============================================================ */
+
+
+!function( $ ){
+
+  "use strict"
+
+  /* DROPDOWN PLUGIN DEFINITION
+   * ========================== */
+
+  $.fn.dropdown = function ( selector ) {
+    return this.each(function () {
+      $(this).delegate(selector || d, 'click', function (e) {
+        var li = $(this).parent('li')
+          , isActive = li.hasClass('open')
+
+        clearMenus()
+        !isActive && li.toggleClass('open')
+        return false
+      })
+    })
+  }
+
+  /* APPLY TO STANDARD DROPDOWN ELEMENTS
+   * =================================== */
+
+  var d = 'a.menu, .dropdown-toggle'
+
+  function clearMenus() {
+    $(d).parent('li').removeClass('open')
+  }
+
+  $(function () {
+    $('html').bind("click", clearMenus)
+    $('body').dropdown( '[data-dropdown] a.menu, [data-dropdown] .dropdown-toggle' )
+  })
+
+}( window.jQuery || window.ender );

doc/js/prettify.js 28(+28 -0)

diff --git a/doc/js/prettify.js b/doc/js/prettify.js
new file mode 100644
index 0000000..eef5ad7
--- /dev/null
+++ b/doc/js/prettify.js
@@ -0,0 +1,28 @@
+var q=null;window.PR_SHOULD_USE_CONTINUATION=!0;
+(function(){function L(a){function m(a){var f=a.charCodeAt(0);if(f!==92)return f;var b=a.charAt(1);return(f=r[b])?f:"0"<=b&&b<="7"?parseInt(a.substring(1),8):b==="u"||b==="x"?parseInt(a.substring(2),16):a.charCodeAt(1)}function e(a){if(a<32)return(a<16?"\\x0":"\\x")+a.toString(16);a=String.fromCharCode(a);if(a==="\\"||a==="-"||a==="["||a==="]")a="\\"+a;return a}function h(a){for(var f=a.substring(1,a.length-1).match(/\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\[0-3][0-7]{0,2}|\\[0-7]{1,2}|\\[\S\s]|[^\\]/g),a=
+[],b=[],o=f[0]==="^",c=o?1:0,i=f.length;c<i;++c){var j=f[c];if(/\\[bdsw]/i.test(j))a.push(j);else{var j=m(j),d;c+2<i&&"-"===f[c+1]?(d=m(f[c+2]),c+=2):d=j;b.push([j,d]);d<65||j>122||(d<65||j>90||b.push([Math.max(65,j)|32,Math.min(d,90)|32]),d<97||j>122||b.push([Math.max(97,j)&-33,Math.min(d,122)&-33]))}}b.sort(function(a,f){return a[0]-f[0]||f[1]-a[1]});f=[];j=[NaN,NaN];for(c=0;c<b.length;++c)i=b[c],i[0]<=j[1]+1?j[1]=Math.max(j[1],i[1]):f.push(j=i);b=["["];o&&b.push("^");b.push.apply(b,a);for(c=0;c<
+f.length;++c)i=f[c],b.push(e(i[0])),i[1]>i[0]&&(i[1]+1>i[0]&&b.push("-"),b.push(e(i[1])));b.push("]");return b.join("")}function y(a){for(var f=a.source.match(/\[(?:[^\\\]]|\\[\S\s])*]|\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\\d+|\\[^\dux]|\(\?[!:=]|[()^]|[^()[\\^]+/g),b=f.length,d=[],c=0,i=0;c<b;++c){var j=f[c];j==="("?++i:"\\"===j.charAt(0)&&(j=+j.substring(1))&&j<=i&&(d[j]=-1)}for(c=1;c<d.length;++c)-1===d[c]&&(d[c]=++t);for(i=c=0;c<b;++c)j=f[c],j==="("?(++i,d[i]===void 0&&(f[c]="(?:")):"\\"===j.charAt(0)&&
+(j=+j.substring(1))&&j<=i&&(f[c]="\\"+d[i]);for(i=c=0;c<b;++c)"^"===f[c]&&"^"!==f[c+1]&&(f[c]="");if(a.ignoreCase&&s)for(c=0;c<b;++c)j=f[c],a=j.charAt(0),j.length>=2&&a==="["?f[c]=h(j):a!=="\\"&&(f[c]=j.replace(/[A-Za-z]/g,function(a){a=a.charCodeAt(0);return"["+String.fromCharCode(a&-33,a|32)+"]"}));return f.join("")}for(var t=0,s=!1,l=!1,p=0,d=a.length;p<d;++p){var g=a[p];if(g.ignoreCase)l=!0;else if(/[a-z]/i.test(g.source.replace(/\\u[\da-f]{4}|\\x[\da-f]{2}|\\[^UXux]/gi,""))){s=!0;l=!1;break}}for(var r=
+{b:8,t:9,n:10,v:11,f:12,r:13},n=[],p=0,d=a.length;p<d;++p){g=a[p];if(g.global||g.multiline)throw Error(""+g);n.push("(?:"+y(g)+")")}return RegExp(n.join("|"),l?"gi":"g")}function M(a){function m(a){switch(a.nodeType){case 1:if(e.test(a.className))break;for(var g=a.firstChild;g;g=g.nextSibling)m(g);g=a.nodeName;if("BR"===g||"LI"===g)h[s]="\n",t[s<<1]=y++,t[s++<<1|1]=a;break;case 3:case 4:g=a.nodeValue,g.length&&(g=p?g.replace(/\r\n?/g,"\n"):g.replace(/[\t\n\r ]+/g," "),h[s]=g,t[s<<1]=y,y+=g.length,
+t[s++<<1|1]=a)}}var e=/(?:^|\s)nocode(?:\s|$)/,h=[],y=0,t=[],s=0,l;a.currentStyle?l=a.currentStyle.whiteSpace:window.getComputedStyle&&(l=document.defaultView.getComputedStyle(a,q).getPropertyValue("white-space"));var p=l&&"pre"===l.substring(0,3);m(a);return{a:h.join("").replace(/\n$/,""),c:t}}function B(a,m,e,h){m&&(a={a:m,d:a},e(a),h.push.apply(h,a.e))}function x(a,m){function e(a){for(var l=a.d,p=[l,"pln"],d=0,g=a.a.match(y)||[],r={},n=0,z=g.length;n<z;++n){var f=g[n],b=r[f],o=void 0,c;if(typeof b===
+"string")c=!1;else{var i=h[f.charAt(0)];if(i)o=f.match(i[1]),b=i[0];else{for(c=0;c<t;++c)if(i=m[c],o=f.match(i[1])){b=i[0];break}o||(b="pln")}if((c=b.length>=5&&"lang-"===b.substring(0,5))&&!(o&&typeof o[1]==="string"))c=!1,b="src";c||(r[f]=b)}i=d;d+=f.length;if(c){c=o[1];var j=f.indexOf(c),k=j+c.length;o[2]&&(k=f.length-o[2].length,j=k-c.length);b=b.substring(5);B(l+i,f.substring(0,j),e,p);B(l+i+j,c,C(b,c),p);B(l+i+k,f.substring(k),e,p)}else p.push(l+i,b)}a.e=p}var h={},y;(function(){for(var e=a.concat(m),
+l=[],p={},d=0,g=e.length;d<g;++d){var r=e[d],n=r[3];if(n)for(var k=n.length;--k>=0;)h[n.charAt(k)]=r;r=r[1];n=""+r;p.hasOwnProperty(n)||(l.push(r),p[n]=q)}l.push(/[\S\s]/);y=L(l)})();var t=m.length;return e}function u(a){var m=[],e=[];a.tripleQuotedStrings?m.push(["str",/^(?:'''(?:[^'\\]|\\[\S\s]|''?(?=[^']))*(?:'''|$)|"""(?:[^"\\]|\\[\S\s]|""?(?=[^"]))*(?:"""|$)|'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$))/,q,"'\""]):a.multiLineStrings?m.push(["str",/^(?:'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$)|`(?:[^\\`]|\\[\S\s])*(?:`|$))/,
+q,"'\"`"]):m.push(["str",/^(?:'(?:[^\n\r'\\]|\\.)*(?:'|$)|"(?:[^\n\r"\\]|\\.)*(?:"|$))/,q,"\"'"]);a.verbatimStrings&&e.push(["str",/^@"(?:[^"]|"")*(?:"|$)/,q]);var h=a.hashComments;h&&(a.cStyleComments?(h>1?m.push(["com",/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,q,"#"]):m.push(["com",/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\n\r]*)/,q,"#"]),e.push(["str",/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,q])):m.push(["com",/^#[^\n\r]*/,
+q,"#"]));a.cStyleComments&&(e.push(["com",/^\/\/[^\n\r]*/,q]),e.push(["com",/^\/\*[\S\s]*?(?:\*\/|$)/,q]));a.regexLiterals&&e.push(["lang-regex",/^(?:^^\.?|[!+-]|!=|!==|#|%|%=|&|&&|&&=|&=|\(|\*|\*=|\+=|,|-=|->|\/|\/=|:|::|;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|[?@[^]|\^=|\^\^|\^\^=|{|\||\|=|\|\||\|\|=|~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\s*(\/(?=[^*/])(?:[^/[\\]|\\[\S\s]|\[(?:[^\\\]]|\\[\S\s])*(?:]|$))+\/)/]);(h=a.types)&&e.push(["typ",h]);a=(""+a.keywords).replace(/^ | $/g,
+"");a.length&&e.push(["kwd",RegExp("^(?:"+a.replace(/[\s,]+/g,"|")+")\\b"),q]);m.push(["pln",/^\s+/,q," \r\n\t\xa0"]);e.push(["lit",/^@[$_a-z][\w$@]*/i,q],["typ",/^(?:[@_]?[A-Z]+[a-z][\w$@]*|\w+_t\b)/,q],["pln",/^[$_a-z][\w$@]*/i,q],["lit",/^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i,q,"0123456789"],["pln",/^\\[\S\s]?/,q],["pun",/^.[^\s\w"-$'./@\\`]*/,q]);return x(m,e)}function D(a,m){function e(a){switch(a.nodeType){case 1:if(k.test(a.className))break;if("BR"===a.nodeName)h(a),
+a.parentNode&&a.parentNode.removeChild(a);else for(a=a.firstChild;a;a=a.nextSibling)e(a);break;case 3:case 4:if(p){var b=a.nodeValue,d=b.match(t);if(d){var c=b.substring(0,d.index);a.nodeValue=c;(b=b.substring(d.index+d[0].length))&&a.parentNode.insertBefore(s.createTextNode(b),a.nextSibling);h(a);c||a.parentNode.removeChild(a)}}}}function h(a){function b(a,d){var e=d?a.cloneNode(!1):a,f=a.parentNode;if(f){var f=b(f,1),g=a.nextSibling;f.appendChild(e);for(var h=g;h;h=g)g=h.nextSibling,f.appendChild(h)}return e}
+for(;!a.nextSibling;)if(a=a.parentNode,!a)return;for(var a=b(a.nextSibling,0),e;(e=a.parentNode)&&e.nodeType===1;)a=e;d.push(a)}var k=/(?:^|\s)nocode(?:\s|$)/,t=/\r\n?|\n/,s=a.ownerDocument,l;a.currentStyle?l=a.currentStyle.whiteSpace:window.getComputedStyle&&(l=s.defaultView.getComputedStyle(a,q).getPropertyValue("white-space"));var p=l&&"pre"===l.substring(0,3);for(l=s.createElement("LI");a.firstChild;)l.appendChild(a.firstChild);for(var d=[l],g=0;g<d.length;++g)e(d[g]);m===(m|0)&&d[0].setAttribute("value",
+m);var r=s.createElement("OL");r.className="linenums";for(var n=Math.max(0,m-1|0)||0,g=0,z=d.length;g<z;++g)l=d[g],l.className="L"+(g+n)%10,l.firstChild||l.appendChild(s.createTextNode("\xa0")),r.appendChild(l);a.appendChild(r)}function k(a,m){for(var e=m.length;--e>=0;){var h=m[e];A.hasOwnProperty(h)?window.console&&console.warn("cannot override language handler %s",h):A[h]=a}}function C(a,m){if(!a||!A.hasOwnProperty(a))a=/^\s*</.test(m)?"default-markup":"default-code";return A[a]}function E(a){var m=
+a.g;try{var e=M(a.h),h=e.a;a.a=h;a.c=e.c;a.d=0;C(m,h)(a);var k=/\bMSIE\b/.test(navigator.userAgent),m=/\n/g,t=a.a,s=t.length,e=0,l=a.c,p=l.length,h=0,d=a.e,g=d.length,a=0;d[g]=s;var r,n;for(n=r=0;n<g;)d[n]!==d[n+2]?(d[r++]=d[n++],d[r++]=d[n++]):n+=2;g=r;for(n=r=0;n<g;){for(var z=d[n],f=d[n+1],b=n+2;b+2<=g&&d[b+1]===f;)b+=2;d[r++]=z;d[r++]=f;n=b}for(d.length=r;h<p;){var o=l[h+2]||s,c=d[a+2]||s,b=Math.min(o,c),i=l[h+1],j;if(i.nodeType!==1&&(j=t.substring(e,b))){k&&(j=j.replace(m,"\r"));i.nodeValue=
+j;var u=i.ownerDocument,v=u.createElement("SPAN");v.className=d[a+1];var x=i.parentNode;x.replaceChild(v,i);v.appendChild(i);e<o&&(l[h+1]=i=u.createTextNode(t.substring(b,o)),x.insertBefore(i,v.nextSibling))}e=b;e>=o&&(h+=2);e>=c&&(a+=2)}}catch(w){"console"in window&&console.log(w&&w.stack?w.stack:w)}}var v=["break,continue,do,else,for,if,return,while"],w=[[v,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"],
+"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"],F=[w,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"],G=[w,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"],
+H=[G,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"],w=[w,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"],I=[v,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"],
+J=[v,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"],v=[v,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"],K=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/,N=/\S/,O=u({keywords:[F,H,w,"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END"+
+I,J,v],hashComments:!0,cStyleComments:!0,multiLineStrings:!0,regexLiterals:!0}),A={};k(O,["default-code"]);k(x([],[["pln",/^[^<?]+/],["dec",/^<!\w[^>]*(?:>|$)/],["com",/^<\!--[\S\s]*?(?:--\>|$)/],["lang-",/^<\?([\S\s]+?)(?:\?>|$)/],["lang-",/^<%([\S\s]+?)(?:%>|$)/],["pun",/^(?:<[%?]|[%?]>)/],["lang-",/^<xmp\b[^>]*>([\S\s]+?)<\/xmp\b[^>]*>/i],["lang-js",/^<script\b[^>]*>([\S\s]*?)(<\/script\b[^>]*>)/i],["lang-css",/^<style\b[^>]*>([\S\s]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),
+["default-markup","htm","html","mxml","xhtml","xml","xsl"]);k(x([["pln",/^\s+/,q," \t\r\n"],["atv",/^(?:"[^"]*"?|'[^']*'?)/,q,"\"'"]],[["tag",/^^<\/?[a-z](?:[\w-.:]*\w)?|\/?>$/i],["atn",/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^\s"'>]*(?:[^\s"'/>]|\/(?=\s)))/],["pun",/^[/<->]+/],["lang-js",/^on\w+\s*=\s*"([^"]+)"/i],["lang-js",/^on\w+\s*=\s*'([^']+)'/i],["lang-js",/^on\w+\s*=\s*([^\s"'>]+)/i],["lang-css",/^style\s*=\s*"([^"]+)"/i],["lang-css",/^style\s*=\s*'([^']+)'/i],["lang-css",
+/^style\s*=\s*([^\s"'>]+)/i]]),["in.tag"]);k(x([],[["atv",/^[\S\s]+/]]),["uq.val"]);k(u({keywords:F,hashComments:!0,cStyleComments:!0,types:K}),["c","cc","cpp","cxx","cyc","m"]);k(u({keywords:"null,true,false"}),["json"]);k(u({keywords:H,hashComments:!0,cStyleComments:!0,verbatimStrings:!0,types:K}),["cs"]);k(u({keywords:G,cStyleComments:!0}),["java"]);k(u({keywords:v,hashComments:!0,multiLineStrings:!0}),["bsh","csh","sh"]);k(u({keywords:I,hashComments:!0,multiLineStrings:!0,tripleQuotedStrings:!0}),
+["cv","py"]);k(u({keywords:"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["perl","pl","pm"]);k(u({keywords:J,hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["rb"]);k(u({keywords:w,cStyleComments:!0,regexLiterals:!0}),["js"]);k(u({keywords:"all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes",
+hashComments:3,cStyleComments:!0,multilineStrings:!0,tripleQuotedStrings:!0,regexLiterals:!0}),["coffee"]);k(x([],[["str",/^[\S\s]+/]]),["regex"]);window.prettyPrintOne=function(a,m,e){var h=document.createElement("PRE");h.innerHTML=a;e&&D(h,e);E({g:m,i:e,h:h});return h.innerHTML};window.prettyPrint=function(a){function m(){for(var e=window.PR_SHOULD_USE_CONTINUATION?l.now()+250:Infinity;p<h.length&&l.now()<e;p++){var n=h[p],k=n.className;if(k.indexOf("prettyprint")>=0){var k=k.match(g),f,b;if(b=
+!k){b=n;for(var o=void 0,c=b.firstChild;c;c=c.nextSibling)var i=c.nodeType,o=i===1?o?b:c:i===3?N.test(c.nodeValue)?b:o:o;b=(f=o===b?void 0:o)&&"CODE"===f.tagName}b&&(k=f.className.match(g));k&&(k=k[1]);b=!1;for(o=n.parentNode;o;o=o.parentNode)if((o.tagName==="pre"||o.tagName==="code"||o.tagName==="xmp")&&o.className&&o.className.indexOf("prettyprint")>=0){b=!0;break}b||((b=(b=n.className.match(/\blinenums\b(?::(\d+))?/))?b[1]&&b[1].length?+b[1]:!0:!1)&&D(n,b),d={g:k,h:n,i:b},E(d))}}p<h.length?setTimeout(m,
+250):a&&a()}for(var e=[document.getElementsByTagName("pre"),document.getElementsByTagName("code"),document.getElementsByTagName("xmp")],h=[],k=0;k<e.length;++k)for(var t=0,s=e[k].length;t<s;++t)h.push(e[k][t]);var e=q,l=Date;l.now||(l={now:function(){return+new Date}});var p=0,d,g=/\blang(?:uage)?-([\w.]+)(?!\S)/;m()};window.PR={createSimpleLexer:x,registerLangHandler:k,sourceDecorator:u,PR_ATTRIB_NAME:"atn",PR_ATTRIB_VALUE:"atv",PR_COMMENT:"com",PR_DECLARATION:"dec",PR_KEYWORD:"kwd",PR_LITERAL:"lit",
+PR_NOCODE:"nocode",PR_PLAIN:"pln",PR_PUNCTUATION:"pun",PR_SOURCE:"src",PR_STRING:"str",PR_TAG:"tag",PR_TYPE:"typ"}})();

doc/setup.html 97(+97 -0)

diff --git a/doc/setup.html b/doc/setup.html
new file mode 100644
index 0000000..02190dd
--- /dev/null
+++ b/doc/setup.html
@@ -0,0 +1,97 @@
+<!-- ~ Copyright 2010-2011 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. -->
+
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="utf-8">
+    <title>Collector core | Dwarf</title>
+    <meta name="user guide" content="">
+    <meta name="author" content="Stephane Brossier">
+
+    <!--[if lt IE 9]>
+    <script src="http://html5shim.googlecode.com/svn/trunk/html5.js" type="text/javascript"></script>
+    <![endif]-->
+
+    <link rel=stylesheet type="text/css" href="css/bootstrap.min.css">
+    <link rel=stylesheet type="text/css" href="css/prettify.css">
+    <link rel=stylesheet type="text/css" href="css/killbill.css">
+    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
+    <script src="js/prettify.js"></script>
+    <script src="js/bootstrap-dropdown.js"></script>
+
+    <script type="text/javascript">
+      var _gaq = _gaq || [];
+      _gaq.push(['_setAccount', 'UA-19297396-1']);
+      _gaq.push(['_trackPageview']);
+      (function() {
+        var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
+        ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
+        var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
+      })();
+    </script>
+</head>
+
+<body onload="prettyPrint();">
+
+    <div class="topbar" data-dropdown="dropdown">
+        <div class="topbar-inner">
+            <div class="container-fluid">
+                <a class="brand" href="../index.html">Killbill</a>
+                <ul class="nav">
+                    <li><a href="user.html">User Guide</a></li>
+                    <li class="active"><a href="user.html">Setup/Installation</a></li>
+                    <li><a href="api.html">APIs</a></li>
+                    <li><a href="design.html">Design</a></li>                                                
+                    <li><a href="http://groups.google.com/group/killbill-users">Mailing List</a></li>
+                </ul>
+                <ul class="nav secondary-nav">
+                    <li class="dropdown">
+                        <a href="#" class="dropdown-toggle">Maven sites</a>
+                        <ul class="dropdown-menu">
+                            <li><a href="http://sbrossie.github.com/killbill" target="_blank">Killbill</a></li>
+                        </ul>
+                    </li>
+                </ul>
+                <ul class="nav secondary-nav">
+                    <li class="dropdown">
+                        <a href="#" class="dropdown-toggle">Github projects</a>
+                        <ul class="dropdown-menu">
+                            <li><a href="http://github.com/ning/killbill" target="_blank">Killbill</a></li>
+                        </ul>
+                    </li>
+                </ul>
+            </div>
+        </div>
+    </div>
+    <div class="container-fluid">
+        <div class="sidebar">
+            <div class="well">
+                <h5>Setup</h5>
+                <ul>
+                    <li><a href="#prerequisite">Prerequisite</a></li>
+                    <li><a href="#install">Installation</a></li>
+                    <li><a href="#configuration">Configuration</a></li>
+                    <li><a href="#demo">Demo</a></li>                    
+                </ul>
+            </div>
+        </div>
+        <div class="content">
+            <h2 class="prerequisite">Prerequisite</h2>
+                Bla bla get me prerequisite
+            <h2 class="install">Installation</h2>
+                    install   
+            <h2 class="configuration">Configuration</h2>
+                    Configuration  
+            <h2 class="demo">Demo</h2>
+                   Demo
+        </div>
+    </div>
+</body>
+ 

doc/user.html 102(+102 -0)

diff --git a/doc/user.html b/doc/user.html
new file mode 100644
index 0000000..774fa84
--- /dev/null
+++ b/doc/user.html
@@ -0,0 +1,102 @@
+<!-- ~ Copyright 2010-2011 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. -->
+
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="utf-8">
+    <title>Collector core | Dwarf</title>
+    <meta name="user guide" content="">
+    <meta name="author" content="Stephane Brossier">
+
+    <!--[if lt IE 9]>
+    <script src="http://html5shim.googlecode.com/svn/trunk/html5.js" type="text/javascript"></script>
+    <![endif]-->
+
+    <link rel=stylesheet type="text/css" href="css/bootstrap.min.css">
+    <link rel=stylesheet type="text/css" href="css/prettify.css">
+    <link rel=stylesheet type="text/css" href="css/killbill.css">
+    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
+    <script src="js/prettify.js"></script>
+    <script src="js/bootstrap-dropdown.js"></script>
+
+    <script type="text/javascript">
+      var _gaq = _gaq || [];
+      _gaq.push(['_setAccount', 'UA-19297396-1']);
+      _gaq.push(['_trackPageview']);
+      (function() {
+        var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
+        ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
+        var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
+      })();
+    </script>
+</head>
+
+<body onload="prettyPrint();">
+
+    <div class="topbar" data-dropdown="dropdown">
+        <div class="topbar-inner">
+            <div class="container-fluid">
+                <a class="brand" href="../index.html">Killbill</a>
+                <ul class="nav">
+                    <li class="active"><a href="user.html">User Guide</a></li>
+                    <li><a href="setup.html">Setup/Installation</a></li>
+                    <li><a href="api.html">APIs</a></li>
+                    <li><a href="design.html">Design</a></li>                                                
+                    <li><a href="http://groups.google.com/group/killbill-users">Mailing List</a></li>
+                </ul>
+                <ul class="nav secondary-nav">
+                    <li class="dropdown">
+                        <a href="#" class="dropdown-toggle">Maven sites</a>
+                        <ul class="dropdown-menu">
+                            <li><a href="http://sbrossie.github.com/killbill" target="_blank">Killbill</a></li>
+                        </ul>
+                    </li>
+                </ul>
+                <ul class="nav secondary-nav">
+                    <li class="dropdown">
+                        <a href="#" class="dropdown-toggle">Github projects</a>
+                        <ul class="dropdown-menu">
+                            <li><a href="http://github.com/ning/killbill" target="_blank">Killbill</a></li>
+                        </ul>
+                    </li>
+                </ul>
+            </div>
+        </div>
+    </div>
+    
+    
+    <div class="container-fluid">
+        <div class="sidebar">
+            <div class="well">
+                <h5>User Guide</h5>
+                <ul>
+                    <li><a href="#getting-started">Getting Started</a></li>
+                    <li><a href="#catalog-configuration">Catalog Configuration</a></li>
+                    <li><a href="#automatic-billing">Automatic Billing</a></li>
+                    <li><a href="#proration">Proration</a></li>                    
+                    <li><a href="#payment-pluggin">Admin UI</a></li>                    
+                </ul>
+            </div>
+        </div>
+        <div class="content">
+            <h2 class="getting-started">Getting Started</h2>
+                Bla bla get me started    
+            <h2 class="catalog-configuration">Catalog Configuration</h2>
+                    Bla bla catalog config   
+            <h2 class="automatic-billing">Automatic Billing</h2>
+                    Automatic billing   
+            <h2 class="proration">Proration Logic</h2>
+                    Bla bla proration   
+            <h2 class="payment-pluggin">Payment Pluggin</h2>
+                   Bla bla payment plugin
+        </div>
+    </div>
+</body>
+ 

entitlement/pom.xml 54(+24 -30)

diff --git a/entitlement/pom.xml b/entitlement/pom.xml
index 5f8b125..245e200 100644
--- a/entitlement/pom.xml
+++ b/entitlement/pom.xml
@@ -39,23 +39,6 @@
         </dependency>
         <dependency>
             <groupId>com.ning.billing</groupId>
-            <artifactId>killbill-catalog</artifactId>
-            <type>test-jar</type>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>com.ning.billing</groupId>
-            <artifactId>killbill-catalog</artifactId>
-            <scope>test</scope>
-        </dependency>
-         <dependency>
-            <groupId>com.ning.billing</groupId>
-            <artifactId>killbill-util</artifactId>
-            <type>test-jar</type>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>com.ning.billing</groupId>
             <artifactId>killbill-util</artifactId>
         </dependency>
         <dependency>
@@ -63,14 +46,7 @@
             <artifactId>commons-io</artifactId>
             <scope>test</scope>
         </dependency>
-        <!-- Same here, this is really debatable whether or not we should keep that here -->
-        <dependency>
-            <groupId>com.ning.billing</groupId>
-            <artifactId>killbill-account</artifactId>
-            <type>test-jar</type>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
+       <dependency>
             <groupId>joda-time</groupId>
             <artifactId>joda-time</artifactId>
         </dependency>
@@ -87,11 +63,6 @@
             <artifactId>slf4j-log4j12</artifactId>
         </dependency>
         <dependency>
-            <groupId>org.testng</groupId>
-            <artifactId>testng</artifactId>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
             <groupId>org.codehaus.jackson</groupId>
             <artifactId>jackson-core-asl</artifactId>
         </dependency>
@@ -108,11 +79,34 @@
             <artifactId>stringtemplate</artifactId>
             <scope>runtime</scope>
         </dependency>
+        <!-- TEST SCOPE -->
         <dependency>
             <groupId>mysql</groupId>
             <artifactId>mysql-connector-mxj</artifactId>
             <scope>test</scope>
         </dependency>
+                <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-catalog</artifactId>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-catalog</artifactId>
+            <scope>test</scope>
+        </dependency>
+         <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-util</artifactId>
+            <type>test-jar</type>
+            <scope>test</scope>
+       </dependency> 
+         <dependency>
+            <groupId>org.testng</groupId>
+            <artifactId>testng</artifactId>
+            <scope>test</scope>
+        </dependency>
         <dependency>
             <groupId>mysql</groupId>
             <artifactId>mysql-connector-mxj-db-files</artifactId>
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/alignment/PlanAligner.java b/entitlement/src/main/java/com/ning/billing/entitlement/alignment/PlanAligner.java
index 82a83bf..0e44528 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/alignment/PlanAligner.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/alignment/PlanAligner.java
@@ -21,7 +21,7 @@ import com.ning.billing.ErrorCode;
 import com.ning.billing.catalog.api.*;
 import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
 import com.ning.billing.entitlement.api.user.SubscriptionData;
-import com.ning.billing.entitlement.api.user.SubscriptionTransition;
+import com.ning.billing.entitlement.api.user.SubscriptionTransitionData;
 import com.ning.billing.entitlement.exceptions.EntitlementError;
 import com.ning.billing.util.clock.DefaultClock;
 import org.joda.time.DateTime;
@@ -119,7 +119,7 @@ public class PlanAligner  {
     public TimedPhase getNextTimedPhase(final SubscriptionData subscription, final DateTime requestedDate, final DateTime effectiveDate) {
         try {
 
-            SubscriptionTransition lastPlanTransition = subscription.getInitialTransitionForCurrentPlan();
+            SubscriptionTransitionData lastPlanTransition = subscription.getInitialTransitionForCurrentPlan();
             if (effectiveDate.isBefore(lastPlanTransition.getEffectiveTransitionTime())) {
                 throw new EntitlementError(String.format("Cannot specify an effectiveDate prior to last Plan Change, subscription = %s, effectiveDate = %s",
                         subscription.getId(), effectiveDate));
@@ -129,11 +129,12 @@ public class PlanAligner  {
             // If we never had any Plan change, borrow the logics for createPlan alignment
             case MIGRATE_ENTITLEMENT:
             case CREATE:
+            case RE_CREATE:                
                 List<TimedPhase> timedPhases = getTimedPhaseOnCreate(subscription.getStartDate(),
                         subscription.getBundleStartDate(),
                         lastPlanTransition.getNextPlan(),
                         lastPlanTransition.getNextPhase().getPhaseType(),
-                        lastPlanTransition.getNextPriceList(),
+                        lastPlanTransition.getNextPriceList().getName(),
                         requestedDate);
                 return getTimedPhase(timedPhases, effectiveDate, WhichPhase.NEXT);
             // If we went through Plan changes, borrow the logics for changePlan alignement
@@ -142,9 +143,9 @@ public class PlanAligner  {
                         subscription.getBundleStartDate(),
                         lastPlanTransition.getPreviousPhase(),
                         lastPlanTransition.getPreviousPlan(),
-                        lastPlanTransition.getPreviousPriceList(),
+                        lastPlanTransition.getPreviousPriceList().getName(),
                         lastPlanTransition.getNextPlan(),
-                        lastPlanTransition.getNextPriceList(),
+                        lastPlanTransition.getNextPriceList().getName(),
                         requestedDate,
                         effectiveDate,
                         WhichPhase.NEXT);
@@ -192,7 +193,7 @@ public class PlanAligner  {
                 subscription.getBundleStartDate(),
                 subscription.getCurrentPhase(),
                 subscription.getCurrentPlan(),
-                subscription.getCurrentPriceList(),
+                subscription.getCurrentPriceList().getName(),
                 nextPlan,
                 nextPriceList,
                 requestedDate,
@@ -211,11 +212,6 @@ public class PlanAligner  {
 
         Catalog catalog = catalogService.getFullCatalog();
         ProductCategory currentCategory = currentPlan.getProduct().getCategory();
-        // STEPH tiered ADDON not implemented yet
-        if (currentCategory != ProductCategory.BASE) {
-            throw new EntitlementError(String.format("Only implemented changePlan for BasePlan"));
-        }
-
         PlanPhaseSpecifier fromPlanPhaseSpecifier = new PlanPhaseSpecifier(currentPlan.getProduct().getName(),
                 currentCategory,
                 currentPlan.getBillingPeriod(),
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/billing/DefaultChargeThruApi.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/billing/DefaultChargeThruApi.java
new file mode 100644
index 0000000..21b97f8
--- /dev/null
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/billing/DefaultChargeThruApi.java
@@ -0,0 +1,55 @@
+/*
+w * Copyright 2010-2011 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.entitlement.api.billing;
+
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+
+import com.google.inject.Inject;
+import com.ning.billing.entitlement.api.SubscriptionFactory;
+import com.ning.billing.entitlement.api.user.DefaultSubscriptionFactory.SubscriptionBuilder;
+import com.ning.billing.entitlement.api.user.SubscriptionData;
+import com.ning.billing.entitlement.engine.dao.EntitlementDao;
+import com.ning.billing.util.callcontext.CallContext;
+
+public class DefaultChargeThruApi implements ChargeThruApi {
+    private final EntitlementDao entitlementDao;
+    private final SubscriptionFactory subscriptionFactory;
+  
+    @Inject
+    public DefaultChargeThruApi(final SubscriptionFactory subscriptionFactory, final EntitlementDao dao) {
+        super();
+        this.subscriptionFactory = subscriptionFactory;
+        this.entitlementDao = dao;
+    }
+    
+    @Override
+    public UUID getAccountIdFromSubscriptionId(final UUID subscriptionId) {
+        return entitlementDao.getAccountIdFromSubscriptionId(subscriptionId);
+    }
+
+    @Override
+    public void setChargedThroughDate(final UUID subscriptionId, final DateTime ctd, CallContext context) {
+        SubscriptionData subscription = (SubscriptionData) entitlementDao.getSubscriptionFromId(subscriptionFactory, subscriptionId);
+
+        SubscriptionBuilder builder = new SubscriptionBuilder(subscription)
+            .setChargedThroughDate(ctd)
+            .setPaidThroughDate(subscription.getPaidThroughDate());
+        entitlementDao.updateChargedThroughDate(new SubscriptionData(builder), context);
+    }
+}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/migration/AccountMigrationData.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/migration/AccountMigrationData.java
index 24c4e07..07a0034 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/migration/AccountMigrationData.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/migration/AccountMigrationData.java
@@ -20,7 +20,7 @@ import java.util.List;
 
 import com.ning.billing.entitlement.api.user.SubscriptionBundleData;
 import com.ning.billing.entitlement.api.user.SubscriptionData;
-import com.ning.billing.entitlement.api.user.SubscriptionFactory.SubscriptionBuilder;
+import com.ning.billing.entitlement.api.user.DefaultSubscriptionFactory.SubscriptionBuilder;
 import com.ning.billing.entitlement.events.EntitlementEvent;
 import com.ning.billing.entitlement.events.user.ApiEventMigrateBilling;
 
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/migration/DefaultEntitlementMigrationApi.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/migration/DefaultEntitlementMigrationApi.java
index d0fb35b..b09fc17 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/migration/DefaultEntitlementMigrationApi.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/migration/DefaultEntitlementMigrationApi.java
@@ -31,12 +31,12 @@ import com.google.inject.Inject;
 import com.ning.billing.catalog.api.ProductCategory;
 import com.ning.billing.entitlement.alignment.MigrationPlanAligner;
 import com.ning.billing.entitlement.alignment.TimedMigration;
+import com.ning.billing.entitlement.api.SubscriptionFactory;
 import com.ning.billing.entitlement.api.migration.AccountMigrationData.BundleMigrationData;
 import com.ning.billing.entitlement.api.migration.AccountMigrationData.SubscriptionMigrationData;
 import com.ning.billing.entitlement.api.user.SubscriptionBundleData;
 import com.ning.billing.entitlement.api.user.SubscriptionData;
-import com.ning.billing.entitlement.api.user.SubscriptionFactory;
-import com.ning.billing.entitlement.api.user.SubscriptionFactory.SubscriptionBuilder;
+import com.ning.billing.entitlement.api.user.DefaultSubscriptionFactory.SubscriptionBuilder;
 import com.ning.billing.entitlement.engine.dao.EntitlementDao;
 import com.ning.billing.entitlement.events.EntitlementEvent;
 import com.ning.billing.entitlement.events.EntitlementEvent.EventType;
@@ -72,11 +72,11 @@ public class DefaultEntitlementMigrationApi implements EntitlementMigrationApi {
     @Override
     public void migrate(EntitlementAccountMigration toBeMigrated, CallContext context)
     throws EntitlementMigrationApiException {
-        AccountMigrationData accountMigrationData = createAccountMigrationData(toBeMigrated);
+        AccountMigrationData accountMigrationData = createAccountMigrationData(toBeMigrated, context);
         dao.migrate(toBeMigrated.getAccountKey(), accountMigrationData, context);
     }
 
-    private AccountMigrationData createAccountMigrationData(EntitlementAccountMigration toBeMigrated)
+    private AccountMigrationData createAccountMigrationData(EntitlementAccountMigration toBeMigrated, CallContext context)
     throws EntitlementMigrationApiException  {
 
         final UUID accountId = toBeMigrated.getAccountKey();
@@ -86,7 +86,7 @@ public class DefaultEntitlementMigrationApi implements EntitlementMigrationApi {
 
         for (final EntitlementBundleMigration curBundle : toBeMigrated.getBundles()) {
 
-            SubscriptionBundleData bundleData = new SubscriptionBundleData(curBundle.getBundleKey(), accountId);
+            SubscriptionBundleData bundleData = new SubscriptionBundleData(curBundle.getBundleKey(), accountId, clock.getUTCNow());
             List<SubscriptionMigrationData> bundleSubscriptionData = new LinkedList<AccountMigrationData.SubscriptionMigrationData>();
 
 
@@ -118,10 +118,11 @@ public class DefaultEntitlementMigrationApi implements EntitlementMigrationApi {
             for (EntitlementSubscriptionMigration curSub : sortedSubscriptions) {
                 SubscriptionMigrationData data = null;
                 if (bundleStartDate == null) {
-                    data = createInitialSubscription(bundleData.getId(), curSub.getCategory(), curSub.getSubscriptionCases(), now, curSub.getChargedThroughDate());
+                    data = createInitialSubscription(bundleData.getId(), curSub.getCategory(), curSub.getSubscriptionCases(), now, curSub.getChargedThroughDate(), context);
                     bundleStartDate = data.getInitialEvents().get(0).getEffectiveDate();
                 } else {
-                    data = createSubscriptionMigrationDataWithBundleDate(bundleData.getId(), curSub.getCategory(), curSub.getSubscriptionCases(), now, bundleStartDate, curSub.getChargedThroughDate());
+                    data = createSubscriptionMigrationDataWithBundleDate(bundleData.getId(), curSub.getCategory(), curSub.getSubscriptionCases(), now,
+                    		bundleStartDate, curSub.getChargedThroughDate(), context);
                 }
                 if (data != null) {
                     bundleSubscriptionData.add(data);
@@ -135,7 +136,7 @@ public class DefaultEntitlementMigrationApi implements EntitlementMigrationApi {
     }
 
     private SubscriptionMigrationData createInitialSubscription(UUID bundleId, ProductCategory productCategory,
-            EntitlementSubscriptionMigrationCase [] input, DateTime now, DateTime ctd)
+            EntitlementSubscriptionMigrationCase [] input, DateTime now, DateTime ctd, CallContext context)
         throws EntitlementMigrationApiException {
 
         TimedMigration [] events = migrationAligner.getEventsMigration(input, now);
@@ -148,11 +149,11 @@ public class DefaultEntitlementMigrationApi implements EntitlementMigrationApi {
             .setBundleStartDate(migrationStartDate)
             .setStartDate(migrationStartDate),
             emptyEvents);
-        return new SubscriptionMigrationData(subscriptionData, toEvents(subscriptionData, now, ctd, events));
+        return new SubscriptionMigrationData(subscriptionData, toEvents(subscriptionData, now, ctd, events, context));
     }
 
     private SubscriptionMigrationData createSubscriptionMigrationDataWithBundleDate(UUID bundleId, ProductCategory productCategory,
-            EntitlementSubscriptionMigrationCase [] input, DateTime now, DateTime bundleStartDate, DateTime ctd)
+            EntitlementSubscriptionMigrationCase [] input, DateTime now, DateTime bundleStartDate, DateTime ctd, CallContext context)
     throws EntitlementMigrationApiException {
         TimedMigration [] events = migrationAligner.getEventsMigration(input, now);
         DateTime migrationStartDate= events[0].getEventTime();
@@ -164,10 +165,10 @@ public class DefaultEntitlementMigrationApi implements EntitlementMigrationApi {
             .setBundleStartDate(bundleStartDate)
             .setStartDate(migrationStartDate),
             emptyEvents);
-        return new SubscriptionMigrationData(subscriptionData, toEvents(subscriptionData, now, ctd, events));
+        return new SubscriptionMigrationData(subscriptionData, toEvents(subscriptionData, now, ctd, events, context));
     }
 
-    private List<EntitlementEvent> toEvents(SubscriptionData subscriptionData, DateTime now, DateTime ctd, TimedMigration [] migrationEvents) {
+    private List<EntitlementEvent> toEvents(SubscriptionData subscriptionData, DateTime now, DateTime ctd, TimedMigration [] migrationEvents, CallContext context) {
 
 
         ApiEventMigrateEntitlement creationEvent = null;
@@ -189,6 +190,7 @@ public class DefaultEntitlementMigrationApi implements EntitlementMigrationApi {
                 .setEffectiveDate(cur.getEventTime())
                 .setProcessedDate(now)
                 .setRequestedDate(now)
+                .setUserToken(context.getUserToken())
                 .setFromDisk(true);
 
                 switch(cur.getApiEventType()) {
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/SubscriptionApiService.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/SubscriptionApiService.java
new file mode 100644
index 0000000..5b0f192
--- /dev/null
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/SubscriptionApiService.java
@@ -0,0 +1,54 @@
+/* 
+ * Copyright 2010-2011 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.entitlement.api;
+
+import java.util.List;
+
+import org.joda.time.DateTime;
+
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.PhaseType;
+import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
+import com.ning.billing.entitlement.api.user.SubscriptionData;
+import com.ning.billing.entitlement.api.user.SubscriptionStatusDryRun;
+import com.ning.billing.entitlement.api.user.DefaultSubscriptionFactory.SubscriptionBuilder;
+import com.ning.billing.util.callcontext.CallContext;
+
+public interface SubscriptionApiService {
+
+    public SubscriptionData createPlan(SubscriptionBuilder builder, Plan plan, PhaseType initialPhase,
+            String realPriceList, DateTime requestedDate, DateTime effectiveDate, DateTime processedDate,
+            CallContext context)
+        throws EntitlementUserApiException;
+
+    public boolean recreatePlan(SubscriptionData subscription, PlanPhaseSpecifier spec, DateTime requestedDate, CallContext context)
+        throws EntitlementUserApiException;
+
+
+    public boolean cancel(SubscriptionData subscription, DateTime requestedDate, boolean eot, CallContext context)
+        throws EntitlementUserApiException;
+
+    public boolean uncancel(SubscriptionData subscription, CallContext context)
+        throws EntitlementUserApiException;
+
+    public boolean changePlan(SubscriptionData subscription, String productName, BillingPeriod term,
+            String priceList, DateTime requestedDate, CallContext context)
+        throws EntitlementUserApiException;
+
+    public void commitCustomFields(SubscriptionData subscription, CallContext context);
+}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/SubscriptionFactory.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/SubscriptionFactory.java
new file mode 100644
index 0000000..2b12487
--- /dev/null
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/SubscriptionFactory.java
@@ -0,0 +1,27 @@
+/* 
+ * Copyright 2010-2011 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.entitlement.api;
+
+import java.util.List;
+
+import com.ning.billing.entitlement.api.user.SubscriptionData;
+import com.ning.billing.entitlement.api.user.DefaultSubscriptionFactory.SubscriptionBuilder;
+import com.ning.billing.entitlement.events.EntitlementEvent;
+
+public interface SubscriptionFactory {
+    
+    public SubscriptionData createSubscription(SubscriptionBuilder builder, List<EntitlementEvent> events);
+}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/DefaultDeletedEvent.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/DefaultDeletedEvent.java
new file mode 100644
index 0000000..14b1e9e
--- /dev/null
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/DefaultDeletedEvent.java
@@ -0,0 +1,42 @@
+/* 
+ * Copyright 2010-2011 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.entitlement.api.timeline;
+
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline.DeletedEvent;
+
+public class DefaultDeletedEvent implements DeletedEvent {
+
+    private final UUID id;
+    private final DateTime effectiveDate;
+    
+    public DefaultDeletedEvent(UUID id, DateTime effectiveDate) {
+        this.id = id;
+        this.effectiveDate = effectiveDate;
+    }
+    
+    @Override
+    public UUID getEventId() {
+        return id;
+    }
+    
+    public DateTime getEffectiveDate() {
+        return effectiveDate;
+    }
+}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/DefaultEntitlementTimelineApi.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/DefaultEntitlementTimelineApi.java
new file mode 100644
index 0000000..9f73725
--- /dev/null
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/DefaultEntitlementTimelineApi.java
@@ -0,0 +1,465 @@
+/* 
+ * Copyright 2010-2011 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.entitlement.api.timeline;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+
+import com.google.inject.Inject;
+import com.google.inject.name.Named;
+import com.ning.billing.ErrorCode;
+import com.ning.billing.catalog.api.CatalogApiException;
+import com.ning.billing.catalog.api.CatalogService;
+import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.entitlement.api.SubscriptionFactory;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline.NewEvent;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.entitlement.api.user.SubscriptionBundleData;
+import com.ning.billing.entitlement.api.user.SubscriptionData;
+import com.ning.billing.entitlement.api.user.DefaultSubscriptionFactory.SubscriptionBuilder;
+import com.ning.billing.entitlement.api.user.SubscriptionTransitionData;
+import com.ning.billing.entitlement.engine.dao.EntitlementDao;
+import com.ning.billing.entitlement.events.EntitlementEvent;
+import com.ning.billing.entitlement.glue.DefaultEntitlementModule;
+import com.ning.billing.util.callcontext.CallContext;
+
+public class DefaultEntitlementTimelineApi implements EntitlementTimelineApi {
+
+    private final EntitlementDao dao;
+    private final SubscriptionFactory factory;
+    private final RepairEntitlementLifecycleDao repairDao;
+    private final CatalogService catalogService;
+
+
+    private enum RepairType  {
+        BASE_REPAIR,
+        ADD_ON_REPAIR,
+        STANDALONE_REPAIR
+    }
+
+
+    @Inject
+    public DefaultEntitlementTimelineApi(@Named(DefaultEntitlementModule.REPAIR_NAMED) final SubscriptionFactory factory, final CatalogService catalogService,
+            @Named(DefaultEntitlementModule.REPAIR_NAMED) final RepairEntitlementLifecycleDao repairDao, final EntitlementDao dao) {
+        this.catalogService = catalogService;
+        this.dao = dao;
+        this.repairDao = repairDao;
+        this.factory = factory;
+    }
+
+
+    @Override
+    public BundleTimeline getBundleRepair(final UUID bundleId) 
+    throws EntitlementRepairException {
+
+        try {
+            SubscriptionBundle bundle = dao.getSubscriptionBundleFromId(bundleId);
+            if (bundle == null) {
+                throw new EntitlementRepairException(ErrorCode.ENT_REPAIR_UNKNOWN_BUNDLE, bundleId);
+            }
+            final List<Subscription> subscriptions = dao.getSubscriptions(factory, bundleId);
+            if (subscriptions.size() == 0) {
+                throw new EntitlementRepairException(ErrorCode.ENT_REPAIR_NO_ACTIVE_SUBSCRIPTIONS, bundleId);
+            }
+            final String viewId = getViewId(((SubscriptionBundleData) bundle).getLastSysUpdateTime(), subscriptions);
+            final List<SubscriptionTimeline> repairs = createGetSubscriptionRepairList(subscriptions, Collections.<SubscriptionTimeline>emptyList()); 
+            return createGetBundleRepair(bundleId, bundle.getKey(), viewId, repairs);
+        } catch (CatalogApiException e) {
+            throw new EntitlementRepairException(e);
+        }
+    }
+
+
+
+    @Override
+    public BundleTimeline repairBundle(final BundleTimeline input, final boolean dryRun, final CallContext context)
+    throws EntitlementRepairException {
+
+        try {
+
+            SubscriptionBundle bundle = dao.getSubscriptionBundleFromId(input.getBundleId());
+            if (bundle == null) {
+                throw new EntitlementRepairException(ErrorCode.ENT_REPAIR_UNKNOWN_BUNDLE, input.getBundleId());
+            }
+            
+
+            // Subscriptions are ordered with BASE subscription first-- if exists
+            final List<Subscription> subscriptions = dao.getSubscriptions(factory, input.getBundleId());
+            if (subscriptions.size() == 0) {
+                throw new EntitlementRepairException(ErrorCode.ENT_REPAIR_NO_ACTIVE_SUBSCRIPTIONS, input.getBundleId());
+            }
+
+            final String viewId = getViewId(((SubscriptionBundleData) bundle).getLastSysUpdateTime(), subscriptions);
+            if (!viewId.equals(input.getViewId())) {
+                throw new EntitlementRepairException(ErrorCode.ENT_REPAIR_VIEW_CHANGED,input.getBundleId(), input.getViewId(), viewId);
+            }
+
+            DateTime firstDeletedBPEventTime = null;
+            DateTime lastRemainingBPEventTime = null;
+
+            boolean isBasePlanRecreate = false;
+            DateTime newBundleStartDate = null;
+
+            SubscriptionDataRepair baseSubscriptionRepair = null;
+            List<SubscriptionDataRepair> addOnSubscriptionInRepair = new LinkedList<SubscriptionDataRepair>();
+            List<SubscriptionDataRepair> inRepair =  new LinkedList<SubscriptionDataRepair>();
+            for (Subscription cur : subscriptions) {
+
+                //
+                SubscriptionTimeline curRepair = findAndCreateSubscriptionRepair(cur.getId(), input.getSubscriptions());
+                if (curRepair != null) {
+                    SubscriptionDataRepair curInputRepair = ((SubscriptionDataRepair) cur);
+                    final List<EntitlementEvent> remaining = getRemainingEventsAndValidateDeletedEvents(curInputRepair, firstDeletedBPEventTime, curRepair.getDeletedEvents());
+
+                    final boolean isPlanRecreate = (curRepair.getNewEvents().size() > 0 
+                            && (curRepair.getNewEvents().get(0).getSubscriptionTransitionType() == SubscriptionTransitionType.CREATE 
+                                    || curRepair.getNewEvents().get(0).getSubscriptionTransitionType() == SubscriptionTransitionType.RE_CREATE));
+
+                    final DateTime newSubscriptionStartDate = isPlanRecreate ? curRepair.getNewEvents().get(0).getRequestedDate() : null;
+
+                    if (isPlanRecreate && remaining.size() != 0) {
+                        throw new EntitlementRepairException(ErrorCode.ENT_REPAIR_SUB_RECREATE_NOT_EMPTY, cur.getId(), cur.getBundleId());
+                    }
+
+                    if (!isPlanRecreate && remaining.size() == 0) {
+                        throw new EntitlementRepairException(ErrorCode.ENT_REPAIR_SUB_EMPTY, cur.getId(), cur.getBundleId());
+                    }
+
+                    if (cur.getCategory() == ProductCategory.BASE) {
+
+                        int bpTransitionSize =((SubscriptionData) cur).getAllTransitions().size();
+                        lastRemainingBPEventTime = (remaining.size() > 0) ? curInputRepair.getAllTransitions().get(remaining.size() - 1).getEffectiveTransitionTime() : null;
+                        firstDeletedBPEventTime =  (remaining.size() < bpTransitionSize) ? curInputRepair.getAllTransitions().get(remaining.size()).getEffectiveTransitionTime() : null;
+
+                        isBasePlanRecreate = isPlanRecreate;
+                        newBundleStartDate = newSubscriptionStartDate;
+                    }
+
+                    if (curRepair.getNewEvents().size() > 0) {
+                        DateTime lastRemainingEventTime = (remaining.size() == 0) ? null : curInputRepair.getAllTransitions().get(remaining.size() - 1).getEffectiveTransitionTime();
+                        validateFirstNewEvent(curInputRepair, curRepair.getNewEvents().get(0), lastRemainingBPEventTime, lastRemainingEventTime);
+                    }
+
+
+                    SubscriptionDataRepair curOutputRepair = createSubscriptionDataRepair(curInputRepair, newBundleStartDate, newSubscriptionStartDate, remaining);
+                    repairDao.initializeRepair(curInputRepair.getId(), remaining);
+                    inRepair.add(curOutputRepair);
+                    if (curOutputRepair.getCategory() == ProductCategory.ADD_ON) {
+                        // Check if ADD_ON RE_CREATE is before BP start
+                        if (isPlanRecreate && subscriptions.get(0).getStartDate().isAfter(curRepair.getNewEvents().get(0).getRequestedDate())) {
+                            throw new EntitlementRepairException(ErrorCode.ENT_REPAIR_AO_CREATE_BEFORE_BP_START, cur.getId(), cur.getBundleId());                            
+                        }
+                        addOnSubscriptionInRepair.add(curOutputRepair);
+                    } else if (curOutputRepair.getCategory() == ProductCategory.BASE) {
+                        baseSubscriptionRepair = curOutputRepair;
+                    }
+                }
+            }
+
+            final RepairType repairType = getRepairType(subscriptions.get(0), (baseSubscriptionRepair != null));
+            switch(repairType) {
+            case BASE_REPAIR:
+                // We need to add any existing addon that are not in the input repair list
+                for (Subscription cur : subscriptions) {
+                    if (cur.getCategory() == ProductCategory.ADD_ON && !inRepair.contains(cur)) {
+                        SubscriptionDataRepair curOutputRepair = createSubscriptionDataRepair((SubscriptionDataRepair) cur, newBundleStartDate, null, ((SubscriptionDataRepair) cur).getEvents());
+                        repairDao.initializeRepair(curOutputRepair.getId(), ((SubscriptionDataRepair) cur).getEvents());
+                        inRepair.add(curOutputRepair);
+                        addOnSubscriptionInRepair.add(curOutputRepair);
+                    }
+                }
+
+                break;
+            case ADD_ON_REPAIR:
+                // We need to set the baseSubscription as it is useful to calculate addon validity
+                SubscriptionDataRepair baseSubscription =  (SubscriptionDataRepair) subscriptions.get(0);
+                baseSubscriptionRepair = createSubscriptionDataRepair(baseSubscription, baseSubscription.getBundleStartDate(), baseSubscription.getStartDate(), baseSubscription.getEvents());
+                break;
+            case STANDALONE_REPAIR:
+            default:
+                break;
+
+            }
+
+            validateBasePlanRecreate(isBasePlanRecreate, subscriptions, input.getSubscriptions());
+            validateInputSubscriptionsKnown(subscriptions, input.getSubscriptions());
+
+
+            Collection<NewEvent> newEvents = createOrderedNewEventInput(input.getSubscriptions());
+            Iterator<NewEvent> it = newEvents.iterator();
+            while (it.hasNext()) {
+                DefaultNewEvent cur = (DefaultNewEvent) it.next();
+                SubscriptionDataRepair curDataRepair = findSubscriptionDataRepair(cur.getSubscriptionId(), inRepair);
+                if (curDataRepair == null) {
+                    throw new EntitlementRepairException(ErrorCode.ENT_REPAIR_UNKNOWN_SUBSCRIPTION, cur.getSubscriptionId());
+                }
+                curDataRepair.addNewRepairEvent(cur, baseSubscriptionRepair, addOnSubscriptionInRepair, context);
+            }
+            
+            if (dryRun) {
+                
+                baseSubscriptionRepair.addFutureAddonCancellation(addOnSubscriptionInRepair, context);
+
+                final List<SubscriptionTimeline> repairs = createGetSubscriptionRepairList(subscriptions, convertDataRepair(inRepair)); 
+                return createGetBundleRepair(input.getBundleId(), bundle.getKey(), input.getViewId(), repairs);
+            } else {
+                dao.repair(bundle.getAccountId(), input.getBundleId(), inRepair, context);
+                return getBundleRepair(input.getBundleId());
+            }
+        } catch (CatalogApiException e) {
+            throw new EntitlementRepairException(e);
+        } finally {
+            repairDao.cleanup();
+        }
+    }
+
+ 
+    
+    
+    private RepairType getRepairType(final Subscription firstSubscription, final boolean gotBaseSubscription) {
+        if (firstSubscription.getCategory() == ProductCategory.BASE) {
+            return gotBaseSubscription ? RepairType.BASE_REPAIR : RepairType.ADD_ON_REPAIR;
+        } else {
+            return RepairType.STANDALONE_REPAIR;
+        }
+    }
+
+    private void validateBasePlanRecreate(boolean isBasePlanRecreate, List<Subscription> subscriptions, List<SubscriptionTimeline> input) 
+    throws EntitlementRepairException  {
+
+        if (!isBasePlanRecreate) {
+            return;
+        }
+        if (subscriptions.size() != input.size()) {
+            throw new EntitlementRepairException(ErrorCode.ENT_REPAIR_BP_RECREATE_MISSING_AO, subscriptions.get(0).getBundleId());
+        }
+        for (SubscriptionTimeline cur : input) {
+            if (cur.getNewEvents().size() != 0 
+                    && (cur.getNewEvents().get(0).getSubscriptionTransitionType() != SubscriptionTransitionType.CREATE
+                            && cur.getNewEvents().get(0).getSubscriptionTransitionType() != SubscriptionTransitionType.RE_CREATE)) {
+                throw new EntitlementRepairException(ErrorCode.ENT_REPAIR_BP_RECREATE_MISSING_AO_CREATE, subscriptions.get(0).getBundleId());
+            }
+        }
+    }
+
+
+    private void validateInputSubscriptionsKnown(List<Subscription> subscriptions, List<SubscriptionTimeline> input)
+    throws EntitlementRepairException {
+
+        for (SubscriptionTimeline cur : input) {
+            boolean found = false;
+            for (Subscription s : subscriptions) {
+                if (s.getId().equals(cur.getId())) {
+                    found = true;
+                    break;
+                }
+            }
+            if (!found) {
+                throw new EntitlementRepairException(ErrorCode.ENT_REPAIR_UNKNOWN_SUBSCRIPTION, cur.getId());
+            }
+        }
+    }
+
+    private void validateFirstNewEvent(final SubscriptionData data, final NewEvent firstNewEvent, final DateTime lastBPRemainingTime, final DateTime lastRemainingTime) 
+    throws EntitlementRepairException {
+        if (lastBPRemainingTime != null &&
+                firstNewEvent.getRequestedDate().isBefore(lastBPRemainingTime)) {
+            throw new EntitlementRepairException(ErrorCode.ENT_REPAIR_NEW_EVENT_BEFORE_LAST_BP_REMAINING, firstNewEvent.getSubscriptionTransitionType(), data.getId());
+        }
+        if (lastRemainingTime != null &&
+                firstNewEvent.getRequestedDate().isBefore(lastRemainingTime)) {
+            throw new EntitlementRepairException(ErrorCode.ENT_REPAIR_NEW_EVENT_BEFORE_LAST_AO_REMAINING, firstNewEvent.getSubscriptionTransitionType(), data.getId());
+        }
+
+    }
+
+    private Collection<NewEvent> createOrderedNewEventInput(List<SubscriptionTimeline> subscriptionsReapir) {
+        TreeSet<NewEvent> newEventSet = new TreeSet<SubscriptionTimeline.NewEvent>(new Comparator<NewEvent>() {
+            @Override
+            public int compare(NewEvent o1, NewEvent o2) {
+                return o1.getRequestedDate().compareTo(o2.getRequestedDate());
+            }
+        });
+        for (SubscriptionTimeline cur : subscriptionsReapir) {
+            for (NewEvent e : cur.getNewEvents()) {
+                newEventSet.add(new DefaultNewEvent(cur.getId(), e.getPlanPhaseSpecifier(), e.getRequestedDate(), e.getSubscriptionTransitionType()));    
+            }
+        }
+        return newEventSet;
+    }
+
+
+    private List<EntitlementEvent> getRemainingEventsAndValidateDeletedEvents(final SubscriptionDataRepair data, final DateTime firstBPDeletedTime,
+            final List<SubscriptionTimeline.DeletedEvent> deletedEvents) 
+            throws EntitlementRepairException  {
+
+        if (deletedEvents == null || deletedEvents.size() == 0) {
+            return data.getEvents();
+        }
+
+        int nbDeleted = 0;
+        LinkedList<EntitlementEvent> result = new LinkedList<EntitlementEvent>();
+        for (EntitlementEvent cur : data.getEvents()) {
+
+            boolean foundDeletedEvent = false;
+            for (SubscriptionTimeline.DeletedEvent d : deletedEvents) {
+                if (cur.getId().equals(d.getEventId())) {
+                    foundDeletedEvent = true;
+                    nbDeleted++;
+                    break;
+                }
+            }
+            if (!foundDeletedEvent && nbDeleted > 0) {
+                throw new EntitlementRepairException(ErrorCode.ENT_REPAIR_INVALID_DELETE_SET, cur.getId(), data.getId());
+            }
+            if (firstBPDeletedTime != null && 
+                    ! cur.getEffectiveDate().isBefore(firstBPDeletedTime) &&
+                    ! foundDeletedEvent) {
+                throw new EntitlementRepairException(ErrorCode.ENT_REPAIR_MISSING_AO_DELETE_EVENT, cur.getId(), data.getId());
+            }
+
+            if (nbDeleted == 0) {
+                result.add(cur);
+            }
+        }
+        if (nbDeleted != deletedEvents.size()) {
+            for (SubscriptionTimeline.DeletedEvent d : deletedEvents) {
+                boolean found = false;
+                for (SubscriptionTransitionData cur : data.getAllTransitions()) {
+                    if (cur.getId().equals(d.getEventId())) {
+                        found = true;
+                        continue;
+                    }
+                }
+                if (!found) {
+                    throw new EntitlementRepairException(ErrorCode.ENT_REPAIR_NON_EXISTENT_DELETE_EVENT, d.getEventId(), data.getId());
+                }
+            }
+
+        }
+        return result;
+    }
+
+
+    private String getViewId(DateTime lastUpdateBundleDate, List<Subscription> subscriptions) {
+        StringBuilder tmp = new StringBuilder();
+        long lastOrderedId = -1;
+        for (Subscription cur : subscriptions) {
+            lastOrderedId = lastOrderedId < ((SubscriptionData) cur).getLastEventOrderedId() ? ((SubscriptionData) cur).getLastEventOrderedId() : lastOrderedId;
+        }
+        tmp.append(lastOrderedId);
+        tmp.append("-");
+        tmp.append(lastUpdateBundleDate.toDate().getTime());
+        return tmp.toString();
+    }
+
+    private BundleTimeline createGetBundleRepair(final UUID bundleId, final String externalKey, final String viewId, final List<SubscriptionTimeline> repairList) {
+        return new BundleTimeline() {
+            @Override
+            public String getViewId() {
+                return viewId;
+            }
+            @Override
+            public List<SubscriptionTimeline> getSubscriptions() {
+                return repairList;
+            }
+            @Override
+            public UUID getBundleId() {
+                return bundleId;
+            }
+            @Override
+            public String getExternalKey() {
+                return externalKey;
+            }
+        };
+
+    }
+
+    private List<SubscriptionTimeline> createGetSubscriptionRepairList(final List<Subscription> subscriptions, final List<SubscriptionTimeline> inRepair) throws CatalogApiException {
+
+        final List<SubscriptionTimeline> result = new LinkedList<SubscriptionTimeline>();
+        Set<UUID> repairIds = new TreeSet<UUID>();
+        for (final SubscriptionTimeline cur : inRepair) {
+            repairIds.add(cur.getId());
+            result.add(cur);
+        }
+        for (final Subscription cur : subscriptions) {
+            if ( !repairIds.contains(cur.getId())) { 
+                result.add(new DefaultSubscriptionTimeline((SubscriptionDataRepair) cur, catalogService.getFullCatalog()));
+            }
+        }
+        return result;
+    }
+
+
+    private List<SubscriptionTimeline> convertDataRepair(List<SubscriptionDataRepair> input) throws CatalogApiException  {
+        List<SubscriptionTimeline> result = new LinkedList<SubscriptionTimeline>();
+        for (SubscriptionDataRepair cur : input) {
+            result.add(new DefaultSubscriptionTimeline(cur, catalogService.getFullCatalog()));
+        }
+        return result;
+    }
+
+    private SubscriptionDataRepair findSubscriptionDataRepair(final UUID targetId, final List<SubscriptionDataRepair> input) {
+        for (SubscriptionDataRepair cur : input) {
+            if (cur.getId().equals(targetId)) {
+                return cur;
+            }
+        }
+        return null;
+    }
+
+
+    private SubscriptionDataRepair createSubscriptionDataRepair(final SubscriptionData curData, final DateTime newBundleStartDate, final DateTime newSubscriptionStartDate, final List<EntitlementEvent> initialEvents) {
+        SubscriptionBuilder builder = new SubscriptionBuilder(curData);
+        builder.setActiveVersion(curData.getActiveVersion() + 1);
+        if (newBundleStartDate != null) {
+            builder.setBundleStartDate(newBundleStartDate);
+        }
+        if (newSubscriptionStartDate != null) {
+            builder.setStartDate(newSubscriptionStartDate);
+        }
+        if (initialEvents.size() > 0) {
+            for (EntitlementEvent cur : initialEvents) {
+                cur.setActiveVersion(builder.getActiveVersion());
+            }
+        }
+        SubscriptionDataRepair result = (SubscriptionDataRepair) factory.createSubscription(builder, initialEvents);
+        return result;
+    }
+
+
+    private SubscriptionTimeline findAndCreateSubscriptionRepair(final UUID target, final List<SubscriptionTimeline> input) {
+        for (SubscriptionTimeline cur : input) {
+            if (target.equals(cur.getId())) {
+                return new DefaultSubscriptionTimeline(cur);
+            }
+        }
+        return null;
+    }
+}
+
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/DefaultNewEvent.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/DefaultNewEvent.java
new file mode 100644
index 0000000..87ece9f
--- /dev/null
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/DefaultNewEvent.java
@@ -0,0 +1,58 @@
+/* 
+ * Copyright 2010-2011 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.entitlement.api.timeline;
+
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline.NewEvent;
+
+public class DefaultNewEvent implements NewEvent {
+
+    private final UUID subscriptionId;
+    private final PlanPhaseSpecifier spec;
+    private final DateTime requestedDate;
+    private final SubscriptionTransitionType transitionType;
+    
+    public DefaultNewEvent(final UUID subscriptionId, final PlanPhaseSpecifier spec, final DateTime requestedDate, final SubscriptionTransitionType transitionType) {
+        this.subscriptionId = subscriptionId;
+        this.spec = spec;
+        this.requestedDate = requestedDate;
+        this.transitionType = transitionType;
+    }
+    
+    @Override
+    public PlanPhaseSpecifier getPlanPhaseSpecifier() {
+        return spec;
+    }
+
+    @Override
+    public DateTime getRequestedDate() {
+        return requestedDate;
+    }
+
+    @Override
+    public SubscriptionTransitionType getSubscriptionTransitionType() {
+        return transitionType;
+    }
+    
+    public UUID getSubscriptionId() {
+        return subscriptionId;
+    }
+}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/DefaultRepairEntitlementEvent.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/DefaultRepairEntitlementEvent.java
new file mode 100644
index 0000000..938f99f
--- /dev/null
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/DefaultRepairEntitlementEvent.java
@@ -0,0 +1,118 @@
+/* 
+ * Copyright 2010-2011 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.entitlement.api.timeline;
+
+import java.util.UUID;
+
+import org.codehaus.jackson.annotate.JsonCreator;
+import org.codehaus.jackson.annotate.JsonIgnore;
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.joda.time.DateTime;
+
+import com.ning.billing.entitlement.api.timeline.RepairEntitlementEvent;
+
+public class DefaultRepairEntitlementEvent implements RepairEntitlementEvent {
+
+    private final UUID userToken;
+    private final UUID bundleId;
+    private final UUID accountId;
+    private final DateTime effectiveDate;
+    
+    
+    @JsonCreator
+    public DefaultRepairEntitlementEvent(@JsonProperty("userToken") final UUID userToken,
+            @JsonProperty("accountId") final UUID accountId,
+            @JsonProperty("bundleId") final UUID bundleId,
+            @JsonProperty("effectiveDate") final DateTime effectiveDate) {
+        this.userToken = userToken;
+        this.bundleId = bundleId;
+        this.accountId = accountId;
+        this.effectiveDate = effectiveDate;
+    }
+    
+    @JsonIgnore
+    @Override
+    public BusEventType getBusEventType() {
+        return BusEventType.BUNDLE_REPAIR;
+    }
+
+    @Override
+    public UUID getUserToken() {
+        return userToken;
+    }
+
+    @Override
+    public UUID getBundleId() {
+        return bundleId;
+    }
+
+    @Override
+    public UUID getAccountId() {
+        return accountId;
+    }
+
+    @Override
+    public DateTime getEffectiveDate() {
+        return effectiveDate;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result
+                + ((accountId == null) ? 0 : accountId.hashCode());
+        result = prime * result
+                + ((bundleId == null) ? 0 : bundleId.hashCode());
+        result = prime * result
+                + ((effectiveDate == null) ? 0 : effectiveDate.hashCode());
+        result = prime * result
+                + ((userToken == null) ? 0 : userToken.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        DefaultRepairEntitlementEvent other = (DefaultRepairEntitlementEvent) obj;
+        if (accountId == null) {
+            if (other.accountId != null)
+                return false;
+        } else if (!accountId.equals(other.accountId))
+            return false;
+        if (bundleId == null) {
+            if (other.bundleId != null)
+                return false;
+        } else if (!bundleId.equals(other.bundleId))
+            return false;
+        if (effectiveDate == null) {
+            if (other.effectiveDate != null)
+                return false;
+        } else if (effectiveDate.compareTo(other.effectiveDate) != 0)
+            return false;
+        if (userToken == null) {
+            if (other.userToken != null)
+                return false;
+        } else if (!userToken.equals(other.userToken))
+            return false;
+        return true;
+    }
+}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/DefaultSubscriptionTimeline.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/DefaultSubscriptionTimeline.java
new file mode 100644
index 0000000..4080326
--- /dev/null
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/DefaultSubscriptionTimeline.java
@@ -0,0 +1,288 @@
+/* 
+ * Copyright 2010-2011 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.entitlement.api.timeline;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.Catalog;
+import com.ning.billing.catalog.api.CatalogApiException;
+import com.ning.billing.catalog.api.PhaseType;
+import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
+import com.ning.billing.entitlement.api.user.SubscriptionTransitionData;
+import com.ning.billing.entitlement.events.EntitlementEvent;
+import com.ning.billing.entitlement.events.phase.PhaseEvent;
+import com.ning.billing.entitlement.events.user.ApiEvent;
+import com.ning.billing.entitlement.events.user.ApiEventType;
+
+public class DefaultSubscriptionTimeline implements SubscriptionTimeline  {
+
+    private final UUID id;
+    private final List<ExistingEvent> existingEvents;
+    private final List<NewEvent> newEvents;
+    private final List<DeletedEvent> deletedEvents;    
+    
+    public DefaultSubscriptionTimeline(final UUID id) {
+        this.id = id;
+        this.existingEvents = Collections.<SubscriptionTimeline.ExistingEvent>emptyList();
+        this.deletedEvents =  Collections.<SubscriptionTimeline.DeletedEvent>emptyList();
+        this.newEvents = Collections.<SubscriptionTimeline.NewEvent>emptyList();
+    }
+    
+    public DefaultSubscriptionTimeline(SubscriptionTimeline input) {
+        this.id = input.getId();
+        this.existingEvents = (input.getExistingEvents() != null) ? new ArrayList<SubscriptionTimeline.ExistingEvent>(input.getExistingEvents()) : 
+            Collections.<SubscriptionTimeline.ExistingEvent>emptyList();
+        sortExistingEvent(this.existingEvents);
+        this.deletedEvents = (input.getDeletedEvents() != null) ? new ArrayList<SubscriptionTimeline.DeletedEvent>(input.getDeletedEvents()) : 
+            Collections.<SubscriptionTimeline.DeletedEvent>emptyList();
+        this.newEvents = (input.getNewEvents() != null) ? new ArrayList<SubscriptionTimeline.NewEvent>(input.getNewEvents()) : 
+            Collections.<SubscriptionTimeline.NewEvent>emptyList();
+        sortNewEvent(this.newEvents);
+    }
+    
+     // CTOR for returning events only
+    public DefaultSubscriptionTimeline(SubscriptionDataRepair input, Catalog catalog) throws CatalogApiException {
+        this.id = input.getId();
+        this.existingEvents = toExistingEvents(catalog, input.getActiveVersion(), input.getCategory(), input.getEvents());
+        this.deletedEvents = null;
+        this.newEvents = null;
+    }
+    
+   private List<ExistingEvent> toExistingEvents(final Catalog catalog, final long activeVersion, final ProductCategory category, final List<EntitlementEvent> events) 
+       throws CatalogApiException {
+        
+        List<ExistingEvent> result = new LinkedList<SubscriptionTimeline.ExistingEvent>();
+        
+        String prevProductName = null; 
+        BillingPeriod prevBillingPeriod = null;
+        String prevPriceListName = null;
+        PhaseType prevPhaseType = null;
+        
+        DateTime startDate = null;
+        
+        for (final EntitlementEvent cur : events) {
+            
+            // First active event is used to figure out which catalog version to use.
+            //startDate = (startDate == null && cur.getActiveVersion() == activeVersion) ?  cur.getEffectiveDate() : startDate;
+            
+            // STEPH that needs tp be reviewed if we support mutli version events
+            if (cur.getActiveVersion() != activeVersion) {
+                continue;
+            }
+            startDate = (startDate == null) ?  cur.getEffectiveDate() : startDate;
+            
+            
+            String productName = null; 
+            BillingPeriod billingPeriod = null;
+            String priceListName = null;
+            PhaseType phaseType = null;
+
+            ApiEventType apiType = null;
+            switch (cur.getType()) {
+            case PHASE:
+                PhaseEvent phaseEV = (PhaseEvent) cur;
+                phaseType = catalog.findPhase(phaseEV.getPhase(), cur.getEffectiveDate(), startDate).getPhaseType();
+                productName = prevProductName;
+                billingPeriod = catalog.findPhase(phaseEV.getPhase(), cur.getEffectiveDate(), startDate).getBillingPeriod();
+                priceListName = prevPriceListName;
+                break;
+
+            case API_USER:
+                ApiEvent userEV = (ApiEvent) cur;
+                apiType = userEV.getEventType();
+                Plan plan =  (userEV.getEventPlan() != null) ? catalog.findPlan(userEV.getEventPlan(), cur.getRequestedDate(), startDate) : null;
+                phaseType = (userEV.getEventPlanPhase() != null) ? catalog.findPhase(userEV.getEventPlanPhase(), cur.getEffectiveDate(), startDate).getPhaseType() : prevPhaseType;
+                productName = (plan != null) ? plan.getProduct().getName() : prevProductName;
+                billingPeriod = (userEV.getEventPlanPhase() != null) ? catalog.findPhase(userEV.getEventPlanPhase(), cur.getEffectiveDate(), startDate).getBillingPeriod() : prevBillingPeriod;
+                priceListName = (userEV.getPriceList() != null) ? userEV.getPriceList() : prevPriceListName;
+                break;
+            }
+
+            final SubscriptionTransitionType transitionType = SubscriptionTransitionData.toSubscriptionTransitionType(cur.getType(), apiType);
+            
+            final PlanPhaseSpecifier spec = new PlanPhaseSpecifier(productName, category, billingPeriod, priceListName, phaseType);
+            result.add(new ExistingEvent() {
+                @Override
+                public SubscriptionTransitionType getSubscriptionTransitionType() {
+                    return transitionType;
+                }
+                @Override
+                public DateTime getRequestedDate() {
+                    return cur.getRequestedDate();
+                }
+                @Override
+                public PlanPhaseSpecifier getPlanPhaseSpecifier() {
+                    return spec;
+                }
+                @Override
+                public UUID getEventId() {
+                    return cur.getId();
+                }
+                @Override
+                public DateTime getEffectiveDate() {
+                    return cur.getEffectiveDate();
+                }
+            });
+            
+            prevProductName = productName; 
+            prevBillingPeriod = billingPeriod;
+            prevPriceListName = priceListName;
+            prevPhaseType = phaseType;
+
+        }
+        sortExistingEvent(result);
+        return result;
+    }
+   
+   
+   /*
+   
+   private List<ExistingEvent> toExistingEvents(final Catalog catalog, final long processingVersion, final ProductCategory category, final List<EntitlementEvent> events, List<ExistingEvent> result)
+       throws CatalogApiException {
+       
+       
+       String prevProductName = null; 
+       BillingPeriod prevBillingPeriod = null;
+       String prevPriceListName = null;
+       PhaseType prevPhaseType = null;
+       
+       DateTime startDate = null;
+       
+       for (final EntitlementEvent cur : events) {
+           
+           if (processingVersion != cur.getActiveVersion()) {
+               continue;
+           }
+           
+           // First active event is used to figure out which catalog version to use.
+           startDate = (startDate == null && cur.getActiveVersion() == processingVersion) ?  cur.getEffectiveDate() : startDate;
+           
+           String productName = null; 
+           BillingPeriod billingPeriod = null;
+           String priceListName = null;
+           PhaseType phaseType = null;
+
+           ApiEventType apiType = null;
+           switch (cur.getType()) {
+           case PHASE:
+               PhaseEvent phaseEV = (PhaseEvent) cur;
+               phaseType = catalog.findPhase(phaseEV.getPhase(), cur.getEffectiveDate(), startDate).getPhaseType();
+               productName = prevProductName;
+               billingPeriod = prevBillingPeriod;
+               priceListName = prevPriceListName;
+               break;
+
+           case API_USER:
+               ApiEvent userEV = (ApiEvent) cur;
+               apiType = userEV.getEventType();
+               Plan plan =  (userEV.getEventPlan() != null) ? catalog.findPlan(userEV.getEventPlan(), cur.getRequestedDate(), startDate) : null;
+               phaseType = (userEV.getEventPlanPhase() != null) ? catalog.findPhase(userEV.getEventPlanPhase(), cur.getEffectiveDate(), startDate).getPhaseType() : prevPhaseType;
+               productName = (plan != null) ? plan.getProduct().getName() : prevProductName;
+               billingPeriod = (plan != null) ? plan.getBillingPeriod() : prevBillingPeriod;
+               priceListName = (userEV.getPriceList() != null) ? userEV.getPriceList() : prevPriceListName;
+               break;
+           }
+
+           final SubscriptionTransitionType transitionType = SubscriptionTransitionData.toSubscriptionTransitionType(cur.getType(), apiType);
+           
+           final PlanPhaseSpecifier spec = new PlanPhaseSpecifier(productName, category, billingPeriod, priceListName, phaseType);
+           result.add(new ExistingEvent() {
+               @Override
+               public SubscriptionTransitionType getSubscriptionTransitionType() {
+                   return transitionType;
+               }
+               @Override
+               public DateTime getRequestedDate() {
+                   return cur.getRequestedDate();
+               }
+               @Override
+               public PlanPhaseSpecifier getPlanPhaseSpecifier() {
+                   return spec;
+               }
+               @Override
+               public UUID getEventId() {
+                   return cur.getId();
+               }
+               @Override
+               public DateTime getEffectiveDate() {
+                   return cur.getEffectiveDate();
+               }
+           });
+           prevProductName = productName; 
+           prevBillingPeriod = billingPeriod;
+           prevPriceListName = priceListName;
+           prevPhaseType = phaseType;
+       }
+   }
+   */
+   
+   
+    
+    
+    
+    
+    @Override
+    public UUID getId() {
+        return id;
+    }
+    
+    @Override
+    public List<DeletedEvent> getDeletedEvents() {
+        return deletedEvents;
+    }
+
+    @Override
+    public List<NewEvent> getNewEvents() {
+        return newEvents;
+    }
+    
+    @Override
+    public List<ExistingEvent> getExistingEvents() {
+        return existingEvents;
+    }
+    
+    private void sortExistingEvent(final List<ExistingEvent> events) {
+        if (events != null) {
+            Collections.sort(events, new Comparator<ExistingEvent>() {
+                @Override
+                public int compare(ExistingEvent arg0, ExistingEvent arg1) {
+                    return arg0.getEffectiveDate().compareTo(arg1.getEffectiveDate());
+                }
+            });
+        }
+    }
+    private void sortNewEvent(final List<NewEvent> events) {
+        if (events != null) {
+            Collections.sort(events, new Comparator<NewEvent>() {
+                @Override
+                public int compare(NewEvent arg0, NewEvent arg1) {
+                    return arg0.getRequestedDate().compareTo(arg1.getRequestedDate());
+                }
+            });
+        }
+    }
+}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/RepairEntitlementLifecycleDao.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/RepairEntitlementLifecycleDao.java
new file mode 100644
index 0000000..dff77b3
--- /dev/null
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/RepairEntitlementLifecycleDao.java
@@ -0,0 +1,28 @@
+/* 
+ * Copyright 2010-2011 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.entitlement.api.timeline;
+
+import java.util.List;
+import java.util.UUID;
+
+import com.ning.billing.entitlement.events.EntitlementEvent;
+
+public interface RepairEntitlementLifecycleDao {
+
+    public void initializeRepair(final UUID subscriptionId, final List<EntitlementEvent> initialEvents);
+    
+    public void cleanup();
+}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/RepairSubscriptionApiService.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/RepairSubscriptionApiService.java
new file mode 100644
index 0000000..dcc0c95
--- /dev/null
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/RepairSubscriptionApiService.java
@@ -0,0 +1,36 @@
+/* 
+ * Copyright 2010-2011 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.entitlement.api.timeline;
+
+
+import com.google.inject.Inject;
+import com.google.inject.name.Named;
+import com.ning.billing.catalog.api.CatalogService;
+import com.ning.billing.entitlement.alignment.PlanAligner;
+import com.ning.billing.entitlement.api.SubscriptionApiService;
+import com.ning.billing.entitlement.api.user.DefaultSubscriptionApiService;
+import com.ning.billing.entitlement.engine.dao.EntitlementDao;
+import com.ning.billing.entitlement.glue.DefaultEntitlementModule;
+import com.ning.billing.util.clock.Clock;
+
+public class RepairSubscriptionApiService extends DefaultSubscriptionApiService implements SubscriptionApiService {
+
+    @Inject
+    public RepairSubscriptionApiService(Clock clock, @Named(DefaultEntitlementModule.REPAIR_NAMED) EntitlementDao dao,
+            CatalogService catalogService, PlanAligner planAligner) {
+        super(clock, dao, catalogService, planAligner);
+    }
+}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/RepairSubscriptionFactory.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/RepairSubscriptionFactory.java
new file mode 100644
index 0000000..61933c3
--- /dev/null
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/RepairSubscriptionFactory.java
@@ -0,0 +1,56 @@
+/* 
+ * Copyright 2010-2011 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.entitlement.api.timeline;
+
+import java.util.List;
+
+import com.google.inject.Inject;
+import com.google.inject.name.Named;
+import com.ning.billing.catalog.api.CatalogService;
+import com.ning.billing.entitlement.api.SubscriptionApiService;
+import com.ning.billing.entitlement.api.SubscriptionFactory;
+import com.ning.billing.entitlement.api.user.DefaultSubscriptionApiService;
+import com.ning.billing.entitlement.api.user.DefaultSubscriptionFactory;
+import com.ning.billing.entitlement.api.user.SubscriptionData;
+import com.ning.billing.entitlement.api.user.DefaultSubscriptionFactory.SubscriptionBuilder;
+import com.ning.billing.entitlement.engine.addon.AddonUtils;
+import com.ning.billing.entitlement.engine.dao.EntitlementDao;
+import com.ning.billing.entitlement.events.EntitlementEvent;
+import com.ning.billing.entitlement.glue.DefaultEntitlementModule;
+import com.ning.billing.util.clock.Clock;
+
+public class RepairSubscriptionFactory extends DefaultSubscriptionFactory implements SubscriptionFactory {
+
+    private final AddonUtils addonUtils;
+    private final EntitlementDao repairDao;
+    
+    @Inject
+    public RepairSubscriptionFactory(@Named(DefaultEntitlementModule.REPAIR_NAMED) SubscriptionApiService apiService,
+            @Named(DefaultEntitlementModule.REPAIR_NAMED) EntitlementDao dao,
+            Clock clock, CatalogService catalogService, AddonUtils addonUtils) {
+        super(apiService, clock, catalogService);
+        this.addonUtils = addonUtils;
+        this.repairDao = dao;
+    }
+     
+    @Override
+    public SubscriptionData createSubscription(SubscriptionBuilder builder,
+            List<EntitlementEvent> events) {
+        SubscriptionData subscription = new SubscriptionDataRepair(builder, events, apiService, repairDao, clock, addonUtils, catalogService);
+        subscription.rebuildTransitions(events, catalogService.getFullCatalog());
+        return subscription;
+    }
+}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/SubscriptionDataRepair.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/SubscriptionDataRepair.java
new file mode 100644
index 0000000..7f526d4
--- /dev/null
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/SubscriptionDataRepair.java
@@ -0,0 +1,210 @@
+/* 
+ * Copyright 2010-2011 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.entitlement.api.timeline;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+import org.joda.time.DateTime;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.Collections2;
+import com.ning.billing.ErrorCode;
+import com.ning.billing.catalog.api.Catalog;
+import com.ning.billing.catalog.api.CatalogApiException;
+import com.ning.billing.catalog.api.CatalogService;
+import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.catalog.api.Product;
+import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.entitlement.api.SubscriptionApiService;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
+import com.ning.billing.entitlement.api.user.SubscriptionData;
+import com.ning.billing.entitlement.api.user.DefaultSubscriptionFactory.SubscriptionBuilder;
+import com.ning.billing.entitlement.api.user.SubscriptionTransitionData;
+import com.ning.billing.entitlement.engine.addon.AddonUtils;
+import com.ning.billing.entitlement.engine.dao.EntitlementDao;
+import com.ning.billing.entitlement.events.EntitlementEvent;
+import com.ning.billing.entitlement.events.EntitlementEvent.EventType;
+import com.ning.billing.entitlement.events.user.ApiEventBuilder;
+import com.ning.billing.entitlement.events.user.ApiEventCancel;
+
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.clock.Clock;
+
+public class SubscriptionDataRepair extends SubscriptionData {
+
+    private final AddonUtils addonUtils;
+    private final Clock clock;
+    private final EntitlementDao repairDao;
+    private final CatalogService catalogService;
+    
+    private final List<EntitlementEvent> initialEvents;
+
+    // Low level events are ONLY used for Repair APIs
+    protected List<EntitlementEvent> events;
+
+
+    public SubscriptionDataRepair(SubscriptionBuilder builder, List<EntitlementEvent> initialEvents, SubscriptionApiService apiService,
+            EntitlementDao dao, Clock clock, AddonUtils addonUtils, CatalogService catalogService) {
+        super(builder, apiService, clock);
+        this.repairDao = dao;
+        this.addonUtils = addonUtils;
+        this.clock = clock;
+        this.catalogService = catalogService;
+        this.initialEvents = initialEvents;
+    }
+
+    
+    DateTime getLastUserEventEffectiveDate() {
+        DateTime res = null;
+        for (EntitlementEvent cur : events) {
+            if (cur.getActiveVersion() != getActiveVersion()) {
+                break;
+            }
+            if (cur.getType() == EventType.PHASE) {
+                continue;
+            }
+            res = cur.getEffectiveDate();
+        }
+        return res;
+    }
+
+    public void addNewRepairEvent(final DefaultNewEvent input, final SubscriptionDataRepair baseSubscription, final List<SubscriptionDataRepair> addonSubscriptions, final CallContext context)
+    throws EntitlementRepairException {
+
+        try {
+            final PlanPhaseSpecifier spec = input.getPlanPhaseSpecifier();
+            switch(input.getSubscriptionTransitionType()) {
+            case CREATE:
+            case RE_CREATE:
+                recreate(spec, input.getRequestedDate(), context);
+                checkAddonRights(baseSubscription);
+                break;
+            case CHANGE:
+                changePlan(spec.getProductName(), spec.getBillingPeriod(), spec.getPriceListName(), input.getRequestedDate(), context);
+                checkAddonRights(baseSubscription);
+                trickleDownBPEffectForAddon(addonSubscriptions, getLastUserEventEffectiveDate(), context);
+                break;
+            case CANCEL:
+                cancel(input.getRequestedDate(), false, context);
+                trickleDownBPEffectForAddon(addonSubscriptions, getLastUserEventEffectiveDate(), context);
+                break;
+            case PHASE:
+                break;
+            default:
+                throw new EntitlementRepairException(ErrorCode.ENT_REPAIR_UNKNOWN_TYPE, input.getSubscriptionTransitionType(), id);
+            }
+        } catch (EntitlementUserApiException e) {
+            throw new EntitlementRepairException(e);
+        } catch (CatalogApiException e) {
+            throw new EntitlementRepairException(e);
+        }
+    }
+
+
+    public void addFutureAddonCancellation(List<SubscriptionDataRepair> addOnSubscriptionInRepair, final CallContext context) {
+
+        if (category != ProductCategory.BASE) {
+            return;
+        }
+
+        SubscriptionTransitionData pendingTransition = getPendingTransitionData();
+        if (pendingTransition == null) {
+            return;
+        }
+        Product baseProduct = (pendingTransition.getTransitionType() == SubscriptionTransitionType.CANCEL) ? null : 
+            pendingTransition.getNextPlan().getProduct();
+
+        addAddonCancellationIfRequired(addOnSubscriptionInRepair, baseProduct, pendingTransition.getEffectiveTransitionTime(), context);
+    }
+    
+    private void trickleDownBPEffectForAddon(final List<SubscriptionDataRepair> addOnSubscriptionInRepair, final DateTime effectiveDate, final CallContext context)
+     throws EntitlementUserApiException {
+
+        if (category != ProductCategory.BASE) {
+            return;
+        }
+
+        Product baseProduct = (getState() == SubscriptionState.CANCELLED ) ?
+                null : getCurrentPlan().getProduct();
+        addAddonCancellationIfRequired(addOnSubscriptionInRepair, baseProduct, effectiveDate, context);
+    }
+    
+    
+    
+    private void addAddonCancellationIfRequired(final List<SubscriptionDataRepair> addOnSubscriptionInRepair, Product baseProduct, final DateTime effectiveDate, final CallContext context) {
+
+        DateTime now = clock.getUTCNow();
+        Iterator<SubscriptionDataRepair> it = addOnSubscriptionInRepair.iterator();
+        while (it.hasNext()) {
+            SubscriptionDataRepair cur = it.next();
+            if (cur.getState() == SubscriptionState.CANCELLED ||
+                    cur.getCategory() != ProductCategory.ADD_ON) {
+                continue;
+            }
+            Plan addonCurrentPlan = cur.getCurrentPlan();
+            if (baseProduct == null ||
+                    addonUtils.isAddonIncluded(baseProduct, addonCurrentPlan) ||
+                    ! addonUtils.isAddonAvailable(baseProduct, addonCurrentPlan)) {
+
+                EntitlementEvent cancelEvent = new ApiEventCancel(new ApiEventBuilder()
+                .setSubscriptionId(cur.getId())
+                .setActiveVersion(cur.getActiveVersion())
+                .setProcessedDate(now)
+                .setEffectiveDate(effectiveDate)
+                .setRequestedDate(now)
+                .setUserToken(context.getUserToken())
+                .setFromDisk(true));
+                repairDao.cancelSubscription(cur.getId(), cancelEvent, context, 0);
+                cur.rebuildTransitions(repairDao.getEventsForSubscription(cur.getId()), catalogService.getFullCatalog());
+            }
+        }
+    }
+
+    private void checkAddonRights(final SubscriptionDataRepair baseSubscription) 
+        throws EntitlementUserApiException, CatalogApiException  {
+        if (category == ProductCategory.ADD_ON) {
+            addonUtils.checkAddonCreationRights(baseSubscription, getCurrentPlan());
+        }
+    }
+    
+    public void rebuildTransitions(final List<EntitlementEvent> inputEvents, final Catalog catalog) {
+        this.events = inputEvents;
+        super.rebuildTransitions(inputEvents, catalog);
+    }
+
+    public List<EntitlementEvent> getEvents() {
+        return events;
+    }
+
+    public List<EntitlementEvent> getInitialEvents() {
+        return initialEvents;
+    }
+
+    
+    public Collection<EntitlementEvent> getNewEvents() {
+        Collection<EntitlementEvent> newEvents  = Collections2.filter(events, new Predicate<EntitlementEvent>() {
+            @Override
+            public boolean apply(EntitlementEvent input) {
+                return ! initialEvents.contains(input);
+            }
+        });
+        return newEvents;
+    }
+}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/DefaultEntitlementUserApi.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/DefaultEntitlementUserApi.java
index 5554c9c..7d92852 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/DefaultEntitlementUserApi.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/DefaultEntitlementUserApi.java
@@ -16,26 +16,30 @@
 
 package com.ning.billing.entitlement.api.user;
 
+import java.util.LinkedList;
 import java.util.List;
 import java.util.UUID;
 
-import com.ning.billing.catalog.api.Catalog;
-import com.ning.billing.util.callcontext.CallContext;
 import org.joda.time.DateTime;
+
 import com.google.inject.Inject;
 import com.ning.billing.ErrorCode;
+import com.ning.billing.catalog.api.Catalog;
 import com.ning.billing.catalog.api.CatalogApiException;
 import com.ning.billing.catalog.api.CatalogService;
 import com.ning.billing.catalog.api.Plan;
 import com.ning.billing.catalog.api.PlanPhase;
 import com.ning.billing.catalog.api.PlanPhaseSpecifier;
 import com.ning.billing.catalog.api.PriceListSet;
-import com.ning.billing.catalog.api.Product;
+import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.entitlement.api.SubscriptionFactory;
 import com.ning.billing.entitlement.api.user.Subscription.SubscriptionState;
-import com.ning.billing.entitlement.api.user.SubscriptionFactory.SubscriptionBuilder;
+import com.ning.billing.entitlement.api.user.DefaultSubscriptionFactory.SubscriptionBuilder;
+import com.ning.billing.entitlement.api.user.SubscriptionStatusDryRun.DryRunChangeReason;
 import com.ning.billing.entitlement.engine.addon.AddonUtils;
 import com.ning.billing.entitlement.engine.dao.EntitlementDao;
 import com.ning.billing.entitlement.exceptions.EntitlementError;
+import com.ning.billing.util.callcontext.CallContext;
 import com.ning.billing.util.clock.Clock;
 import com.ning.billing.util.clock.DefaultClock;
 
@@ -43,13 +47,13 @@ public class DefaultEntitlementUserApi implements EntitlementUserApi {
     private final Clock clock;
     private final EntitlementDao dao;
     private final CatalogService catalogService;
-    private final SubscriptionApiService apiService;
+    private final DefaultSubscriptionApiService apiService;
     private final AddonUtils addonUtils;
     private final SubscriptionFactory subscriptionFactory;
 
     @Inject
     public DefaultEntitlementUserApi(Clock clock, EntitlementDao dao, CatalogService catalogService,
-            SubscriptionApiService apiService, final SubscriptionFactory subscriptionFactory, AddonUtils addonUtils) {
+            DefaultSubscriptionApiService apiService, final SubscriptionFactory subscriptionFactory, AddonUtils addonUtils) {
         super();
         this.clock = clock;
         this.apiService = apiService;
@@ -59,19 +63,32 @@ public class DefaultEntitlementUserApi implements EntitlementUserApi {
         this.subscriptionFactory = subscriptionFactory;
     }
 
+    
     @Override
-    public SubscriptionBundle getBundleFromId(UUID id) {
-        return dao.getSubscriptionBundleFromId(id);
+    public SubscriptionBundle getBundleFromId(UUID id) throws EntitlementUserApiException {
+        SubscriptionBundle result = dao.getSubscriptionBundleFromId(id);
+        if (result == null) {
+            throw new EntitlementUserApiException(ErrorCode.ENT_GET_INVALID_BUNDLE_ID, id.toString());
+        }
+        return result;
     }
 
     @Override
-    public Subscription getSubscriptionFromId(UUID id) {
-        return dao.getSubscriptionFromId(subscriptionFactory, id);
+    public Subscription getSubscriptionFromId(UUID id) throws EntitlementUserApiException {
+        Subscription result = dao.getSubscriptionFromId(subscriptionFactory, id);
+        if (result == null) {
+            throw new EntitlementUserApiException(ErrorCode.ENT_INVALID_SUBSCRIPTION_ID, id);
+        }
+        return result;
     }
 
     @Override
-    public SubscriptionBundle getBundleForKey(String bundleKey) {
-        return dao.getSubscriptionBundleFromKey(bundleKey);
+    public SubscriptionBundle getBundleForKey(String bundleKey) throws EntitlementUserApiException {
+        SubscriptionBundle result =  dao.getSubscriptionBundleFromKey(bundleKey);
+        if (result == null) {
+            throw new EntitlementUserApiException(ErrorCode.ENT_GET_INVALID_BUNDLE_KEY, bundleKey);
+        }
+        return result;
     }
 
     @Override
@@ -90,9 +107,18 @@ public class DefaultEntitlementUserApi implements EntitlementUserApi {
     }
 
     @Override
+    public Subscription getBaseSubscription(UUID bundleId) throws EntitlementUserApiException {
+        Subscription result =  dao.getBaseSubscription(subscriptionFactory, bundleId);
+        if (result == null) {
+            throw new EntitlementUserApiException(ErrorCode.ENT_GET_NO_SUCH_BASE_SUBSCRIPTION, bundleId);
+        }
+        return result;
+    }
+    
+
     public SubscriptionBundle createBundleForAccount(UUID accountId, String bundleName, CallContext context)
     throws EntitlementUserApiException {
-        SubscriptionBundleData bundle = new SubscriptionBundleData(bundleName, accountId);
+        SubscriptionBundleData bundle = new SubscriptionBundleData(bundleName, accountId, clock.getUTCNow());
         return dao.createSubscriptionBundle(bundle, context);
     }
 
@@ -141,7 +167,10 @@ public class DefaultEntitlementUserApi implements EntitlementUserApi {
                 if (baseSubscription == null) {
                     throw new EntitlementUserApiException(ErrorCode.ENT_CREATE_NO_BP, bundleId);
                 }
-                checkAddonCreationRights(baseSubscription, plan);
+                if (effectiveDate.isBefore(baseSubscription.getStartDate())) {
+                    throw new EntitlementUserApiException(ErrorCode.ENT_INVALID_REQUESTED_DATE, requestedDate.toString());
+                }
+                addonUtils.checkAddonCreationRights(baseSubscription, plan);
                 bundleStartDate = baseSubscription.getStartDate();
                 break;
             case STANDALONE:
@@ -157,7 +186,7 @@ public class DefaultEntitlementUserApi implements EntitlementUserApi {
             }
 
             SubscriptionData subscription = apiService.createPlan(new SubscriptionBuilder()
-                .setId(UUID.randomUUID())
+                 .setId(UUID.randomUUID())
                 .setBundleId(bundleId)
                 .setCategory(plan.getProduct().getCategory())
                 .setBundleStartDate(bundleStartDate)
@@ -171,39 +200,58 @@ public class DefaultEntitlementUserApi implements EntitlementUserApi {
     }
 
 
-    private void checkAddonCreationRights(SubscriptionData baseSubscription, Plan targetAddOnPlan)
-        throws EntitlementUserApiException, CatalogApiException {
-
-        if (baseSubscription.getState() != SubscriptionState.ACTIVE) {
-            throw new EntitlementUserApiException(ErrorCode.ENT_CREATE_AO_BP_NON_ACTIVE, targetAddOnPlan.getName());
+    @Override
+    public DateTime getNextBillingDate(UUID accountId) {
+        List<SubscriptionBundle> bundles = getBundlesForAccount(accountId);
+        DateTime result = null;
+        for(SubscriptionBundle bundle : bundles) {
+            List<Subscription> subscriptions = getSubscriptionsForBundle(bundle.getId());
+            for(Subscription subscription : subscriptions) {
+                DateTime chargedThruDate = subscription.getChargedThroughDate();
+                if(result == null ||
+                        (chargedThruDate != null && chargedThruDate.isBefore(result))) {
+                    result = subscription.getChargedThroughDate();
+                }
+            }
         }
+        return result;
+    }
 
-        Product baseProduct = baseSubscription.getCurrentPlan().getProduct();
-        if (addonUtils.isAddonIncluded(baseProduct, targetAddOnPlan)) {
-            throw new EntitlementUserApiException(ErrorCode.ENT_CREATE_AO_ALREADY_INCLUDED,
-                    targetAddOnPlan.getName(), baseSubscription.getCurrentPlan().getProduct().getName());
-        }
 
-        if (!addonUtils.isAddonAvailable(baseProduct, targetAddOnPlan)) {
-            throw new EntitlementUserApiException(ErrorCode.ENT_CREATE_AO_NOT_AVAILABLE,
-                    targetAddOnPlan.getName(), baseSubscription.getCurrentPlan().getProduct().getName());
+    @Override
+    public List<SubscriptionStatusDryRun> getDryRunChangePlanStatus(UUID subscriptionId, String baseProductName, DateTime requestedDate)
+            throws EntitlementUserApiException {
+
+        Subscription subscription = dao.getSubscriptionFromId(subscriptionFactory, subscriptionId);
+        if (subscription == null) {
+            throw new EntitlementUserApiException(ErrorCode.ENT_INVALID_SUBSCRIPTION_ID, subscriptionId);
+        }
+        if (subscription.getCategory() != ProductCategory.BASE) {
+            throw new EntitlementUserApiException(ErrorCode.ENT_CHANGE_DRY_RUN_NOT_BP);
+        }
+        
+        List<SubscriptionStatusDryRun> result = new LinkedList<SubscriptionStatusDryRun>();
+        
+        List<Subscription> bundleSubscriptions = dao.getSubscriptions(subscriptionFactory, subscription.getBundleId());
+        for (Subscription cur : bundleSubscriptions) {
+            if (cur.getId().equals(subscriptionId)) {
+                continue;
+            }
+            
+            DryRunChangeReason reason = null;
+            if (addonUtils.isAddonIncludedFromProdName(baseProductName, requestedDate, cur.getCurrentPlan())) {
+                reason = DryRunChangeReason.AO_INCLUDED_IN_NEW_PLAN;
+            } else if (addonUtils.isAddonAvailableFromProdName(baseProductName, requestedDate, cur.getCurrentPlan())) {
+                reason = DryRunChangeReason.AO_AVAILABLE_IN_NEW_PLAN;
+            } else {
+                reason = DryRunChangeReason.AO_NOT_AVAILABLE_IN_NEW_PLAN;
+            }
+            SubscriptionStatusDryRun status = new DefaultSubscriptionStatusDryRun(cur.getId(), 
+                    cur.getCurrentPlan().getProduct().getName(), cur.getCurrentPhase().getPhaseType(),
+                    cur.getCurrentPlan().getBillingPeriod(),
+                    cur.getCurrentPriceList().getName(), reason);
+            result.add(status);
         }
+        return result;
     }
-
-	@Override
-	public DateTime getNextBillingDate(UUID accountId) {
-		List<SubscriptionBundle> bundles = getBundlesForAccount(accountId);
-		DateTime result = null;
-		for(SubscriptionBundle bundle : bundles) {
-			List<Subscription> subscriptions = getSubscriptionsForBundle(bundle.getId());
-			for(Subscription subscription : subscriptions) {
-				DateTime chargedThruDate = subscription.getChargedThroughDate();
-				if(result == null ||
-						(chargedThruDate != null && chargedThruDate.isBefore(result))) {
-					result = subscription.getChargedThroughDate();
-				}
-			}
-		}
-		return result;
-	}
 }
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/DefaultSubscriptionEvent.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/DefaultSubscriptionEvent.java
new file mode 100644
index 0000000..3cf74ec
--- /dev/null
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/DefaultSubscriptionEvent.java
@@ -0,0 +1,371 @@
+/* 
+ * Copyright 2010-2011 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.entitlement.api.user;
+
+import java.util.UUID;
+
+import org.codehaus.jackson.annotate.JsonCreator;
+import org.codehaus.jackson.annotate.JsonIgnore;
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.joda.time.DateTime;
+
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
+import com.ning.billing.entitlement.api.user.Subscription.SubscriptionState;
+
+public class DefaultSubscriptionEvent implements SubscriptionEvent {
+
+    private final Long totalOrdering;
+    private final UUID subscriptionId;
+    private final UUID bundleId;
+    private final UUID eventId;
+    private final DateTime requestedTransitionTime;
+    private final DateTime effectiveTransitionTime;
+    private final SubscriptionState previousState;
+    private final String previousPriceList;
+    private final String previousPlan;
+    private final String previousPhase;
+    private final SubscriptionState nextState;
+    private final String nextPriceList;
+    private final String nextPlan;
+    private final String nextPhase;
+    private final Integer remainingEventsForUserOperation;
+    private final UUID userToken;
+    private final SubscriptionTransitionType transitionType;
+
+    private final DateTime startDate;
+    
+    public DefaultSubscriptionEvent(final SubscriptionTransitionData in, final DateTime startDate) {
+        this(in.getId(),
+                in.getSubscriptionId(),
+                in.getBundleId(),
+                in.getRequestedTransitionTime(),
+                in.getEffectiveTransitionTime(),
+                in.getPreviousState(),
+                (in.getPreviousPlan() != null ) ? in.getPreviousPlan().getName() : null,
+                (in.getPreviousPhase() != null) ? in.getPreviousPhase().getName() : null,
+                (in.getPreviousPriceList() != null) ? in.getPreviousPriceList().getName() : null,
+                in.getNextState(),
+                (in.getNextPlan() != null) ? in.getNextPlan().getName() : null,
+                (in.getNextPhase() != null) ? in.getNextPhase().getName() : null,
+                (in.getNextPriceList() != null) ? in.getNextPriceList().getName() : null,
+                in.getTotalOrdering(),
+                in.getUserToken(),
+                in.getTransitionType(),
+                in.getRemainingEventsForUserOperation(),
+                startDate);
+    }
+    
+    @JsonCreator
+    public DefaultSubscriptionEvent(@JsonProperty("eventId") UUID eventId,
+            @JsonProperty("subscriptionId") UUID subscriptionId,
+            @JsonProperty("bundleId") UUID bundleId,
+            @JsonProperty("requestedTransitionTime") DateTime requestedTransitionTime,
+            @JsonProperty("effectiveTransitionTime") DateTime effectiveTransitionTime,
+            @JsonProperty("previousState") SubscriptionState previousState,
+            @JsonProperty("previousPlan") String previousPlan,
+            @JsonProperty("previousPhase") String previousPhase,
+            @JsonProperty("previousPriceList") String previousPriceList,
+            @JsonProperty("nextState") SubscriptionState nextState,
+            @JsonProperty("nextPlan") String nextPlan,
+            @JsonProperty("nextPhase") String nextPhase,
+            @JsonProperty("nextPriceList") String nextPriceList,
+            @JsonProperty("totalOrdering") Long totalOrdering,
+            @JsonProperty("userToken") UUID userToken,
+            @JsonProperty("transitionType") SubscriptionTransitionType transitionType,
+            @JsonProperty("remainingEventsForUserOperation") Integer remainingEventsForUserOperation,
+            @JsonProperty("startDate") DateTime startDate) {
+        super();
+        this.eventId = eventId;
+        this.subscriptionId = subscriptionId;
+        this.bundleId = bundleId;
+        this.requestedTransitionTime = requestedTransitionTime;
+        this.effectiveTransitionTime = effectiveTransitionTime;
+        this.previousState = previousState;
+        this.previousPriceList = previousPriceList;
+        this.previousPlan = previousPlan;
+        this.previousPhase = previousPhase;
+        this.nextState = nextState;
+        this.nextPlan = nextPlan;
+        this.nextPriceList = nextPriceList;
+        this.nextPhase = nextPhase;
+        this.totalOrdering = totalOrdering;
+        this.userToken = userToken;
+        this.transitionType = transitionType;
+        this.remainingEventsForUserOperation = remainingEventsForUserOperation;
+        this.startDate = startDate;
+    }
+    
+    @JsonIgnore
+    @Override
+    public BusEventType getBusEventType() {
+        return BusEventType.SUBSCRIPTION_TRANSITION;
+    }
+
+    @JsonProperty("eventId")
+    @Override
+    public UUID getId() {
+        return eventId;
+    }
+
+    @Override
+    public UUID getSubscriptionId() {
+        return subscriptionId;
+    }
+
+    @Override
+    public UUID getBundleId() {
+        return bundleId;
+    }
+
+
+    @Override
+    public SubscriptionState getPreviousState() {
+        return previousState;
+    }
+
+    @Override
+    public String getPreviousPlan() {
+        return previousPlan;
+    }
+
+    @Override
+    public String getPreviousPhase() {
+        return previousPhase;
+    }
+
+    @Override
+    public String getNextPlan() {
+        return nextPlan;
+    }
+
+    @Override
+    public String getNextPhase() {
+        return nextPhase;
+    }
+
+    @Override
+    public SubscriptionState getNextState() {
+        return nextState;
+    }
+
+
+    @Override
+    public String getPreviousPriceList() {
+        return previousPriceList;
+    }
+
+    @Override
+    public String getNextPriceList() {
+        return nextPriceList;
+    }
+    
+    @Override
+    public UUID getUserToken() {
+        return userToken;
+    }
+    
+    @Override
+    public Integer getRemainingEventsForUserOperation() {
+        return remainingEventsForUserOperation;
+    }
+
+
+    @Override
+    public DateTime getRequestedTransitionTime() {
+        return requestedTransitionTime;
+    }
+
+    @Override
+    public DateTime getEffectiveTransitionTime() {
+        return effectiveTransitionTime;
+    }
+
+    @Override
+    public Long getTotalOrdering() {
+        return totalOrdering;
+    }
+
+    @Override
+    public SubscriptionTransitionType getTransitionType() {
+        return transitionType;
+    }
+    
+    @JsonProperty("startDate")
+    @Override
+    public DateTime getSubscriptionStartDate() {
+        return startDate;
+    }
+
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result
+                + ((bundleId == null) ? 0 : bundleId.hashCode());
+        result = prime
+                * result
+                + ((effectiveTransitionTime == null) ? 0
+                        : effectiveTransitionTime.hashCode());
+        result = prime * result + ((eventId == null) ? 0 : eventId.hashCode());
+        result = prime * result
+                + ((nextPhase == null) ? 0 : nextPhase.hashCode());
+        result = prime * result
+                + ((nextPlan == null) ? 0 : nextPlan.hashCode());
+        result = prime * result
+                + ((nextPriceList == null) ? 0 : nextPriceList.hashCode());
+        result = prime * result
+                + ((nextState == null) ? 0 : nextState.hashCode());
+        result = prime * result
+                + ((previousPhase == null) ? 0 : previousPhase.hashCode());
+        result = prime * result
+                + ((previousPlan == null) ? 0 : previousPlan.hashCode());
+        result = prime
+                * result
+                + ((previousPriceList == null) ? 0 : previousPriceList
+                        .hashCode());
+        result = prime * result
+                + ((previousState == null) ? 0 : previousState.hashCode());
+        result = prime
+                * result
+                + ((remainingEventsForUserOperation == null) ? 0
+                        : remainingEventsForUserOperation.hashCode());
+        result = prime
+                * result
+                + ((requestedTransitionTime == null) ? 0
+                        : requestedTransitionTime.hashCode());
+        result = prime * result
+                + ((subscriptionId == null) ? 0 : subscriptionId.hashCode());
+        result = prime * result
+                + ((totalOrdering == null) ? 0 : totalOrdering.hashCode());
+        result = prime * result
+                + ((transitionType == null) ? 0 : transitionType.hashCode());
+        result = prime * result
+                + ((userToken == null) ? 0 : userToken.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        DefaultSubscriptionEvent other = (DefaultSubscriptionEvent) obj;
+        if (bundleId == null) {
+            if (other.bundleId != null)
+                return false;
+        } else if (!bundleId.equals(other.bundleId))
+            return false;
+        if (effectiveTransitionTime == null) {
+            if (other.effectiveTransitionTime != null)
+                return false;
+        } else if (effectiveTransitionTime
+                .compareTo(other.effectiveTransitionTime) != 0)
+            return false;
+        if (eventId == null) {
+            if (other.eventId != null)
+                return false;
+        } else if (!eventId.equals(other.eventId))
+            return false;
+        if (nextPhase == null) {
+            if (other.nextPhase != null)
+                return false;
+        } else if (!nextPhase.equals(other.nextPhase))
+            return false;
+        if (nextPlan == null) {
+            if (other.nextPlan != null)
+                return false;
+        } else if (!nextPlan.equals(other.nextPlan))
+            return false;
+        if (nextPriceList == null) {
+            if (other.nextPriceList != null)
+                return false;
+        } else if (!nextPriceList.equals(other.nextPriceList))
+            return false;
+        if (nextState != other.nextState)
+            return false;
+        if (previousPhase == null) {
+            if (other.previousPhase != null)
+                return false;
+        } else if (!previousPhase.equals(other.previousPhase))
+            return false;
+        if (previousPlan == null) {
+            if (other.previousPlan != null)
+                return false;
+        } else if (!previousPlan.equals(other.previousPlan))
+            return false;
+        if (previousPriceList == null) {
+            if (other.previousPriceList != null)
+                return false;
+        } else if (!previousPriceList.equals(other.previousPriceList))
+            return false;
+        if (previousState != other.previousState)
+            return false;
+        if (remainingEventsForUserOperation == null) {
+            if (other.remainingEventsForUserOperation != null)
+                return false;
+        } else if (!remainingEventsForUserOperation
+                .equals(other.remainingEventsForUserOperation))
+            return false;
+        if (requestedTransitionTime == null) {
+            if (other.requestedTransitionTime != null)
+                return false;
+        } else if (requestedTransitionTime
+                .compareTo(other.requestedTransitionTime) != 0)
+            return false;
+        if (subscriptionId == null) {
+            if (other.subscriptionId != null)
+                return false;
+        } else if (!subscriptionId.equals(other.subscriptionId))
+            return false;
+        if (totalOrdering == null) {
+            if (other.totalOrdering != null)
+                return false;
+        } else if (!totalOrdering.equals(other.totalOrdering))
+            return false;
+        if (transitionType != other.transitionType)
+            return false;
+        if (userToken == null) {
+            if (other.userToken != null)
+                return false;
+        } else if (!userToken.equals(other.userToken))
+            return false;
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        return "DefaultSubscriptionEvent [transitionType=" + transitionType
+                + ", effectiveTransitionTime=" + effectiveTransitionTime        
+                + ", totalOrdering=" + totalOrdering
+                + ", subscriptionId=" + subscriptionId + ", bundleId="
+                + bundleId + ", eventId=" + eventId
+                + ", requestedTransitionTime=" + requestedTransitionTime
+                + ", previousState=" + previousState + ", previousPriceList="
+                + previousPriceList + ", previousPlan=" + previousPlan
+                + ", previousPhase=" + previousPhase + ", nextState="
+                + nextState + ", nextPriceList=" + nextPriceList
+                + ", nextPlan=" + nextPlan + ", nextPhase=" + nextPhase
+                + ", remainingEventsForUserOperation="
+                + remainingEventsForUserOperation + ", userToken=" + userToken
+                + ", startDate=" + startDate + "]";
+                
+    }
+    
+}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/DefaultSubscriptionStatusDryRun.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/DefaultSubscriptionStatusDryRun.java
new file mode 100644
index 0000000..9971f41
--- /dev/null
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/DefaultSubscriptionStatusDryRun.java
@@ -0,0 +1,75 @@
+/* 
+ * Copyright 2010-2011 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.entitlement.api.user;
+
+import java.util.UUID;
+
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.PhaseType;
+
+public class DefaultSubscriptionStatusDryRun implements SubscriptionStatusDryRun {
+        
+    private final UUID id;
+    private final String productName;
+    private final PhaseType phaseType;
+    private final BillingPeriod billingPeriod;
+    private final String priceList;
+    private final DryRunChangeReason reason;
+    
+    
+    public DefaultSubscriptionStatusDryRun(final UUID id, final String productName,
+            final PhaseType phaseType, final BillingPeriod billingPeriod, final String priceList,
+            final DryRunChangeReason reason) {
+        super();
+        this.id = id;
+        this.productName = productName;
+        this.phaseType = phaseType;
+        this.billingPeriod = billingPeriod;
+        this.priceList = priceList;
+        this.reason = reason;
+    }
+
+    @Override
+    public UUID getId() {
+        return id;
+    }
+
+    @Override
+    public String getProductName() {
+        return productName;
+    }
+
+    @Override
+    public PhaseType getPhaseType() {
+        return phaseType;
+    }
+
+    
+    @Override
+    public BillingPeriod getBillingPeriod() {
+        return billingPeriod;
+    }
+
+    @Override
+    public String getPriceList() {
+        return priceList;
+    }
+
+    @Override
+    public DryRunChangeReason getReason() {
+        return reason;
+    }
+}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionBundleData.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionBundleData.java
index 8cc2573..4da1fe0 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionBundleData.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionBundleData.java
@@ -16,9 +16,12 @@
 
 package com.ning.billing.entitlement.api.user;
 
+import java.util.UUID;
+
 import org.joda.time.DateTime;
 
-import java.util.UUID;
+import com.ning.billing.junction.api.BlockingState;
+import com.ning.billing.overdue.OverdueState;
 
 public class SubscriptionBundleData implements SubscriptionBundle {
 
@@ -26,17 +29,25 @@ public class SubscriptionBundleData implements SubscriptionBundle {
     private final String key;
     private final UUID accountId;
     private final DateTime startDate;
+    private final DateTime lastSysTimeUpdate; 
+    private final OverdueState<SubscriptionBundle> overdueState;
+    
+    public SubscriptionBundleData(String name, UUID accountId, DateTime startDate) {
+        this(UUID.randomUUID(), name, accountId, startDate, startDate);
+    }
 
-    public SubscriptionBundleData(String name, UUID accountId) {
-        this(UUID.randomUUID(), name, accountId, null);
+    public SubscriptionBundleData(UUID id, String key, UUID accountId, DateTime startDate, DateTime lastSysUpdate) {
+        this(id, key, accountId, startDate, lastSysUpdate, null);
     }
 
-    public SubscriptionBundleData(UUID id, String key, UUID accountId, DateTime startDate) {
+    public SubscriptionBundleData(UUID id, String key, UUID accountId, DateTime startDate, DateTime lastSysUpdate, OverdueState<SubscriptionBundle> overdueState) {
         super();
         this.id = id;
         this.key = key;
         this.accountId = accountId;
         this.startDate = startDate;
+        this.lastSysTimeUpdate = lastSysUpdate;
+        this.overdueState = overdueState;
     }
 
     @Override
@@ -54,10 +65,23 @@ public class SubscriptionBundleData implements SubscriptionBundle {
         return accountId;
     }
 
-
     // STEPH do we need it ? and should we return that and when is that populated/updated?
     @Override
     public DateTime getStartDate() {
         return startDate;
     }
+    
+    public DateTime getLastSysUpdateTime() {
+        return lastSysTimeUpdate;
+    }
+    
+    @Override
+    public OverdueState<SubscriptionBundle> getOverdueState() {
+        return overdueState;
+    }
+
+    @Override
+    public BlockingState getBlockingState() {
+        throw new UnsupportedOperationException();
+    }
 }
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionData.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionData.java
index a67c502..815410a 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionData.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionData.java
@@ -16,6 +16,19 @@
 
 package com.ning.billing.entitlement.api.user;
 
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.UUID;
+
+import javax.annotation.Nullable;
+
+import com.ning.billing.util.dao.ObjectType;
+import org.joda.time.DateTime;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import com.ning.billing.catalog.api.ActionPolicy;
 import com.ning.billing.catalog.api.BillingPeriod;
 import com.ning.billing.catalog.api.Catalog;
@@ -23,9 +36,11 @@ import com.ning.billing.catalog.api.CatalogApiException;
 import com.ning.billing.catalog.api.Plan;
 import com.ning.billing.catalog.api.PlanPhase;
 import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.catalog.api.PriceList;
 import com.ning.billing.catalog.api.ProductCategory;
-import com.ning.billing.entitlement.api.user.SubscriptionFactory.SubscriptionBuilder;
-import com.ning.billing.entitlement.api.user.SubscriptionTransition.SubscriptionTransitionType;
+import com.ning.billing.entitlement.api.SubscriptionApiService;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
+import com.ning.billing.entitlement.api.user.DefaultSubscriptionFactory.SubscriptionBuilder;
 import com.ning.billing.entitlement.api.user.SubscriptionTransitionDataIterator.Kind;
 import com.ning.billing.entitlement.api.user.SubscriptionTransitionDataIterator.Order;
 import com.ning.billing.entitlement.api.user.SubscriptionTransitionDataIterator.TimeLimit;
@@ -35,58 +50,51 @@ import com.ning.billing.entitlement.events.phase.PhaseEvent;
 import com.ning.billing.entitlement.events.user.ApiEvent;
 import com.ning.billing.entitlement.events.user.ApiEventType;
 import com.ning.billing.entitlement.exceptions.EntitlementError;
+import com.ning.billing.junction.api.BlockingState;
 import com.ning.billing.util.callcontext.CallContext;
 import com.ning.billing.util.clock.Clock;
 import com.ning.billing.util.customfield.CustomField;
-
 import com.ning.billing.util.entity.ExtendedEntityBase;
-import org.joda.time.DateTime;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import javax.annotation.Nullable;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.UUID;
 
 public class SubscriptionData extends ExtendedEntityBase implements Subscription {
 
     private final static Logger log = LoggerFactory.getLogger(SubscriptionData.class);
 
-    private final Clock clock;
-    private final SubscriptionApiService apiService;
+
+    protected final Clock clock;
+    protected final SubscriptionApiService apiService;
     //
     // Final subscription fields
     //
-    private final UUID bundleId;
-    private final DateTime startDate;
-    private final DateTime bundleStartDate;
-    private final ProductCategory category;
+    protected final UUID bundleId;
+    protected final DateTime startDate;
+    protected final DateTime bundleStartDate;
+    protected final ProductCategory category;
 
     //
-    // Those can be modified through non User APIs, and a new Subscription object would be created
+    // Those can be modified through non User APIs, and a new Subscription
+    // object would be created
     //
-    private final long activeVersion;
-    private final DateTime chargedThroughDate;
-    private final DateTime paidThroughDate;
+    protected final long activeVersion;
+    protected final DateTime chargedThroughDate;
+    protected final DateTime paidThroughDate;
 
+    
     //
     // User APIs (create, change, cancel,...) will recompute those each time,
     // so the user holding that subscription object get the correct state when
     // the call completes
     //
-    private LinkedList<SubscriptionTransitionData> transitions;
+    protected LinkedList<SubscriptionTransitionData> transitions;
 
     // Transient object never returned at the API
     public SubscriptionData(SubscriptionBuilder builder) {
         this(builder, null, null);
     }
 
-    public SubscriptionData(SubscriptionBuilder builder, @Nullable SubscriptionApiService apiService,
-                            @Nullable Clock clock) {
-        super(builder.getId(), null, null);
+    public SubscriptionData(SubscriptionBuilder builder,
+            @Nullable SubscriptionApiService apiService, @Nullable Clock clock) {
+        super(builder.getId());
         this.apiService = apiService;
         this.clock = clock;
         this.bundleId = builder.getBundleId();
@@ -99,12 +107,13 @@ public class SubscriptionData extends ExtendedEntityBase implements Subscription
     }
 
     @Override
-    public String getObjectName() {
-        return "Subscription";
+    public ObjectType getObjectType() {
+        return ObjectType.SUBSCRIPTION;
     }
 
     @Override
-    public void saveFieldValue(String fieldName, @Nullable String fieldValue, CallContext context) {
+    public void saveFieldValue(String fieldName, @Nullable String fieldValue,
+            CallContext context) {
         super.setFieldValue(fieldName, fieldValue);
         apiService.commitCustomFields(this, context);
     }
@@ -133,107 +142,105 @@ public class SubscriptionData extends ExtendedEntityBase implements Subscription
 
     @Override
     public SubscriptionState getState() {
-        return (getPreviousTransition() == null) ? null : getPreviousTransition().getNextState();
+        return (getPreviousTransition() == null) ? null
+                : getPreviousTransition().getNextState();
     }
 
     @Override
     public PlanPhase getCurrentPhase() {
-        return (getPreviousTransition() == null) ? null : getPreviousTransition().getNextPhase();
+        return (getPreviousTransitionData() == null) ? null
+                : getPreviousTransitionData().getNextPhase();
     }
 
-
     @Override
     public Plan getCurrentPlan() {
-        return (getPreviousTransition() == null) ? null : getPreviousTransition().getNextPlan();
+        return (getPreviousTransitionData() == null) ? null
+                : getPreviousTransitionData().getNextPlan();
     }
 
     @Override
-    public String getCurrentPriceList() {
-        return (getPreviousTransition() == null) ? null : getPreviousTransition().getNextPriceList();
-    }
+    public PriceList getCurrentPriceList() {
+        return (getPreviousTransitionData() == null) ? null :
+            getPreviousTransitionData().getNextPriceList();
 
+    }
 
     @Override
     public DateTime getEndDate() {
-        SubscriptionTransition latestTransition = getPreviousTransition();
+        SubscriptionEvent latestTransition = getPreviousTransition();
         if (latestTransition.getNextState() == SubscriptionState.CANCELLED) {
             return latestTransition.getEffectiveTransitionTime();
         }
         return null;
     }
 
-
     @Override
-    public void cancel(DateTime requestedDate, boolean eot, CallContext context) throws EntitlementUserApiException  {
-        apiService.cancel(this, requestedDate, eot, context);
+    public boolean cancel(DateTime requestedDate, boolean eot,
+            CallContext context) throws EntitlementUserApiException {
+        return apiService.cancel(this, requestedDate, eot, context);
     }
 
     @Override
-    public void uncancel(CallContext context) throws EntitlementUserApiException {
-        apiService.uncancel(this, context);
+    public boolean uncancel(CallContext context)
+            throws EntitlementUserApiException {
+        return apiService.uncancel(this, context);
     }
 
     @Override
-    public void changePlan(String productName, BillingPeriod term,
-            String priceList, DateTime requestedDate, CallContext context) throws EntitlementUserApiException {
-        apiService.changePlan(this, productName, term, priceList, requestedDate, context);
+    public boolean changePlan(String productName, BillingPeriod term,
+            String priceList, DateTime requestedDate, CallContext context)
+            throws EntitlementUserApiException {
+        return apiService.changePlan(this, productName, term, priceList,
+                requestedDate, context);
     }
 
     @Override
-    public void recreate(PlanPhaseSpecifier spec, DateTime requestedDate, CallContext context)
-            throws EntitlementUserApiException {
-        apiService.recreatePlan(this, spec, requestedDate, context);
+    public boolean recreate(PlanPhaseSpecifier spec, DateTime requestedDate,
+            CallContext context) throws EntitlementUserApiException {
+        return apiService.recreatePlan(this, spec, requestedDate, context);
     }
 
-    public List<SubscriptionTransition> getBillingTransitions() {
-
-        if (transitions == null) {
-            return Collections.emptyList();
-        }
-        List<SubscriptionTransition> result = new ArrayList<SubscriptionTransition>();
-        SubscriptionTransitionDataIterator it = new SubscriptionTransitionDataIterator(clock, transitions,
-                Order.ASC_FROM_PAST, Kind.BILLING, Visibility.ALL, TimeLimit.ALL);
-        while (it.hasNext()) {
-            result.add(it.next());
+    @Override
+    public SubscriptionEvent getPendingTransition() {
+        SubscriptionTransitionData data = getPendingTransitionData();
+        if (data == null) {
+            return null;
         }
-        return result;
+        return new DefaultSubscriptionEvent(data, startDate);
     }
-
+    
     @Override
-    public SubscriptionTransition getPendingTransition() {
+    public BlockingState getBlockingState() {
+        throw new UnsupportedOperationException();
+    }
 
+    protected SubscriptionTransitionData getPendingTransitionData() {
         if (transitions == null) {
             return null;
         }
-        SubscriptionTransitionDataIterator it = new SubscriptionTransitionDataIterator(clock, transitions,
-                Order.ASC_FROM_PAST, Kind.ENTITLEMENT, Visibility.ALL, TimeLimit.FUTURE_ONLY);
+        SubscriptionTransitionDataIterator it = new SubscriptionTransitionDataIterator(
+                clock, transitions, Order.ASC_FROM_PAST, Kind.ENTITLEMENT,
+                Visibility.ALL, TimeLimit.FUTURE_ONLY);
         return it.hasNext() ? it.next() : null;
     }
-
+    
     @Override
-    public SubscriptionTransition getPreviousTransition() {
-        if (transitions == null) {
+    public SubscriptionEvent getPreviousTransition() {
+        SubscriptionTransitionData data = getPreviousTransitionData();
+        if (data == null) {
             return null;
         }
-        SubscriptionTransitionDataIterator it = new SubscriptionTransitionDataIterator(clock, transitions,
-                Order.DESC_FROM_FUTURE, Kind.ENTITLEMENT, Visibility.FROM_DISK_ONLY, TimeLimit.PAST_OR_PRESENT_ONLY);
-        return it.hasNext() ? it.next() : null;
+        return new DefaultSubscriptionEvent(data, startDate);
     }
 
-    public SubscriptionTransition getTransitionFromEvent(EntitlementEvent event) {
-        if (transitions == null || event == null) {
+    protected SubscriptionTransitionData getPreviousTransitionData() {
+        if (transitions == null) {
             return null;
         }
-        for (SubscriptionTransition cur : transitions) {
-            if (cur.getId().equals(event.getId())) {
-                return cur;
-            }
-        }
-        return null;
-    }
-
-    public long getActiveVersion() {
-        return activeVersion;
+        SubscriptionTransitionDataIterator it = new SubscriptionTransitionDataIterator(
+                clock, transitions, Order.DESC_FROM_FUTURE, Kind.ENTITLEMENT,
+                Visibility.FROM_DISK_ONLY, TimeLimit.PAST_OR_PRESENT_ONLY);
+        return it.hasNext() ? it.next() : null;
     }
 
     @Override
@@ -255,32 +262,107 @@ public class SubscriptionData extends ExtendedEntityBase implements Subscription
         return paidThroughDate;
     }
 
-    public SubscriptionTransitionData getInitialTransitionForCurrentPlan() {
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result
+                + ((id == null) ? 0 : id.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        SubscriptionData other = (SubscriptionData) obj;
+        if (id == null) {
+            if (other.id != null)
+                return false;
+        } else if (!id.equals(other.id))
+            return false;
+        return true;
+    }
+
+    public List<SubscriptionEvent> getBillingTransitions() {
+
         if (transitions == null) {
-            throw new EntitlementError(String.format("No transitions for subscription %s", getId()));
+            return Collections.emptyList();
         }
+        List<SubscriptionEvent> result = new ArrayList<SubscriptionEvent>();
+        SubscriptionTransitionDataIterator it = new SubscriptionTransitionDataIterator(
+                clock, transitions, Order.ASC_FROM_PAST, Kind.BILLING,
+                Visibility.ALL, TimeLimit.ALL);
+        while (it.hasNext()) {
+            result.add(new DefaultSubscriptionEvent(it.next(), startDate));
+        }
+        return result;
+    }
 
 
-        SubscriptionTransitionDataIterator it = new SubscriptionTransitionDataIterator(clock, transitions,
-                Order.DESC_FROM_FUTURE, Kind.ENTITLEMENT, Visibility.ALL, TimeLimit.PAST_OR_PRESENT_ONLY);
+    public SubscriptionEvent getTransitionFromEvent(final EntitlementEvent event, final int seqId) {
+        if (transitions == null || event == null) {
+            return null;
+        }
+        for (SubscriptionTransitionData  cur : transitions) {
+            if (cur.getId().equals(event.getId())) {
+                SubscriptionTransitionData withSeq = new SubscriptionTransitionData((SubscriptionTransitionData) cur, seqId); 
+                return new DefaultSubscriptionEvent(withSeq, startDate);
+            }
+        }
+        return null;
+    }
+
+    public long getLastEventOrderedId() {
+        SubscriptionTransitionDataIterator it = new SubscriptionTransitionDataIterator(
+                clock, transitions, Order.DESC_FROM_FUTURE, Kind.ENTITLEMENT,
+                Visibility.FROM_DISK_ONLY, TimeLimit.ALL);
+        return it.hasNext() ? it.next().getTotalOrdering() :  -1L;
+    }
+    
+    public long getActiveVersion() {
+        return activeVersion;
+    }
+
+
+    public List<SubscriptionTransitionData> getAllTransitions() {
+        return transitions;
+    }
+
+    public SubscriptionTransitionData getInitialTransitionForCurrentPlan() {
+        if (transitions == null) {
+            throw new EntitlementError(String.format(
+                    "No transitions for subscription %s", getId()));
+        }
+
+        SubscriptionTransitionDataIterator it = new SubscriptionTransitionDataIterator(
+                clock, transitions, Order.DESC_FROM_FUTURE, Kind.ENTITLEMENT,
+                Visibility.ALL, TimeLimit.PAST_OR_PRESENT_ONLY);
         while (it.hasNext()) {
             SubscriptionTransitionData cur = it.next();
-            if (cur.getTransitionType() == SubscriptionTransitionType.CREATE ||
-                    cur.getTransitionType() == SubscriptionTransitionType.RE_CREATE ||
-                    cur.getTransitionType() == SubscriptionTransitionType.CHANGE ||
-                    cur.getTransitionType() == SubscriptionTransitionType.MIGRATE_ENTITLEMENT) {
+            if (cur.getTransitionType() == SubscriptionTransitionType.CREATE
+                    || cur.getTransitionType() == SubscriptionTransitionType.RE_CREATE
+                    || cur.getTransitionType() == SubscriptionTransitionType.CHANGE
+                    || cur.getTransitionType() == SubscriptionTransitionType.MIGRATE_ENTITLEMENT) {
                 return cur;
             }
         }
-        throw new EntitlementError(String.format("Failed to find InitialTransitionForCurrentPlan id = %s", getId().toString()));
+        throw new EntitlementError(String.format(
+                "Failed to find InitialTransitionForCurrentPlan id = %s",
+                getId().toString()));
     }
 
     public boolean isSubscriptionFutureCancelled() {
         if (transitions == null) {
             return false;
         }
-        SubscriptionTransitionDataIterator it = new SubscriptionTransitionDataIterator(clock, transitions,
-                Order.ASC_FROM_PAST, Kind.ENTITLEMENT, Visibility.ALL, TimeLimit.FUTURE_ONLY);
+        SubscriptionTransitionDataIterator it = new SubscriptionTransitionDataIterator(
+                clock, transitions, Order.ASC_FROM_PAST, Kind.ENTITLEMENT,
+                Visibility.ALL, TimeLimit.FUTURE_ONLY);
         while (it.hasNext()) {
             SubscriptionTransitionData cur = it.next();
             if (cur.getTransitionType() == SubscriptionTransitionType.CANCEL) {
@@ -290,62 +372,70 @@ public class SubscriptionData extends ExtendedEntityBase implements Subscription
         return false;
     }
 
-    public DateTime getPlanChangeEffectiveDate(ActionPolicy policy, DateTime requestedDate) {
+    public DateTime getPlanChangeEffectiveDate(ActionPolicy policy,
+            DateTime requestedDate) {
 
         if (policy == ActionPolicy.IMMEDIATE) {
             return requestedDate;
         }
         if (policy != ActionPolicy.END_OF_TERM) {
-            throw new EntitlementError(String.format("Unexpected policy type %s", policy.toString()));
+            throw new EntitlementError(String.format(
+                    "Unexpected policy type %s", policy.toString()));
         }
 
         if (chargedThroughDate == null) {
             return requestedDate;
         } else {
-            return chargedThroughDate.isBefore(requestedDate) ? requestedDate : chargedThroughDate;
+            return chargedThroughDate.isBefore(requestedDate) ? requestedDate
+                    : chargedThroughDate;
         }
     }
 
     public DateTime getCurrentPhaseStart() {
 
         if (transitions == null) {
-            throw new EntitlementError(String.format("No transitions for subscription %s", getId()));
+            throw new EntitlementError(String.format(
+                    "No transitions for subscription %s", getId()));
         }
-        SubscriptionTransitionDataIterator it = new SubscriptionTransitionDataIterator(clock, transitions,
-                Order.DESC_FROM_FUTURE, Kind.ENTITLEMENT, Visibility.ALL, TimeLimit.PAST_OR_PRESENT_ONLY);
+        SubscriptionTransitionDataIterator it = new SubscriptionTransitionDataIterator(
+                clock, transitions, Order.DESC_FROM_FUTURE, Kind.ENTITLEMENT,
+                Visibility.ALL, TimeLimit.PAST_OR_PRESENT_ONLY);
         while (it.hasNext()) {
             SubscriptionTransitionData cur = it.next();
 
-            if (cur.getTransitionType() == SubscriptionTransitionType.PHASE ||
-                    cur.getTransitionType() == SubscriptionTransitionType.CREATE ||
-                    cur.getTransitionType() == SubscriptionTransitionType.RE_CREATE ||
-                    cur.getTransitionType() == SubscriptionTransitionType.CHANGE ||
-                    cur.getTransitionType() == SubscriptionTransitionType.MIGRATE_ENTITLEMENT) {
+            if (cur.getTransitionType() == SubscriptionTransitionType.PHASE
+                    || cur.getTransitionType() == SubscriptionTransitionType.CREATE
+                    || cur.getTransitionType() == SubscriptionTransitionType.RE_CREATE
+                    || cur.getTransitionType() == SubscriptionTransitionType.CHANGE
+                    || cur.getTransitionType() == SubscriptionTransitionType.MIGRATE_ENTITLEMENT) {
                 return cur.getEffectiveTransitionTime();
             }
         }
-        throw new EntitlementError(String.format("Failed to find CurrentPhaseStart id = %s", getId().toString()));
+        throw new EntitlementError(String.format(
+                "Failed to find CurrentPhaseStart id = %s", getId().toString()));
     }
 
-    public void rebuildTransitions(final List<EntitlementEvent> events, final Catalog catalog) {
+    public void rebuildTransitions(final List<EntitlementEvent> inputEvents,
+            final Catalog catalog) {
 
-        if (events == null) {
+        if (inputEvents == null) {
             return;
         }
 
         SubscriptionState nextState = null;
         String nextPlanName = null;
         String nextPhaseName = null;
-        String nextPriceList = null;
+        String nextPriceListName = null; 
+        UUID nextUserToken = null;
 
         SubscriptionState previousState = null;
-        String previousPriceList = null;
+        PriceList previousPriceList = null;
 
         transitions = new LinkedList<SubscriptionTransitionData>();
         Plan previousPlan = null;
         PlanPhase previousPhase = null;
 
-        for (final EntitlementEvent cur : events) {
+        for (final EntitlementEvent cur : inputEvents) {
 
             if (!cur.isActive() || cur.getActiveVersion() < activeVersion) {
                 continue;
@@ -366,7 +456,9 @@ public class SubscriptionData extends ExtendedEntityBase implements Subscription
                 ApiEvent userEV = (ApiEvent) cur;
                 apiEventType = userEV.getEventType();
                 isFromDisk = userEV.isFromDisk();
-                switch(apiEventType) {
+                nextUserToken = userEV.getUserToken();
+
+                switch (apiEventType) {
                 case MIGRATE_BILLING:
                 case MIGRATE_ENTITLEMENT:
                 case CREATE:
@@ -378,12 +470,12 @@ public class SubscriptionData extends ExtendedEntityBase implements Subscription
                     nextState = SubscriptionState.ACTIVE;
                     nextPlanName = userEV.getEventPlan();
                     nextPhaseName = userEV.getEventPlanPhase();
-                    nextPriceList = userEV.getPriceList();
+                    nextPriceListName = userEV.getPriceList();
                     break;
                 case CHANGE:
                     nextPlanName = userEV.getEventPlan();
                     nextPhaseName = userEV.getEventPlanPhase();
-                    nextPriceList = userEV.getPriceList();
+                    nextPriceListName = userEV.getPriceList();
                     break;
                 case CANCEL:
                     nextState = SubscriptionState.CANCELLED;
@@ -393,42 +485,37 @@ public class SubscriptionData extends ExtendedEntityBase implements Subscription
                 case UNCANCEL:
                     break;
                 default:
-                    throw new EntitlementError(String.format("Unexpected UserEvent type = %s",
-                            userEV.getEventType().toString()));
+                    throw new EntitlementError(String.format(
+                            "Unexpected UserEvent type = %s", userEV
+                                    .getEventType().toString()));
                 }
                 break;
             default:
-                throw new EntitlementError(String.format("Unexpected Event type = %s",
-                        cur.getType()));
+                throw new EntitlementError(String.format(
+                        "Unexpected Event type = %s", cur.getType()));
             }
 
-
             Plan nextPlan = null;
             PlanPhase nextPhase = null;
+            PriceList nextPriceList = null;
+
             try {
                 nextPlan = (nextPlanName != null) ? catalog.findPlan(nextPlanName, cur.getRequestedDate(), getStartDate()) : null;
                 nextPhase = (nextPhaseName != null) ? catalog.findPhase(nextPhaseName, cur.getRequestedDate(), getStartDate()) : null;
+                nextPriceList = (nextPriceListName != null) ? catalog.findPriceList(nextPriceListName, cur.getRequestedDate()) : null;
             } catch (CatalogApiException e) {
-                log.error(String.format("Failed to build transition for subscription %s", id), e);
+                log.error(String.format(
+                        "Failed to build transition for subscription %s", id),
+                        e);
             }
-            SubscriptionTransitionData transition =
-                new SubscriptionTransitionData(cur.getId(),
-                        id,
-                        bundleId,
-                        cur.getType(),
-                        apiEventType,
-                        cur.getRequestedDate(),
-                        cur.getEffectiveDate(),
-                        previousState,
-                        previousPlan,
-                        previousPhase,
-                        previousPriceList,
-                        nextState,
-                        nextPlan,
-                        nextPhase,
-                        nextPriceList,
-                        cur.getTotalOrdering(),
-                        isFromDisk);
+            SubscriptionTransitionData transition = new SubscriptionTransitionData(
+                    cur.getId(), id, bundleId, cur.getType(), apiEventType,
+                    cur.getRequestedDate(), cur.getEffectiveDate(),
+                    previousState, previousPlan, previousPhase,
+                    previousPriceList, nextState, nextPlan, nextPhase,
+                    nextPriceList, cur.getTotalOrdering(), nextUserToken,
+                    isFromDisk);
+
             transitions.add(transition);
 
             previousState = nextState;
@@ -437,5 +524,4 @@ public class SubscriptionData extends ExtendedEntityBase implements Subscription
             previousPriceList = nextPriceList;
         }
     }
-
 }
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransitionData.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransitionData.java
index f03193b..d5885d5 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransitionData.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransitionData.java
@@ -16,20 +16,25 @@
 
 package com.ning.billing.entitlement.api.user;
 
+import java.util.UUID;
+
+import org.codehaus.jackson.annotate.JsonIgnore;
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.joda.time.DateTime;
+
 import com.ning.billing.catalog.api.Plan;
 import com.ning.billing.catalog.api.PlanPhase;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
+import com.ning.billing.catalog.api.PriceList;
 import com.ning.billing.entitlement.api.user.Subscription.SubscriptionState;
 import com.ning.billing.entitlement.events.EntitlementEvent.EventType;
 import com.ning.billing.entitlement.events.user.ApiEventType;
 import com.ning.billing.entitlement.exceptions.EntitlementError;
-import org.joda.time.DateTime;
-
-import java.util.UUID;
 
-public class SubscriptionTransitionData implements SubscriptionTransition {
+public class SubscriptionTransitionData /* implements SubscriptionEvent */ {
 
 
-    private final long totalOrdering;
+    private final Long totalOrdering;
     private final UUID subscriptionId;
     private final UUID bundleId;
     private final UUID eventId;
@@ -38,20 +43,36 @@ public class SubscriptionTransitionData implements SubscriptionTransition {
     private final DateTime requestedTransitionTime;
     private final DateTime effectiveTransitionTime;
     private final SubscriptionState previousState;
-    private final String previousPriceList;
+    private final PriceList previousPriceList;
     private final Plan previousPlan;
     private final PlanPhase previousPhase;
     private final SubscriptionState nextState;
-    private final String nextPriceList;
+    private final PriceList nextPriceList;
     private final Plan nextPlan;
     private final PlanPhase nextPhase;
-    private final boolean isFromDisk;
-
-    public SubscriptionTransitionData(UUID eventId, UUID subscriptionId, UUID bundleId, EventType eventType,
-            ApiEventType apiEventType, DateTime requestedTransitionTime, DateTime effectiveTransitionTime,
-            SubscriptionState previousState, Plan previousPlan, PlanPhase previousPhase, String previousPriceList,
-            SubscriptionState nextState, Plan nextPlan, PlanPhase nextPhase, String nextPriceList,
-            long totalOrdering, boolean isFromDisk) {
+    private final Boolean isFromDisk;
+    private final Integer remainingEventsForUserOperation;
+    private final UUID userToken;
+
+
+    public SubscriptionTransitionData(UUID eventId,
+            UUID subscriptionId,
+            UUID bundleId,
+            EventType eventType,
+            ApiEventType apiEventType,
+            DateTime requestedTransitionTime,
+            DateTime effectiveTransitionTime,
+            SubscriptionState previousState,
+            Plan previousPlan,
+            PlanPhase previousPhase,
+            PriceList previousPriceList,
+            SubscriptionState nextState,
+            Plan nextPlan,
+            PlanPhase nextPhase,
+            PriceList nextPriceList,
+            Long totalOrdering,
+            UUID userToken,
+            Boolean isFromDisk) {
         super();
         this.eventId = eventId;
         this.subscriptionId = subscriptionId;
@@ -70,67 +91,93 @@ public class SubscriptionTransitionData implements SubscriptionTransition {
         this.nextPhase = nextPhase;
         this.totalOrdering = totalOrdering;
         this.isFromDisk = isFromDisk;
+        this.userToken = userToken;
+        this.remainingEventsForUserOperation = 0;
+    }
+    
+    public SubscriptionTransitionData(final SubscriptionTransitionData input, final int remainingEventsForUserOperation) {
+        super();
+        this.eventId = input.getId();
+        this.subscriptionId = input.getSubscriptionId();
+        this.bundleId = input.getBundleId();
+        this.eventType = input.getEventType();
+        this.apiEventType = input.getApiEventType();
+        this.requestedTransitionTime = input.getRequestedTransitionTime();
+        this.effectiveTransitionTime = input.getEffectiveTransitionTime();
+        this.previousState = input.getPreviousState();
+        this.previousPriceList = input.getPreviousPriceList();
+        this.previousPlan = input.getPreviousPlan();
+        this.previousPhase = input.getPreviousPhase();
+        this.nextState = input.getNextState();
+        this.nextPlan = input.getNextPlan();
+        this.nextPriceList = input.getNextPriceList();
+        this.nextPhase = input.getNextPhase();
+        this.totalOrdering = input.getTotalOrdering();
+        this.isFromDisk = input.isFromDisk();
+        this.userToken = input.getUserToken();
+        this.remainingEventsForUserOperation = remainingEventsForUserOperation;
     }
 
-    @Override
+
     public UUID getId() {
         return eventId;
     }
 
-    @Override
     public UUID getSubscriptionId() {
         return subscriptionId;
     }
 
-    @Override
     public UUID getBundleId() {
         return bundleId;
     }
 
-
-    @Override
     public SubscriptionState getPreviousState() {
         return previousState;
     }
 
-    @Override
     public Plan getPreviousPlan() {
         return previousPlan;
     }
 
-    @Override
     public PlanPhase getPreviousPhase() {
         return previousPhase;
     }
 
-    @Override
     public Plan getNextPlan() {
         return nextPlan;
     }
 
-    @Override
     public PlanPhase getNextPhase() {
         return nextPhase;
     }
 
-    @Override
     public SubscriptionState getNextState() {
         return nextState;
     }
 
 
-    @Override
-    public String getPreviousPriceList() {
+    public PriceList getPreviousPriceList() {
         return previousPriceList;
     }
 
-    @Override
-    public String getNextPriceList() {
+    public PriceList getNextPriceList() {
         return nextPriceList;
     }
+    
+	public UUID getUserToken() {
+		return userToken;
+	}
+	
+	public Integer getRemainingEventsForUserOperation() {
+		return remainingEventsForUserOperation;
+	}
+
 
-    @Override
     public SubscriptionTransitionType getTransitionType() {
+        return toSubscriptionTransitionType(eventType, apiEventType);
+    }
+    
+    public static SubscriptionTransitionType toSubscriptionTransitionType(EventType eventType, ApiEventType apiEventType) {
         switch(eventType) {
         case API_USER:
             return apiEventType.getSubscriptionTransitionType();
@@ -141,21 +188,20 @@ public class SubscriptionTransitionData implements SubscriptionTransition {
         }
     }
 
-    @Override
     public DateTime getRequestedTransitionTime() {
         return requestedTransitionTime;
     }
 
-    @Override
     public DateTime getEffectiveTransitionTime() {
         return effectiveTransitionTime;
     }
 
-    public long getTotalOrdering() {
+
+    public Long getTotalOrdering() {
         return totalOrdering;
     }
 
-    public boolean isFromDisk() {
+    public Boolean isFromDisk() {
         return isFromDisk;
     }
 
@@ -168,7 +214,6 @@ public class SubscriptionTransitionData implements SubscriptionTransition {
     }
 
 
-
     @Override
     public String toString() {
         return "SubscriptionTransition [eventId=" + eventId
@@ -185,5 +230,4 @@ public class SubscriptionTransitionData implements SubscriptionTransition {
             + ", nextPriceList " + nextPriceList
             + ", nextPhase=" + ((nextPhase != null) ? nextPhase.getName() : null) + "]";
     }
-
 }
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransitionDataIterator.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransitionDataIterator.java
index fbab9b2..355628d 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransitionDataIterator.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransitionDataIterator.java
@@ -19,7 +19,7 @@ package com.ning.billing.entitlement.api.user;
 import java.util.Iterator;
 import java.util.LinkedList;
 
-import com.ning.billing.entitlement.api.user.SubscriptionTransition.SubscriptionTransitionType;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
 import com.ning.billing.entitlement.exceptions.EntitlementError;
 import com.ning.billing.util.clock.Clock;
 
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/addon/AddonUtils.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/addon/AddonUtils.java
index b2c9405..2a4a550 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/engine/addon/AddonUtils.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/addon/AddonUtils.java
@@ -28,8 +28,9 @@ import com.ning.billing.catalog.api.Plan;
 import com.ning.billing.catalog.api.Product;
 import com.ning.billing.entitlement.api.user.Subscription;
 import com.ning.billing.entitlement.api.user.Subscription.SubscriptionState;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
 import com.ning.billing.entitlement.api.user.SubscriptionData;
-import com.ning.billing.entitlement.api.user.SubscriptionTransition;
+import com.ning.billing.entitlement.api.user.SubscriptionEvent;
 import com.ning.billing.entitlement.exceptions.EntitlementError;
 
 public class AddonUtils {
@@ -43,8 +44,35 @@ public class AddonUtils {
         this.catalogService = catalogService;
     }
 
+    public void checkAddonCreationRights(SubscriptionData baseSubscription, Plan targetAddOnPlan)
+    throws EntitlementUserApiException, CatalogApiException {
 
-    public boolean isAddonAvailable(final String basePlanName, final DateTime requestedDate, final Plan targetAddOnPlan) {
+        if (baseSubscription.getState() != SubscriptionState.ACTIVE) {
+            throw new EntitlementUserApiException(ErrorCode.ENT_CREATE_AO_BP_NON_ACTIVE, targetAddOnPlan.getName());
+        }
+
+        Product baseProduct = baseSubscription.getCurrentPlan().getProduct();
+        if (isAddonIncluded(baseProduct, targetAddOnPlan)) {
+            throw new EntitlementUserApiException(ErrorCode.ENT_CREATE_AO_ALREADY_INCLUDED,
+                    targetAddOnPlan.getName(), baseSubscription.getCurrentPlan().getProduct().getName());
+        }
+
+        if (!isAddonAvailable(baseProduct, targetAddOnPlan)) {
+            throw new EntitlementUserApiException(ErrorCode.ENT_CREATE_AO_NOT_AVAILABLE,
+                    targetAddOnPlan.getName(), baseSubscription.getCurrentPlan().getProduct().getName());
+        }
+    }
+
+    public boolean isAddonAvailableFromProdName(final String baseProductName, final DateTime requestedDate, final Plan targetAddOnPlan) {
+        try {
+            Product product = catalogService.getFullCatalog().findProduct(baseProductName, requestedDate);
+            return isAddonAvailable(product, targetAddOnPlan);
+        } catch (CatalogApiException e) {
+            throw new EntitlementError(e);
+        }
+    }
+
+    public boolean isAddonAvailableFromPlanName(final String basePlanName, final DateTime requestedDate, final Plan targetAddOnPlan) {
         try {
             Plan plan = catalogService.getFullCatalog().findPlan(basePlanName, requestedDate);
             Product product = plan.getProduct();
@@ -65,9 +93,19 @@ public class AddonUtils {
         }
         return false;
     }
+    
+    public boolean isAddonIncludedFromProdName(final String baseProductName, final DateTime requestedDate, final Plan targetAddOnPlan) {
+        try {            
+            Product product = catalogService.getFullCatalog().findProduct(baseProductName, requestedDate);
+            return isAddonIncluded(product, targetAddOnPlan);
+        } catch (CatalogApiException e) {
+            throw new EntitlementError(e);
+        }
+
+    }
 
-    public boolean isAddonIncluded(final String basePlanName,  final DateTime requestedDate, final Plan targetAddOnPlan) {
-        try {
+    public boolean isAddonIncludedFromPlanName(final String basePlanName, final DateTime requestedDate, final Plan targetAddOnPlan) {
+        try {            
             Plan plan = catalogService.getFullCatalog().findPlan(basePlanName, requestedDate);
             Product product = plan.getProduct();
             return isAddonIncluded(product, targetAddOnPlan);
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/Engine.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/Engine.java
index 32ffb90..344f47e 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/Engine.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/Engine.java
@@ -18,36 +18,29 @@ package com.ning.billing.entitlement.engine.core;
 
 
 
+import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 import java.util.UUID;
 
-
-import com.ning.billing.util.callcontext.CallContext;
-import com.ning.billing.util.callcontext.CallContextFactory;
-import com.ning.billing.util.callcontext.CallOrigin;
-import com.ning.billing.util.callcontext.UserType;
 import org.joda.time.DateTime;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import com.google.inject.Inject;
 
+
 import com.ning.billing.catalog.api.Plan;
 import com.ning.billing.catalog.api.Product;
 import com.ning.billing.catalog.api.ProductCategory;
 import com.ning.billing.config.EntitlementConfig;
+import com.ning.billing.config.NotificationConfig;
 import com.ning.billing.entitlement.alignment.PlanAligner;
 import com.ning.billing.entitlement.alignment.TimedPhase;
 import com.ning.billing.entitlement.api.EntitlementService;
-import com.ning.billing.entitlement.api.billing.DefaultEntitlementBillingApi;
-import com.ning.billing.entitlement.api.billing.EntitlementBillingApi;
-import com.ning.billing.entitlement.api.migration.DefaultEntitlementMigrationApi;
-import com.ning.billing.entitlement.api.migration.EntitlementMigrationApi;
-import com.ning.billing.entitlement.api.user.DefaultEntitlementUserApi;
-import com.ning.billing.entitlement.api.user.EntitlementUserApi;
+import com.ning.billing.entitlement.api.SubscriptionFactory;
 import com.ning.billing.entitlement.api.user.Subscription;
-import com.ning.billing.entitlement.api.user.SubscriptionFactory;
 import com.ning.billing.entitlement.api.user.Subscription.SubscriptionState;
 import com.ning.billing.entitlement.api.user.SubscriptionData;
 import com.ning.billing.entitlement.engine.addon.AddonUtils;
@@ -62,10 +55,13 @@ import com.ning.billing.entitlement.events.user.ApiEventCancel;
 import com.ning.billing.entitlement.exceptions.EntitlementError;
 import com.ning.billing.lifecycle.LifecycleHandlerType;
 import com.ning.billing.lifecycle.LifecycleHandlerType.LifecycleLevel;
-import com.ning.billing.util.clock.Clock;
 import com.ning.billing.util.bus.Bus;
 import com.ning.billing.util.bus.Bus.EventBusException;
-import com.ning.billing.util.notificationq.NotificationConfig;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.callcontext.CallContextFactory;
+import com.ning.billing.util.callcontext.CallOrigin;
+import com.ning.billing.util.callcontext.UserType;
+import com.ning.billing.util.clock.Clock;
 import com.ning.billing.util.notificationq.NotificationQueue;
 import com.ning.billing.util.notificationq.NotificationQueueService;
 import com.ning.billing.util.notificationq.NotificationQueueService.NotificationQueueAlreadyExists;
@@ -73,6 +69,7 @@ import com.ning.billing.util.notificationq.NotificationQueueService.Notification
 
 public class Engine implements EventListener, EntitlementService {
 
+	
     public static final String NOTIFICATION_QUEUE_NAME = "subscription-events";
     public static final String ENTITLEMENT_SERVICE_NAME = "entitlement-service";
 
@@ -81,9 +78,6 @@ public class Engine implements EventListener, EntitlementService {
     private final Clock clock;
     private final EntitlementDao dao;
     private final PlanAligner planAligner;
-    private final EntitlementUserApi userApi;
-    private final EntitlementBillingApi billingApi;
-    private final EntitlementMigrationApi migrationApi;
     private final AddonUtils addonUtils;
     private final Bus eventBus;
 
@@ -95,9 +89,8 @@ public class Engine implements EventListener, EntitlementService {
 
     @Inject
     public Engine(Clock clock, EntitlementDao dao, PlanAligner planAligner,
-            EntitlementConfig config, DefaultEntitlementUserApi userApi,
-            DefaultEntitlementBillingApi billingApi,
-            DefaultEntitlementMigrationApi migrationApi, AddonUtils addonUtils, Bus eventBus,
+            EntitlementConfig config,
+            AddonUtils addonUtils, Bus eventBus,
             NotificationQueueService notificationQueueService,
             SubscriptionFactory subscriptionFactory,
             CallContextFactory factory) {
@@ -105,9 +98,6 @@ public class Engine implements EventListener, EntitlementService {
         this.clock = clock;
         this.dao = dao;
         this.planAligner = planAligner;
-        this.userApi = userApi;
-        this.billingApi = billingApi;
-        this.migrationApi = migrationApi;
         this.addonUtils = addonUtils;
         this.config = config;
         this.eventBus = eventBus;
@@ -129,32 +119,29 @@ public class Engine implements EventListener, EntitlementService {
                     NOTIFICATION_QUEUE_NAME,
                     new NotificationQueueHandler() {
                 @Override
-                public void handleReadyNotification(String notificationKey, DateTime eventDateTime) {
-                    EntitlementEvent event = dao.getEventById(UUID.fromString(notificationKey));
+                public void handleReadyNotification(final String inputKey, final DateTime eventDateTime) {
+                	
+                	EntitlementNotificationKey key = new EntitlementNotificationKey(inputKey);
+                    final EntitlementEvent event = dao.getEventById(key.getEventId());
                     if (event == null) {
-                        log.warn("Failed to extract event for notification key {}", notificationKey);
-                    } else {
-                        final CallContext context = factory.createCallContext("SubscriptionEventQueue", CallOrigin.INTERNAL, UserType.SYSTEM);
-                        processEventReady(event, context);
+                        log.warn("Failed to extract event for notification key {}", inputKey);
+                        return;
                     }
+                    final UUID userToken =  (event.getType() == EventType.API_USER) ? ((ApiEvent) event).getUserToken() : null;
+                    final CallContext context = factory.createCallContext("SubscriptionEventQueue", CallOrigin.INTERNAL, UserType.SYSTEM, userToken);
+                    processEventReady(event, key.getSeqId(), context);
                 }
             },
             new NotificationConfig() {
+                
                 @Override
-                public boolean isNotificationProcessingOff() {
-                    return config.isEventProcessingOff();
-                }
-                @Override
-                public long getNotificationSleepTimeMs() {
-                    return config.getNotificationSleepTimeMs();
-                }
-                @Override
-                public int getDaoMaxReadyEvents() {
-                    return config.getDaoMaxReadyEvents();
+                public long getSleepTimeMs() {
+                    return config.getSleepTimeMs();
                 }
+                
                 @Override
-                public long getDaoClaimTimeMs() {
-                    return config.getDaoClaimTimeMs();
+                public boolean isNotificationProcessingOff() {
+                    return config.isNotificationProcessingOff();
                 }
             });
         } catch (NotificationQueueAlreadyExists e) {
@@ -175,24 +162,7 @@ public class Engine implements EventListener, EntitlementService {
     }
 
     @Override
-    public EntitlementUserApi getUserApi() {
-        return userApi;
-    }
-
-    @Override
-    public EntitlementBillingApi getBillingApi() {
-        return billingApi;
-    }
-
-
-    @Override
-    public EntitlementMigrationApi getMigrationApi() {
-        return migrationApi;
-    }
-
-
-    @Override
-    public void processEventReady(EntitlementEvent event, CallContext context) {
+    public void processEventReady(final EntitlementEvent event, final int seqId, final CallContext context) {
         if (!event.isActive()) {
             return;
         }
@@ -201,17 +171,24 @@ public class Engine implements EventListener, EntitlementService {
             log.warn("Failed to retrieve subscription for id %s", event.getSubscriptionId());
             return;
         }
+        if (subscription.getActiveVersion() > event.getActiveVersion()) {
+            // Skip repaired events
+            return;
+        }
+        
         //
         // Do any internal processing on that event before we send the event to the bus
         //
+        
+        int theRealSeqId = seqId;
         if (event.getType() == EventType.PHASE) {
             onPhaseEvent(subscription, context);
         } else if (event.getType() == EventType.API_USER &&
                 subscription.getCategory() == ProductCategory.BASE) {
-            onBasePlanEvent(subscription, (ApiEvent) event, context);
+        	theRealSeqId = onBasePlanEvent(subscription, (ApiEvent) event, context);
         }
         try {
-            eventBus.post(subscription.getTransitionFromEvent(event));
+            eventBus.post(subscription.getTransitionFromEvent(event, theRealSeqId));
         } catch (EventBusException e) {
             log.warn("Failed to post entitlement event " + event, e);
         }
@@ -233,7 +210,7 @@ public class Engine implements EventListener, EntitlementService {
         }
     }
 
-    private void onBasePlanEvent(SubscriptionData baseSubscription, ApiEvent event, CallContext context) {
+    private int onBasePlanEvent(SubscriptionData baseSubscription, ApiEvent event, CallContext context) {
 
         DateTime now = clock.getUTCNow();
 
@@ -242,6 +219,9 @@ public class Engine implements EventListener, EntitlementService {
 
         List<Subscription> subscriptions = dao.getSubscriptions(subscriptionFactory, baseSubscription.getBundleId());
 
+        
+        Map<UUID, EntitlementEvent> addOnCancellations = new HashMap<UUID, EntitlementEvent>();
+        
         Iterator<Subscription> it = subscriptions.iterator();
         while (it.hasNext()) {
             SubscriptionData cur = (SubscriptionData) it.next();
@@ -262,9 +242,18 @@ public class Engine implements EventListener, EntitlementService {
                 .setProcessedDate(now)
                 .setEffectiveDate(event.getEffectiveDate())
                 .setRequestedDate(now)
+                .setUserToken(context.getUserToken())
                 .setFromDisk(true));
-                dao.cancelSubscription(cur.getId(), cancelEvent, context);
+                
+                addOnCancellations.put(cur.getId(), cancelEvent);
             }
         }
+        final int addOnSize = addOnCancellations.size();
+        int cancelSeq = addOnSize - 1;
+        for (final UUID key : addOnCancellations.keySet()) {
+            dao.cancelSubscription(key, addOnCancellations.get(key), context, cancelSeq);
+            cancelSeq--;
+        }
+        return addOnSize;
     }
 }
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/EntitlementNotificationKey.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/EntitlementNotificationKey.java
new file mode 100644
index 0000000..a4d19b0
--- /dev/null
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/EntitlementNotificationKey.java
@@ -0,0 +1,91 @@
+/* 
+ * Copyright 2010-2011 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.entitlement.engine.core;
+
+import java.util.UUID;
+import com.ning.billing.util.notificationq.NotificationKey;
+
+public class EntitlementNotificationKey implements NotificationKey {
+
+	private static final String DELIMITER = ":";
+	
+	private final UUID eventId;
+	private final int seqId;
+	
+	public EntitlementNotificationKey(final UUID eventId, int seqId) {
+		this.eventId = eventId;
+		this.seqId = seqId;
+	}
+
+	public EntitlementNotificationKey(final UUID eventId) {
+		this(eventId, 0);
+	}
+	
+	public EntitlementNotificationKey(final String input) {
+			
+		String [] parts = input.split(DELIMITER);
+		eventId = UUID.fromString(parts[0]);
+		if (parts.length == 2) {
+			seqId = Integer.valueOf(parts[1]);
+		} else {
+			seqId = 0;
+		}
+	}
+	
+	public UUID getEventId() {
+		return eventId;
+	}
+
+	public int getSeqId() {
+		return seqId;
+	}
+
+	public String toString() {
+		if (seqId == 0) {
+			return eventId.toString();
+		} else {
+			return eventId.toString() + ":" + seqId;
+		}
+	}
+
+	@Override
+	public int hashCode() {
+		final int prime = 31;
+		int result = 1;
+		result = prime * result + ((eventId == null) ? 0 : eventId.hashCode());
+		result = prime * result + seqId;
+		return result;
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (this == obj)
+			return true;
+		if (obj == null)
+			return false;
+		if (getClass() != obj.getClass())
+			return false;
+		EntitlementNotificationKey other = (EntitlementNotificationKey) obj;
+		if (eventId == null) {
+			if (other.eventId != null)
+				return false;
+		} else if (!eventId.equals(other.eventId))
+			return false;
+		if (seqId != other.seqId)
+			return false;
+		return true;
+	}
+}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/EventListener.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/EventListener.java
index f6b13f9..1272dba 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/EventListener.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/EventListener.java
@@ -22,6 +22,6 @@ import com.ning.billing.util.callcontext.CallContext;
 
 public interface EventListener {
 
-    public void processEventReady(EntitlementEvent event, CallContext context);
+    public void processEventReady(final EntitlementEvent event, final int seqId, final CallContext context);
 
 }
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/BundleSqlDao.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/BundleSqlDao.java
index bccd559..b6becbb 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/BundleSqlDao.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/BundleSqlDao.java
@@ -16,12 +16,14 @@
 
 package com.ning.billing.entitlement.engine.dao;
 
-import com.ning.billing.entitlement.api.user.SubscriptionBundle;
-import com.ning.billing.entitlement.api.user.SubscriptionBundleData;
-import com.ning.billing.util.callcontext.CallContext;
-import com.ning.billing.util.callcontext.CallContextBinder;
-import com.ning.billing.util.dao.BinderBase;
-import com.ning.billing.util.dao.MapperBase;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Date;
+import java.util.List;
+import java.util.UUID;
+
+import com.ning.billing.util.dao.AuditSqlDao;
+import com.ning.billing.util.entity.dao.EntitySqlDao;
 import org.joda.time.DateTime;
 import org.skife.jdbi.v2.SQLStatement;
 import org.skife.jdbi.v2.StatementContext;
@@ -36,52 +38,60 @@ import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
 import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
 import org.skife.jdbi.v2.tweak.ResultSetMapper;
 
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.util.List;
-import java.util.UUID;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.entitlement.api.user.SubscriptionBundleData;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.callcontext.CallContextBinder;
+import com.ning.billing.util.dao.BinderBase;
+import com.ning.billing.util.dao.MapperBase;
+
 
 @ExternalizedSqlViaStringTemplate3()
-public interface BundleSqlDao extends Transactional<BundleSqlDao>, CloseMe, Transmogrifier {
+public interface BundleSqlDao extends Transactional<BundleSqlDao>, EntitySqlDao<SubscriptionBundle>,
+                                      AuditSqlDao, CloseMe, Transmogrifier {
 
     @SqlUpdate
     public void insertBundle(@Bind(binder = SubscriptionBundleBinder.class) SubscriptionBundleData bundle,
                              @CallContextBinder final CallContext context);
 
+    @SqlUpdate
+    public void updateBundleLastSysTime(@Bind("id") String id, @Bind("lastSysUpdateDate") Date lastSysUpdate);
+    
     @SqlQuery
     @Mapper(ISubscriptionBundleSqlMapper.class)
     public SubscriptionBundle getBundleFromId(@Bind("id") String id);
 
     @SqlQuery
     @Mapper(ISubscriptionBundleSqlMapper.class)
-    public SubscriptionBundle getBundleFromKey(@Bind("name") String name);
+    public SubscriptionBundle getBundleFromKey(@Bind("externalKey") String externalKey);
 
     @SqlQuery
     @Mapper(ISubscriptionBundleSqlMapper.class)
-    public List<SubscriptionBundle> getBundleFromAccount(@Bind("account_id") String accountId);
+    public List<SubscriptionBundle> getBundleFromAccount(@Bind("accountId") String accountId);
 
     public static class SubscriptionBundleBinder extends BinderBase implements Binder<Bind, SubscriptionBundleData> {
         @Override
         public void bind(@SuppressWarnings("rawtypes") SQLStatement stmt, Bind bind, SubscriptionBundleData bundle) {
             stmt.bind("id", bundle.getId().toString());
-            stmt.bind("start_dt", getDate(bundle.getStartDate()));
-            stmt.bind("name", bundle.getKey());
-            stmt.bind("account_id", bundle.getAccountId().toString());
+            stmt.bind("startDate", getDate(bundle.getStartDate()));
+            stmt.bind("externalKey", bundle.getKey());
+            stmt.bind("accountId", bundle.getAccountId().toString());
+            stmt.bind("lastSysUpdateDate", getDate(bundle.getLastSysUpdateTime()));
         }
     }
 
     public static class ISubscriptionBundleSqlMapper extends MapperBase implements ResultSetMapper<SubscriptionBundle> {
+        
         @Override
         public SubscriptionBundle map(int arg, ResultSet r,
                 StatementContext ctx) throws SQLException {
-
             UUID id = UUID.fromString(r.getString("id"));
-            String name = r.getString("name");
+            String key = r.getString("external_key");
             UUID accountId = UUID.fromString(r.getString("account_id"));
-            DateTime startDate = getDate(r, "start_dt");
-            SubscriptionBundleData bundle = new SubscriptionBundleData(id, name, accountId, startDate);
+            DateTime startDate = getDate(r, "start_date");
+            DateTime lastSysUpdateDate = getDate(r, "last_sys_update_date");
+            SubscriptionBundleData bundle = new SubscriptionBundleData(id, key, accountId, startDate, lastSysUpdateDate);
             return bundle;
         }
-
     }
 }
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EntitlementDao.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EntitlementDao.java
index d79444a..5e95c88 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EntitlementDao.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EntitlementDao.java
@@ -17,16 +17,18 @@
 package com.ning.billing.entitlement.engine.dao;
 
 import java.util.List;
+import java.util.Map;
 import java.util.UUID;
 
 import com.ning.billing.util.callcontext.CallContext;
 
+import com.ning.billing.entitlement.api.SubscriptionFactory;
 import com.ning.billing.entitlement.api.migration.AccountMigrationData;
+import com.ning.billing.entitlement.api.timeline.SubscriptionDataRepair;
 import com.ning.billing.entitlement.api.user.Subscription;
 import com.ning.billing.entitlement.api.user.SubscriptionBundle;
 import com.ning.billing.entitlement.api.user.SubscriptionBundleData;
 import com.ning.billing.entitlement.api.user.SubscriptionData;
-import com.ning.billing.entitlement.api.user.SubscriptionFactory;
 import com.ning.billing.entitlement.events.EntitlementEvent;
 
 public interface EntitlementDao {
@@ -41,7 +43,7 @@ public interface EntitlementDao {
 
     public Subscription getSubscriptionFromId(final SubscriptionFactory factory, final UUID subscriptionId);
 
-    // Account retrieval
+    // ACCOUNT retrieval
     public UUID getAccountIdFromSubscriptionId(final UUID subscriptionId);
 
     // Subscription retrieval
@@ -52,13 +54,15 @@ public interface EntitlementDao {
     public List<Subscription> getSubscriptionsForKey(final SubscriptionFactory factory, final String bundleKey);
 
     // Update
-    public void updateSubscription(final SubscriptionData subscription, final CallContext context);
+    public void updateChargedThroughDate(final SubscriptionData subscription, final CallContext context);
 
     // Event apis
     public void createNextPhaseEvent(final UUID subscriptionId, final EntitlementEvent nextPhase, final CallContext context);
 
     public EntitlementEvent getEventById(final UUID eventId);
 
+    public Map<UUID, List<EntitlementEvent>> getEventsForBundle(final UUID bundleId);
+    
     public List<EntitlementEvent> getEventsForSubscription(final UUID subscriptionId);
 
     public List<EntitlementEvent> getPendingEventsForSubscription(final UUID subscriptionId);
@@ -68,14 +72,17 @@ public interface EntitlementDao {
 
     public void recreateSubscription(final UUID subscriptionId, final List<EntitlementEvent> recreateEvents, final CallContext context);
 
-    public void cancelSubscription(final UUID subscriptionId, final EntitlementEvent cancelEvent, final CallContext context);
-
+    public void cancelSubscription(final UUID subscriptionId, final EntitlementEvent cancelEvent, final CallContext context, final int cancelSeq);
+    
     public void uncancelSubscription(final UUID subscriptionId, final List<EntitlementEvent> uncancelEvents, final CallContext context);
 
     public void changePlan(final UUID subscriptionId, final List<EntitlementEvent> changeEvents, final CallContext context);
 
     public void migrate(final UUID accountId, final AccountMigrationData data, final CallContext context);
 
+    // Repair
+    public void repair(final UUID accountId, final UUID bundleId, final List<SubscriptionDataRepair> inRepair, final CallContext context);
+    
     // Custom Fields
     public void saveCustomFields(final SubscriptionData subscription, final CallContext context);
 }
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/RepairEntitlementDao.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/RepairEntitlementDao.java
new file mode 100644
index 0000000..cdfc221
--- /dev/null
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/RepairEntitlementDao.java
@@ -0,0 +1,249 @@
+/* 
+ * Copyright 2010-2011 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.entitlement.engine.dao;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.UUID;
+
+import com.ning.billing.entitlement.api.SubscriptionFactory;
+import com.ning.billing.entitlement.api.migration.AccountMigrationData;
+import com.ning.billing.entitlement.api.timeline.RepairEntitlementLifecycleDao;
+import com.ning.billing.entitlement.api.timeline.SubscriptionDataRepair;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.entitlement.api.user.SubscriptionBundleData;
+import com.ning.billing.entitlement.api.user.SubscriptionData;
+import com.ning.billing.entitlement.events.EntitlementEvent;
+import com.ning.billing.entitlement.exceptions.EntitlementError;
+import com.ning.billing.util.callcontext.CallContext;
+
+public class RepairEntitlementDao implements EntitlementDao, RepairEntitlementLifecycleDao {
+
+    private final ThreadLocal<Map<UUID, SubscriptionRepairEvent>> preThreadsInRepairSubscriptions = new ThreadLocal<Map<UUID, SubscriptionRepairEvent>>();
+    
+    private final static class SubscriptionRepairEvent {
+        
+        private final Set<EntitlementEvent> events;
+        
+        public SubscriptionRepairEvent(List<EntitlementEvent> initialEvents) {
+            events = new TreeSet<EntitlementEvent>(new Comparator<EntitlementEvent>() {
+                @Override
+                public int compare(EntitlementEvent o1, EntitlementEvent o2) {
+                    return o1.compareTo(o2);
+                }
+            });
+            if (initialEvents != null) {
+                events.addAll(initialEvents);
+            }
+        }
+        
+        public Set<EntitlementEvent> getEvents() {
+            return events;
+        }
+        
+        public void addEvents(List<EntitlementEvent> newEvents) {
+            events.addAll(newEvents);
+        }
+    }
+    
+    private Map<UUID, SubscriptionRepairEvent> getRepairMap() {
+        if (preThreadsInRepairSubscriptions.get() == null) {
+            preThreadsInRepairSubscriptions.set(new HashMap<UUID, SubscriptionRepairEvent>());
+        }
+        return preThreadsInRepairSubscriptions.get();
+    }
+    
+    private SubscriptionRepairEvent getRepairSubscriptionEvents(UUID subscriptionId) {
+        Map<UUID, SubscriptionRepairEvent> map = getRepairMap();
+        return map.get(subscriptionId);
+    }
+    
+    @Override
+    public List<EntitlementEvent> getEventsForSubscription(UUID subscriptionId) {
+        SubscriptionRepairEvent target =  getRepairSubscriptionEvents(subscriptionId);
+        return new LinkedList<EntitlementEvent>(target.getEvents());
+    }
+
+    @Override
+    public void createSubscription(SubscriptionData subscription,
+            List<EntitlementEvent> createEvents, CallContext context) {
+        addEvents(subscription.getId(), createEvents);
+    }
+
+    @Override
+    public void recreateSubscription(UUID subscriptionId,
+            List<EntitlementEvent> recreateEvents, CallContext context) {
+        addEvents(subscriptionId, recreateEvents);
+    }
+
+    @Override
+    public void cancelSubscription(UUID subscriptionId,
+            EntitlementEvent cancelEvent, CallContext context, int cancelSeq) {
+        long activeVersion = cancelEvent.getActiveVersion();
+        addEvents(subscriptionId, Collections.singletonList(cancelEvent));
+        SubscriptionRepairEvent target =  getRepairSubscriptionEvents(subscriptionId);
+        boolean foundCancelEvent = false;
+        for (EntitlementEvent cur : target.getEvents()) {
+            if (cur.getId().equals(cancelEvent.getId())) {
+                foundCancelEvent = true;
+            } else if (foundCancelEvent) { 
+                cur.setActiveVersion(activeVersion - 1);
+            }
+        }
+    }
+
+    
+    @Override
+    public void changePlan(UUID subscriptionId,
+            List<EntitlementEvent> changeEvents, CallContext context) {
+        addEvents(subscriptionId, changeEvents);        
+    }
+
+    @Override
+    public void initializeRepair(UUID subscriptionId, List<EntitlementEvent> initialEvents) {
+        Map<UUID, SubscriptionRepairEvent> map = getRepairMap();
+        if (map.get(subscriptionId) == null) {
+            SubscriptionRepairEvent value = new SubscriptionRepairEvent(initialEvents);
+            map.put(subscriptionId, value);
+        } else {
+            throw new EntitlementError(String.format("Unexpected SubscriptionRepairEvent %s for thread %s", subscriptionId, Thread.currentThread().getName()));
+        }
+    }
+
+    @Override
+    public void cleanup() {
+        Map<UUID, SubscriptionRepairEvent> map = getRepairMap();
+        map.clear();
+    }
+
+    
+    private void addEvents(UUID subscriptionId, List<EntitlementEvent> events) {
+        SubscriptionRepairEvent target =  getRepairSubscriptionEvents(subscriptionId);
+        target.addEvents(events);        
+    }
+
+    
+    @Override
+    public void uncancelSubscription(UUID subscriptionId,
+            List<EntitlementEvent> uncancelEvents, CallContext context) {
+        throw new EntitlementError("Not implemented");        
+    }
+    
+    @Override
+    public List<SubscriptionBundle> getSubscriptionBundleForAccount(UUID accountId) {
+        throw new EntitlementError("Not implemented");
+    }
+
+    @Override
+    public SubscriptionBundle getSubscriptionBundleFromKey(String bundleKey) {
+        throw new EntitlementError("Not implemented");
+    }
+
+    @Override
+    public SubscriptionBundle getSubscriptionBundleFromId(UUID bundleId) {
+        throw new EntitlementError("Not implemented");
+    }
+
+    @Override
+    public SubscriptionBundle createSubscriptionBundle(
+            SubscriptionBundleData bundle, CallContext context) {
+        throw new EntitlementError("Not implemented");
+    }
+
+    @Override
+    public Subscription getSubscriptionFromId(SubscriptionFactory factory,
+            UUID subscriptionId) {
+        throw new EntitlementError("Not implemented");
+    }
+
+    @Override
+    public UUID getAccountIdFromSubscriptionId(UUID subscriptionId) {
+        throw new EntitlementError("Not implemented");
+    }
+
+    @Override
+    public Subscription getBaseSubscription(SubscriptionFactory factory,
+            UUID bundleId) {
+        throw new EntitlementError("Not implemented");
+    }
+
+    @Override
+    public List<Subscription> getSubscriptions(SubscriptionFactory factory,
+            UUID bundleId) {
+        throw new EntitlementError("Not implemented");
+    }
+
+    @Override
+    public List<Subscription> getSubscriptionsForKey(
+            SubscriptionFactory factory, String bundleKey) {
+        throw new EntitlementError("Not implemented");
+    }
+
+    @Override
+    public void updateChargedThroughDate(SubscriptionData subscription,
+            CallContext context) {
+        throw new EntitlementError("Not implemented");
+    }
+
+    @Override
+    public void createNextPhaseEvent(UUID subscriptionId,
+            EntitlementEvent nextPhase, CallContext context) {
+        throw new EntitlementError("Not implemented");
+    }
+
+    @Override
+    public EntitlementEvent getEventById(UUID eventId) {
+        throw new EntitlementError("Not implemented");
+    }
+
+    @Override
+    public Map<UUID, List<EntitlementEvent>> getEventsForBundle(UUID bundleId) {
+        throw new EntitlementError("Not implemented");
+    }
+
+
+    @Override
+    public List<EntitlementEvent> getPendingEventsForSubscription(
+            UUID subscriptionId) {
+        throw new EntitlementError("Not implemented");
+    }
+
+
+    @Override
+    public void migrate(UUID accountId, AccountMigrationData data,
+            CallContext context) {
+        throw new EntitlementError("Not implemented");
+    }
+
+    @Override
+    public void saveCustomFields(SubscriptionData subscription,
+            CallContext context) {
+        throw new EntitlementError("Not implemented");
+    }
+
+    @Override
+    public void repair(UUID accountId, UUID bundleId, List<SubscriptionDataRepair> inRepair,
+            CallContext context) {
+        throw new EntitlementError("Not implemented");
+    }
+}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/SubscriptionSqlDao.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/SubscriptionSqlDao.java
index 04f2965..a43117d 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/SubscriptionSqlDao.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/SubscriptionSqlDao.java
@@ -19,7 +19,8 @@ package com.ning.billing.entitlement.engine.dao;
 import com.ning.billing.catalog.api.ProductCategory;
 import com.ning.billing.entitlement.api.user.Subscription;
 import com.ning.billing.entitlement.api.user.SubscriptionData;
-import com.ning.billing.entitlement.api.user.SubscriptionFactory.SubscriptionBuilder;
+import com.ning.billing.entitlement.api.user.DefaultSubscriptionFactory.SubscriptionBuilder;
+import com.ning.billing.util.dao.AuditSqlDao;
 import com.ning.billing.util.callcontext.CallContext;
 import com.ning.billing.util.callcontext.CallContextBinder;
 import com.ning.billing.util.dao.BinderBase;
@@ -45,7 +46,7 @@ import java.util.List;
 import java.util.UUID;
 
 @ExternalizedSqlViaStringTemplate3()
-public interface SubscriptionSqlDao extends Transactional<SubscriptionSqlDao>, CloseMe, Transmogrifier {
+public interface SubscriptionSqlDao extends Transactional<SubscriptionSqlDao>, AuditSqlDao, CloseMe, Transmogrifier {
 	@SqlUpdate
     public void insertSubscription(@Bind(binder = SubscriptionBinder.class) SubscriptionData sub,
                                    @CallContextBinder final CallContext context);
@@ -56,24 +57,32 @@ public interface SubscriptionSqlDao extends Transactional<SubscriptionSqlDao>, C
 
     @SqlQuery
     @Mapper(SubscriptionMapper.class)
-    public List<Subscription> getSubscriptionsFromBundleId(@Bind("bundle_id") String bundleId);
+    public List<Subscription> getSubscriptionsFromBundleId(@Bind("bundleId") String bundleId);
 
     @SqlUpdate
-    public void updateSubscription(@Bind("id") String id, @Bind("active_version") long activeVersion,
-                                   @Bind("ctd_dt") Date ctd, @Bind("ptd_dt") Date ptd,
-                                   @CallContextBinder final CallContext context);
-   
+    public void updateChargedThroughDate(@Bind("id") String id, @Bind("chargedThroughDate") Date chargedThroughDate,
+                                        @CallContextBinder final CallContext context);
+
+    @SqlUpdate void updateActiveVersion(@Bind("id") String id, @Bind("activeVersion") long activeVersion,
+            @CallContextBinder final CallContext context);
+    
+    @SqlUpdate
+    public void updateForRepair(@Bind("id") String id, @Bind("activeVersion") long activeVersion,
+            @Bind("startDate") Date startDate,
+            @Bind("bundleStartDate") Date bundleStartDate,
+            @CallContextBinder final CallContext context);
+
     public static class SubscriptionBinder extends BinderBase implements Binder<Bind, SubscriptionData> {
         @Override
         public void bind(@SuppressWarnings("rawtypes") SQLStatement stmt, Bind bind, SubscriptionData sub) {
             stmt.bind("id", sub.getId().toString());
-            stmt.bind("bundle_id", sub.getBundleId().toString());
+            stmt.bind("bundleId", sub.getBundleId().toString());
             stmt.bind("category", sub.getCategory().toString());
-            stmt.bind("start_dt", getDate(sub.getStartDate()));
-            stmt.bind("bundle_start_dt", getDate(sub.getBundleStartDate()));
-            stmt.bind("active_version", sub.getActiveVersion());
-            stmt.bind("ctd_dt", getDate(sub.getChargedThroughDate()));
-            stmt.bind("ptd_dt", getDate(sub.getPaidThroughDate()));
+            stmt.bind("startDate", getDate(sub.getStartDate()));
+            stmt.bind("bundleStartDate", getDate(sub.getBundleStartDate()));
+            stmt.bind("activeVersion", sub.getActiveVersion());
+            stmt.bind("chargedThroughDate", getDate(sub.getChargedThroughDate()));
+            stmt.bind("paidThroughDate", getDate(sub.getPaidThroughDate()));
         }
     }
 
@@ -85,10 +94,10 @@ public interface SubscriptionSqlDao extends Transactional<SubscriptionSqlDao>, C
             UUID id = UUID.fromString(r.getString("id"));
             UUID bundleId = UUID.fromString(r.getString("bundle_id"));
             ProductCategory category = ProductCategory.valueOf(r.getString("category"));
-            DateTime bundleStartDate = getDate(r, "bundle_start_dt");
-            DateTime startDate = getDate(r, "start_dt");
-            DateTime ctd = getDate(r, "ctd_dt");
-            DateTime ptd = getDate(r, "ptd_dt");
+            DateTime bundleStartDate = getDate(r, "bundle_start_date");
+            DateTime startDate = getDate(r, "start_date");
+            DateTime ctd = getDate(r, "charged_through_date");
+            DateTime ptd = getDate(r, "paid_through_date");
             long activeVersion = r.getLong("active_version");
 
             return new SubscriptionData(new SubscriptionBuilder()
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/events/EntitlementEvent.java b/entitlement/src/main/java/com/ning/billing/entitlement/events/EntitlementEvent.java
index d3895c0..8e7688b 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/events/EntitlementEvent.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/events/EntitlementEvent.java
@@ -16,12 +16,13 @@
 
 package com.ning.billing.entitlement.events;
 
+import com.ning.billing.util.entity.Entity;
 import org.joda.time.DateTime;
 
 import java.util.UUID;
 
 
-public interface EntitlementEvent extends Comparable<EntitlementEvent> {
+public interface EntitlementEvent extends Comparable<EntitlementEvent>, Entity {
 
     public enum EventType {
         API_USER,
@@ -32,8 +33,6 @@ public interface EntitlementEvent extends Comparable<EntitlementEvent> {
 
     public long getTotalOrdering();
 
-    public UUID getId();
-
     public long getActiveVersion();
 
     public void setActiveVersion(long activeVersion);
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/events/EventBase.java b/entitlement/src/main/java/com/ning/billing/entitlement/events/EventBase.java
index 5861e5d..4afc75d 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/events/EventBase.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/events/EventBase.java
@@ -17,7 +17,6 @@
 package com.ning.billing.entitlement.events;
 
 import com.ning.billing.entitlement.events.user.ApiEvent;
-import com.ning.billing.entitlement.exceptions.EntitlementError;
 import org.joda.time.DateTime;
 
 import java.util.UUID;
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/events/phase/PhaseEventData.java b/entitlement/src/main/java/com/ning/billing/entitlement/events/phase/PhaseEventData.java
index 47546ea..23f7b2b 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/events/phase/PhaseEventData.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/events/phase/PhaseEventData.java
@@ -17,7 +17,6 @@
 package com.ning.billing.entitlement.events.phase;
 
 
-import com.ning.billing.entitlement.alignment.TimedPhase;
 import com.ning.billing.entitlement.api.user.SubscriptionData;
 import com.ning.billing.entitlement.events.EventBase;
 import org.joda.time.DateTime;
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEvent.java b/entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEvent.java
index c26b168..20a6569 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEvent.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEvent.java
@@ -16,6 +16,8 @@
 
 package com.ning.billing.entitlement.events.user;
 
+import java.util.UUID;
+
 import com.ning.billing.entitlement.events.EntitlementEvent;
 
 
@@ -30,5 +32,7 @@ public interface ApiEvent extends EntitlementEvent {
     public String getPriceList();
 
     public boolean isFromDisk();
+    
+    public UUID getUserToken();
 
 }
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEventBase.java b/entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEventBase.java
index f67c6b7..d2ea706 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEventBase.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEventBase.java
@@ -17,7 +17,6 @@
 package com.ning.billing.entitlement.events.user;
 
 import com.ning.billing.entitlement.events.EventBase;
-import org.joda.time.DateTime;
 
 import java.util.UUID;
 
@@ -28,6 +27,7 @@ public class ApiEventBase extends EventBase implements ApiEvent {
     private final String eventPlan;
     private final String eventPlanPhase;
     private final String eventPriceList;
+    private final UUID userToken;
     private final boolean fromDisk;
 
     public ApiEventBase(ApiEventBuilder builder) {
@@ -37,28 +37,9 @@ public class ApiEventBase extends EventBase implements ApiEvent {
         this.eventPlan = builder.getEventPlan();
         this.eventPlanPhase = builder.getEventPlanPhase();
         this.fromDisk = builder.isFromDisk();
+        this.userToken = builder.getUserToken();
     }
 
-/*
-    public ApiEventBase(UUID subscriptionId, DateTime bundleStartDate, DateTime processed, String planName, String phaseName,
-            String priceList, DateTime requestedDate,  ApiEventType eventType, DateTime effectiveDate, long activeVersion) {
-        super(subscriptionId, requestedDate, effectiveDate, processed, activeVersion, true);
-        this.eventType = eventType;
-        this.eventPriceList = priceList;
-        this.eventPlan = planName;
-        this.eventPlanPhase = phaseName;
-    }
-
-    public ApiEventBase(UUID subscriptionId, DateTime bundleStartDate, DateTime processed,
-            DateTime requestedDate,  ApiEventType eventType, DateTime effectiveDate, long activeVersion) {
-        super(subscriptionId, requestedDate, effectiveDate, processed, activeVersion, true);
-        this.eventType = eventType;
-        this.eventPriceList = null;
-        this.eventPlan = null;
-        this.eventPlanPhase = null;
-    }
-*/
-
     @Override
     public ApiEventType getEventType() {
         return eventType;
@@ -83,6 +64,12 @@ public class ApiEventBase extends EventBase implements ApiEvent {
     public String getPriceList() {
         return eventPriceList;
     }
+    
+	@Override
+	public UUID getUserToken() {
+		return userToken;
+	}
+
 
     @Override
     public boolean isFromDisk() {
@@ -105,6 +92,7 @@ public class ApiEventBase extends EventBase implements ApiEvent {
                 + ", getActiveVersion()=" + getActiveVersion()
                 + ", getProcessedDate()=" + getProcessedDate()
                 + ", getSubscriptionId()=" + getSubscriptionId()
+                + ", evetnToken()=" + getUserToken()
                 + ", isActive()=" + isActive() + "]";
     }
 }
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEventBuilder.java b/entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEventBuilder.java
index b7e9764..b6be427 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEventBuilder.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEventBuilder.java
@@ -16,6 +16,8 @@
 
 package com.ning.billing.entitlement.events.user;
 
+import java.util.UUID;
+
 import com.ning.billing.entitlement.events.EventBaseBuilder;
 
 public class ApiEventBuilder extends EventBaseBuilder<ApiEventBuilder> {
@@ -24,6 +26,7 @@ public class ApiEventBuilder extends EventBaseBuilder<ApiEventBuilder> {
     private String eventPlan;
     private String eventPlanPhase;
     private String eventPriceList;
+    private UUID userToken;
     private boolean fromDisk;
 
 
@@ -50,11 +53,21 @@ public class ApiEventBuilder extends EventBaseBuilder<ApiEventBuilder> {
     public String getEventPriceList() {
         return eventPriceList;
     }
+    
+    public UUID getUserToken() {
+    	return userToken;
+    }
 
     public boolean isFromDisk() {
         return fromDisk;
     }
 
+    public ApiEventBuilder setUserToken(UUID userToken) {
+        this.userToken = userToken;
+        return this;
+    }
+
+    
     public ApiEventBuilder setFromDisk(boolean fromDisk) {
         this.fromDisk = fromDisk;
         return this;
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEventType.java b/entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEventType.java
index a279f52..d1eae92 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEventType.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEventType.java
@@ -16,7 +16,7 @@
 
 package com.ning.billing.entitlement.events.user;
 
-import com.ning.billing.entitlement.api.user.SubscriptionTransition.SubscriptionTransitionType;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
 
 
 public enum ApiEventType {
diff --git a/entitlement/src/main/resources/com/ning/billing/entitlement/ddl.sql b/entitlement/src/main/resources/com/ning/billing/entitlement/ddl.sql
index bef27aa..f86f170 100644
--- a/entitlement/src/main/resources/com/ning/billing/entitlement/ddl.sql
+++ b/entitlement/src/main/resources/com/ning/billing/entitlement/ddl.sql
@@ -1,49 +1,56 @@
 DROP TABLE IF EXISTS events;
 DROP TABLE IF EXISTS entitlement_events;
 CREATE TABLE entitlement_events (
-    id int(11) unsigned NOT NULL AUTO_INCREMENT,
-    event_id char(36) NOT NULL,
+    record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
+    id char(36) NOT NULL,
     event_type varchar(9) NOT NULL,
     user_type varchar(25) DEFAULT NULL,
-    requested_dt datetime NOT NULL,
-    effective_dt datetime NOT NULL,
+    requested_date datetime NOT NULL,
+    effective_date datetime NOT NULL,
     subscription_id char(36) NOT NULL,
     plan_name varchar(64) DEFAULT NULL,
     phase_name varchar(128) DEFAULT NULL,
-    plist_name varchar(64) DEFAULT NULL,
+    price_list_name varchar(64) DEFAULT NULL,
+    user_token char(36),
     current_version int(11) DEFAULT 1,
     is_active bool DEFAULT 1,
     created_by varchar(50) NOT NULL,
     created_date datetime NOT NULL,
     updated_by varchar(50) NOT NULL,
     updated_date datetime NOT NULL,
-    PRIMARY KEY(id)
+    PRIMARY KEY(record_id)
 ) ENGINE=innodb;
-CREATE INDEX idx_ent_1 ON entitlement_events(subscription_id,is_active,effective_dt);
-CREATE INDEX idx_ent_2 ON entitlement_events(subscription_id,effective_dt,created_date,requested_dt,id);
+CREATE UNIQUE INDEX entitlement_events_id ON entitlement_events(id);
+CREATE INDEX idx_ent_1 ON entitlement_events(subscription_id,is_active,effective_date);
+CREATE INDEX idx_ent_2 ON entitlement_events(subscription_id,effective_date,created_date,requested_date,id);
 
 DROP TABLE IF EXISTS subscriptions;
 CREATE TABLE subscriptions (
+    record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
     id char(36) NOT NULL,
     bundle_id char(36) NOT NULL,
     category varchar(32) NOT NULL,
-    start_dt datetime NOT NULL,
-    bundle_start_dt datetime NOT NULL,
+    start_date datetime NOT NULL,
+    bundle_start_date datetime NOT NULL,
     active_version int(11) DEFAULT 1,
-    ctd_dt datetime DEFAULT NULL,
-    ptd_dt datetime DEFAULT NULL,
+    charged_through_date datetime DEFAULT NULL,
+    paid_through_date datetime DEFAULT NULL,
     created_by varchar(50) NOT NULL,
     created_date datetime NOT NULL,
     updated_by varchar(50) NOT NULL,
     updated_date datetime NOT NULL,
-    PRIMARY KEY(id)
+    PRIMARY KEY(record_id)
 ) ENGINE=innodb;
+CREATE UNIQUE INDEX subscriptions_id ON subscriptions(id);
 
 DROP TABLE IF EXISTS bundles;
 CREATE TABLE bundles (
+    record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
     id char(36) NOT NULL,
-    start_dt datetime, /*NOT NULL*/
-    name varchar(64) NOT NULL,
+    start_date datetime, /*NOT NULL*/
+    external_key varchar(64) NOT NULL,
     account_id char(36) NOT NULL,
-    PRIMARY KEY(id)
+    last_sys_update_date datetime,
+    PRIMARY KEY(record_id)
 ) ENGINE=innodb;
+ CREATE UNIQUE INDEX bundles_id ON bundles(id);
diff --git a/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/BundleSqlDao.sql.stg b/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/BundleSqlDao.sql.stg
index 4f0e4db..2b0467d 100644
--- a/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/BundleSqlDao.sql.stg
+++ b/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/BundleSqlDao.sql.stg
@@ -1,53 +1,68 @@
 group BundleSqlDao;
 
+fields(prefix) ::= <<
+    <prefix>id,
+    <prefix>start_date,
+    <prefix>external_key,
+    <prefix>account_id,
+    <prefix>last_sys_update_date
+>>
+
 insertBundle() ::= <<
-    insert into bundles (
-      id
-      , start_dt
-      , name
-      , account_id
-    ) values (
-      :id
-      , :start_dt
-      , :name
-      , :account_id
-    );
->>
-
-getBundleFromId(id) ::= <<
-    select
-      id
-      , start_dt
-      , name
-      , account_id
+    insert into bundles (<fields()>)
+    values (:id, :startDate, :externalKey, :accountId, :lastSysUpdateDate);
+>>
+
+updateBundleLastSysTime()  ::= <<
+    update bundles
+    set
+        last_sys_update_date = :lastSysUpdateDate
+    where id = :id
+    ;
+>>
+
+getBundleFromId() ::= <<
+    select <fields()>
     from bundles
     where
       id = :id
     ;
 >>
 
-getBundleFromKey(name) ::= <<
-    select
-      id
-      , start_dt
-      , name
-      , account_id
+getBundleFromKey() ::= <<
+    select <fields()>
     from bundles
     where
-      name = :name
+      external_key = :externalKey
     ;
 >>
 
-
-getBundleFromAccount(account_id) ::= <<
-    select
-      id
-      , start_dt
-      , name
-      , account_id
+getBundleFromAccount() ::= <<
+    select <fields()>
     from bundles
     where
-      account_id = :account_id
+      account_id = :accountId
     ;
 >>
 
+getRecordId() ::= <<
+    SELECT record_id
+    FROM bundles
+    WHERE id = :id;
+>>
+
+auditFields(prefix) ::= <<
+    <prefix>table_name,
+    <prefix>record_id,
+    <prefix>change_type,
+    <prefix>change_date,
+    <prefix>changed_by,
+    <prefix>reason_code,
+    <prefix>comments,
+    <prefix>user_token
+>>
+
+insertAuditFromTransaction() ::= <<
+    INSERT INTO audit_log(<auditFields()>)
+    VALUES(:tableName, :recordId, :changeType, :createdDate, :userName, NULL, NULL, NULL);
+>>
\ No newline at end of file
diff --git a/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/SubscriptionSqlDao.sql.stg b/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/SubscriptionSqlDao.sql.stg
index 780c06a..1b16b00 100644
--- a/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/SubscriptionSqlDao.sql.stg
+++ b/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/SubscriptionSqlDao.sql.stg
@@ -5,24 +5,24 @@ insertSubscription() ::= <<
         id
       , bundle_id
       , category
-      , start_dt
-      , bundle_start_dt
+      , start_date
+      , bundle_start_date
       , active_version
-      , ctd_dt
-      , ptd_dt
+      , charged_through_date
+      , paid_through_date
       , created_by
       , created_date
       , updated_by
       , updated_date
     ) values (
         :id
-      , :bundle_id
+      , :bundleId
       , :category
-      , :start_dt
-      , :bundle_start_dt
-      , :active_version
-      , :ctd_dt
-      , :ptd_dt
+      , :startDate
+      , :bundleStartDate
+      , :activeVersion
+      , :chargedThroughDate
+      , :paidThroughDate
       , :userName
       , :createdDate
       , :userName
@@ -30,44 +30,86 @@ insertSubscription() ::= <<
     );
 >>
 
-getSubscriptionFromId(id) ::= <<
+getSubscriptionFromId() ::= <<
     select
         id
       , bundle_id
       , category
-      , start_dt
-      , bundle_start_dt
+      , start_date
+      , bundle_start_date
       , active_version
-      , ctd_dt
-      , ptd_dt    
+      , charged_through_date
+      , paid_through_date    
     from subscriptions
     where id = :id
     ;
 >>
 
-getSubscriptionsFromBundleId(bundle_id) ::= <<
+getSubscriptionsFromBundleId() ::= <<
     select
       id
       , bundle_id
       , category
-      , start_dt
-      , bundle_start_dt
+      , start_date
+      , bundle_start_date
       , active_version
-      , ctd_dt
-      , ptd_dt    
+      , charged_through_date
+      , paid_through_date    
     from subscriptions
-    where bundle_id = :bundle_id
+    where bundle_id = :bundleId
     ;
 >>
 
-updateSubscription(id, active_version, ctd_dt, ptd_dt) ::= <<
+updateChargedThroughDate() ::= <<
     update subscriptions
     set
-      active_version = :active_version
-      , ctd_dt = :ctd_dt
-      , ptd_dt = :ptd_dt
+      charged_through_date = :chargedThroughDate
       , updated_by = :userName
       , updated_date = :updatedDate
     where id = :id
     ;
 >>
+
+updateActiveVersion() ::= <<
+    update subscriptions
+    set
+      active_version = :activeVersion
+      , updated_by = :userName
+      , updated_date = :updatedDate
+    where id = :id
+    ;
+>>
+
+updateForRepair() ::= <<
+    update subscriptions
+    set
+      active_version = :activeVersion
+      , start_date = :startDate
+      , bundle_start_date = :bundleStartDate
+      , updated_by = :userName
+      , updated_date = :updatedDate
+    where id = :id
+    ;
+>>
+
+getRecordId() ::= <<
+    SELECT record_id
+    FROM subscriptions
+    WHERE id = :id;
+>>
+
+auditFields(prefix) ::= <<
+    <prefix>table_name,
+    <prefix>record_id,
+    <prefix>change_type,
+    <prefix>change_date,
+    <prefix>changed_by,
+    <prefix>reason_code,
+    <prefix>comments,
+    <prefix>user_token
+>>
+
+insertAuditFromTransaction() ::= <<
+    INSERT INTO audit_log(<auditFields()>)
+    VALUES(:tableName, :recordId, :changeType, :createdDate, :userName, NULL, NULL, NULL);
+>>
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/migration/TestMigration.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/migration/TestMigration.java
index 1d679a9..2681a0b 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/migration/TestMigration.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/migration/TestMigration.java
@@ -27,15 +27,15 @@ import java.util.List;
 import java.util.UUID;
 
 import org.joda.time.DateTime;
+import org.joda.time.Interval;
 import org.testng.Assert;
 
+import com.ning.billing.api.TestApiListener.NextEvent;
 import com.ning.billing.catalog.api.BillingPeriod;
-import com.ning.billing.catalog.api.Duration;
 import com.ning.billing.catalog.api.PhaseType;
 import com.ning.billing.catalog.api.PlanPhaseSpecifier;
 import com.ning.billing.catalog.api.PriceListSet;
 import com.ning.billing.catalog.api.ProductCategory;
-import com.ning.billing.entitlement.api.ApiTestListener.NextEvent;
 import com.ning.billing.entitlement.api.TestApiBase;
 import com.ning.billing.entitlement.api.migration.EntitlementMigrationApi.EntitlementAccountMigration;
 import com.ning.billing.entitlement.api.migration.EntitlementMigrationApi.EntitlementBundleMigration;
@@ -44,15 +44,16 @@ import com.ning.billing.entitlement.api.migration.EntitlementMigrationApi.Entitl
 import com.ning.billing.entitlement.api.user.Subscription;
 import com.ning.billing.entitlement.api.user.Subscription.SubscriptionState;
 import com.ning.billing.entitlement.api.user.SubscriptionBundle;
-import org.testng.annotations.Test;
 
-@Test(groups = {"slow"})
 public abstract class TestMigration extends TestApiBase {
     public void testSingleBasePlan() {
 
         try {
+            
+            log.info("Starting testSingleBasePlan");
+
             final DateTime startDate = clock.getUTCNow().minusMonths(2);
-            DateTime beforeMigration = clock.getUTCNow();
+            DateTime beforeMigration =  clock.getUTCNow();
             EntitlementAccountMigration toBeMigrated = createAccountWithRegularBasePlan(startDate);
             DateTime afterMigration = clock.getUTCNow();
 
@@ -69,11 +70,13 @@ public abstract class TestMigration extends TestApiBase {
             Subscription subscription = subscriptions.get(0);
             assertDateWithin(subscription.getStartDate(), beforeMigration, afterMigration);
             assertEquals(subscription.getEndDate(), null);
-            assertEquals(subscription.getCurrentPriceList(), PriceListSet.DEFAULT_PRICELIST_NAME);
+            assertEquals(subscription.getCurrentPriceList().getName(), PriceListSet.DEFAULT_PRICELIST_NAME);
             assertEquals(subscription.getCurrentPhase().getPhaseType(), PhaseType.EVERGREEN);
             assertEquals(subscription.getState(), SubscriptionState.ACTIVE);
             assertEquals(subscription.getCurrentPlan().getName(), "assault-rifle-annual");
             assertEquals(subscription.getChargedThroughDate(), startDate.plusYears(1));
+
+            assertListenerStatus();
         } catch (EntitlementMigrationApiException e) {
             Assert.fail("", e);
         }
@@ -81,6 +84,7 @@ public abstract class TestMigration extends TestApiBase {
 
     public void testPlanWithAddOn() {
         try {
+            log.info("Starting testPlanWithAddOn");
             DateTime beforeMigration = clock.getUTCNow();
             final DateTime initalBPStart = clock.getUTCNow().minusMonths(3);
             final DateTime initalAddonStart = clock.getUTCNow().minusMonths(1).plusDays(7);
@@ -103,7 +107,7 @@ public abstract class TestMigration extends TestApiBase {
                     subscriptions.get(0) : subscriptions.get(1);
             assertDateWithin(baseSubscription.getStartDate(), beforeMigration, afterMigration);
             assertEquals(baseSubscription.getEndDate(), null);
-            assertEquals(baseSubscription.getCurrentPriceList(), PriceListSet.DEFAULT_PRICELIST_NAME);
+            assertEquals(baseSubscription.getCurrentPriceList().getName(), PriceListSet.DEFAULT_PRICELIST_NAME);
             assertEquals(baseSubscription.getCurrentPhase().getPhaseType(), PhaseType.EVERGREEN);
             assertEquals(baseSubscription.getState(), SubscriptionState.ACTIVE);
             assertEquals(baseSubscription.getCurrentPlan().getName(), "shotgun-annual");
@@ -111,14 +115,17 @@ public abstract class TestMigration extends TestApiBase {
 
             Subscription aoSubscription = (subscriptions.get(0).getCurrentPlan().getProduct().getCategory() == ProductCategory.ADD_ON) ?
                     subscriptions.get(0) : subscriptions.get(1);
-            assertEquals(aoSubscription.getStartDate(), initalAddonStart);
+            // initalAddonStart.plusMonths(1).minusMonths(1) may be different from initalAddonStart, depending on exact date
+            // e.g : March 31 + 1 month => April 30 and April 30 - 1 month = March 30 which is != March 31 !!!! 
+            assertEquals(aoSubscription.getStartDate(), initalAddonStart.plusMonths(1).minusMonths(1));
             assertEquals(aoSubscription.getEndDate(), null);
-            assertEquals(aoSubscription.getCurrentPriceList(), PriceListSet.DEFAULT_PRICELIST_NAME);
+            assertEquals(aoSubscription.getCurrentPriceList().getName(), PriceListSet.DEFAULT_PRICELIST_NAME);
             assertEquals(aoSubscription.getCurrentPhase().getPhaseType(), PhaseType.DISCOUNT);
             assertEquals(aoSubscription.getState(), SubscriptionState.ACTIVE);
             assertEquals(aoSubscription.getCurrentPlan().getName(), "telescopic-scope-monthly");
             assertEquals(aoSubscription.getChargedThroughDate(), initalAddonStart.plusMonths(1));
 
+            assertListenerStatus();
         } catch (EntitlementMigrationApiException e) {
             Assert.fail("", e);
         }
@@ -127,7 +134,7 @@ public abstract class TestMigration extends TestApiBase {
     public void testSingleBasePlanFutureCancelled() {
 
         try {
-
+            log.info("Starting testSingleBasePlanFutureCancelled");
             final DateTime startDate = clock.getUTCNow().minusMonths(1);
             DateTime beforeMigration = clock.getUTCNow();
             EntitlementAccountMigration toBeMigrated = createAccountWithRegularBasePlanFutreCancelled(startDate);
@@ -146,26 +153,30 @@ public abstract class TestMigration extends TestApiBase {
             assertEquals(subscriptions.size(), 1);
             Subscription subscription = subscriptions.get(0);
             assertDateWithin(subscription.getStartDate(), beforeMigration, afterMigration);
-            assertEquals(subscription.getCurrentPriceList(), PriceListSet.DEFAULT_PRICELIST_NAME);
+            assertEquals(subscription.getCurrentPriceList().getName(), PriceListSet.DEFAULT_PRICELIST_NAME);
             assertEquals(subscription.getCurrentPhase().getPhaseType(), PhaseType.EVERGREEN);
             assertEquals(subscription.getState(), SubscriptionState.ACTIVE);
             assertEquals(subscription.getCurrentPlan().getName(), "assault-rifle-annual");
             assertEquals(subscription.getChargedThroughDate(), startDate.plusYears(1));
 
+
             testListener.pushExpectedEvent(NextEvent.MIGRATE_BILLING);
             testListener.pushExpectedEvent(NextEvent.CANCEL);
-            Duration oneYear = getDurationYear(1);
-            clock.setDeltaFromReality(oneYear, 0);
+            
+            Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusYears(1));
+            clock.addDeltaFromReality(it.toDurationMillis());
             assertTrue(testListener.isCompleted(5000));
 
             assertDateWithin(subscription.getStartDate(), beforeMigration, afterMigration);
             assertNotNull(subscription.getEndDate());
             assertTrue(subscription.getEndDate().isAfterNow());
-            assertEquals(subscription.getCurrentPriceList(), PriceListSet.DEFAULT_PRICELIST_NAME);
+            assertEquals(subscription.getCurrentPriceList().getName(), PriceListSet.DEFAULT_PRICELIST_NAME);
             assertEquals(subscription.getCurrentPhase(), null);
             assertEquals(subscription.getState(), SubscriptionState.CANCELLED);
             assertNull(subscription.getCurrentPlan());
 
+            assertListenerStatus();
+            
         } catch (EntitlementMigrationApiException e) {
             Assert.fail("", e);
         }
@@ -174,6 +185,8 @@ public abstract class TestMigration extends TestApiBase {
     public void testSingleBasePlanWithPendingPhase() {
 
         try {
+            
+            log.info("Starting testSingleBasePlanWithPendingPhase");
             final DateTime trialDate = clock.getUTCNow().minusDays(10);
             EntitlementAccountMigration toBeMigrated = createAccountFuturePendingPhase(trialDate);
 
@@ -191,7 +204,7 @@ public abstract class TestMigration extends TestApiBase {
 
             assertEquals(subscription.getStartDate(), trialDate);
             assertEquals(subscription.getEndDate(), null);
-            assertEquals(subscription.getCurrentPriceList(), PriceListSet.DEFAULT_PRICELIST_NAME);
+            assertEquals(subscription.getCurrentPriceList().getName(), PriceListSet.DEFAULT_PRICELIST_NAME);
             assertEquals(subscription.getCurrentPhase().getPhaseType(), PhaseType.TRIAL);
             assertEquals(subscription.getState(), SubscriptionState.ACTIVE);
             assertEquals(subscription.getCurrentPlan().getName(), "assault-rifle-monthly");
@@ -199,18 +212,21 @@ public abstract class TestMigration extends TestApiBase {
 
             testListener.pushExpectedEvent(NextEvent.MIGRATE_BILLING);
             testListener.pushExpectedEvent(NextEvent.PHASE);
-            Duration thirtyDays = getDurationDay(30);
-            clock.setDeltaFromReality(thirtyDays, 0);
+
+            Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(30));
+            clock.addDeltaFromReality(it.toDurationMillis());
             assertTrue(testListener.isCompleted(5000));
 
             assertEquals(subscription.getStartDate(), trialDate);
             assertEquals(subscription.getEndDate(), null);
-            assertEquals(subscription.getCurrentPriceList(), PriceListSet.DEFAULT_PRICELIST_NAME);
+            assertEquals(subscription.getCurrentPriceList().getName(), PriceListSet.DEFAULT_PRICELIST_NAME);
             assertEquals(subscription.getCurrentPhase().getPhaseType(), PhaseType.EVERGREEN);
             assertEquals(subscription.getState(), SubscriptionState.ACTIVE);
             assertEquals(subscription.getCurrentPlan().getName(), "assault-rifle-monthly");
             assertEquals(subscription.getCurrentPhase().getName(), "assault-rifle-monthly-evergreen");
 
+            assertListenerStatus();
+            
         } catch (EntitlementMigrationApiException e) {
             Assert.fail("", e);
         }
@@ -219,6 +235,7 @@ public abstract class TestMigration extends TestApiBase {
     public void testSingleBasePlanWithPendingChange() {
 
         try {
+            log.info("Starting testSingleBasePlanWithPendingChange");
             DateTime beforeMigration = clock.getUTCNow();
             EntitlementAccountMigration toBeMigrated = createAccountFuturePendingChange();
             DateTime afterMigration = clock.getUTCNow();
@@ -236,24 +253,27 @@ public abstract class TestMigration extends TestApiBase {
             Subscription subscription = subscriptions.get(0);
             assertDateWithin(subscription.getStartDate(), beforeMigration, afterMigration);
             assertEquals(subscription.getEndDate(), null);
-            assertEquals(subscription.getCurrentPriceList(), PriceListSet.DEFAULT_PRICELIST_NAME);
+            assertEquals(subscription.getCurrentPriceList().getName(), PriceListSet.DEFAULT_PRICELIST_NAME);
             assertEquals(subscription.getCurrentPhase().getPhaseType(), PhaseType.EVERGREEN);
             assertEquals(subscription.getState(), SubscriptionState.ACTIVE);
             assertEquals(subscription.getCurrentPlan().getName(), "assault-rifle-monthly");
 
             testListener.pushExpectedEvent(NextEvent.CHANGE);
-            Duration oneMonth = getDurationMonth(1);
-            clock.setDeltaFromReality(oneMonth, 0);
+            
+            Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusMonths(1));
+            clock.addDeltaFromReality(it.toDurationMillis());
             assertTrue(testListener.isCompleted(5000));
 
             assertDateWithin(subscription.getStartDate(), beforeMigration, afterMigration);
             assertEquals(subscription.getEndDate(), null);
-            assertEquals(subscription.getCurrentPriceList(), PriceListSet.DEFAULT_PRICELIST_NAME);
+            assertEquals(subscription.getCurrentPriceList().getName(), PriceListSet.DEFAULT_PRICELIST_NAME);
 
             assertEquals(subscription.getCurrentPhase().getPhaseType(), PhaseType.EVERGREEN);
             assertEquals(subscription.getState(), SubscriptionState.ACTIVE);
             assertEquals(subscription.getCurrentPlan().getName(), "shotgun-annual");
 
+            assertListenerStatus();
+            
         } catch (EntitlementMigrationApiException e) {
             Assert.fail("", e);
         }
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/migration/TestMigrationMemory.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/migration/TestMigrationMemory.java
index 6ea53bc..0195d69 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/migration/TestMigrationMemory.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/migration/TestMigrationMemory.java
@@ -31,26 +31,26 @@ public class TestMigrationMemory extends TestMigration {
     }
 
     @Override
-    @Test(enabled=false, groups="fast")
+    @Test(enabled=true, groups="fast")
     public void testSingleBasePlan() {
         super.testSingleBasePlan();
     }
 
     @Override
-    @Test(enabled=false, groups="fast")
+    @Test(enabled=true, groups="fast")
     public void testSingleBasePlanFutureCancelled() {
         super.testSingleBasePlanFutureCancelled();
     }
 
     @Override
-    @Test(enabled=false, groups="fast")
+    @Test(enabled=true, groups="fast")
     public void testPlanWithAddOn() {
         super.testPlanWithAddOn();
     }
 
 
     @Override
-    @Test(enabled=false, groups="fast")
+    @Test(enabled=true, groups="fast")
     public void testSingleBasePlanWithPendingPhase() {
         super.testSingleBasePlanWithPendingPhase();
     }
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/migration/TestMigrationSql.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/migration/TestMigrationSql.java
index 587ca46..34b2266 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/migration/TestMigrationSql.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/migration/TestMigrationSql.java
@@ -23,9 +23,7 @@ import com.google.inject.Injector;
 import com.google.inject.Stage;
 import com.ning.billing.entitlement.glue.MockEngineModuleSql;
 
-@Test(groups = "slow")
 public class TestMigrationSql extends TestMigration {
-
     @Override
     protected Injector getInjector() {
         return Guice.createInjector(Stage.DEVELOPMENT, new MockEngineModuleSql());
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/TestApiBase.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/TestApiBase.java
index 8379269..7d1389e 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/TestApiBase.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/TestApiBase.java
@@ -26,24 +26,26 @@ import java.net.URL;
 import java.util.List;
 import java.util.UUID;
 
-import com.ning.billing.util.callcontext.CallContext;
-import com.ning.billing.util.callcontext.TestCallContext;
+import javax.annotation.Nullable;
+
 import org.apache.commons.io.IOUtils;
 import org.joda.time.DateTime;
 import org.joda.time.DateTimeZone;
+import org.joda.time.Period;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.testng.Assert;
-import org.testng.ITestResult;
 import org.testng.annotations.AfterClass;
 import org.testng.annotations.AfterMethod;
-import org.testng.annotations.AfterSuite;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.BeforeSuite;
 
 import com.google.inject.Injector;
+import com.google.inject.Key;
 import com.ning.billing.account.api.AccountData;
+import com.ning.billing.api.TestApiListener;
+import com.ning.billing.api.TestApiListener.NextEvent;
+import com.ning.billing.api.TestListenerStatus;
 import com.ning.billing.catalog.DefaultCatalogService;
 import com.ning.billing.catalog.api.BillingPeriod;
 import com.ning.billing.catalog.api.Catalog;
@@ -56,14 +58,14 @@ import com.ning.billing.catalog.api.ProductCategory;
 import com.ning.billing.catalog.api.TimeUnit;
 import com.ning.billing.config.EntitlementConfig;
 import com.ning.billing.dbi.MysqlTestingHelper;
-import com.ning.billing.entitlement.api.ApiTestListener.NextEvent;
-import com.ning.billing.entitlement.api.billing.EntitlementBillingApi;
+import com.ning.billing.entitlement.api.billing.ChargeThruApi;
 import com.ning.billing.entitlement.api.migration.EntitlementMigrationApi;
+import com.ning.billing.entitlement.api.timeline.EntitlementTimelineApi;
 import com.ning.billing.entitlement.api.user.EntitlementUserApi;
 import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
 import com.ning.billing.entitlement.api.user.SubscriptionBundle;
 import com.ning.billing.entitlement.api.user.SubscriptionData;
-import com.ning.billing.entitlement.api.user.SubscriptionTransition;
+import com.ning.billing.entitlement.api.user.SubscriptionEvent;
 import com.ning.billing.entitlement.engine.core.Engine;
 import com.ning.billing.entitlement.engine.dao.EntitlementDao;
 import com.ning.billing.entitlement.engine.dao.MockEntitlementDao;
@@ -72,24 +74,27 @@ import com.ning.billing.entitlement.events.EntitlementEvent;
 import com.ning.billing.entitlement.events.phase.PhaseEvent;
 import com.ning.billing.entitlement.events.user.ApiEvent;
 import com.ning.billing.entitlement.events.user.ApiEventType;
+import com.ning.billing.util.bus.BusService;
+import com.ning.billing.util.bus.DefaultBusService;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.callcontext.TestCallContext;
 import com.ning.billing.util.clock.Clock;
 import com.ning.billing.util.clock.ClockMock;
-import com.ning.billing.util.bus.DefaultBusService;
-import com.ning.billing.util.bus.BusService;
-
-import javax.annotation.Nullable;
+import com.ning.billing.util.glue.RealImplementation;
 
 
-public abstract class TestApiBase {
+public abstract class TestApiBase implements TestListenerStatus {
+    
     protected static final Logger log = LoggerFactory.getLogger(TestApiBase.class);
 
     protected static final long DAY_IN_MS = (24 * 3600 * 1000);
 
     protected EntitlementService entitlementService;
     protected EntitlementUserApi entitlementApi;
-    protected EntitlementBillingApi billingApi;
+    protected ChargeThruApi billingApi;
 
     protected EntitlementMigrationApi migrationApi;
+    protected EntitlementTimelineApi repairApi;
 
     protected CatalogService catalogService;
     protected EntitlementConfig config;
@@ -99,12 +104,23 @@ public abstract class TestApiBase {
 
     protected AccountData accountData;
     protected Catalog catalog;
-    protected ApiTestListener testListener;
+    protected TestApiListener testListener;
     protected SubscriptionBundle bundle;
 
     private MysqlTestingHelper helper;
     protected CallContext context = new TestCallContext("Api Test");
 
+    private boolean isListenerFailed;
+    private String listenerFailedMsg;    
+
+    //
+    // The date on which we make our test start; just to ensure that running tests at different dates does not
+    // produce different results. nothing specific about that date; we could change it to anything.
+    //
+    protected DateTime testStartDate = new DateTime(2012, 5, 7, 0, 3, 42, 0);
+
+
+    
     public static void loadSystemPropertiesFromClasspath(final String resource) {
         final URL url = TestApiBase.class.getResource(resource);
         assertNotNull(url);
@@ -128,37 +144,57 @@ public abstract class TestApiBase {
         } catch (Exception e) {
             log.warn("Failed to tearDown test properly ", e);
         }
-        //if(helper != null) { helper.stopMysql(); }
     }
 
+    
+    @Override
+    public void failed(final String msg) {
+        this.isListenerFailed = true;
+        this.listenerFailedMsg = msg;
+    }
+
+    @Override
+    public void resetTestListenerStatus() {
+        this.isListenerFailed = false;
+        this.listenerFailedMsg = null;
+    }
+    
     @BeforeClass(alwaysRun = true)
-    public void setup() {
+    public void setup() throws Exception {
 
         loadSystemPropertiesFromClasspath("/entitlement.properties");
         final Injector g = getInjector();
 
         entitlementService = g.getInstance(EntitlementService.class);
+        EntitlementUserApi entApi = (EntitlementUserApi)g.getInstance(Key.get(EntitlementUserApi.class, RealImplementation.class));
+        entitlementApi = entApi;
+        billingApi = g.getInstance(ChargeThruApi.class);
+        migrationApi = g.getInstance(EntitlementMigrationApi.class);
+        repairApi = g.getInstance(EntitlementTimelineApi.class);
         catalogService = g.getInstance(CatalogService.class);
         busService = g.getInstance(BusService.class);
         config = g.getInstance(EntitlementConfig.class);
         dao = g.getInstance(EntitlementDao.class);
         clock = (ClockMock) g.getInstance(Clock.class);
         helper = (isSqlTest(dao)) ? g.getInstance(MysqlTestingHelper.class) : null;
-
-        try {
-            ((DefaultCatalogService) catalogService).loadCatalog();
-            ((DefaultBusService) busService).startBus();
-            ((Engine) entitlementService).initialize();
-            init();
-        } catch (Exception e) {
-        }
+        init();
     }
 
-    private static boolean isSqlTest(EntitlementDao theDao) {
-        return (! (theDao instanceof MockEntitlementDaoMemory));
+    private void init() throws Exception {
+
+        setupDao();
+
+        ((DefaultCatalogService) catalogService).loadCatalog();
+        ((Engine) entitlementService).initialize();
+
+        accountData = getAccountData();
+        assertNotNull(accountData);
+        catalog = catalogService.getFullCatalog();
+        assertNotNull(catalog);
+        testListener = new TestApiListener(this);
     }
 
-    private void setupMySQL() throws IOException {
+    private void setupDao() throws IOException {
         if (helper != null) {
             final String entitlementDdl = IOUtils.toString(TestApiBase.class.getResourceAsStream("/com/ning/billing/entitlement/ddl.sql"));
             final String utilDdl = IOUtils.toString(TestApiBase.class.getResourceAsStream("/com/ning/billing/util/ddl.sql"));
@@ -168,68 +204,87 @@ public abstract class TestApiBase {
         }
     }
 
-    private void init() throws Exception {
-
-        setupMySQL();
-
-        accountData = getAccountData();
-        assertNotNull(accountData);
-
-        catalog = catalogService.getFullCatalog();
-        assertNotNull(catalog);
-
-
-        testListener = new ApiTestListener(busService.getBus());
-        entitlementApi = entitlementService.getUserApi();
-        billingApi = entitlementService.getBillingApi();
-        migrationApi = entitlementService.getMigrationApi();
+    private static boolean isSqlTest(EntitlementDao theDao) {
+        return (! (theDao instanceof MockEntitlementDaoMemory));
     }
-
+   
     @BeforeMethod(alwaysRun = true)
-    public void setupTest() {
+    public void setupTest() throws Exception {
 
         log.warn("RESET TEST FRAMEWORK\n\n");
 
+        // CLEANUP ALL DB TABLES OR IN MEMORY STRUCTURES
+        cleanupDao();
+        
+        // RESET LIST OF EXPECTED EVENTS
         if (testListener != null) {
             testListener.reset();
+            resetTestListenerStatus();
         }
-
+        
+        // RESET CLOCK
         clock.resetDeltaFromReality();
-        ((MockEntitlementDao) dao).reset();
-
-        try {
-            busService.getBus().register(testListener);
-            UUID accountId = UUID.randomUUID();
-            bundle = entitlementApi.createBundleForAccount(accountId, "myDefaultBundle", context);
-        } catch (Exception e) {
-            Assert.fail(e.getMessage());
-        }
-        assertNotNull(bundle);
 
+        // START BUS AND REGISTER LISTENER
+        busService.getBus().start();
+        busService.getBus().register(testListener);
+        
+        // START NOTIFICATION QUEUE FOR ENTITLEMENT
         ((Engine)entitlementService).start();
+        
+        // SETUP START DATE
+        clock.setDeltaFromReality(testStartDate.getMillis() - clock.getUTCNow().getMillis());
+        
+        // CREATE NEW BUNDLE FOR TEST
+        UUID accountId = UUID.randomUUID();
+        bundle = entitlementApi.createBundleForAccount(accountId, "myDefaultBundle", context);
+        assertNotNull(bundle);
     }
 
     @AfterMethod(alwaysRun = true)
-    public void cleanupTest() {
-        try {
-            busService.getBus().unregister(testListener);
-            ((Engine)entitlementService).stop();
-        } catch (Exception e) {
-            Assert.fail(e.getMessage());
-        }
+    public void cleanupTest() throws Exception {
+        
+        // UNREGISTER TEST LISTENER AND STOP BUS
+        busService.getBus().unregister(testListener);
+        busService.getBus().stop();
+        
+        // STOP NOTIFICATION QUEUE
+        ((Engine)entitlementService).stop();
+
         log.warn("DONE WITH TEST\n");
     }
+    
+    protected void assertListenerStatus() {
+        if (isListenerFailed) {
+            log.error(listenerFailedMsg);
+            Assert.fail(listenerFailedMsg);
+        }
+    }
+    
+    private void cleanupDao() {
+        if (helper != null) {
+            helper.cleanupAllTables();
+        } else {
+            ((MockEntitlementDao) dao).reset();
+        }
+    }
 
-    protected SubscriptionData createSubscription(final String productName, final BillingPeriod term, final String planSet) throws EntitlementUserApiException {
-        return createSubscriptionWithBundle(bundle.getId(), productName, term, planSet);
+    protected SubscriptionData createSubscription(final String productName, final BillingPeriod term, final String planSet, final DateTime requestedDate)
+        throws EntitlementUserApiException {
+        return createSubscriptionWithBundle(bundle.getId(), productName, term, planSet, requestedDate);
+    }
+    protected SubscriptionData createSubscription(final String productName, final BillingPeriod term, final String planSet)
+    throws EntitlementUserApiException {
+        return createSubscriptionWithBundle(bundle.getId(), productName, term, planSet, null);
     }
 
-    protected SubscriptionData createSubscriptionWithBundle(final UUID bundleId, final String productName, final BillingPeriod term, final String planSet) throws EntitlementUserApiException {
+    protected SubscriptionData createSubscriptionWithBundle(final UUID bundleId, final String productName, final BillingPeriod term, final String planSet, final DateTime requestedDate)
+        throws EntitlementUserApiException {
+        
         testListener.pushExpectedEvent(NextEvent.CREATE);
-
         SubscriptionData subscription = (SubscriptionData) entitlementApi.createSubscription(bundleId,
                 new PlanPhaseSpecifier(productName, ProductCategory.BASE, term, planSet, null),
-                clock.getUTCNow(), context);
+                requestedDate == null ? clock.getUTCNow() : requestedDate, context);
         assertNotNull(subscription);
         assertTrue(testListener.isCompleted(5000));
         return subscription;
@@ -262,7 +317,6 @@ public abstract class TestApiBase {
         }
     }
 
-
     protected void assertDateWithin(DateTime in, DateTime lower, DateTime upper) {
         assertTrue(in.isEqual(lower) || in.isAfter(lower));
         assertTrue(in.isEqual(upper) || in.isBefore(upper));
@@ -283,6 +337,10 @@ public abstract class TestApiBase {
             public DateTime addToDateTime(DateTime dateTime) {
                 return null;
             }
+            @Override
+            public Period toJodaPeriod() {
+                throw new UnsupportedOperationException();
+            }
         };
         return result;
     }
@@ -302,6 +360,10 @@ public abstract class TestApiBase {
             public DateTime addToDateTime(DateTime dateTime) {
                 return null;  //To change body of implemented methods use File | Settings | File Templates.
             }
+            @Override
+            public Period toJodaPeriod() {
+                throw new UnsupportedOperationException();
+            }
         };
         return result;
     }
@@ -320,7 +382,11 @@ public abstract class TestApiBase {
 
             @Override
             public DateTime addToDateTime(DateTime dateTime) {
-                return null;  //To change body of implemented methods use File | Settings | File Templates.
+                return dateTime.plusYears(years);  
+            }
+            @Override
+            public Period toJodaPeriod() {
+                throw new UnsupportedOperationException();
             }
         };
         return result;
@@ -349,6 +415,16 @@ public abstract class TestApiBase {
             }
 
             @Override
+            public boolean isMigrated() {
+                return false;
+            }
+
+            @Override
+            public boolean isNotifiedForInvoices() {
+                return false;
+            }
+
+            @Override
             public String getExternalKey() {
                 return "k123456";
             }
@@ -427,8 +503,8 @@ public abstract class TestApiBase {
         }
     }
 
-    protected void printSubscriptionTransitions(List<SubscriptionTransition> transitions) {
-        for (SubscriptionTransition cur : transitions) {
+    protected void printSubscriptionTransitions(List<SubscriptionEvent> transitions) {
+        for (SubscriptionEvent cur : transitions) {
             log.debug("Transition " + cur);
         }
     }
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/TestEventJson.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/TestEventJson.java
new file mode 100644
index 0000000..49e9ed7
--- /dev/null
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/TestEventJson.java
@@ -0,0 +1,73 @@
+/* 
+ * Copyright 2010-2011 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.entitlement.api;
+
+import java.util.UUID;
+
+import org.codehaus.jackson.map.ObjectMapper;
+import org.codehaus.jackson.map.SerializationConfig;
+import org.joda.time.DateTime;
+import org.testng.Assert;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Test;
+
+import com.ning.billing.entitlement.api.timeline.DefaultRepairEntitlementEvent;
+import com.ning.billing.entitlement.api.timeline.RepairEntitlementEvent;
+import com.ning.billing.entitlement.api.user.DefaultSubscriptionEvent;
+import com.ning.billing.entitlement.api.user.Subscription.SubscriptionState;
+import com.ning.billing.entitlement.api.user.SubscriptionEvent;
+
+public class TestEventJson {
+
+
+    private ObjectMapper mapper = new ObjectMapper();
+
+    @BeforeTest(groups= {"fast"})
+    public void setup() {
+        mapper = new ObjectMapper();
+        mapper.disable(SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS);
+    }
+
+    @Test(groups= {"fast"})
+    public void testSubscriptionEvent() throws Exception {
+        
+
+        SubscriptionEvent e = new DefaultSubscriptionEvent(UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID(), new DateTime(), new DateTime(),
+                SubscriptionState.ACTIVE, "pro", "TRIAL", "DEFAULT", SubscriptionState.CANCELLED, null, null, null, 3L, UUID.randomUUID(), SubscriptionTransitionType.CANCEL, 0, new DateTime());
+            
+        String json = mapper.writeValueAsString(e);
+
+        Class<?> claz = Class.forName(DefaultSubscriptionEvent.class.getName());
+        Object obj =  mapper.readValue(json, claz);
+        Assert.assertTrue(obj.equals(e));
+
+    }
+    
+    @Test(groups= {"fast"})
+    public void testRepairEntitlementEvent() throws Exception {
+        RepairEntitlementEvent e = new DefaultRepairEntitlementEvent(UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID(), new DateTime());
+        
+        String json = mapper.writeValueAsString(e);
+
+        Class<?> claz = Class.forName(DefaultRepairEntitlementEvent.class.getName());
+        Object obj =  mapper.readValue(json, claz);
+        Assert.assertTrue(obj.equals(e));
+    }
+        
+
+    
+    
+}
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/timeline/TestApiBaseRepair.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/timeline/TestApiBaseRepair.java
new file mode 100644
index 0000000..4fc11ab
--- /dev/null
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/timeline/TestApiBaseRepair.java
@@ -0,0 +1,222 @@
+/* 
+ * Copyright 2010-2011 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.entitlement.api.timeline;
+
+import static org.testng.Assert.assertEquals;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+
+
+import com.ning.billing.ErrorCode;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.PhaseType;
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
+import com.ning.billing.entitlement.api.TestApiBase;
+import com.ning.billing.entitlement.api.timeline.BundleTimeline;
+import com.ning.billing.entitlement.api.timeline.EntitlementRepairException;
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline;
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline.DeletedEvent;
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline.ExistingEvent;
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline.NewEvent;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
+
+
+public abstract class TestApiBaseRepair extends TestApiBase {
+
+    protected final static Logger log = LoggerFactory.getLogger(TestApiBaseRepair.class);
+    
+    public interface TestWithExceptionCallback {
+        public void doTest() throws EntitlementRepairException, EntitlementUserApiException;
+    }
+    
+    public static class TestWithException {
+        public void withException(TestWithExceptionCallback callback, ErrorCode code) throws Exception {
+            try {
+                callback.doTest();
+                Assert.fail("Failed to catch exception " + code);
+            } catch (EntitlementRepairException e) {
+                assertEquals(e.getCode(), code.getCode());
+            }
+        }
+    }
+
+    
+    protected SubscriptionTimeline createSubscriptionReapir(final UUID id, final List<DeletedEvent> deletedEvents, final List<NewEvent> newEvents) {
+        return new SubscriptionTimeline() {
+            @Override
+            public UUID getId() {
+                return id;
+            }
+            @Override
+            public List<NewEvent> getNewEvents() {
+                return newEvents;
+            }
+            @Override
+            public List<ExistingEvent> getExistingEvents() {
+                return null;
+            }
+            @Override
+            public List<DeletedEvent> getDeletedEvents() {
+                return deletedEvents;
+            }
+        };
+    }
+
+    protected BundleTimeline createBundleRepair(final UUID bundleId, final String viewId, final List<SubscriptionTimeline> subscriptionRepair) {
+        return new BundleTimeline() {
+            @Override
+            public String getViewId() {
+                return viewId;
+            }
+            @Override
+            public List<SubscriptionTimeline> getSubscriptions() {
+                return subscriptionRepair;
+            }
+            @Override
+            public UUID getBundleId() {
+                return bundleId;
+            }
+            @Override
+            public String getExternalKey() {
+                return null;
+            }
+        };
+    }
+
+    protected ExistingEvent createExistingEventForAssertion(final SubscriptionTransitionType type, 
+            final String productName, final PhaseType phaseType, final ProductCategory category, final String priceListName, final BillingPeriod billingPeriod,
+            final DateTime effectiveDateTime) {
+
+        final PlanPhaseSpecifier spec = new PlanPhaseSpecifier(productName, category, billingPeriod, priceListName, phaseType);
+        ExistingEvent ev = new ExistingEvent() {
+            @Override
+            public SubscriptionTransitionType getSubscriptionTransitionType() {
+                return type;
+            }
+             @Override
+            public DateTime getRequestedDate() {
+                 return null;
+            }
+            @Override
+            public PlanPhaseSpecifier getPlanPhaseSpecifier() {
+                return spec;
+            }
+            @Override
+            public UUID getEventId() {
+                return null;
+            }
+            @Override
+            public DateTime getEffectiveDate() {
+                return effectiveDateTime;
+            }
+        };
+        return ev;
+    }
+    
+    protected SubscriptionTimeline getSubscriptionRepair(final UUID id, final BundleTimeline bundleRepair) {
+        for (SubscriptionTimeline cur : bundleRepair.getSubscriptions()) {
+            if (cur.getId().equals(id)) {
+                return cur;
+            }
+        }
+        Assert.fail("Failed to find SubscriptionReapir " + id);
+        return null;
+    }
+    protected void validateExistingEventForAssertion(final ExistingEvent expected, final ExistingEvent input) {
+        
+        log.info(String.format("Got %s -> Expected %s", input.getPlanPhaseSpecifier().getProductName(), expected.getPlanPhaseSpecifier().getProductName()));
+        assertEquals(input.getPlanPhaseSpecifier().getProductName(), expected.getPlanPhaseSpecifier().getProductName());
+        log.info(String.format("Got %s -> Expected %s", input.getPlanPhaseSpecifier().getPhaseType(), expected.getPlanPhaseSpecifier().getPhaseType()));
+        assertEquals(input.getPlanPhaseSpecifier().getPhaseType(), expected.getPlanPhaseSpecifier().getPhaseType());
+        log.info(String.format("Got %s -> Expected %s", input.getPlanPhaseSpecifier().getProductCategory(), expected.getPlanPhaseSpecifier().getProductCategory()));
+        assertEquals(input.getPlanPhaseSpecifier().getProductCategory(), expected.getPlanPhaseSpecifier().getProductCategory());                    
+        log.info(String.format("Got %s -> Expected %s", input.getPlanPhaseSpecifier().getPriceListName(), expected.getPlanPhaseSpecifier().getPriceListName()));
+        assertEquals(input.getPlanPhaseSpecifier().getPriceListName(), expected.getPlanPhaseSpecifier().getPriceListName());                    
+        log.info(String.format("Got %s -> Expected %s", input.getPlanPhaseSpecifier().getBillingPeriod(), expected.getPlanPhaseSpecifier().getBillingPeriod()));
+        assertEquals(input.getPlanPhaseSpecifier().getBillingPeriod(), expected.getPlanPhaseSpecifier().getBillingPeriod());
+        log.info(String.format("Got %s -> Expected %s", input.getEffectiveDate(), expected.getEffectiveDate()));
+        assertEquals(input.getEffectiveDate(), expected.getEffectiveDate());        
+    }
+    
+    protected DeletedEvent createDeletedEvent(final UUID eventId) {
+        return new DeletedEvent() {
+            @Override
+            public UUID getEventId() {
+                return eventId;
+            }
+        };
+    }
+
+    protected NewEvent createNewEvent(final SubscriptionTransitionType type, final DateTime requestedDate, final PlanPhaseSpecifier spec) {
+
+        return new NewEvent() {
+            @Override
+            public SubscriptionTransitionType getSubscriptionTransitionType() {
+                return type;
+            }
+            @Override
+            public DateTime getRequestedDate() {
+                return requestedDate;
+            }
+            @Override
+            public PlanPhaseSpecifier getPlanPhaseSpecifier() {
+                return spec;
+            }
+        };
+    }
+
+    protected void sortEventsOnBundle(final BundleTimeline bundle) {
+        if (bundle.getSubscriptions() == null) {
+            return;
+        }
+        for (SubscriptionTimeline cur : bundle.getSubscriptions()) {
+            if (cur.getExistingEvents() != null) {
+                sortExistingEvent(cur.getExistingEvents());
+            }
+            if (cur.getNewEvents() != null) {
+                sortNewEvent(cur.getNewEvents());
+            }
+        }
+    }
+
+    protected void sortExistingEvent(final List<ExistingEvent> events) {
+        Collections.sort(events, new Comparator<ExistingEvent>() {
+            @Override
+            public int compare(ExistingEvent arg0, ExistingEvent arg1) {
+                return arg0.getEffectiveDate().compareTo(arg1.getEffectiveDate());
+            }
+        });
+    }
+    protected void sortNewEvent(final List<NewEvent> events) {
+        Collections.sort(events, new Comparator<NewEvent>() {
+            @Override
+            public int compare(NewEvent arg0, NewEvent arg1) {
+                return arg0.getRequestedDate().compareTo(arg1.getRequestedDate());
+            }
+        });
+    }
+
+}
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
new file mode 100644
index 0000000..9e864cd
--- /dev/null
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/timeline/TestRepairBP.java
@@ -0,0 +1,752 @@
+/* 
+ * Copyright 2010-2011 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.entitlement.api.timeline;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
+
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.joda.time.Interval;
+import org.testng.annotations.Test;
+
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Stage;
+import com.ning.billing.ErrorCode;
+import com.ning.billing.api.TestApiListener.NextEvent;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.PhaseType;
+import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.catalog.api.PlanPhase;
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.catalog.api.PriceListSet;
+import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline.DeletedEvent;
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline.ExistingEvent;
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline.NewEvent;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.api.user.Subscription.SubscriptionState;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
+import com.ning.billing.entitlement.api.user.SubscriptionData;
+import com.ning.billing.entitlement.api.user.SubscriptionEvents;
+import com.ning.billing.entitlement.glue.MockEngineModuleSql;
+
+public class TestRepairBP extends TestApiBaseRepair {
+
+    @Override
+    public Injector getInjector() {
+        return Guice.createInjector(Stage.DEVELOPMENT, new MockEngineModuleSql());
+    }
+
+    @Test(groups={"slow"})
+    public void testFetchBundleRepair() throws Exception  {
+
+        log.info("Starting testFetchBundleRepair");
+        
+        String baseProduct = "Shotgun";
+        BillingPeriod baseTerm = BillingPeriod.MONTHLY;
+        String basePriceList = PriceListSet.DEFAULT_PRICELIST_NAME;
+
+        // CREATE BP
+        Subscription baseSubscription = createSubscription(baseProduct, baseTerm, basePriceList);
+
+        String aoProduct = "Telescopic-Scope";
+        BillingPeriod aoTerm = BillingPeriod.MONTHLY;
+        String aoPriceList = PriceListSet.DEFAULT_PRICELIST_NAME;
+
+        SubscriptionData aoSubscription = createSubscription(aoProduct, aoTerm, aoPriceList);
+
+        BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
+        List<SubscriptionTimeline> subscriptionRepair = bundleRepair.getSubscriptions();
+        assertEquals(subscriptionRepair.size(), 2);
+
+        for (SubscriptionTimeline cur : subscriptionRepair) {
+            assertNull(cur.getDeletedEvents());
+            assertNull(cur.getNewEvents());                
+
+            List<ExistingEvent> events = cur.getExistingEvents();
+            assertEquals(events.size(), 2);
+            sortExistingEvent(events);
+
+            assertEquals(events.get(0).getSubscriptionTransitionType(), SubscriptionTransitionType.CREATE);
+            assertEquals(events.get(1).getSubscriptionTransitionType(), SubscriptionTransitionType.PHASE);                    
+            final boolean isBP = cur.getId().equals(baseSubscription.getId());
+            if (isBP) {
+                assertEquals(cur.getId(), baseSubscription.getId());
+
+                assertEquals(events.get(0).getPlanPhaseSpecifier().getProductName(), baseProduct);
+                assertEquals(events.get(0).getPlanPhaseSpecifier().getPhaseType(), PhaseType.TRIAL);
+                assertEquals(events.get(0).getPlanPhaseSpecifier().getProductCategory(),ProductCategory.BASE);                    
+                assertEquals(events.get(0).getPlanPhaseSpecifier().getPriceListName(), basePriceList);                    
+                assertEquals(events.get(0).getPlanPhaseSpecifier().getBillingPeriod(), BillingPeriod.NO_BILLING_PERIOD);
+
+                assertEquals(events.get(1).getPlanPhaseSpecifier().getProductName(), baseProduct);
+                assertEquals(events.get(1).getPlanPhaseSpecifier().getPhaseType(), PhaseType.EVERGREEN);
+                assertEquals(events.get(1).getPlanPhaseSpecifier().getProductCategory(),ProductCategory.BASE);                    
+                assertEquals(events.get(1).getPlanPhaseSpecifier().getPriceListName(), basePriceList);                    
+                assertEquals(events.get(1).getPlanPhaseSpecifier().getBillingPeriod(), baseTerm);
+            } else {
+                assertEquals(cur.getId(), aoSubscription.getId());
+
+                assertEquals(events.get(0).getPlanPhaseSpecifier().getProductName(), aoProduct);
+                assertEquals(events.get(0).getPlanPhaseSpecifier().getPhaseType(), PhaseType.DISCOUNT);                    
+                assertEquals(events.get(0).getPlanPhaseSpecifier().getProductCategory(),ProductCategory.ADD_ON); 
+                assertEquals(events.get(0).getPlanPhaseSpecifier().getPriceListName(), aoPriceList); 
+                assertEquals(events.get(1).getPlanPhaseSpecifier().getBillingPeriod(), aoTerm);                    
+
+                assertEquals(events.get(1).getPlanPhaseSpecifier().getProductName(), aoProduct);
+                assertEquals(events.get(1).getPlanPhaseSpecifier().getPhaseType(), PhaseType.EVERGREEN);                    
+                assertEquals(events.get(1).getPlanPhaseSpecifier().getProductCategory(),ProductCategory.ADD_ON); 
+                assertEquals(events.get(1).getPlanPhaseSpecifier().getPriceListName(), aoPriceList);  
+                assertEquals(events.get(1).getPlanPhaseSpecifier().getBillingPeriod(), aoTerm);                    
+            }
+        }
+        assertListenerStatus();
+    }
+    
+    @Test(groups={"slow"})
+    public void testBPRepairWithCancellationOnstart() throws Exception {
+
+        log.info("Starting testBPRepairWithCancellationOnstart");
+        
+        String baseProduct = "Shotgun";
+        DateTime startDate = clock.getUTCNow();
+        
+        // CREATE BP
+        Subscription baseSubscription = createSubscription(baseProduct, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, startDate);
+
+        // Stays in trial-- for instance
+        Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(10));
+        clock.addDeltaFromReality(it.toDurationMillis());
+
+        BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
+        sortEventsOnBundle(bundleRepair);
+
+        List<DeletedEvent> des = new LinkedList<SubscriptionTimeline.DeletedEvent>();
+        des.add(createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(1).getEventId()));
+        NewEvent ne = createNewEvent(SubscriptionTransitionType.CANCEL, baseSubscription.getStartDate(), null);
+        
+        
+        SubscriptionTimeline sRepair = createSubscriptionReapir(baseSubscription.getId(), des, Collections.singletonList(ne));
+        
+        // FIRST ISSUE DRY RUN
+        BundleTimeline bRepair =  createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(sRepair));
+        
+        boolean dryRun = true;
+        BundleTimeline dryRunBundleRepair = repairApi.repairBundle(bRepair, dryRun, context);
+        sortEventsOnBundle(dryRunBundleRepair);
+        List<SubscriptionTimeline> subscriptionRepair = dryRunBundleRepair.getSubscriptions();
+        assertEquals(subscriptionRepair.size(), 1);
+        SubscriptionTimeline cur = subscriptionRepair.get(0);
+        int index = 0;
+        List<ExistingEvent> events = subscriptionRepair.get(0).getExistingEvents();
+        assertEquals(events.size(), 2);
+        List<ExistingEvent> expected = new LinkedList<SubscriptionTimeline.ExistingEvent>();
+        expected.add(createExistingEventForAssertion(SubscriptionTransitionType.CREATE, baseProduct, PhaseType.TRIAL,
+                ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.NO_BILLING_PERIOD, baseSubscription.getStartDate()));
+        expected.add(createExistingEventForAssertion(SubscriptionTransitionType.CANCEL, baseProduct, PhaseType.TRIAL,
+                ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.NO_BILLING_PERIOD,baseSubscription.getStartDate()));
+
+        for (ExistingEvent e : expected) {
+           validateExistingEventForAssertion(e, events.get(index++));           
+        }
+        
+        SubscriptionData dryRunBaseSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(baseSubscription.getId());
+        
+        assertEquals(dryRunBaseSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION);
+        assertEquals(dryRunBaseSubscription.getBundleId(), bundle.getId());
+        assertEquals(dryRunBaseSubscription.getStartDate(), baseSubscription.getStartDate());
+
+        Plan currentPlan = dryRunBaseSubscription.getCurrentPlan();
+        assertNotNull(currentPlan);
+        assertEquals(currentPlan.getProduct().getName(), baseProduct);
+        assertEquals(currentPlan.getProduct().getCategory(), ProductCategory.BASE);
+        assertEquals(currentPlan.getBillingPeriod(), BillingPeriod.MONTHLY);
+
+        PlanPhase currentPhase = dryRunBaseSubscription.getCurrentPhase();
+        assertNotNull(currentPhase);
+        assertEquals(currentPhase.getPhaseType(), PhaseType.TRIAL);
+        
+       // SECOND RE-ISSUE CALL-- NON DRY RUN
+        dryRun = false;
+        testListener.pushExpectedEvent(NextEvent.REPAIR_BUNDLE);
+        BundleTimeline realRunBundleRepair = repairApi.repairBundle(bRepair, dryRun, context);
+        assertTrue(testListener.isCompleted(5000));
+        
+        subscriptionRepair = realRunBundleRepair.getSubscriptions();
+        assertEquals(subscriptionRepair.size(), 1);
+        cur = subscriptionRepair.get(0);
+        assertEquals(cur.getId(), baseSubscription.getId());
+        index = 0;
+        for (ExistingEvent e : expected) {
+           validateExistingEventForAssertion(e, events.get(index++));           
+        }
+        SubscriptionData realRunBaseSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(baseSubscription.getId());
+        assertEquals(realRunBaseSubscription.getAllTransitions().size(), 2);
+        
+        
+        assertEquals(realRunBaseSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1);
+        assertEquals(realRunBaseSubscription.getBundleId(), bundle.getId());
+        assertEquals(realRunBaseSubscription.getStartDate(), startDate);
+
+        assertEquals(realRunBaseSubscription.getState(), SubscriptionState.CANCELLED);
+        
+        assertListenerStatus();
+    }
+    
+    @Test(groups={"slow"})
+    public void testBPRepairReplaceCreateBeforeTrial() throws Exception {
+        
+        log.info("Starting testBPRepairReplaceCreateBeforeTrial");
+        
+        String baseProduct = "Shotgun";
+        String newBaseProduct = "Assault-Rifle";
+        
+        DateTime startDate = clock.getUTCNow();
+        int clockShift = -1;
+        DateTime restartDate =  startDate.plusDays(clockShift).minusDays(1);
+        LinkedList<ExistingEvent> expected = new LinkedList<SubscriptionTimeline.ExistingEvent>();
+        
+        expected.add(createExistingEventForAssertion(SubscriptionTransitionType.CREATE, newBaseProduct, PhaseType.TRIAL,
+                ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.NO_BILLING_PERIOD, restartDate));
+        expected.add(createExistingEventForAssertion(SubscriptionTransitionType.PHASE, newBaseProduct, PhaseType.EVERGREEN,
+                    ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, restartDate.plusDays(30)));
+
+        testBPRepairCreate(true, startDate, clockShift, baseProduct, newBaseProduct, expected);
+        assertListenerStatus();
+    }
+
+    @Test(groups={"slow"}, enabled=true)
+    public void testBPRepairReplaceCreateInTrial() throws Exception {
+        
+        log.info("Starting testBPRepairReplaceCreateInTrial");
+        
+        String baseProduct = "Shotgun";
+        String newBaseProduct = "Assault-Rifle";
+        
+        DateTime startDate = clock.getUTCNow();
+        int clockShift = 10;
+        DateTime restartDate =  startDate.plusDays(clockShift).minusDays(1);
+        LinkedList<ExistingEvent> expected = new LinkedList<SubscriptionTimeline.ExistingEvent>();
+        
+        expected.add(createExistingEventForAssertion(SubscriptionTransitionType.CREATE, newBaseProduct, PhaseType.TRIAL,
+                ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.NO_BILLING_PERIOD, restartDate));
+        expected.add(createExistingEventForAssertion(SubscriptionTransitionType.PHASE, newBaseProduct, PhaseType.EVERGREEN,
+                    ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, restartDate.plusDays(30)));
+
+        UUID baseSubscriptionId = testBPRepairCreate(true, startDate, clockShift, baseProduct, newBaseProduct, expected);
+        
+        testListener.pushExpectedEvent(NextEvent.PHASE);
+        Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(32));
+        clock.addDeltaFromReality(it.toDurationMillis());
+        assertTrue(testListener.isCompleted(5000));
+        
+        // CHECK WHAT"S GOING ON AFTER WE MOVE CLOCK-- FUTURE MOTIFICATION SHOULD KICK IN
+        SubscriptionData subscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(baseSubscriptionId);
+        
+        assertEquals(subscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1);
+        assertEquals(subscription.getBundleId(), bundle.getId());
+        assertEquals(subscription.getStartDate(), restartDate);
+        assertEquals(subscription.getBundleStartDate(), restartDate);        
+
+        Plan currentPlan = subscription.getCurrentPlan();
+        assertNotNull(currentPlan);
+        assertEquals(currentPlan.getProduct().getName(), newBaseProduct);
+        assertEquals(currentPlan.getProduct().getCategory(), ProductCategory.BASE);
+        assertEquals(currentPlan.getBillingPeriod(), BillingPeriod.MONTHLY);
+
+        PlanPhase currentPhase = subscription.getCurrentPhase();
+        assertNotNull(currentPhase);
+        assertEquals(currentPhase.getPhaseType(), PhaseType.EVERGREEN);
+        
+        assertListenerStatus();
+    }
+
+    
+    @Test(groups={"slow"})
+    public void testBPRepairReplaceCreateAfterTrial() throws Exception {
+        
+        log.info("Starting testBPRepairReplaceCreateAfterTrial");
+        
+        String baseProduct = "Shotgun";
+        String newBaseProduct = "Assault-Rifle";
+        
+        DateTime startDate = clock.getUTCNow();
+        int clockShift = 40;
+        DateTime restartDate =  startDate.plusDays(clockShift).minusDays(1);
+        LinkedList<ExistingEvent> expected = new LinkedList<SubscriptionTimeline.ExistingEvent>();
+        
+        expected.add(createExistingEventForAssertion(SubscriptionTransitionType.CREATE, newBaseProduct, PhaseType.TRIAL,
+                ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.NO_BILLING_PERIOD, restartDate));
+        expected.add(createExistingEventForAssertion(SubscriptionTransitionType.PHASE, newBaseProduct, PhaseType.EVERGREEN,
+                    ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, restartDate.plusDays(30)));
+
+        testBPRepairCreate(false, startDate, clockShift, baseProduct, newBaseProduct, expected);
+        assertListenerStatus();
+    }
+    
+    
+    private UUID testBPRepairCreate(boolean inTrial, DateTime startDate, int clockShift, 
+            String baseProduct, String newBaseProduct, List<ExistingEvent> expectedEvents) throws Exception {
+
+        log.info("Starting testBPRepairCreate");
+        
+        // CREATE BP
+        Subscription baseSubscription = createSubscription(baseProduct, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, startDate);
+
+        // MOVE CLOCK
+        if (clockShift > 0) {
+            if (!inTrial) {
+                testListener.pushExpectedEvent(NextEvent.PHASE);
+            }               
+            
+            Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(clockShift));
+            clock.addDeltaFromReality(it.toDurationMillis());
+            if (!inTrial) {
+                assertTrue(testListener.isCompleted(5000));
+            }
+        }
+
+        BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
+        sortEventsOnBundle(bundleRepair);
+        
+        DateTime newCreateTime = baseSubscription.getStartDate().plusDays(clockShift - 1);
+
+        PlanPhaseSpecifier spec = new PlanPhaseSpecifier(newBaseProduct, ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.TRIAL);
+
+        NewEvent ne = createNewEvent(SubscriptionTransitionType.CREATE, newCreateTime, spec);
+        List<DeletedEvent> des = new LinkedList<SubscriptionTimeline.DeletedEvent>();
+        des.add(createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(0).getEventId()));
+        des.add(createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(1).getEventId()));
+
+        SubscriptionTimeline sRepair = createSubscriptionReapir(baseSubscription.getId(), des, Collections.singletonList(ne));
+        
+        // FIRST ISSUE DRY RUN
+        BundleTimeline bRepair =  createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(sRepair));
+        
+        boolean dryRun = true;        
+        BundleTimeline dryRunBundleRepair = repairApi.repairBundle(bRepair, dryRun, context);
+        List<SubscriptionTimeline> subscriptionRepair = dryRunBundleRepair.getSubscriptions();
+        assertEquals(subscriptionRepair.size(), 1);
+        SubscriptionTimeline cur = subscriptionRepair.get(0);
+        assertEquals(cur.getId(), baseSubscription.getId());
+
+        List<ExistingEvent> events = cur.getExistingEvents();
+        assertEquals(expectedEvents.size(), events.size());
+        int index = 0;
+        for (ExistingEvent e : expectedEvents) {
+           validateExistingEventForAssertion(e, events.get(index++));           
+        }
+        SubscriptionData dryRunBaseSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(baseSubscription.getId());
+        
+        assertEquals(dryRunBaseSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION);
+        assertEquals(dryRunBaseSubscription.getBundleId(), bundle.getId());
+        assertEquals(dryRunBaseSubscription.getStartDate(), baseSubscription.getStartDate());
+
+        Plan currentPlan = dryRunBaseSubscription.getCurrentPlan();
+        assertNotNull(currentPlan);
+        assertEquals(currentPlan.getProduct().getName(), baseProduct);
+        assertEquals(currentPlan.getProduct().getCategory(), ProductCategory.BASE);
+        assertEquals(currentPlan.getBillingPeriod(), BillingPeriod.MONTHLY);
+
+        PlanPhase currentPhase = dryRunBaseSubscription.getCurrentPhase();
+        assertNotNull(currentPhase);
+        if (inTrial) {
+            assertEquals(currentPhase.getPhaseType(), PhaseType.TRIAL);
+        } else {
+            assertEquals(currentPhase.getPhaseType(), PhaseType.EVERGREEN);
+        }
+        
+       // SECOND RE-ISSUE CALL-- NON DRY RUN
+        dryRun = false;
+        testListener.pushExpectedEvent(NextEvent.REPAIR_BUNDLE);
+        BundleTimeline realRunBundleRepair = repairApi.repairBundle(bRepair, dryRun, context);
+        assertTrue(testListener.isCompleted(5000));
+        subscriptionRepair = realRunBundleRepair.getSubscriptions();
+        assertEquals(subscriptionRepair.size(), 1);
+        cur = subscriptionRepair.get(0);
+        assertEquals(cur.getId(), baseSubscription.getId());
+
+        events = cur.getExistingEvents();
+        for (ExistingEvent e : events) {
+            log.info(String.format("%s, %s, %s, %s", e.getSubscriptionTransitionType(), e.getEffectiveDate(), e.getPlanPhaseSpecifier().getProductName(),  e.getPlanPhaseSpecifier().getPhaseType()));
+        }
+        assertEquals(events.size(), expectedEvents.size());
+        index = 0;
+        for (ExistingEvent e : expectedEvents) {
+           validateExistingEventForAssertion(e, events.get(index++));           
+        }
+        SubscriptionData realRunBaseSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(baseSubscription.getId());
+        assertEquals(realRunBaseSubscription.getAllTransitions().size(), 2);
+        
+        
+        assertEquals(realRunBaseSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1);
+        assertEquals(realRunBaseSubscription.getBundleId(), bundle.getId());
+        assertEquals(realRunBaseSubscription.getStartDate(), newCreateTime);
+
+        currentPlan = realRunBaseSubscription.getCurrentPlan();
+        assertNotNull(currentPlan);
+        assertEquals(currentPlan.getProduct().getName(), newBaseProduct);
+        assertEquals(currentPlan.getProduct().getCategory(), ProductCategory.BASE);
+        assertEquals(currentPlan.getBillingPeriod(), BillingPeriod.MONTHLY);
+
+        currentPhase = realRunBaseSubscription.getCurrentPhase();
+        assertNotNull(currentPhase);
+        assertEquals(currentPhase.getPhaseType(), PhaseType.TRIAL);
+        
+        return baseSubscription.getId();
+    }
+
+    @Test(groups={"slow"})
+    public void testBPRepairAddChangeInTrial() throws Exception {
+        
+        log.info("Starting testBPRepairAddChangeInTrial");
+        
+        String baseProduct = "Shotgun";
+        String newBaseProduct = "Assault-Rifle";
+        
+        DateTime startDate = clock.getUTCNow();
+        int clockShift = 10;
+        DateTime changeDate =  startDate.plusDays(clockShift).minusDays(1);
+        LinkedList<ExistingEvent> expected = new LinkedList<SubscriptionTimeline.ExistingEvent>();
+        
+        expected.add(createExistingEventForAssertion(SubscriptionTransitionType.CREATE, baseProduct, PhaseType.TRIAL,
+                ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.NO_BILLING_PERIOD, startDate));
+        expected.add(createExistingEventForAssertion(SubscriptionTransitionType.CHANGE, newBaseProduct, PhaseType.TRIAL,
+                    ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.NO_BILLING_PERIOD, changeDate));
+        expected.add(createExistingEventForAssertion(SubscriptionTransitionType.PHASE, newBaseProduct, PhaseType.EVERGREEN,
+                    ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, startDate.plusDays(30)));
+
+        UUID baseSubscriptionId = testBPRepairAddChange(true, startDate, clockShift, baseProduct, newBaseProduct, expected, 3);
+        
+        // CHECK WHAT"S GOING ON AFTER WE MOVE CLOCK-- FUTURE MOTIFICATION SHOULD KICK IN
+        testListener.pushExpectedEvent(NextEvent.PHASE);
+        Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(32));
+        clock.addDeltaFromReality(it.toDurationMillis());
+        assertTrue(testListener.isCompleted(5000));
+        SubscriptionData subscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(baseSubscriptionId);
+        
+        assertEquals(subscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1);
+        assertEquals(subscription.getBundleId(), bundle.getId());
+        assertEquals(subscription.getStartDate(), startDate);
+        assertEquals(subscription.getBundleStartDate(), startDate);        
+
+        Plan currentPlan = subscription.getCurrentPlan();
+        assertNotNull(currentPlan);
+        assertEquals(currentPlan.getProduct().getName(), newBaseProduct);
+        assertEquals(currentPlan.getProduct().getCategory(), ProductCategory.BASE);
+        assertEquals(currentPlan.getBillingPeriod(), BillingPeriod.MONTHLY);
+
+        PlanPhase currentPhase = subscription.getCurrentPhase();
+        assertNotNull(currentPhase);
+        assertEquals(currentPhase.getPhaseType(), PhaseType.EVERGREEN);
+        
+        assertListenerStatus();
+    }
+
+    @Test(groups={"slow"})
+    public void testBPRepairAddChangeAfterTrial() throws Exception {
+        
+        log.info("Starting testBPRepairAddChangeAfterTrial");
+        
+        String baseProduct = "Shotgun";
+        String newBaseProduct = "Assault-Rifle";
+        
+        DateTime startDate = clock.getUTCNow();
+        int clockShift = 40;
+        DateTime changeDate =  startDate.plusDays(clockShift).minusDays(1);
+        
+        LinkedList<ExistingEvent> expected = new LinkedList<SubscriptionTimeline.ExistingEvent>();
+        expected.add(createExistingEventForAssertion(SubscriptionTransitionType.CREATE, baseProduct, PhaseType.TRIAL,
+                ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.NO_BILLING_PERIOD, startDate));
+        expected.add(createExistingEventForAssertion(SubscriptionTransitionType.PHASE, baseProduct, PhaseType.EVERGREEN,
+                ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, startDate.plusDays(30)));
+        expected.add(createExistingEventForAssertion(SubscriptionTransitionType.CHANGE, newBaseProduct, PhaseType.EVERGREEN,
+                ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, changeDate));
+        testBPRepairAddChange(false, startDate, clockShift, baseProduct, newBaseProduct, expected, 3);
+    
+        assertListenerStatus();
+    }
+    
+
+    private UUID testBPRepairAddChange(boolean inTrial, DateTime startDate, int clockShift, 
+            String baseProduct, String newBaseProduct, List<ExistingEvent> expectedEvents, int expectedTransitions) throws Exception {
+
+        
+        // CREATE BP
+        Subscription baseSubscription = createSubscription(baseProduct, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, startDate);
+
+        // MOVE CLOCK
+        if (!inTrial) {
+            testListener.pushExpectedEvent(NextEvent.PHASE);
+        }               
+        
+        Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(clockShift));
+        clock.addDeltaFromReality(it.toDurationMillis());
+        if (!inTrial) {
+            assertTrue(testListener.isCompleted(5000));
+        }
+
+        BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
+        sortEventsOnBundle(bundleRepair);
+        
+        DateTime changeTime = baseSubscription.getStartDate().plusDays(clockShift - 1);
+
+        PlanPhaseSpecifier spec = new PlanPhaseSpecifier(newBaseProduct, ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.TRIAL);
+
+        NewEvent ne = createNewEvent(SubscriptionTransitionType.CHANGE, changeTime, spec);
+        List<DeletedEvent> des = new LinkedList<SubscriptionTimeline.DeletedEvent>();
+        if (inTrial) {
+            des.add(createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(1).getEventId()));
+        }
+        SubscriptionTimeline sRepair = createSubscriptionReapir(baseSubscription.getId(), des, Collections.singletonList(ne));
+        
+        // FIRST ISSUE DRY RUN
+        BundleTimeline bRepair =  createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(sRepair));
+        
+        boolean dryRun = true;
+        BundleTimeline dryRunBundleRepair = repairApi.repairBundle(bRepair, dryRun, context);
+        
+        List<SubscriptionTimeline> subscriptionRepair = dryRunBundleRepair.getSubscriptions();
+        assertEquals(subscriptionRepair.size(), 1);
+        SubscriptionTimeline cur = subscriptionRepair.get(0);
+        assertEquals(cur.getId(), baseSubscription.getId());
+
+        List<ExistingEvent> events = cur.getExistingEvents();
+       assertEquals(expectedEvents.size(), events.size());
+       int index = 0;
+       for (ExistingEvent e : expectedEvents) {
+           validateExistingEventForAssertion(e, events.get(index++));           
+       }
+        SubscriptionData dryRunBaseSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(baseSubscription.getId());
+        
+        assertEquals(dryRunBaseSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION);
+        assertEquals(dryRunBaseSubscription.getBundleId(), bundle.getId());
+        assertEquals(dryRunBaseSubscription.getStartDate(), baseSubscription.getStartDate());
+
+        Plan currentPlan = dryRunBaseSubscription.getCurrentPlan();
+        assertNotNull(currentPlan);
+        assertEquals(currentPlan.getProduct().getName(), baseProduct);
+        assertEquals(currentPlan.getProduct().getCategory(), ProductCategory.BASE);
+        assertEquals(currentPlan.getBillingPeriod(), BillingPeriod.MONTHLY);
+
+        PlanPhase currentPhase = dryRunBaseSubscription.getCurrentPhase();
+        assertNotNull(currentPhase);
+        if (inTrial) {
+            assertEquals(currentPhase.getPhaseType(), PhaseType.TRIAL);
+        } else {
+            assertEquals(currentPhase.getPhaseType(), PhaseType.EVERGREEN);
+        }
+        
+        
+       // SECOND RE-ISSUE CALL-- NON DRY RUN
+        dryRun = false;
+        testListener.pushExpectedEvent(NextEvent.REPAIR_BUNDLE);
+        BundleTimeline realRunBundleRepair = repairApi.repairBundle(bRepair, dryRun, context);
+        assertTrue(testListener.isCompleted(5000));
+
+        subscriptionRepair = realRunBundleRepair.getSubscriptions();
+        assertEquals(subscriptionRepair.size(), 1);
+        cur = subscriptionRepair.get(0);
+        assertEquals(cur.getId(), baseSubscription.getId());
+
+        events = cur.getExistingEvents();
+        assertEquals(expectedEvents.size(), events.size());
+        index = 0;
+        for (ExistingEvent e : expectedEvents) {
+           validateExistingEventForAssertion(e, events.get(index++));           
+        }
+        SubscriptionData realRunBaseSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(baseSubscription.getId());
+        assertEquals(realRunBaseSubscription.getAllTransitions().size(), expectedTransitions);
+        
+        
+        assertEquals(realRunBaseSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1);
+        assertEquals(realRunBaseSubscription.getBundleId(), bundle.getId());
+        assertEquals(realRunBaseSubscription.getStartDate(), baseSubscription.getStartDate());
+
+        currentPlan = realRunBaseSubscription.getCurrentPlan();
+        assertNotNull(currentPlan);
+        assertEquals(currentPlan.getProduct().getName(), newBaseProduct);
+        assertEquals(currentPlan.getProduct().getCategory(), ProductCategory.BASE);
+        assertEquals(currentPlan.getBillingPeriod(), BillingPeriod.MONTHLY);
+
+        currentPhase = realRunBaseSubscription.getCurrentPhase();
+        assertNotNull(currentPhase);
+        if (inTrial) {
+            assertEquals(currentPhase.getPhaseType(), PhaseType.TRIAL);
+        } else {
+            assertEquals(currentPhase.getPhaseType(), PhaseType.EVERGREEN);
+        }
+        return baseSubscription.getId();
+    }
+    
+    @Test(groups={"slow"})
+    public void testRepairWithFurureCancelEvent() throws Exception {
+      
+        log.info("Starting testRepairWithFurureCancelEvent");
+        
+        DateTime startDate = clock.getUTCNow();
+        
+        // CREATE BP
+        Subscription baseSubscription = createSubscription("Shotgun", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, startDate);
+
+        // MOVE CLOCK -- OUT OF TRIAL
+        testListener.pushExpectedEvent(NextEvent.PHASE);
+        
+        Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(35));
+        clock.addDeltaFromReality(it.toDurationMillis());
+        assertTrue(testListener.isCompleted(5000));
+        
+        // SET CTD to BASE SUBSCRIPTION SP CANCEL OCCURS EOT
+        DateTime newChargedThroughDate = baseSubscription.getStartDate().plusDays(30).plusMonths(1);
+        billingApi.setChargedThroughDate(baseSubscription.getId(), newChargedThroughDate, context);
+        baseSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(baseSubscription.getId());
+
+        
+        DateTime requestedChange = clock.getUTCNow();
+        baseSubscription.changePlan("Pistol", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, requestedChange, context);
+        
+        
+        // CHECK CHANGE DID NOT OCCUR YET
+        Plan currentPlan = baseSubscription.getCurrentPlan();
+        assertNotNull(currentPlan);
+        assertEquals(currentPlan.getProduct().getName(), "Shotgun");
+        assertEquals(currentPlan.getProduct().getCategory(), ProductCategory.BASE);
+        assertEquals(currentPlan.getBillingPeriod(), BillingPeriod.MONTHLY);
+        
+        
+        DateTime repairTime = clock.getUTCNow().minusDays(1);
+        BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
+        sortEventsOnBundle(bundleRepair);
+        
+        PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Assault-Rifle", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.EVERGREEN);
+
+        NewEvent ne = createNewEvent(SubscriptionTransitionType.CHANGE, repairTime, spec);
+        List<DeletedEvent> des = new LinkedList<SubscriptionTimeline.DeletedEvent>();
+        des.add(createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(2).getEventId()));
+
+        SubscriptionTimeline sRepair = createSubscriptionReapir(baseSubscription.getId(), des, Collections.singletonList(ne));
+        
+        // SKIP DRY RUN AND DO REPAIR...
+        BundleTimeline bRepair =  createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(sRepair));
+        
+        boolean dryRun = false;
+        testListener.pushExpectedEvent(NextEvent.REPAIR_BUNDLE);
+        repairApi.repairBundle(bRepair, dryRun, context);
+        assertTrue(testListener.isCompleted(5000));
+     
+        baseSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(baseSubscription.getId());
+        
+        assertEquals(((SubscriptionData) baseSubscription).getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1);
+        assertEquals(baseSubscription.getBundleId(), bundle.getId());
+        assertEquals(baseSubscription.getStartDate(), baseSubscription.getStartDate());
+
+        currentPlan = baseSubscription.getCurrentPlan();
+        assertNotNull(currentPlan);
+        assertEquals(currentPlan.getProduct().getName(), "Assault-Rifle");
+        assertEquals(currentPlan.getProduct().getCategory(), ProductCategory.BASE);
+        assertEquals(currentPlan.getBillingPeriod(), BillingPeriod.MONTHLY);
+
+        PlanPhase currentPhase = baseSubscription.getCurrentPhase();
+        assertNotNull(currentPhase);
+        assertEquals(currentPhase.getPhaseType(), PhaseType.EVERGREEN);
+        
+        assertListenerStatus();
+    }
+    
+    
+    // Needs real SQL backend to be tested properly
+    @Test(groups={"slow"})
+    public void testENT_REPAIR_VIEW_CHANGED_newEvent() throws Exception {
+       
+        log.info("Starting testENT_REPAIR_VIEW_CHANGED_newEvent");
+        
+        TestWithException test = new TestWithException();
+        DateTime startDate = clock.getUTCNow();
+        
+        final Subscription baseSubscription = createSubscription("Shotgun", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, startDate);
+        
+        test.withException(new TestWithExceptionCallback() {
+            @Override
+            public void doTest() throws EntitlementRepairException, EntitlementUserApiException {
+
+                BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
+                sortEventsOnBundle(bundleRepair);
+                PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Assault-Rifle", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.EVERGREEN);
+                NewEvent ne = createNewEvent(SubscriptionTransitionType.CHANGE, baseSubscription.getStartDate().plusDays(10), spec);
+                List<DeletedEvent> des = new LinkedList<SubscriptionTimeline.DeletedEvent>();
+                des.add(createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(0).getEventId()));                
+                des.add(createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(1).getEventId()));                                
+                SubscriptionTimeline sRepair = createSubscriptionReapir(baseSubscription.getId(), des, Collections.singletonList(ne));
+
+                BundleTimeline bRepair =  createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(sRepair));
+
+                testListener.pushExpectedEvent(NextEvent.CHANGE);
+                DateTime changeTime = clock.getUTCNow();
+                baseSubscription.changePlan("Assault-Rifle", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, changeTime, context);
+                assertTrue(testListener.isCompleted(5000));
+                
+                repairApi.repairBundle(bRepair, true, context);
+                assertListenerStatus();
+            }
+        }, ErrorCode.ENT_REPAIR_VIEW_CHANGED);
+    }
+
+    @Test(groups={"slow"}, enabled=false)
+    public void testENT_REPAIR_VIEW_CHANGED_ctd() throws Exception {
+       
+        log.info("Starting testENT_REPAIR_VIEW_CHANGED_ctd");
+        
+        TestWithException test = new TestWithException();
+        DateTime startDate = clock.getUTCNow();
+        
+        final Subscription baseSubscription = createSubscription("Shotgun", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, startDate);
+        
+        test.withException(new TestWithExceptionCallback() {
+            @Override
+            public void doTest() throws EntitlementRepairException, EntitlementUserApiException {
+
+                BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
+                sortEventsOnBundle(bundleRepair);
+                PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Assault-Rifle", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.EVERGREEN);
+                NewEvent ne = createNewEvent(SubscriptionTransitionType.CHANGE, baseSubscription.getStartDate().plusDays(10), spec);
+                List<DeletedEvent> des = new LinkedList<SubscriptionTimeline.DeletedEvent>();
+                des.add(createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(0).getEventId()));                
+                des.add(createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(1).getEventId()));                                
+                SubscriptionTimeline sRepair = createSubscriptionReapir(baseSubscription.getId(), des, Collections.singletonList(ne));
+
+                BundleTimeline bRepair =  createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(sRepair));
+
+                DateTime newChargedThroughDate = baseSubscription.getStartDate().plusDays(30).plusMonths(1);
+                billingApi.setChargedThroughDate(baseSubscription.getId(), newChargedThroughDate, context);
+                entitlementApi.getSubscriptionFromId(baseSubscription.getId());
+
+                repairApi.repairBundle(bRepair, true, context);
+                
+                assertListenerStatus();
+            }
+        }, ErrorCode.ENT_REPAIR_VIEW_CHANGED);
+    }
+
+}
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/timeline/TestRepairWithAO.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/timeline/TestRepairWithAO.java
new file mode 100644
index 0000000..d7fd642
--- /dev/null
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/timeline/TestRepairWithAO.java
@@ -0,0 +1,782 @@
+/* 
+ * Copyright 2010-2011 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.entitlement.api.timeline;
+
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+
+import org.joda.time.DateTime;
+import org.joda.time.Interval;
+import org.testng.annotations.Test;
+
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Stage;
+import com.ning.billing.api.TestApiListener.NextEvent;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.PhaseType;
+import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.catalog.api.PlanPhase;
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.catalog.api.PriceListSet;
+import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline.DeletedEvent;
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline.ExistingEvent;
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline.NewEvent;
+import com.ning.billing.entitlement.api.user.Subscription.SubscriptionState;
+import com.ning.billing.entitlement.api.user.SubscriptionData;
+import com.ning.billing.entitlement.api.user.SubscriptionEvents;
+import com.ning.billing.entitlement.glue.MockEngineModuleSql;
+
+public class TestRepairWithAO extends TestApiBaseRepair {
+
+    @Override
+    public Injector getInjector() {
+        return Guice.createInjector(Stage.DEVELOPMENT, new MockEngineModuleSql());
+    }    
+
+    @Test(groups={"slow"})
+    public void testRepairChangeBPWithAddonIncluded() throws Exception {
+        
+        log.info("Starting testRepairChangeBPWithAddonIncluded");
+        
+        String baseProduct = "Shotgun";
+        BillingPeriod baseTerm = BillingPeriod.MONTHLY;
+        String basePriceList = PriceListSet.DEFAULT_PRICELIST_NAME;
+
+        // CREATE BP
+        SubscriptionData baseSubscription = createSubscription(baseProduct, baseTerm, basePriceList);
+
+        // MOVE CLOCK A LITTLE BIT-- STILL IN TRIAL
+        Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(4));
+        clock.addDeltaFromReality(it.toDurationMillis());
+
+        SubscriptionData aoSubscription = createSubscription("Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME);
+        
+        SubscriptionData aoSubscription2 = createSubscription("Laser-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME);
+
+        // MOVE CLOCK A LITTLE BIT MORE -- STILL IN TRIAL
+        it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(3));
+        clock.addDeltaFromReality(it.toDurationMillis());
+
+        BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
+        sortEventsOnBundle(bundleRepair);
+        
+        // Quick check
+        SubscriptionTimeline bpRepair = getSubscriptionRepair(baseSubscription.getId(), bundleRepair);
+        assertEquals(bpRepair.getExistingEvents().size(), 2);
+        
+        SubscriptionTimeline aoRepair = getSubscriptionRepair(aoSubscription.getId(), bundleRepair);
+        assertEquals(aoRepair.getExistingEvents().size(), 2);
+
+        SubscriptionTimeline aoRepair2 = getSubscriptionRepair(aoSubscription2.getId(), bundleRepair);
+        assertEquals(aoRepair2.getExistingEvents().size(), 2);
+
+        DateTime bpChangeDate = clock.getUTCNow().minusDays(1);
+        
+        List<DeletedEvent> des = new LinkedList<SubscriptionTimeline.DeletedEvent>();
+        des.add(createDeletedEvent(bpRepair.getExistingEvents().get(1).getEventId()));        
+        
+        PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Assault-Rifle", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.TRIAL);
+        NewEvent ne = createNewEvent(SubscriptionTransitionType.CHANGE, bpChangeDate, spec);
+        
+        bpRepair = createSubscriptionReapir(baseSubscription.getId(), des, Collections.singletonList(ne));
+        
+        bundleRepair =  createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(bpRepair));
+        
+        boolean dryRun = true;
+        BundleTimeline dryRunBundleRepair = repairApi.repairBundle(bundleRepair, dryRun, context);
+                
+        aoRepair = getSubscriptionRepair(aoSubscription.getId(), dryRunBundleRepair);
+        assertEquals(aoRepair.getExistingEvents().size(), 2);
+
+        aoRepair2 = getSubscriptionRepair(aoSubscription2.getId(), dryRunBundleRepair);
+        assertEquals(aoRepair.getExistingEvents().size(), 2);
+
+        bpRepair = getSubscriptionRepair(baseSubscription.getId(), dryRunBundleRepair);
+        assertEquals(bpRepair.getExistingEvents().size(), 3);        
+        
+        // Check expected for AO
+        List<ExistingEvent> expectedAO = new LinkedList<SubscriptionTimeline.ExistingEvent>();
+        expectedAO.add(createExistingEventForAssertion(SubscriptionTransitionType.CREATE, "Telescopic-Scope", PhaseType.DISCOUNT,
+                ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, aoSubscription.getStartDate()));
+        expectedAO.add(createExistingEventForAssertion(SubscriptionTransitionType.CANCEL, "Telescopic-Scope", PhaseType.DISCOUNT,
+                ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, bpChangeDate));
+        int index = 0;
+        for (ExistingEvent e : expectedAO) {
+           validateExistingEventForAssertion(e, aoRepair.getExistingEvents().get(index++));           
+        }
+
+        List<ExistingEvent> expectedAO2 = new LinkedList<SubscriptionTimeline.ExistingEvent>();
+        expectedAO2.add(createExistingEventForAssertion(SubscriptionTransitionType.CREATE, "Laser-Scope", PhaseType.DISCOUNT,
+                ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, aoSubscription2.getStartDate()));
+        expectedAO2.add(createExistingEventForAssertion(SubscriptionTransitionType.PHASE, "Laser-Scope", PhaseType.EVERGREEN,
+                ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, aoSubscription2.getStartDate().plusMonths(1)));
+        index = 0;
+        for (ExistingEvent e : expectedAO2) {
+           validateExistingEventForAssertion(e, aoRepair2.getExistingEvents().get(index++));           
+        }
+        
+        // Check expected for BP        
+        List<ExistingEvent> expectedBP = new LinkedList<SubscriptionTimeline.ExistingEvent>();
+        expectedBP.add(createExistingEventForAssertion(SubscriptionTransitionType.CREATE, "Shotgun", PhaseType.TRIAL,
+                ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.NO_BILLING_PERIOD, baseSubscription.getStartDate()));
+        expectedBP.add(createExistingEventForAssertion(SubscriptionTransitionType.CHANGE, "Assault-Rifle", PhaseType.TRIAL,
+                ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.NO_BILLING_PERIOD, bpChangeDate));
+        expectedBP.add(createExistingEventForAssertion(SubscriptionTransitionType.PHASE, "Assault-Rifle", PhaseType.EVERGREEN,
+                ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, baseSubscription.getStartDate().plusDays(30)));
+        index = 0;
+        for (ExistingEvent e : expectedBP) {
+           validateExistingEventForAssertion(e, bpRepair.getExistingEvents().get(index++));           
+        }
+
+        
+        SubscriptionData newAoSubscription = (SubscriptionData)  entitlementApi.getSubscriptionFromId(aoSubscription.getId());
+        assertEquals(newAoSubscription.getState(), SubscriptionState.ACTIVE);
+        assertEquals(newAoSubscription.getAllTransitions().size(), 2);
+        assertEquals(newAoSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION);
+
+        SubscriptionData newAoSubscription2 = (SubscriptionData)  entitlementApi.getSubscriptionFromId(aoSubscription2.getId());
+        assertEquals(newAoSubscription2.getState(), SubscriptionState.ACTIVE);
+        assertEquals(newAoSubscription2.getAllTransitions().size(), 2);
+        assertEquals(newAoSubscription2.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION);
+
+        
+        SubscriptionData newBaseSubscription = (SubscriptionData)  entitlementApi.getSubscriptionFromId(baseSubscription.getId());
+        assertEquals(newBaseSubscription.getState(), SubscriptionState.ACTIVE);
+        assertEquals(newBaseSubscription.getAllTransitions().size(), 2);
+        assertEquals(newBaseSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION);
+        
+        dryRun = false;        
+        testListener.pushExpectedEvent(NextEvent.REPAIR_BUNDLE);
+        BundleTimeline realRunBundleRepair = repairApi.repairBundle(bundleRepair, dryRun, context);
+        assertTrue(testListener.isCompleted(5000));
+
+        
+        
+        aoRepair = getSubscriptionRepair(aoSubscription.getId(), realRunBundleRepair);
+        assertEquals(aoRepair.getExistingEvents().size(), 2);
+
+        bpRepair = getSubscriptionRepair(baseSubscription.getId(), realRunBundleRepair);
+        assertEquals(bpRepair.getExistingEvents().size(), 3);        
+        
+        index = 0;
+        for (ExistingEvent e : expectedAO) {
+           validateExistingEventForAssertion(e, aoRepair.getExistingEvents().get(index++));           
+        }
+        
+        index = 0;
+        for (ExistingEvent e : expectedAO2) {
+           validateExistingEventForAssertion(e, aoRepair2.getExistingEvents().get(index++));           
+        }
+
+        index = 0;
+        for (ExistingEvent e : expectedBP) {
+           validateExistingEventForAssertion(e, bpRepair.getExistingEvents().get(index++));           
+        }
+
+        newAoSubscription = (SubscriptionData)  entitlementApi.getSubscriptionFromId(aoSubscription.getId());
+        assertEquals(newAoSubscription.getState(), SubscriptionState.CANCELLED);
+        assertEquals(newAoSubscription.getAllTransitions().size(), 2);
+        assertEquals(newAoSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1);
+            
+        newAoSubscription2 = (SubscriptionData)  entitlementApi.getSubscriptionFromId(aoSubscription2.getId());
+        assertEquals(newAoSubscription2.getState(), SubscriptionState.ACTIVE);
+        assertEquals(newAoSubscription2.getAllTransitions().size(), 2);
+        assertEquals(newAoSubscription2.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1);
+
+        
+        newBaseSubscription = (SubscriptionData)  entitlementApi.getSubscriptionFromId(baseSubscription.getId());
+        assertEquals(newBaseSubscription.getState(), SubscriptionState.ACTIVE);
+        assertEquals(newBaseSubscription.getAllTransitions().size(), 3);
+        assertEquals(newBaseSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1);
+    }
+
+    @Test(groups={"slow"})
+    public void testRepairChangeBPWithAddonNonAvailable() throws Exception {
+        
+        log.info("Starting testRepairChangeBPWithAddonNonAvailable");
+        
+        String baseProduct = "Shotgun";
+        BillingPeriod baseTerm = BillingPeriod.MONTHLY;
+        String basePriceList = PriceListSet.DEFAULT_PRICELIST_NAME;
+
+        // CREATE BP
+        SubscriptionData baseSubscription = createSubscription(baseProduct, baseTerm, basePriceList);
+
+        // MOVE CLOCK A LITTLE BIT-- STILL IN TRIAL
+        Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(3));
+        clock.addDeltaFromReality(it.toDurationMillis());
+
+        SubscriptionData aoSubscription = createSubscription("Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME);
+        
+        // MOVE CLOCK A LITTLE BIT MORE -- AFTER TRIAL
+        testListener.pushExpectedEvent(NextEvent.PHASE);
+        testListener.pushExpectedEvent(NextEvent.PHASE);
+        
+        it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(32));
+        clock.addDeltaFromReality(it.toDurationMillis());
+        assertTrue(testListener.isCompleted(7000));        
+
+        BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
+        sortEventsOnBundle(bundleRepair);
+        
+        // Quick check
+        SubscriptionTimeline bpRepair = getSubscriptionRepair(baseSubscription.getId(), bundleRepair);
+        assertEquals(bpRepair.getExistingEvents().size(), 2);
+        
+        SubscriptionTimeline aoRepair = getSubscriptionRepair(aoSubscription.getId(), bundleRepair);
+        assertEquals(aoRepair.getExistingEvents().size(), 2);
+
+        DateTime bpChangeDate = clock.getUTCNow().minusDays(1);
+        
+        PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Pistol", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.EVERGREEN);
+        NewEvent ne = createNewEvent(SubscriptionTransitionType.CHANGE, bpChangeDate, spec);
+        
+        bpRepair = createSubscriptionReapir(baseSubscription.getId(), Collections.<SubscriptionTimeline.DeletedEvent>emptyList(), Collections.singletonList(ne));
+        
+        bundleRepair =  createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(bpRepair));
+        
+        boolean dryRun = true;
+        BundleTimeline dryRunBundleRepair = repairApi.repairBundle(bundleRepair, dryRun, context);
+                
+        aoRepair = getSubscriptionRepair(aoSubscription.getId(), dryRunBundleRepair);
+        assertEquals(aoRepair.getExistingEvents().size(), 3);
+
+        bpRepair = getSubscriptionRepair(baseSubscription.getId(), dryRunBundleRepair);
+        assertEquals(bpRepair.getExistingEvents().size(), 3);        
+        
+        // Check expected for AO
+        List<ExistingEvent> expectedAO = new LinkedList<SubscriptionTimeline.ExistingEvent>();
+        expectedAO.add(createExistingEventForAssertion(SubscriptionTransitionType.CREATE, "Telescopic-Scope", PhaseType.DISCOUNT,
+                ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, aoSubscription.getStartDate()));
+        expectedAO.add(createExistingEventForAssertion(SubscriptionTransitionType.CREATE, "Telescopic-Scope", PhaseType.EVERGREEN,
+                ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, baseSubscription.getStartDate().plusMonths(1)));
+        expectedAO.add(createExistingEventForAssertion(SubscriptionTransitionType.CANCEL, "Telescopic-Scope", PhaseType.EVERGREEN,
+                ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, bpChangeDate));
+        int index = 0;
+        for (ExistingEvent e : expectedAO) {
+           validateExistingEventForAssertion(e, aoRepair.getExistingEvents().get(index++));           
+        }
+
+        // Check expected for BP        
+        List<ExistingEvent> expectedBP = new LinkedList<SubscriptionTimeline.ExistingEvent>();
+        expectedBP.add(createExistingEventForAssertion(SubscriptionTransitionType.CREATE, "Shotgun", PhaseType.TRIAL,
+                ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.NO_BILLING_PERIOD, baseSubscription.getStartDate()));
+        expectedBP.add(createExistingEventForAssertion(SubscriptionTransitionType.PHASE, "Shotgun", PhaseType.EVERGREEN,
+                ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, baseSubscription.getStartDate().plusDays(30)));
+        expectedBP.add(createExistingEventForAssertion(SubscriptionTransitionType.CHANGE, "Pistol", PhaseType.EVERGREEN,
+                ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, bpChangeDate));
+        index = 0;
+        for (ExistingEvent e : expectedBP) {
+           validateExistingEventForAssertion(e, bpRepair.getExistingEvents().get(index++));           
+        }
+        
+        SubscriptionData newAoSubscription = (SubscriptionData)  entitlementApi.getSubscriptionFromId(aoSubscription.getId());
+        assertEquals(newAoSubscription.getState(), SubscriptionState.ACTIVE);
+        assertEquals(newAoSubscription.getAllTransitions().size(), 2);
+        assertEquals(newAoSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION);
+
+        SubscriptionData newBaseSubscription = (SubscriptionData)  entitlementApi.getSubscriptionFromId(baseSubscription.getId());
+        assertEquals(newBaseSubscription.getState(), SubscriptionState.ACTIVE);
+        assertEquals(newBaseSubscription.getAllTransitions().size(), 2);
+        assertEquals(newBaseSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION);
+        
+        dryRun = false;
+        testListener.pushExpectedEvent(NextEvent.REPAIR_BUNDLE);
+        BundleTimeline realRunBundleRepair = repairApi.repairBundle(bundleRepair, dryRun, context);
+        assertTrue(testListener.isCompleted(5000));
+
+        aoRepair = getSubscriptionRepair(aoSubscription.getId(), realRunBundleRepair);
+        assertEquals(aoRepair.getExistingEvents().size(), 3);
+
+        bpRepair = getSubscriptionRepair(baseSubscription.getId(), realRunBundleRepair);
+        assertEquals(bpRepair.getExistingEvents().size(), 3);        
+        
+        index = 0;
+        for (ExistingEvent e : expectedAO) {
+           validateExistingEventForAssertion(e, aoRepair.getExistingEvents().get(index++));           
+        }
+        
+        index = 0;
+        for (ExistingEvent e : expectedBP) {
+           validateExistingEventForAssertion(e, bpRepair.getExistingEvents().get(index++));           
+        }
+
+        newAoSubscription = (SubscriptionData)  entitlementApi.getSubscriptionFromId(aoSubscription.getId());
+        assertEquals(newAoSubscription.getState(), SubscriptionState.CANCELLED);
+        assertEquals(newAoSubscription.getAllTransitions().size(), 3);
+        assertEquals(newAoSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1);
+            
+        newBaseSubscription = (SubscriptionData)  entitlementApi.getSubscriptionFromId(baseSubscription.getId());
+        assertEquals(newBaseSubscription.getState(), SubscriptionState.ACTIVE);
+        assertEquals(newBaseSubscription.getAllTransitions().size(), 3);
+        assertEquals(newBaseSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1);
+    }
+
+    @Test(groups={"slow"})
+    public void testRepairCancelBP_EOT_WithAddons() throws Exception {
+        
+        log.info("Starting testRepairCancelBP_EOT_WithAddons");
+        
+        String baseProduct = "Shotgun";
+        BillingPeriod baseTerm = BillingPeriod.MONTHLY;
+        String basePriceList = PriceListSet.DEFAULT_PRICELIST_NAME;
+
+        // CREATE BP
+        SubscriptionData baseSubscription = createSubscription(baseProduct, baseTerm, basePriceList);
+
+        // MOVE CLOCK A LITTLE BIT-- STILL IN TRIAL
+        Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(4));
+        clock.addDeltaFromReality(it.toDurationMillis());
+
+
+        SubscriptionData aoSubscription = createSubscription("Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME);
+        
+        // MOVE CLOCK A LITTLE BIT MORE -- AFTER TRIAL
+        testListener.pushExpectedEvent(NextEvent.PHASE);
+        testListener.pushExpectedEvent(NextEvent.PHASE);
+        
+        it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(40));
+        clock.addDeltaFromReality(it.toDurationMillis());
+        assertTrue(testListener.isCompleted(7000));
+        
+        // SET CTD to BASE SUBSCRIPTION SP CANCEL OCCURS EOT
+        DateTime newChargedThroughDate = baseSubscription.getStartDate().plusDays(30).plusMonths(1);
+        billingApi.setChargedThroughDate(baseSubscription.getId(), newChargedThroughDate, context);
+        baseSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(baseSubscription.getId());
+
+        BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
+        sortEventsOnBundle(bundleRepair);
+        
+        // Quick check
+        SubscriptionTimeline bpRepair = getSubscriptionRepair(baseSubscription.getId(), bundleRepair);
+        assertEquals(bpRepair.getExistingEvents().size(), 2);
+        
+        SubscriptionTimeline aoRepair = getSubscriptionRepair(aoSubscription.getId(), bundleRepair);
+        assertEquals(aoRepair.getExistingEvents().size(), 2);
+
+        DateTime bpCancelDate = clock.getUTCNow().minusDays(1);
+        NewEvent ne = createNewEvent(SubscriptionTransitionType.CANCEL, bpCancelDate, null);
+        bpRepair = createSubscriptionReapir(baseSubscription.getId(), Collections.<SubscriptionTimeline.DeletedEvent>emptyList(), Collections.singletonList(ne));
+        bundleRepair =  createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(bpRepair));
+        
+        boolean dryRun = true;
+        BundleTimeline dryRunBundleRepair = repairApi.repairBundle(bundleRepair, dryRun, context);
+                
+        aoRepair = getSubscriptionRepair(aoSubscription.getId(), dryRunBundleRepair);
+        assertEquals(aoRepair.getExistingEvents().size(), 3);
+
+        bpRepair = getSubscriptionRepair(baseSubscription.getId(), dryRunBundleRepair);
+        assertEquals(bpRepair.getExistingEvents().size(), 3);        
+        
+        // Check expected for AO
+        List<ExistingEvent> expectedAO = new LinkedList<SubscriptionTimeline.ExistingEvent>();
+        expectedAO.add(createExistingEventForAssertion(SubscriptionTransitionType.CREATE, "Telescopic-Scope", PhaseType.DISCOUNT,
+                ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, aoSubscription.getStartDate()));
+        expectedAO.add(createExistingEventForAssertion(SubscriptionTransitionType.PHASE, "Telescopic-Scope", PhaseType.EVERGREEN,
+                ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, baseSubscription.getStartDate().plusMonths(1)));
+        expectedAO.add(createExistingEventForAssertion(SubscriptionTransitionType.CANCEL, "Telescopic-Scope", PhaseType.EVERGREEN,
+                ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, newChargedThroughDate));
+
+        int index = 0;
+        for (ExistingEvent e : expectedAO) {
+           validateExistingEventForAssertion(e, aoRepair.getExistingEvents().get(index++));           
+        }
+
+        // Check expected for BP        
+        List<ExistingEvent> expectedBP = new LinkedList<SubscriptionTimeline.ExistingEvent>();
+        expectedBP.add(createExistingEventForAssertion(SubscriptionTransitionType.CREATE, "Shotgun", PhaseType.TRIAL,
+                ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.NO_BILLING_PERIOD, baseSubscription.getStartDate()));
+        expectedBP.add(createExistingEventForAssertion(SubscriptionTransitionType.PHASE, "Shotgun", PhaseType.EVERGREEN,
+                ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, baseSubscription.getStartDate().plusDays(30)));
+        expectedBP.add(createExistingEventForAssertion(SubscriptionTransitionType.CANCEL, "Shotgun", PhaseType.EVERGREEN,
+                ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, newChargedThroughDate));
+        index = 0;
+        for (ExistingEvent e : expectedBP) {
+           validateExistingEventForAssertion(e, bpRepair.getExistingEvents().get(index++));           
+        }
+        
+        SubscriptionData newAoSubscription = (SubscriptionData)  entitlementApi.getSubscriptionFromId(aoSubscription.getId());
+        assertEquals(newAoSubscription.getState(), SubscriptionState.ACTIVE);
+        assertEquals(newAoSubscription.getAllTransitions().size(), 2);
+        assertEquals(newAoSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION);
+
+        SubscriptionData newBaseSubscription = (SubscriptionData)  entitlementApi.getSubscriptionFromId(baseSubscription.getId());
+        assertEquals(newBaseSubscription.getState(), SubscriptionState.ACTIVE);
+        assertEquals(newBaseSubscription.getAllTransitions().size(), 2);
+        assertEquals(newBaseSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION);
+        
+        dryRun = false;
+        testListener.pushExpectedEvent(NextEvent.REPAIR_BUNDLE);
+        BundleTimeline realRunBundleRepair = repairApi.repairBundle(bundleRepair, dryRun, context);
+        assertTrue(testListener.isCompleted(5000));
+        
+        aoRepair = getSubscriptionRepair(aoSubscription.getId(), realRunBundleRepair);
+        assertEquals(aoRepair.getExistingEvents().size(), 3);
+
+        bpRepair = getSubscriptionRepair(baseSubscription.getId(), realRunBundleRepair);
+        assertEquals(bpRepair.getExistingEvents().size(), 3);        
+        
+        index = 0;
+        for (ExistingEvent e : expectedAO) {
+           validateExistingEventForAssertion(e, aoRepair.getExistingEvents().get(index++));           
+        }
+        
+        index = 0;
+        for (ExistingEvent e : expectedBP) {
+           validateExistingEventForAssertion(e, bpRepair.getExistingEvents().get(index++));           
+        }
+
+        newAoSubscription = (SubscriptionData)  entitlementApi.getSubscriptionFromId(aoSubscription.getId());
+        assertEquals(newAoSubscription.getState(), SubscriptionState.ACTIVE);
+        assertEquals(newAoSubscription.getAllTransitions().size(), 3);
+        assertEquals(newAoSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1);
+            
+        newBaseSubscription = (SubscriptionData)  entitlementApi.getSubscriptionFromId(baseSubscription.getId());
+        assertEquals(newBaseSubscription.getState(), SubscriptionState.ACTIVE);
+        assertEquals(newBaseSubscription.getAllTransitions().size(), 3);
+        assertEquals(newBaseSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1);
+        
+        // MOVE CLOCK AFTER CANCEL DATE
+        testListener.pushExpectedEvent(NextEvent.CANCEL);
+        testListener.pushExpectedEvent(NextEvent.CANCEL);
+        
+        it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(32));
+        clock.addDeltaFromReality(it.toDurationMillis());
+        assertTrue(testListener.isCompleted(7000));
+
+        newAoSubscription = (SubscriptionData)  entitlementApi.getSubscriptionFromId(aoSubscription.getId());
+        assertEquals(newAoSubscription.getState(), SubscriptionState.CANCELLED);
+        assertEquals(newAoSubscription.getAllTransitions().size(), 3);
+        assertEquals(newAoSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1);
+            
+        newBaseSubscription = (SubscriptionData)  entitlementApi.getSubscriptionFromId(baseSubscription.getId());
+        assertEquals(newBaseSubscription.getState(), SubscriptionState.CANCELLED);
+        assertEquals(newBaseSubscription.getAllTransitions().size(), 3);
+        assertEquals(newBaseSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1);
+    }
+
+    
+    
+    @Test(groups={"slow"})
+    public void testRepairCancelAO() throws Exception {
+        
+        log.info("Starting testRepairCancelAO");
+        
+        String baseProduct = "Shotgun";
+        BillingPeriod baseTerm = BillingPeriod.MONTHLY;
+        String basePriceList = PriceListSet.DEFAULT_PRICELIST_NAME;
+
+        // CREATE BP
+        SubscriptionData baseSubscription = createSubscription(baseProduct, baseTerm, basePriceList);
+
+        // MOVE CLOCK A LITTLE BIT-- STILL IN TRIAL
+        Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(4));
+        clock.addDeltaFromReality(it.toDurationMillis());
+
+        SubscriptionData aoSubscription = createSubscription("Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME);
+
+        // MOVE CLOCK A LITTLE BIT MORE -- STILL IN TRIAL
+        it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(3));
+        clock.addDeltaFromReality(it.toDurationMillis());
+
+        BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
+        sortEventsOnBundle(bundleRepair);
+        
+        // Quick check
+        SubscriptionTimeline bpRepair = getSubscriptionRepair(baseSubscription.getId(), bundleRepair);
+        assertEquals(bpRepair.getExistingEvents().size(), 2);
+        
+        SubscriptionTimeline aoRepair = getSubscriptionRepair(aoSubscription.getId(), bundleRepair);
+        assertEquals(aoRepair.getExistingEvents().size(), 2);
+        
+
+        List<DeletedEvent> des = new LinkedList<SubscriptionTimeline.DeletedEvent>();
+        des.add(createDeletedEvent(aoRepair.getExistingEvents().get(1).getEventId()));        
+        DateTime aoCancelDate = aoSubscription.getStartDate().plusDays(1);
+        
+        NewEvent ne = createNewEvent(SubscriptionTransitionType.CANCEL, aoCancelDate, null);
+        
+        SubscriptionTimeline saoRepair = createSubscriptionReapir(aoSubscription.getId(), des, Collections.singletonList(ne));
+        
+        BundleTimeline bRepair =  createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(saoRepair));
+        
+        boolean dryRun = true;
+        BundleTimeline dryRunBundleRepair = repairApi.repairBundle(bRepair, dryRun, context);
+        
+        aoRepair = getSubscriptionRepair(aoSubscription.getId(), dryRunBundleRepair);
+        assertEquals(aoRepair.getExistingEvents().size(), 2);
+        
+        bpRepair = getSubscriptionRepair(baseSubscription.getId(), bundleRepair);
+        assertEquals(bpRepair.getExistingEvents().size(), 2);        
+        
+        List<ExistingEvent> expected = new LinkedList<SubscriptionTimeline.ExistingEvent>();
+        expected.add(createExistingEventForAssertion(SubscriptionTransitionType.CREATE, "Telescopic-Scope", PhaseType.DISCOUNT,
+                ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, aoSubscription.getStartDate()));
+        expected.add(createExistingEventForAssertion(SubscriptionTransitionType.CANCEL, "Telescopic-Scope", PhaseType.DISCOUNT,
+                ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, aoCancelDate));
+        int index = 0;
+        for (ExistingEvent e : expected) {
+           validateExistingEventForAssertion(e, aoRepair.getExistingEvents().get(index++));           
+        }
+        SubscriptionData newAoSubscription = (SubscriptionData)  entitlementApi.getSubscriptionFromId(aoSubscription.getId());
+        assertEquals(newAoSubscription.getState(), SubscriptionState.ACTIVE);
+        assertEquals(newAoSubscription.getAllTransitions().size(), 2);
+        assertEquals(newAoSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION);
+                
+        SubscriptionData newBaseSubscription = (SubscriptionData)  entitlementApi.getSubscriptionFromId(baseSubscription.getId());
+        assertEquals(newBaseSubscription.getState(), SubscriptionState.ACTIVE);
+        assertEquals(newBaseSubscription.getAllTransitions().size(), 2);
+        assertEquals(newBaseSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION);
+        
+        dryRun = false;
+        testListener.pushExpectedEvent(NextEvent.REPAIR_BUNDLE);
+        BundleTimeline realRunBundleRepair = repairApi.repairBundle(bRepair, dryRun, context);
+        assertTrue(testListener.isCompleted(5000));
+
+        
+        aoRepair = getSubscriptionRepair(aoSubscription.getId(), realRunBundleRepair);
+        assertEquals(aoRepair.getExistingEvents().size(), 2);
+        index = 0;
+        for (ExistingEvent e : expected) {
+           validateExistingEventForAssertion(e, aoRepair.getExistingEvents().get(index++));           
+        }
+        
+        newAoSubscription = (SubscriptionData)  entitlementApi.getSubscriptionFromId(aoSubscription.getId());
+        assertEquals(newAoSubscription.getState(), SubscriptionState.CANCELLED);
+        assertEquals(newAoSubscription.getAllTransitions().size(), 2);
+        assertEquals(newAoSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1);        
+                
+        newBaseSubscription = (SubscriptionData)  entitlementApi.getSubscriptionFromId(baseSubscription.getId());
+        assertEquals(newBaseSubscription.getState(), SubscriptionState.ACTIVE);
+        assertEquals(newBaseSubscription.getAllTransitions().size(), 2);
+        assertEquals(newBaseSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION);
+    }
+    
+    
+    @Test(groups={"slow"})
+    public void testRepairRecreateAO() throws Exception {
+        
+        log.info("Starting testRepairRecreateAO");
+        
+        String baseProduct = "Shotgun";
+        BillingPeriod baseTerm = BillingPeriod.MONTHLY;
+        String basePriceList = PriceListSet.DEFAULT_PRICELIST_NAME;
+
+        // CREATE BP
+        SubscriptionData baseSubscription = createSubscription(baseProduct, baseTerm, basePriceList);
+
+        // MOVE CLOCK A LITTLE BIT-- STILL IN TRIAL
+        Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(4));
+        clock.addDeltaFromReality(it.toDurationMillis());
+
+        SubscriptionData aoSubscription = createSubscription("Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME);
+
+        // MOVE CLOCK A LITTLE BIT MORE -- STILL IN TRIAL
+        it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(3));
+        clock.addDeltaFromReality(it.toDurationMillis());
+
+        BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
+        sortEventsOnBundle(bundleRepair);
+        
+        // Quick check
+        SubscriptionTimeline bpRepair = getSubscriptionRepair(baseSubscription.getId(), bundleRepair);
+        assertEquals(bpRepair.getExistingEvents().size(), 2);
+        
+        SubscriptionTimeline aoRepair = getSubscriptionRepair(aoSubscription.getId(), bundleRepair);
+        assertEquals(aoRepair.getExistingEvents().size(), 2);
+        
+
+        List<DeletedEvent> des = new LinkedList<SubscriptionTimeline.DeletedEvent>();
+        des.add(createDeletedEvent(aoRepair.getExistingEvents().get(0).getEventId()));        
+        des.add(createDeletedEvent(aoRepair.getExistingEvents().get(1).getEventId()));
+
+        DateTime aoRecreateDate = aoSubscription.getStartDate().plusDays(1);
+        PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Telescopic-Scope", ProductCategory.ADD_ON, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.DISCOUNT);
+        NewEvent ne = createNewEvent(SubscriptionTransitionType.CREATE, aoRecreateDate, spec);
+        
+        SubscriptionTimeline saoRepair = createSubscriptionReapir(aoSubscription.getId(), des, Collections.singletonList(ne));
+        
+        BundleTimeline bRepair =  createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(saoRepair));
+        
+        boolean dryRun = true;
+        BundleTimeline dryRunBundleRepair = repairApi.repairBundle(bRepair, dryRun, context);
+        
+        aoRepair = getSubscriptionRepair(aoSubscription.getId(), dryRunBundleRepair);
+        assertEquals(aoRepair.getExistingEvents().size(), 2);
+        
+        
+        List<ExistingEvent> expected = new LinkedList<SubscriptionTimeline.ExistingEvent>();
+        expected.add(createExistingEventForAssertion(SubscriptionTransitionType.CREATE, "Telescopic-Scope", PhaseType.DISCOUNT,
+                ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, aoRecreateDate));
+        expected.add(createExistingEventForAssertion(SubscriptionTransitionType.PHASE, "Telescopic-Scope", PhaseType.EVERGREEN,
+                    ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, baseSubscription.getStartDate().plusMonths(1) /* Bundle align */));
+        int index = 0;
+        for (ExistingEvent e : expected) {
+           validateExistingEventForAssertion(e, aoRepair.getExistingEvents().get(index++));           
+        }
+        SubscriptionData newAoSubscription = (SubscriptionData)  entitlementApi.getSubscriptionFromId(aoSubscription.getId());
+        assertEquals(newAoSubscription.getState(), SubscriptionState.ACTIVE);
+        assertEquals(newAoSubscription.getAllTransitions().size(), 2);
+        assertEquals(newAoSubscription.getStartDate(), aoSubscription.getStartDate());
+        assertEquals(newAoSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION);        
+        
+        // NOW COMMIT
+        dryRun = false;
+        testListener.pushExpectedEvent(NextEvent.REPAIR_BUNDLE);
+        BundleTimeline realRunBundleRepair = repairApi.repairBundle(bRepair, dryRun, context);
+        assertTrue(testListener.isCompleted(5000));
+        
+        aoRepair = getSubscriptionRepair(aoSubscription.getId(), realRunBundleRepair);
+        assertEquals(aoRepair.getExistingEvents().size(), 2);
+        index = 0;
+        for (ExistingEvent e : expected) {
+           validateExistingEventForAssertion(e, aoRepair.getExistingEvents().get(index++));           
+        }
+        
+        newAoSubscription = (SubscriptionData)  entitlementApi.getSubscriptionFromId(aoSubscription.getId());
+        assertEquals(newAoSubscription.getState(), SubscriptionState.ACTIVE);
+        assertEquals(newAoSubscription.getAllTransitions().size(), 2);
+        assertEquals(newAoSubscription.getStartDate(), aoRecreateDate);
+        assertEquals(newAoSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1);        
+
+    }
+    
+    // Fasten your seatbelt here:
+    //
+    // We are doing repair for multi-phase tiered-addon with different alignment:
+    // Telescopic-Scope -> Laser-Scope
+    // Tiered ADON logic
+    // . Both multi phase
+    // . Telescopic-Scope (bundle align) and Laser-Scope is Subscription align
+    //
+    @Test(groups={"slow"})
+    public void testRepairChangeAOOK() throws Exception {
+        
+        log.info("Starting testRepairChangeAOOK");
+        
+        String baseProduct = "Shotgun";
+        BillingPeriod baseTerm = BillingPeriod.MONTHLY;
+        String basePriceList = PriceListSet.DEFAULT_PRICELIST_NAME;
+
+        // CREATE BP
+        SubscriptionData baseSubscription = createSubscription(baseProduct, baseTerm, basePriceList);
+
+        // MOVE CLOCK A LITTLE BIT-- STILL IN TRIAL
+        Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(4));
+        clock.addDeltaFromReality(it.toDurationMillis());
+
+        SubscriptionData aoSubscription = createSubscription("Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME);
+
+        // MOVE CLOCK A LITTLE BIT MORE -- STILL IN TRIAL
+        it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(3));
+        clock.addDeltaFromReality(it.toDurationMillis());
+        
+        BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
+        sortEventsOnBundle(bundleRepair);
+        
+        // Quick check
+        SubscriptionTimeline bpRepair = getSubscriptionRepair(baseSubscription.getId(), bundleRepair);
+        assertEquals(bpRepair.getExistingEvents().size(), 2);
+        
+        SubscriptionTimeline aoRepair = getSubscriptionRepair(aoSubscription.getId(), bundleRepair);
+        assertEquals(aoRepair.getExistingEvents().size(), 2);
+
+        List<DeletedEvent> des = new LinkedList<SubscriptionTimeline.DeletedEvent>();
+        des.add(createDeletedEvent(aoRepair.getExistingEvents().get(1).getEventId()));        
+        DateTime aoChangeDate = aoSubscription.getStartDate().plusDays(1);
+        PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Laser-Scope", ProductCategory.ADD_ON, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.TRIAL);
+        NewEvent ne = createNewEvent(SubscriptionTransitionType.CHANGE, aoChangeDate, spec);
+        
+        SubscriptionTimeline saoRepair = createSubscriptionReapir(aoSubscription.getId(), des, Collections.singletonList(ne));
+        
+        BundleTimeline bRepair =  createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(saoRepair));
+        
+        boolean dryRun = true;
+        BundleTimeline dryRunBundleRepair = repairApi.repairBundle(bRepair, dryRun, context);
+        
+        aoRepair = getSubscriptionRepair(aoSubscription.getId(), dryRunBundleRepair);
+        assertEquals(aoRepair.getExistingEvents().size(), 3);
+        
+        
+        List<ExistingEvent> expected = new LinkedList<SubscriptionTimeline.ExistingEvent>();
+        expected.add(createExistingEventForAssertion(SubscriptionTransitionType.CREATE, "Telescopic-Scope", PhaseType.DISCOUNT,
+                ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, aoSubscription.getStartDate()));
+        expected.add(createExistingEventForAssertion(SubscriptionTransitionType.CHANGE, "Laser-Scope", PhaseType.DISCOUNT,
+                ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, aoChangeDate));
+        expected.add(createExistingEventForAssertion(SubscriptionTransitionType.PHASE, "Laser-Scope", PhaseType.EVERGREEN,
+                ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY,
+                aoSubscription.getStartDate().plusMonths(1) /* Subscription alignment */));
+                
+        int index = 0;
+        for (ExistingEvent e : expected) {
+           validateExistingEventForAssertion(e, aoRepair.getExistingEvents().get(index++));           
+        }
+        SubscriptionData newAoSubscription = (SubscriptionData)  entitlementApi.getSubscriptionFromId(aoSubscription.getId());
+        assertEquals(newAoSubscription.getState(), SubscriptionState.ACTIVE);
+        assertEquals(newAoSubscription.getAllTransitions().size(), 2);
+        
+        // AND NOW COMMIT
+        dryRun = false;
+        testListener.pushExpectedEvent(NextEvent.REPAIR_BUNDLE);
+        BundleTimeline realRunBundleRepair = repairApi.repairBundle(bRepair, dryRun, context);
+        assertTrue(testListener.isCompleted(5000));
+        
+        aoRepair = getSubscriptionRepair(aoSubscription.getId(), realRunBundleRepair);
+        assertEquals(aoRepair.getExistingEvents().size(), 3);
+        index = 0;
+        for (ExistingEvent e : expected) {
+           validateExistingEventForAssertion(e, aoRepair.getExistingEvents().get(index++));           
+        }
+        
+        newAoSubscription = (SubscriptionData)  entitlementApi.getSubscriptionFromId(aoSubscription.getId());
+        assertEquals(newAoSubscription.getState(), SubscriptionState.ACTIVE);
+        assertEquals(newAoSubscription.getAllTransitions().size(), 3);
+        
+        
+        assertEquals(newAoSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1);
+        assertEquals(newAoSubscription.getBundleId(), bundle.getId());
+        assertEquals(newAoSubscription.getStartDate(), aoSubscription.getStartDate());
+
+        Plan currentPlan = newAoSubscription.getCurrentPlan();
+        assertNotNull(currentPlan);
+        assertEquals(currentPlan.getProduct().getName(), "Laser-Scope");
+        assertEquals(currentPlan.getProduct().getCategory(), ProductCategory.ADD_ON);
+        assertEquals(currentPlan.getBillingPeriod(), BillingPeriod.MONTHLY);
+
+        PlanPhase currentPhase = newAoSubscription.getCurrentPhase();
+        assertNotNull(currentPhase);
+        assertEquals(currentPhase.getPhaseType(), PhaseType.DISCOUNT);
+        
+        testListener.pushExpectedEvent(NextEvent.PHASE);
+        
+        it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(60));
+        clock.addDeltaFromReality(it.toDurationMillis());
+        assertTrue(testListener.isCompleted(5000));
+        
+        newAoSubscription = (SubscriptionData)  entitlementApi.getSubscriptionFromId(aoSubscription.getId());
+        currentPhase = newAoSubscription.getCurrentPhase();
+        assertNotNull(currentPhase);
+        assertEquals(currentPhase.getPhaseType(), PhaseType.EVERGREEN);
+    }
+}
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/timeline/TestRepairWithError.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/timeline/TestRepairWithError.java
new file mode 100644
index 0000000..54fda89
--- /dev/null
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/timeline/TestRepairWithError.java
@@ -0,0 +1,464 @@
+/* 
+ * Copyright 2010-2011 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.entitlement.api.timeline;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.joda.time.Interval;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Stage;
+import com.ning.billing.ErrorCode;
+import com.ning.billing.api.TestApiListener.NextEvent;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.PhaseType;
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.catalog.api.PriceListSet;
+import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline.DeletedEvent;
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline.NewEvent;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.api.user.SubscriptionData;
+import com.ning.billing.entitlement.glue.MockEngineModuleMemory;
+
+public class TestRepairWithError extends TestApiBaseRepair {
+
+    private static final String baseProduct = "Shotgun";
+    private TestWithException test;
+    private Subscription baseSubscription;
+    private DateTime startDate;
+    @Override
+    public Injector getInjector() {
+        return Guice.createInjector(Stage.DEVELOPMENT, new MockEngineModuleMemory());
+    }
+
+    @BeforeMethod(alwaysRun = true)
+    public void setupTest() throws Exception {
+        super.setupTest();
+        test = new TestWithException();
+        startDate = clock.getUTCNow();
+        baseSubscription = createSubscription(baseProduct, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, startDate);
+    }
+  
+    @Test(groups={"fast"})
+    public void testENT_REPAIR_NEW_EVENT_BEFORE_LAST_BP_REMAINING() throws Exception {
+        
+        log.info("Starting testENT_REPAIR_NEW_EVENT_BEFORE_LAST_BP_REMAINING");
+        
+        test.withException(new TestWithExceptionCallback() {
+            @Override
+            public void doTest() throws EntitlementRepairException {
+
+                // MOVE AFTER TRIAL
+                testListener.pushExpectedEvent(NextEvent.PHASE);
+                
+                Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(40));
+                clock.addDeltaFromReality(it.toDurationMillis());
+
+                assertTrue(testListener.isCompleted(5000));
+                
+                BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
+                sortEventsOnBundle(bundleRepair);
+                PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Assault-Rifle", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.EVERGREEN);
+                NewEvent ne = createNewEvent(SubscriptionTransitionType.CHANGE, baseSubscription.getStartDate().plusDays(10), spec);
+                
+                SubscriptionTimeline sRepair = createSubscriptionReapir(baseSubscription.getId(), Collections.<DeletedEvent>emptyList(), Collections.singletonList(ne));
+                
+                BundleTimeline bRepair =  createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(sRepair));
+
+                repairApi.repairBundle(bRepair, true, context);
+            }
+        }, ErrorCode.ENT_REPAIR_NEW_EVENT_BEFORE_LAST_BP_REMAINING);
+    }
+    
+    @Test(groups={"fast"})
+    public void testENT_REPAIR_INVALID_DELETE_SET() throws Exception {
+        
+        log.info("Starting testENT_REPAIR_INVALID_DELETE_SET");
+        
+        test.withException(new TestWithExceptionCallback() {
+            @Override
+            public void doTest() throws EntitlementRepairException, EntitlementUserApiException {
+
+                Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(3));
+                clock.addDeltaFromReality(it.toDurationMillis());
+                
+                testListener.pushExpectedEvent(NextEvent.CHANGE);
+                DateTime changeTime = clock.getUTCNow();
+                baseSubscription.changePlan("Assault-Rifle", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, changeTime, context);
+                assertTrue(testListener.isCompleted(5000));
+                
+                // MOVE AFTER TRIAL
+                testListener.pushExpectedEvent(NextEvent.PHASE);
+                it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(40));
+                clock.addDeltaFromReality(it.toDurationMillis());
+                assertTrue(testListener.isCompleted(5000));
+                
+                BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
+                sortEventsOnBundle(bundleRepair);
+                PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Assault-Rifle", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.EVERGREEN);
+                NewEvent ne = createNewEvent(SubscriptionTransitionType.CHANGE, baseSubscription.getStartDate().plusDays(10), spec);
+                DeletedEvent de = createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(1).getEventId());                
+
+                SubscriptionTimeline sRepair = createSubscriptionReapir(baseSubscription.getId(), Collections.singletonList(de), Collections.singletonList(ne));
+                BundleTimeline bRepair =  createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(sRepair));
+
+                repairApi.repairBundle(bRepair, true, context);
+            }
+        }, ErrorCode.ENT_REPAIR_INVALID_DELETE_SET);
+    }
+
+    @Test(groups={"fast"})
+    public void testENT_REPAIR_NON_EXISTENT_DELETE_EVENT() throws Exception {
+        
+        log.info("Starting testENT_REPAIR_NON_EXISTENT_DELETE_EVENT");
+        
+        test.withException(new TestWithExceptionCallback() {
+            @Override
+            public void doTest() throws EntitlementRepairException {
+                
+                BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
+                sortEventsOnBundle(bundleRepair);
+                PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Assault-Rifle", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.EVERGREEN);
+                NewEvent ne = createNewEvent(SubscriptionTransitionType.CHANGE, baseSubscription.getStartDate().plusDays(10), spec);
+                DeletedEvent de = createDeletedEvent(UUID.randomUUID());
+                SubscriptionTimeline sRepair = createSubscriptionReapir(baseSubscription.getId(), Collections.singletonList(de), Collections.singletonList(ne));
+                
+                BundleTimeline bRepair =  createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(sRepair));
+
+                repairApi.repairBundle(bRepair, true, context);
+            }
+        }, ErrorCode.ENT_REPAIR_NON_EXISTENT_DELETE_EVENT);
+    }
+    
+    @Test(groups={"fast"})
+    public void testENT_REPAIR_SUB_RECREATE_NOT_EMPTY() throws Exception {
+        
+        log.info("Starting testENT_REPAIR_SUB_RECREATE_NOT_EMPTY");
+        
+        test.withException(new TestWithExceptionCallback() {
+            @Override
+            public void doTest() throws EntitlementRepairException {
+                
+                // MOVE AFTER TRIAL
+                   testListener.pushExpectedEvent(NextEvent.PHASE);
+                   Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(40));
+                   clock.addDeltaFromReality(it.toDurationMillis());
+                   assertTrue(testListener.isCompleted(5000));
+                   
+                   BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
+                   sortEventsOnBundle(bundleRepair);
+                   PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Assault-Rifle", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.EVERGREEN);
+                   NewEvent ne = createNewEvent(SubscriptionTransitionType.CREATE, baseSubscription.getStartDate().plusDays(10), spec);
+                   List<DeletedEvent> des = new LinkedList<SubscriptionTimeline.DeletedEvent>();
+                   des.add(createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(1).getEventId()));                
+                   SubscriptionTimeline sRepair = createSubscriptionReapir(baseSubscription.getId(), des, Collections.singletonList(ne));
+                   
+                   BundleTimeline bRepair =  createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(sRepair));
+
+                   repairApi.repairBundle(bRepair, true, context);
+                
+            }
+        }, ErrorCode.ENT_REPAIR_SUB_RECREATE_NOT_EMPTY);
+    }
+
+    @Test(groups={"fast"})
+    public void testENT_REPAIR_SUB_EMPTY() throws Exception {
+
+        log.info("Starting testENT_REPAIR_SUB_EMPTY");
+        
+        test.withException(new TestWithExceptionCallback() {
+
+            @Override
+            public void doTest() throws EntitlementRepairException {
+                
+             // MOVE AFTER TRIAL
+                testListener.pushExpectedEvent(NextEvent.PHASE);
+                Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(40));
+                clock.addDeltaFromReality(it.toDurationMillis());
+                assertTrue(testListener.isCompleted(5000));
+                
+                BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
+                sortEventsOnBundle(bundleRepair);
+                PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Assault-Rifle", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.EVERGREEN);
+                NewEvent ne = createNewEvent(SubscriptionTransitionType.CHANGE, baseSubscription.getStartDate().plusDays(10), spec);
+                List<DeletedEvent> des = new LinkedList<SubscriptionTimeline.DeletedEvent>();
+                des.add(createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(0).getEventId()));                
+                des.add(createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(1).getEventId()));                                
+                SubscriptionTimeline sRepair = createSubscriptionReapir(baseSubscription.getId(), des, Collections.singletonList(ne));
+                
+                BundleTimeline bRepair =  createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(sRepair));
+
+                repairApi.repairBundle(bRepair, true, context);
+            }
+        }, ErrorCode.ENT_REPAIR_SUB_EMPTY);
+    }
+    
+    @Test(groups={"fast"})
+    public void testENT_REPAIR_AO_CREATE_BEFORE_BP_START() throws Exception {
+        
+        log.info("Starting testENT_REPAIR_AO_CREATE_BEFORE_BP_START");
+        
+        test.withException(new TestWithExceptionCallback() {
+            @Override
+            public void doTest() throws EntitlementRepairException, EntitlementUserApiException {
+               
+
+                // MOVE CLOCK A LITTLE BIT-- STILL IN TRIAL
+                Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(4));
+                clock.addDeltaFromReality(it.toDurationMillis());
+                SubscriptionData aoSubscription = createSubscription("Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME);
+
+                // MOVE CLOCK A LITTLE BIT MORE -- STILL IN TRIAL
+                it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(4));
+                clock.addDeltaFromReality(it.toDurationMillis());
+
+                BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
+                sortEventsOnBundle(bundleRepair);
+                
+                // Quick check
+                SubscriptionTimeline bpRepair = getSubscriptionRepair(baseSubscription.getId(), bundleRepair);
+                assertEquals(bpRepair.getExistingEvents().size(), 2);
+                
+                SubscriptionTimeline aoRepair = getSubscriptionRepair(aoSubscription.getId(), bundleRepair);
+                assertEquals(aoRepair.getExistingEvents().size(), 2);
+                
+
+                List<DeletedEvent> des = new LinkedList<SubscriptionTimeline.DeletedEvent>();
+                des.add(createDeletedEvent(aoRepair.getExistingEvents().get(0).getEventId()));        
+                des.add(createDeletedEvent(aoRepair.getExistingEvents().get(1).getEventId()));
+
+                DateTime aoRecreateDate = aoSubscription.getStartDate().minusDays(5);
+                PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Telescopic-Scope", ProductCategory.ADD_ON, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.DISCOUNT);
+                NewEvent ne = createNewEvent(SubscriptionTransitionType.CREATE, aoRecreateDate, spec);
+                
+                SubscriptionTimeline saoRepair = createSubscriptionReapir(aoSubscription.getId(), des, Collections.singletonList(ne));
+                
+                BundleTimeline bRepair =  createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(saoRepair));
+                
+                boolean dryRun = true;
+                repairApi.repairBundle(bRepair, dryRun, context);
+            }
+        }, ErrorCode.ENT_REPAIR_AO_CREATE_BEFORE_BP_START);
+    }
+    
+    @Test(groups={"fast"})
+    public void testENT_REPAIR_NEW_EVENT_BEFORE_LAST_AO_REMAINING() throws Exception {
+        
+        log.info("Starting testENT_REPAIR_NEW_EVENT_BEFORE_LAST_AO_REMAINING");
+        
+        test.withException(new TestWithExceptionCallback() {
+            @Override
+            public void doTest() throws EntitlementRepairException, EntitlementUserApiException {
+                
+
+                // MOVE CLOCK A LITTLE BIT-- STILL IN TRIAL
+                Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(4));
+                clock.addDeltaFromReality(it.toDurationMillis());
+                SubscriptionData aoSubscription = createSubscription("Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME);
+
+                // MOVE CLOCK A LITTLE BIT MORE -- STILL IN TRIAL
+                it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(4));
+                clock.addDeltaFromReality(it.toDurationMillis());
+
+                BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
+                sortEventsOnBundle(bundleRepair);
+                
+                // Quick check
+                SubscriptionTimeline bpRepair = getSubscriptionRepair(baseSubscription.getId(), bundleRepair);
+                assertEquals(bpRepair.getExistingEvents().size(), 2);
+                
+                SubscriptionTimeline aoRepair = getSubscriptionRepair(aoSubscription.getId(), bundleRepair);
+                assertEquals(aoRepair.getExistingEvents().size(), 2);
+                
+
+                List<DeletedEvent> des = new LinkedList<SubscriptionTimeline.DeletedEvent>();
+                //des.add(createDeletedEvent(aoRepair.getExistingEvents().get(1).getEventId()));        
+                DateTime aoCancelDate = aoSubscription.getStartDate().plusDays(10);
+                
+                NewEvent ne = createNewEvent(SubscriptionTransitionType.CANCEL, aoCancelDate, null);
+                
+                SubscriptionTimeline saoRepair = createSubscriptionReapir(aoSubscription.getId(), des, Collections.singletonList(ne));
+                
+                bundleRepair =  createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(saoRepair));
+                
+                boolean dryRun = true;
+                repairApi.repairBundle(bundleRepair, dryRun, context);
+            }
+        }, ErrorCode.ENT_REPAIR_NEW_EVENT_BEFORE_LAST_AO_REMAINING);
+    }
+
+
+    @Test(groups={"fast"})
+    public void testENT_REPAIR_BP_RECREATE_MISSING_AO() throws Exception {
+        
+        log.info("Starting testENT_REPAIR_BP_RECREATE_MISSING_AO");
+        
+        test.withException(new TestWithExceptionCallback() {
+            @Override
+            public void doTest() throws EntitlementRepairException, EntitlementUserApiException {
+
+              //testListener.pushExpectedEvent(NextEvent.PHASE);
+
+                Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(4));
+                clock.addDeltaFromReality(it.toDurationMillis());
+                //assertTrue(testListener.isCompleted(5000));
+
+                SubscriptionData aoSubscription = createSubscription("Laser-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME);
+                
+                BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
+                sortEventsOnBundle(bundleRepair);
+                
+                DateTime newCreateTime = baseSubscription.getStartDate().plusDays(3);
+
+                PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Pistol", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.TRIAL);
+
+                NewEvent ne = createNewEvent(SubscriptionTransitionType.CREATE, newCreateTime, spec);
+                List<DeletedEvent> des = new LinkedList<SubscriptionTimeline.DeletedEvent>();
+                des.add(createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(0).getEventId()));
+                des.add(createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(1).getEventId()));
+
+                SubscriptionTimeline sRepair = createSubscriptionReapir(baseSubscription.getId(), des, Collections.singletonList(ne));
+                
+                // FIRST ISSUE DRY RUN
+                BundleTimeline bRepair =  createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(sRepair));
+                
+                boolean dryRun = true;
+                repairApi.repairBundle(bRepair, dryRun, context);
+            }
+        }, ErrorCode.ENT_REPAIR_BP_RECREATE_MISSING_AO);
+    }
+    
+    //
+    // CAN'T seem to trigger such case easily, other errors trigger before...
+    //
+    @Test(groups={"fast"}, enabled=false)
+    public void testENT_REPAIR_BP_RECREATE_MISSING_AO_CREATE() throws Exception {
+        
+        log.info("Starting testENT_REPAIR_BP_RECREATE_MISSING_AO_CREATE");
+        
+        test.withException(new TestWithExceptionCallback() {
+            @Override
+            public void doTest() throws EntitlementRepairException, EntitlementUserApiException {
+                /*
+              //testListener.pushExpectedEvent(NextEvent.PHASE);
+
+                Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(4));
+                clock.addDeltaFromReality(it.toDurationMillis());
+
+
+                SubscriptionData aoSubscription = createSubscription("Laser-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME);
+                
+                BundleRepair bundleRepair = repairApi.getBundleRepair(bundle.getId());
+                sortEventsOnBundle(bundleRepair);
+                
+                DateTime newCreateTime = baseSubscription.getStartDate().plusDays(3);
+
+                PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Pistol", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.TRIAL);
+
+                NewEvent ne = createNewEvent(SubscriptionTransitionType.CREATE, newCreateTime, spec);
+                List<DeletedEvent> des = new LinkedList<SubscriptionRepair.DeletedEvent>();
+                des.add(createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(0).getEventId()));
+                des.add(createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(1).getEventId()));
+
+                SubscriptionRepair bpRepair = createSubscriptionReapir(baseSubscription.getId(), des, Collections.singletonList(ne));
+                
+                ne = createNewEvent(SubscriptionTransitionType.CANCEL, clock.getUTCNow().minusDays(1),  null);
+                SubscriptionRepair aoRepair = createSubscriptionReapir(aoSubscription.getId(), Collections.<SubscriptionRepair.DeletedEvent>emptyList(), Collections.singletonList(ne));
+                
+                
+                List<SubscriptionRepair> allRepairs = new LinkedList<SubscriptionRepair>();
+                allRepairs.add(bpRepair);
+                allRepairs.add(aoRepair);
+                bundleRepair =  createBundleRepair(bundle.getId(), bundleRepair.getViewId(), allRepairs);
+                // FIRST ISSUE DRY RUN
+                BundleRepair bRepair =  createBundleRepair(bundle.getId(), bundleRepair.getViewId(), allRepairs);
+                
+                boolean dryRun = true;
+                repairApi.repairBundle(bRepair, dryRun, context);
+                */
+            }
+        }, ErrorCode.ENT_REPAIR_BP_RECREATE_MISSING_AO_CREATE);
+    }
+    
+    @Test(groups={"fast"}, enabled=false)
+    public void testENT_REPAIR_MISSING_AO_DELETE_EVENT() throws Exception {
+        
+        log.info("Starting testENT_REPAIR_MISSING_AO_DELETE_EVENT");
+        
+        test.withException(new TestWithExceptionCallback() {
+            @Override
+            public void doTest() throws EntitlementRepairException, EntitlementUserApiException {
+
+                
+                /*
+                // MOVE CLOCK -- JUST BEFORE END OF TRIAL
+                 *                 
+                Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(29));
+                clock.addDeltaFromReality(it.toDurationMillis());
+
+                clock.setDeltaFromReality(getDurationDay(29), 0);
+                
+                SubscriptionData aoSubscription = createSubscription("Laser-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME);
+                
+                // MOVE CLOCK -- RIGHT OUT OF TRIAL
+                testListener.pushExpectedEvent(NextEvent.PHASE);                
+                clock.addDeltaFromReality(getDurationDay(5));
+                assertTrue(testListener.isCompleted(5000));
+
+                DateTime requestedChange = clock.getUTCNow();
+                baseSubscription.changePlan("Assault-Rifle", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, requestedChange, context);
+
+                DateTime reapairTime = clock.getUTCNow().minusDays(1);
+
+                BundleRepair bundleRepair = repairApi.getBundleRepair(bundle.getId());
+                sortEventsOnBundle(bundleRepair);
+                
+                SubscriptionRepair bpRepair = getSubscriptionRepair(baseSubscription.getId(), bundleRepair);
+                SubscriptionRepair aoRepair = getSubscriptionRepair(aoSubscription.getId(), bundleRepair);
+
+                List<DeletedEvent> bpdes = new LinkedList<SubscriptionRepair.DeletedEvent>();
+                bpdes.add(createDeletedEvent(bpRepair.getExistingEvents().get(2).getEventId()));    
+                bpRepair = createSubscriptionReapir(baseSubscription.getId(), bpdes, Collections.<NewEvent>emptyList());
+                
+                NewEvent ne = createNewEvent(SubscriptionTransitionType.CANCEL, reapairTime, null);
+                aoRepair = createSubscriptionReapir(aoSubscription.getId(), Collections.<SubscriptionRepair.DeletedEvent>emptyList(), Collections.singletonList(ne));
+                
+                List<SubscriptionRepair> allRepairs = new LinkedList<SubscriptionRepair>();
+                allRepairs.add(bpRepair);
+                allRepairs.add(aoRepair);
+                bundleRepair =  createBundleRepair(bundle.getId(), bundleRepair.getViewId(), allRepairs);
+                
+                boolean dryRun = false;
+                repairApi.repairBundle(bundleRepair, dryRun, context);
+                */
+                }
+        }, ErrorCode.ENT_REPAIR_MISSING_AO_DELETE_EVENT);
+    }
+
+}
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiAddOn.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiAddOn.java
index 927ea08..7990084 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiAddOn.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiAddOn.java
@@ -21,13 +21,17 @@ import static org.testng.Assert.assertNotNull;
 import static org.testng.Assert.assertNull;
 import static org.testng.Assert.assertTrue;
 
+import java.util.List;
+
 import org.joda.time.DateTime;
+import org.joda.time.Interval;
 import org.testng.Assert;
 import org.testng.annotations.Test;
 
 import com.google.inject.Guice;
 import com.google.inject.Injector;
 import com.google.inject.Stage;
+import com.ning.billing.api.TestApiListener.NextEvent;
 import com.ning.billing.catalog.api.BillingPeriod;
 import com.ning.billing.catalog.api.CatalogApiException;
 import com.ning.billing.catalog.api.Duration;
@@ -39,8 +43,8 @@ import com.ning.billing.catalog.api.PlanSpecifier;
 import com.ning.billing.catalog.api.PriceListSet;
 import com.ning.billing.catalog.api.ProductCategory;
 import com.ning.billing.entitlement.api.TestApiBase;
-import com.ning.billing.entitlement.api.ApiTestListener.NextEvent;
 import com.ning.billing.entitlement.api.user.Subscription.SubscriptionState;
+import com.ning.billing.entitlement.api.user.SubscriptionStatusDryRun.DryRunChangeReason;
 import com.ning.billing.entitlement.glue.MockEngineModuleSql;
 import com.ning.billing.util.clock.DefaultClock;
 
@@ -54,6 +58,8 @@ public class TestUserApiAddOn extends TestApiBase {
     @Test(enabled=true, groups={"slow"})
     public void testCreateCancelAddon() {
 
+        log.info("Starting testCreateCancelAddon");
+
         try {
             String baseProduct = "Shotgun";
             BillingPeriod baseTerm = BillingPeriod.MONTHLY;
@@ -77,13 +83,17 @@ public class TestUserApiAddOn extends TestApiBase {
 
             assertEquals(aoSubscription.getState(), SubscriptionState.CANCELLED);
 
+            assertListenerStatus();
         } catch (Exception e) {
             Assert.fail(e.getMessage());
         }
     }
 
     @Test(enabled=true, groups={"slow"})
-    public void testCancelBPWthAddon() {
+    public void testCancelBPWithAddon() {
+
+        log.info("Starting testCancelBPWithAddon");
+
         try {
 
             String baseProduct = "Shotgun";
@@ -104,13 +114,14 @@ public class TestUserApiAddOn extends TestApiBase {
             testListener.pushExpectedEvent(NextEvent.PHASE);
 
             // MOVE CLOCK AFTER TRIAL + AO DISCOUNT
-            Duration twoMonths = getDurationMonth(2);
-            clock.setDeltaFromReality(twoMonths, DAY_IN_MS);
+            Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusMonths(2));
+            clock.addDeltaFromReality(it.toDurationMillis());
             assertTrue(testListener.isCompleted(5000));
 
             // SET CTD TO CANCEL IN FUTURE
             DateTime now = clock.getUTCNow();
             Duration ctd = getDurationMonth(1);
+            // Why not just use clock.getUTCNow().plusMonths(1) ?
             DateTime newChargedThroughDate = DefaultClock.addDuration(now, ctd);
             billingApi.setChargedThroughDate(baseSubscription.getId(), newChargedThroughDate, context);
             baseSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(baseSubscription.getId());
@@ -123,18 +134,21 @@ public class TestUserApiAddOn extends TestApiBase {
             assertEquals(aoSubscription.getState(), SubscriptionState.ACTIVE);
             assertTrue(aoSubscription.isSubscriptionFutureCancelled());
 
-
             // MOVE AFTER CANCELLATION
             testListener.reset();
             testListener.pushExpectedEvent(NextEvent.CANCEL);
             testListener.pushExpectedEvent(NextEvent.CANCEL);
-            clock.addDeltaFromReality(ctd);
+            
+            it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusMonths(1));
+            clock.addDeltaFromReality(it.toDurationMillis());
             assertTrue(testListener.isCompleted(5000));
 
             // REFETCH AO SUBSCRIPTION AND CHECK THIS IS CANCELLED
             aoSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(aoSubscription.getId());
             assertEquals(aoSubscription.getState(), SubscriptionState.CANCELLED);
 
+            assertListenerStatus();
+
         } catch (Exception e) {
             Assert.fail(e.getMessage());
         }
@@ -142,7 +156,10 @@ public class TestUserApiAddOn extends TestApiBase {
 
 
     @Test(enabled=true, groups={"slow"})
-    public void testChangeBPWthAddonNonIncluded() {
+    public void testChangeBPWithAddonIncluded() {
+
+        log.info("Starting testChangeBPWithAddonIncluded");
+
         try {
 
             String baseProduct = "Shotgun";
@@ -163,8 +180,8 @@ public class TestUserApiAddOn extends TestApiBase {
             testListener.pushExpectedEvent(NextEvent.PHASE);
 
             // MOVE CLOCK AFTER TRIAL + AO DISCOUNT
-            Duration twoMonths = getDurationMonth(2);
-            clock.setDeltaFromReality(twoMonths, DAY_IN_MS);
+            Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusMonths(2));
+            clock.addDeltaFromReality(it.toDurationMillis());
             assertTrue(testListener.isCompleted(5000));
 
             // SET CTD TO CHANGE IN FUTURE
@@ -179,6 +196,15 @@ public class TestUserApiAddOn extends TestApiBase {
             BillingPeriod newBaseTerm = BillingPeriod.MONTHLY;
             String newBasePriceList = PriceListSet.DEFAULT_PRICELIST_NAME;
 
+            List<SubscriptionStatusDryRun> aoStatus = entitlementApi.getDryRunChangePlanStatus(baseSubscription.getId(), newBaseProduct, now);
+            assertEquals(aoStatus.size(), 1);
+            assertEquals(aoStatus.get(0).getId(), aoSubscription.getId());
+            assertEquals(aoStatus.get(0).getProductName(), aoProduct);
+            assertEquals(aoStatus.get(0).getBillingPeriod(), aoTerm);            
+            assertEquals(aoStatus.get(0).getPhaseType(), aoSubscription.getCurrentPhase().getPhaseType());                        
+            assertEquals(aoStatus.get(0).getPriceList(), aoSubscription.getCurrentPriceList().getName());
+            assertEquals(aoStatus.get(0).getReason(), DryRunChangeReason.AO_INCLUDED_IN_NEW_PLAN);            
+            
             testListener.reset();
             testListener.pushExpectedEvent(NextEvent.CHANGE);
             testListener.pushExpectedEvent(NextEvent.CANCEL);
@@ -189,13 +215,17 @@ public class TestUserApiAddOn extends TestApiBase {
             aoSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(aoSubscription.getId());
             assertEquals(aoSubscription.getState(), SubscriptionState.CANCELLED);
 
+            assertListenerStatus();
         } catch (Exception e) {
             Assert.fail(e.getMessage());
         }
     }
 
     @Test(enabled=true, groups={"slow"})
-    public void testChangeBPWthAddonNonAvailable() {
+    public void testChangeBPWithAddonNonAvailable() {
+
+        log.info("Starting testChangeBPWithAddonNonAvailable");
+
         try {
 
             String baseProduct = "Shotgun";
@@ -216,8 +246,8 @@ public class TestUserApiAddOn extends TestApiBase {
             testListener.pushExpectedEvent(NextEvent.PHASE);
 
             // MOVE CLOCK AFTER TRIAL + AO DISCOUNT
-            Duration twoMonths = getDurationMonth(2);
-            clock.setDeltaFromReality(twoMonths, DAY_IN_MS);
+            Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusMonths(2));
+            clock.addDeltaFromReality(it.toDurationMillis());
             assertTrue(testListener.isCompleted(5000));
 
             // SET CTD TO CANCEL IN FUTURE
@@ -232,9 +262,17 @@ public class TestUserApiAddOn extends TestApiBase {
             BillingPeriod newBaseTerm = BillingPeriod.MONTHLY;
             String newBasePriceList = PriceListSet.DEFAULT_PRICELIST_NAME;
 
+            List<SubscriptionStatusDryRun> aoStatus = entitlementApi.getDryRunChangePlanStatus(baseSubscription.getId(), newBaseProduct, now);
+            assertEquals(aoStatus.size(), 1);
+            assertEquals(aoStatus.get(0).getId(), aoSubscription.getId());
+            assertEquals(aoStatus.get(0).getProductName(), aoProduct);
+            assertEquals(aoStatus.get(0).getBillingPeriod(), aoTerm);   
+            assertEquals(aoStatus.get(0).getPhaseType(), aoSubscription.getCurrentPhase().getPhaseType());                                    
+            assertEquals(aoStatus.get(0).getPriceList(), aoSubscription.getCurrentPriceList().getName());
+            assertEquals(aoStatus.get(0).getReason(), DryRunChangeReason.AO_NOT_AVAILABLE_IN_NEW_PLAN);            
+            
             baseSubscription.changePlan(newBaseProduct, newBaseTerm, newBasePriceList, now, context);
 
-
             // REFETCH AO SUBSCRIPTION AND CHECK THIS IS ACTIVE
             aoSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(aoSubscription.getId());
             assertEquals(aoSubscription.getState(), SubscriptionState.ACTIVE);
@@ -244,7 +282,8 @@ public class TestUserApiAddOn extends TestApiBase {
             testListener.reset();
             testListener.pushExpectedEvent(NextEvent.CHANGE);
             testListener.pushExpectedEvent(NextEvent.CANCEL);
-            clock.addDeltaFromReality(ctd);
+            it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusMonths(1));
+            clock.addDeltaFromReality(it.toDurationMillis());
             assertTrue(testListener.isCompleted(5000));
 
 
@@ -252,6 +291,7 @@ public class TestUserApiAddOn extends TestApiBase {
             aoSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(aoSubscription.getId());
             assertEquals(aoSubscription.getState(), SubscriptionState.CANCELLED);
 
+            assertListenerStatus();
         } catch (Exception e) {
             Assert.fail(e.getMessage());
         }
@@ -260,6 +300,9 @@ public class TestUserApiAddOn extends TestApiBase {
 
     @Test(enabled=true, groups={"slow"})
     public void testAddonCreateWithBundleAlign() {
+
+        log.info("Starting testAddonCreateWithBundleAlign");
+
         try {
             String aoProduct = "Telescopic-Scope";
             BillingPeriod aoTerm = BillingPeriod.MONTHLY;
@@ -275,14 +318,18 @@ public class TestUserApiAddOn extends TestApiBase {
 
             testAddonCreateInternal(aoProduct, aoTerm, aoPriceList, alignement);
 
+            assertListenerStatus();
         } catch (CatalogApiException e) {
             Assert.fail(e.getMessage());
         }
     }
 
+    //TODO MDW - debugging reenable if you find this
     @Test(enabled=true, groups={"slow"})
     public void testAddonCreateWithSubscriptionAlign() {
 
+        log.info("Starting testAddonCreateWithSubscriptionAlign");
+
         try {
             String aoProduct = "Laser-Scope";
             BillingPeriod aoTerm = BillingPeriod.MONTHLY;
@@ -298,13 +345,15 @@ public class TestUserApiAddOn extends TestApiBase {
 
             testAddonCreateInternal(aoProduct, aoTerm, aoPriceList, alignement);
 
-            } catch (CatalogApiException e) {
-                Assert.fail(e.getMessage());
-            }
+            assertListenerStatus();
+        } catch (CatalogApiException e) {
+            Assert.fail(e.getMessage());
+        }
     }
 
 
     private void testAddonCreateInternal(String aoProduct, BillingPeriod aoTerm, String aoPriceList, PlanAlignmentCreate expAlignement) {
+
         try {
 
             String baseProduct = "Shotgun";
@@ -315,9 +364,9 @@ public class TestUserApiAddOn extends TestApiBase {
             SubscriptionData baseSubscription = createSubscription(baseProduct, baseTerm, basePriceList);
 
             // MOVE CLOCK 14 DAYS LATER
-            Duration someTimeLater = getDurationDay(13);
-            clock.setDeltaFromReality(someTimeLater, DAY_IN_MS);
-
+            Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(14));
+            clock.addDeltaFromReality(it.toDurationMillis());
+  
             // CREATE ADDON
             DateTime beforeAOCreation = clock.getUTCNow();
             SubscriptionData aoSubscription = createSubscription(aoProduct, aoTerm, aoPriceList);
@@ -334,46 +383,46 @@ public class TestUserApiAddOn extends TestApiBase {
             assertNotNull(aoCurrentPhase);
             assertEquals(aoCurrentPhase.getPhaseType(), PhaseType.DISCOUNT);
 
-           assertDateWithin(aoSubscription.getStartDate(), beforeAOCreation, afterAOCreation);
-           assertEquals(aoSubscription.getBundleStartDate(), baseSubscription.getBundleStartDate());
+            assertDateWithin(aoSubscription.getStartDate(), beforeAOCreation, afterAOCreation);
+            assertEquals(aoSubscription.getBundleStartDate(), baseSubscription.getBundleStartDate());
 
-           // CHECK next AO PHASE EVENT IS INDEED A MONTH AFTER BP STARTED => BUNDLE ALIGNMENT
-           SubscriptionTransition aoPendingTranstion = aoSubscription.getPendingTransition();
+            // CHECK next AO PHASE EVENT IS INDEED A MONTH AFTER BP STARTED => BUNDLE ALIGNMENT
+            SubscriptionEvent aoPendingTranstion = aoSubscription.getPendingTransition();
 
-           if (expAlignement == PlanAlignmentCreate.START_OF_BUNDLE) {
-               assertEquals(aoPendingTranstion.getEffectiveTransitionTime(), baseSubscription.getStartDate().plusMonths(1));
-           } else {
-               assertEquals(aoPendingTranstion.getEffectiveTransitionTime(), aoSubscription.getStartDate().plusMonths(1));
-           }
+            if (expAlignement == PlanAlignmentCreate.START_OF_BUNDLE) {
+                assertEquals(aoPendingTranstion.getEffectiveTransitionTime(), baseSubscription.getStartDate().plusMonths(1));
+            } else {
+                assertEquals(aoPendingTranstion.getEffectiveTransitionTime(), aoSubscription.getStartDate().plusMonths(1));
+            }
 
-           // ADD TWO PHASE EVENTS (BP + AO)
-           testListener.reset();
-           testListener.pushExpectedEvent(NextEvent.PHASE);
-           testListener.pushExpectedEvent(NextEvent.PHASE);
+            // ADD TWO PHASE EVENTS (BP + AO)
+            testListener.reset();
+            testListener.pushExpectedEvent(NextEvent.PHASE);
+            testListener.pushExpectedEvent(NextEvent.PHASE);
 
-           // MOVE THROUGH TIME TO GO INTO EVERGREEN
-           someTimeLater = aoCurrentPhase.getDuration();
-           clock.addDeltaFromReality(someTimeLater);
-           assertTrue(testListener.isCompleted(5000));
+            // MOVE THROUGH TIME TO GO INTO EVERGREEN
+            it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(33));
+            clock.addDeltaFromReality(it.toDurationMillis());
+            assertTrue(testListener.isCompleted(5000));
 
 
-           // CHECK EVERYTHING AGAIN
-           aoSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(aoSubscription.getId());
+            // CHECK EVERYTHING AGAIN
+            aoSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(aoSubscription.getId());
 
-           aoCurrentPlan = aoSubscription.getCurrentPlan();
-           assertNotNull(aoCurrentPlan);
-           assertEquals(aoCurrentPlan.getProduct().getName(),aoProduct);
-           assertEquals(aoCurrentPlan.getProduct().getCategory(), ProductCategory.ADD_ON);
-           assertEquals(aoCurrentPlan.getBillingPeriod(), aoTerm);
+            aoCurrentPlan = aoSubscription.getCurrentPlan();
+            assertNotNull(aoCurrentPlan);
+            assertEquals(aoCurrentPlan.getProduct().getName(),aoProduct);
+            assertEquals(aoCurrentPlan.getProduct().getCategory(), ProductCategory.ADD_ON);
+            assertEquals(aoCurrentPlan.getBillingPeriod(), aoTerm);
 
-           aoCurrentPhase = aoSubscription.getCurrentPhase();
-           assertNotNull(aoCurrentPhase);
-           assertEquals(aoCurrentPhase.getPhaseType(), PhaseType.EVERGREEN);
+            aoCurrentPhase = aoSubscription.getCurrentPhase();
+            assertNotNull(aoCurrentPhase);
+            assertEquals(aoCurrentPhase.getPhaseType(), PhaseType.EVERGREEN);
 
 
-           aoSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(aoSubscription.getId());
-           aoPendingTranstion = aoSubscription.getPendingTransition();
-           assertNull(aoPendingTranstion);
+            aoSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(aoSubscription.getId());
+            aoPendingTranstion = aoSubscription.getPendingTransition();
+            assertNull(aoPendingTranstion);
 
         } catch (EntitlementUserApiException e) {
             Assert.fail(e.getMessage());
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancel.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancel.java
index 55ad5e8..7fb06e0 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancel.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancel.java
@@ -17,25 +17,24 @@
 package com.ning.billing.entitlement.api.user;
 
 import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
 import static org.testng.Assert.assertNull;
 import static org.testng.Assert.assertTrue;
-import static org.testng.Assert.assertFalse;
 
-import com.ning.billing.entitlement.api.ApiTestListener.NextEvent;
-import com.ning.billing.entitlement.api.billing.EntitlementBillingApiException;
-import com.ning.billing.util.clock.DefaultClock;
 import org.joda.time.DateTime;
+import org.joda.time.Interval;
 import org.testng.Assert;
 
-
+import com.ning.billing.api.TestApiListener.NextEvent;
 import com.ning.billing.catalog.api.BillingPeriod;
 import com.ning.billing.catalog.api.Duration;
+import com.ning.billing.catalog.api.PhaseType;
 import com.ning.billing.catalog.api.Plan;
 import com.ning.billing.catalog.api.PlanPhase;
 import com.ning.billing.catalog.api.PriceListSet;
-import com.ning.billing.catalog.api.PhaseType;
 import com.ning.billing.entitlement.api.TestApiBase;
-import java.util.List;
+import com.ning.billing.entitlement.api.billing.EntitlementBillingApiException;
+import com.ning.billing.util.clock.DefaultClock;
 
 public abstract class TestUserApiCancel extends TestApiBase {
 
@@ -57,8 +56,8 @@ public abstract class TestUserApiCancel extends TestApiBase {
             assertEquals(currentPhase.getPhaseType(), PhaseType.TRIAL);
 
             // ADVANCE TIME still in trial
-            Duration moveALittleInTime = getDurationDay(3);
-            clock.setDeltaFromReality(moveALittleInTime, 0);
+            Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(3));
+            clock.addDeltaFromReality(it.toDurationMillis());
 
             DateTime future = clock.getUTCNow();
             testListener.pushExpectedEvent(NextEvent.CANCEL);
@@ -66,11 +65,12 @@ public abstract class TestUserApiCancel extends TestApiBase {
             // CANCEL in trial period to get IMM policy
             subscription.cancel(clock.getUTCNow(), false, context);
             currentPhase = subscription.getCurrentPhase();
-            testListener.isCompleted(1000);
+            testListener.isCompleted(3000);
 
             assertNull(currentPhase);
             checkNextPhaseChange(subscription, 0, null);
 
+            assertListenerStatus();
         } catch (EntitlementUserApiException e) {
             Assert.fail(e.getMessage());
         }
@@ -97,8 +97,10 @@ public abstract class TestUserApiCancel extends TestApiBase {
 
             // MOVE TO NEXT PHASE
             testListener.pushExpectedEvent(NextEvent.PHASE);
-            clock.setDeltaFromReality(trialPhase.getDuration(), DAY_IN_MS);
-            assertTrue(testListener.isCompleted(2000));
+            Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(31));
+            clock.addDeltaFromReality(it.toDurationMillis());
+
+            assertTrue(testListener.isCompleted(5000));
             trialPhase = subscription.getCurrentPhase();
             assertEquals(trialPhase.getPhaseType(), PhaseType.EVERGREEN);
 
@@ -108,21 +110,25 @@ public abstract class TestUserApiCancel extends TestApiBase {
             billingApi.setChargedThroughDate(subscription.getId(), newChargedThroughDate, context);
             subscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(subscription.getId());
 
-            testListener.pushExpectedEvent(NextEvent.CANCEL);
-
             // CANCEL
+            testListener.setNonExpectedMode();
+            testListener.pushExpectedEvent(NextEvent.CANCEL);
             subscription.cancel(clock.getUTCNow(), false, context);
-            assertFalse(testListener.isCompleted(2000));
+            assertFalse(testListener.isCompleted(3000));
+            testListener.reset();
 
             // MOVE TO EOT + RECHECK
-            clock.addDeltaFromReality(ctd);
+            testListener.pushExpectedEvent(NextEvent.CANCEL);
+            it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusMonths(1));
+            clock.addDeltaFromReality(it.toDurationMillis());
             DateTime future = clock.getUTCNow();
-            assertTrue(testListener.isCompleted(2000));
+            assertTrue(testListener.isCompleted(5000));
 
             PlanPhase currentPhase = subscription.getCurrentPhase();
             assertNull(currentPhase);
             checkNextPhaseChange(subscription, 0, null);
 
+            assertListenerStatus();
         } catch (EntitlementUserApiException e) {
             Assert.fail(e.getMessage());
         }
@@ -150,8 +156,10 @@ public abstract class TestUserApiCancel extends TestApiBase {
 
             // MOVE TO NEXT PHASE
             testListener.pushExpectedEvent(NextEvent.PHASE);
-            clock.setDeltaFromReality(trialPhase.getDuration(), DAY_IN_MS);
-            assertTrue(testListener.isCompleted(2000));
+            
+            Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(31));
+            clock.addDeltaFromReality(it.toDurationMillis());
+            assertTrue(testListener.isCompleted(5000));
             trialPhase = subscription.getCurrentPhase();
             assertEquals(trialPhase.getPhaseType(), PhaseType.EVERGREEN);
 
@@ -159,12 +167,13 @@ public abstract class TestUserApiCancel extends TestApiBase {
 
             // CANCEL
             subscription.cancel(clock.getUTCNow(), false, context);
-            assertTrue(testListener.isCompleted(2000));
+            assertTrue(testListener.isCompleted(5000));
 
             PlanPhase currentPhase = subscription.getCurrentPhase();
             assertNull(currentPhase);
             checkNextPhaseChange(subscription, 0, null);
 
+            assertListenerStatus();
         } catch (EntitlementUserApiException e) {
             Assert.fail(e.getMessage());
         }
@@ -194,8 +203,9 @@ public abstract class TestUserApiCancel extends TestApiBase {
 
             // MOVE TO NEXT PHASE
             testListener.pushExpectedEvent(NextEvent.PHASE);
-            clock.setDeltaFromReality(trialPhase.getDuration(), DAY_IN_MS);
-            assertTrue(testListener.isCompleted(2000));
+            Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(31));
+            clock.addDeltaFromReality(it.toDurationMillis());
+            assertTrue(testListener.isCompleted(5000));
             PlanPhase currentPhase = subscription.getCurrentPhase();
             assertEquals(currentPhase.getPhaseType(), PhaseType.EVERGREEN);
 
@@ -205,27 +215,25 @@ public abstract class TestUserApiCancel extends TestApiBase {
             billingApi.setChargedThroughDate(subscription.getId(), newChargedThroughDate, context);
             subscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(subscription.getId());
 
-            testListener.pushExpectedEvent(NextEvent.CANCEL);
-
-            // CANCEL
+            // CANCEL EOT
             subscription.cancel(clock.getUTCNow(), false, context);
-            assertFalse(testListener.isCompleted(2000));
 
             subscription.uncancel(context);
-
+            
             // MOVE TO EOT + RECHECK
-            clock.addDeltaFromReality(ctd);
-            DateTime future = clock.getUTCNow();
-            assertFalse(testListener.isCompleted(2000));
+            testListener.pushExpectedEvent(NextEvent.UNCANCEL);            
+            it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusMonths(1));
+            clock.addDeltaFromReality(it.toDurationMillis());
+            assertTrue(testListener.isCompleted(5000));
 
             Plan currentPlan = subscription.getCurrentPlan();
             assertEquals(currentPlan.getProduct().getName(), prod);
             currentPhase = subscription.getCurrentPhase();
             assertEquals(currentPhase.getPhaseType(), PhaseType.EVERGREEN);
 
+            assertListenerStatus();
         } catch (EntitlementUserApiException e) {
             Assert.fail(e.getMessage());
         }
     }
-
 }
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancelMemory.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancelMemory.java
index 57e34e9..625002a 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancelMemory.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancelMemory.java
@@ -16,12 +16,13 @@
 
 package com.ning.billing.entitlement.api.user;
 
+import org.testng.annotations.Test;
+
 import com.google.inject.Guice;
 import com.google.inject.Injector;
 import com.google.inject.Stage;
 import com.ning.billing.entitlement.api.billing.EntitlementBillingApiException;
 import com.ning.billing.entitlement.glue.MockEngineModuleMemory;
-import org.testng.annotations.Test;
 
 public class TestUserApiCancelMemory extends TestUserApiCancel {
 
@@ -32,25 +33,25 @@ public class TestUserApiCancelMemory extends TestUserApiCancel {
     }
 
     @Override
-    @Test(enabled=true, groups={"fast-disabled"})
+    @Test(enabled=true, groups={"fast"})
     public void testCancelSubscriptionIMM() {
         super.testCancelSubscriptionIMM();
     }
 
     @Override
-    @Test(enabled=true, groups={"fast-disabled"})
+    @Test(enabled=true, groups={"fast"})
     public void testCancelSubscriptionEOTWithChargeThroughDate() throws EntitlementBillingApiException {
         super.testCancelSubscriptionEOTWithChargeThroughDate();
     }
 
     @Override
-    @Test(enabled=true, groups={"fast-disabled"})
+    @Test(enabled=true, groups={"fast"})
     public void testCancelSubscriptionEOTWithNoChargeThroughDate() {
         super.testCancelSubscriptionEOTWithNoChargeThroughDate();
     }
 
     @Override
-    @Test(enabled=true, groups={"fast-disabled"})
+    @Test(enabled=true, groups={"fast"})
     public void testUncancel() throws EntitlementBillingApiException {
         super.testUncancel();
     }
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancelSql.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancelSql.java
index 469d374..6d02736 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancelSql.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancelSql.java
@@ -16,12 +16,13 @@
 
 package com.ning.billing.entitlement.api.user;
 
+import org.testng.annotations.Test;
+
 import com.google.inject.Guice;
 import com.google.inject.Injector;
 import com.google.inject.Stage;
 import com.ning.billing.entitlement.api.billing.EntitlementBillingApiException;
 import com.ning.billing.entitlement.glue.MockEngineModuleSql;
-import org.testng.annotations.Test;
 
 public class TestUserApiCancelSql extends TestUserApiCancel {
 
@@ -34,7 +35,7 @@ public class TestUserApiCancelSql extends TestUserApiCancel {
     }
 
     @Test(enabled= false, groups={"stress"})
-    public void stressTest() throws EntitlementBillingApiException {
+    public void stressTest() throws Exception {
         for (int i = 0; i < MAX_STRESS_ITERATIONS; i++) {
             cleanupTest();
             setupTest();
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlan.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlan.java
index f7f8c41..b1d2c0b 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlan.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlan.java
@@ -25,18 +25,18 @@ import java.util.ArrayList;
 import java.util.List;
 
 import org.joda.time.DateTime;
+import org.joda.time.Interval;
 import org.testng.Assert;
 
+import com.ning.billing.api.TestApiListener.NextEvent;
 import com.ning.billing.catalog.api.BillingPeriod;
 import com.ning.billing.catalog.api.Duration;
+import com.ning.billing.catalog.api.PhaseType;
 import com.ning.billing.catalog.api.Plan;
 import com.ning.billing.catalog.api.PlanPhase;
 import com.ning.billing.catalog.api.PriceListSet;
-import com.ning.billing.catalog.api.PhaseType;
 import com.ning.billing.catalog.api.ProductCategory;
 import com.ning.billing.entitlement.api.TestApiBase;
-
-import com.ning.billing.entitlement.api.ApiTestListener.NextEvent;
 import com.ning.billing.entitlement.api.billing.EntitlementBillingApiException;
 import com.ning.billing.entitlement.events.EntitlementEvent;
 import com.ning.billing.entitlement.events.user.ApiEvent;
@@ -70,7 +70,7 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
     private void tChangePlanBundleAlignEOTWithNoChargeThroughDate(String fromProd, BillingPeriod fromTerm, String fromPlanSet,
         String toProd, BillingPeriod toTerm, String toPlanSet) {
 
-        log.info("Starting testChangePlanBundleAlignEOTWithNoChargeThroughDateReal");
+        log.info("Starting testChangePlanBundleAlignEOTWithNoChargeThroughDate");
 
         try {
 
@@ -80,21 +80,25 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
             // MOVE TO NEXT PHASE
             PlanPhase currentPhase = subscription.getCurrentPhase();
             testListener.pushExpectedEvent(NextEvent.PHASE);
-            clock.setDeltaFromReality(currentPhase.getDuration(), DAY_IN_MS);
+            
+            Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(31));
+            clock.addDeltaFromReality(it.toDurationMillis());
+
             DateTime futureNow = clock.getUTCNow();
             DateTime nextExpectedPhaseChange = DefaultClock.addDuration(subscription.getStartDate(), currentPhase.getDuration());
             assertTrue(futureNow.isAfter(nextExpectedPhaseChange));
-            assertTrue(testListener.isCompleted(3000));
+            assertTrue(testListener.isCompleted(5000));
 
             // CHANGE PLAN
             testListener.pushExpectedEvent(NextEvent.CHANGE);
             subscription.changePlan(toProd, toTerm, toPlanSet, clock.getUTCNow(), context);
-            assertTrue(testListener.isCompleted(2000));
+            assertTrue(testListener.isCompleted(5000));
 
             // CHECK CHANGE PLAN
             currentPhase = subscription.getCurrentPhase();
             checkChangePlan(subscription, toProd, ProductCategory.BASE, toTerm, PhaseType.EVERGREEN);
 
+            assertListenerStatus();
         } catch (EntitlementUserApiException e) {
             Assert.fail(e.getMessage());
         }
@@ -102,13 +106,14 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
 
 
     protected void testChangePlanBundleAlignEOTWithChargeThroughDate() throws EntitlementBillingApiException {
+        log.info("Starting testChangePlanBundleAlignEOTWithChargeThroughDate");
         testChangePlanBundleAlignEOTWithChargeThroughDate("Shotgun", BillingPeriod.ANNUAL, "gunclubDiscount", "Pistol", BillingPeriod.ANNUAL, "gunclubDiscount");
     }
 
     private void testChangePlanBundleAlignEOTWithChargeThroughDate(String fromProd, BillingPeriod fromTerm, String fromPlanSet,
             String toProd, BillingPeriod toTerm, String toPlanSet) throws EntitlementBillingApiException {
 
-        log.info("Starting testChangeSubscriptionEOTWithChargeThroughDate");
+        
         try {
 
             // CREATE
@@ -120,8 +125,9 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
 
             // MOVE TO NEXT PHASE
             testListener.pushExpectedEvent(NextEvent.PHASE);
-            clock.setDeltaFromReality(trialPhase.getDuration(), DAY_IN_MS);
-            assertTrue(testListener.isCompleted(2000));
+            Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(31));
+            clock.addDeltaFromReality(it.toDurationMillis());
+            assertTrue(testListener.isCompleted(5000));
             PlanPhase currentPhase = subscription.getCurrentPhase();
             assertEquals(currentPhase.getPhaseType(), PhaseType.DISCOUNT);
 
@@ -132,10 +138,11 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
             billingApi.setChargedThroughDate(subscription.getId(), newChargedThroughDate, context);
 
             // RE READ SUBSCRIPTION + CHANGE PLAN
+            testListener.setNonExpectedMode();
             testListener.pushExpectedEvent(NextEvent.CHANGE);
             subscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(subscription.getId());
             subscription.changePlan(toProd, toTerm, toPlanSet, clock.getUTCNow(), context);
-            assertFalse(testListener.isCompleted(2000));
+            assertFalse(testListener.isCompleted(3000));
             testListener.reset();
 
             // CHECK CHANGE PLAN
@@ -153,13 +160,15 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
 
             // MOVE TO EOT
             testListener.pushExpectedEvent(NextEvent.CHANGE);
-            clock.addDeltaFromReality(ctd);
+            it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusMonths(1));
+            clock.addDeltaFromReality(it.toDurationMillis());
             assertTrue(testListener.isCompleted(5000));
 
             subscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(subscription.getId());
             currentPhase = subscription.getCurrentPhase();
             checkChangePlan(subscription, toProd, ProductCategory.BASE, toTerm, PhaseType.DISCOUNT);
 
+            assertListenerStatus();
         } catch (EntitlementUserApiException e) {
             Assert.fail(e.getMessage());
         }
@@ -182,14 +191,14 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
 
             testListener.pushExpectedEvent(NextEvent.CHANGE);
 
-            Duration moveALittleInTime = getDurationDay(3);
-            clock.setDeltaFromReality(moveALittleInTime, 0);
+            Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(3));
+            clock.addDeltaFromReality(it.toDurationMillis());
 
             // CHANGE PLAN IMM
             subscription.changePlan(toProd, toTerm, toPlanSet, clock.getUTCNow(), context);
             checkChangePlan(subscription, toProd, ProductCategory.BASE, toTerm, PhaseType.TRIAL);
 
-            assertTrue(testListener.isCompleted(2000));
+            assertTrue(testListener.isCompleted(5000));
 
             PlanPhase currentPhase = subscription.getCurrentPhase();
             DateTime nextExpectedPhaseChange = DefaultClock.addDuration(subscription.getStartDate(), currentPhase.getDuration());
@@ -197,20 +206,14 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
 
             // NEXT PHASE
             testListener.pushExpectedEvent(NextEvent.PHASE);
-            clock.addDeltaFromReality(currentPhase.getDuration());
+            it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(30));
+            clock.addDeltaFromReality(it.toDurationMillis());
             DateTime futureNow = clock.getUTCNow();
 
-            /*
-            try {
-                Thread.sleep(1000 * 3000);
-            } catch (Exception e) {
-
-            }
-            */
-
             assertTrue(futureNow.isAfter(nextExpectedPhaseChange));
-            assertTrue(testListener.isCompleted(3000));
+            assertTrue(testListener.isCompleted(5000));
 
+            assertListenerStatus();
         } catch (EntitlementUserApiException e) {
             Assert.fail(e.getMessage());
         }
@@ -218,14 +221,13 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
 
 
     protected void testChangePlanChangePlanAlignEOTWithChargeThroughDate() throws EntitlementBillingApiException {
+        log.info("Starting testChangePlanChangePlanAlignEOTWithChargeThroughDate");
         tChangePlanChangePlanAlignEOTWithChargeThroughDate("Shotgun", BillingPeriod.ANNUAL, PriceListSet.DEFAULT_PRICELIST_NAME, "Assault-Rifle", BillingPeriod.ANNUAL, "rescue");
     }
 
     private void tChangePlanChangePlanAlignEOTWithChargeThroughDate(String fromProd, BillingPeriod fromTerm, String fromPlanSet,
             String toProd, BillingPeriod toTerm, String toPlanSet) throws EntitlementBillingApiException {
 
-        log.info("Starting testChangePlanBundleAlignEOTWithChargeThroughDate");
-
         try {
 
             DateTime currentTime = clock.getUTCNow();
@@ -238,9 +240,10 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
             // MOVE TO NEXT PHASE
             testListener.pushExpectedEvent(NextEvent.PHASE);
             currentTime = clock.getUTCNow();
-            clock.setDeltaFromReality(trialPhase.getDuration(), DAY_IN_MS);
+            Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(31));
+            clock.addDeltaFromReality(it.toDurationMillis());
             currentTime = clock.getUTCNow();
-            assertTrue(testListener.isCompleted(2000));
+            assertTrue(testListener.isCompleted(5000));
 
             // SET CTD
             Duration ctd = getDurationMonth(1);
@@ -255,19 +258,22 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
 
             // CHANGE PLAN
             currentTime = clock.getUTCNow();
-
-            testListener.pushExpectedEvent(NextEvent.CHANGE);
             subscription.changePlan(toProd, toTerm, toPlanSet, clock.getUTCNow(), context);
 
             checkChangePlan(subscription, fromProd, ProductCategory.BASE, fromTerm, PhaseType.EVERGREEN);
 
             // CHECK CHANGE DID NOT KICK IN YET
-            assertFalse(testListener.isCompleted(2000));
+            testListener.setNonExpectedMode();
+            testListener.pushExpectedEvent(NextEvent.CHANGE);
+            assertFalse(testListener.isCompleted(3000));
+            testListener.reset();
 
             // MOVE TO AFTER CTD
-            clock.addDeltaFromReality(ctd);
+            testListener.pushExpectedEvent(NextEvent.CHANGE);
+            it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusMonths(1));
+            clock.addDeltaFromReality(it.toDurationMillis());
             currentTime = clock.getUTCNow();
-            assertTrue(testListener.isCompleted(2000));
+            assertTrue(testListener.isCompleted(5000));
 
             // CHECK CORRECT PRODUCT, PHASE, PLAN SET
             String currentProduct =  subscription.getCurrentPlan().getProduct().getName();
@@ -277,22 +283,27 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
             assertNotNull(currentPhase);
             assertEquals(currentPhase.getPhaseType(), PhaseType.DISCOUNT);
 
-            testListener.pushExpectedEvent(NextEvent.PHASE);
-
             // MOVE TIME ABOUT ONE MONTH BEFORE NEXT EXPECTED PHASE CHANGE
-            clock.addDeltaFromReality(getDurationMonth(11));
-
+            testListener.setNonExpectedMode();
+            testListener.pushExpectedEvent(NextEvent.PHASE);
+            it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusMonths(11));
+            clock.addDeltaFromReality(it.toDurationMillis());
             currentTime = clock.getUTCNow();
-            assertFalse(testListener.isCompleted(2000));
+            assertFalse(testListener.isCompleted(3000));
+            testListener.reset();
 
             DateTime nextExpectedPhaseChange = DefaultClock.addDuration(newChargedThroughDate, currentPhase.getDuration());
             checkNextPhaseChange(subscription, 1, nextExpectedPhaseChange);
 
             // MOVE TIME RIGHT AFTER NEXT EXPECTED PHASE CHANGE
-            clock.addDeltaFromReality(getDurationMonth(1));
+            testListener.pushExpectedEvent(NextEvent.PHASE);
+            it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusMonths(1));
+            clock.addDeltaFromReality(it.toDurationMillis());
+
             currentTime = clock.getUTCNow();
-            assertTrue(testListener.isCompleted(2000));
+            assertTrue(testListener.isCompleted(5000));
 
+            assertListenerStatus();
         } catch (EntitlementUserApiException e) {
             Assert.fail(e.getMessage());
         }
@@ -300,6 +311,7 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
 
     protected void testMultipleChangeLastIMM() throws EntitlementBillingApiException {
 
+        log.info("Starting testMultipleChangeLastIMM");
         try {
             SubscriptionData subscription = createSubscription("Assault-Rifle", BillingPeriod.MONTHLY, "gunclubDiscount");
             PlanPhase trialPhase = subscription.getCurrentPhase();
@@ -307,8 +319,10 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
 
             // MOVE TO NEXT PHASE
             testListener.pushExpectedEvent(NextEvent.PHASE);
-            clock.setDeltaFromReality(trialPhase.getDuration(), DAY_IN_MS);
-            assertTrue(testListener.isCompleted(2000));
+            Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(31));
+            clock.addDeltaFromReality(it.toDurationMillis());
+
+            assertTrue(testListener.isCompleted(5000));
 
             // SET CTD
             List<Duration> durationList = new ArrayList<Duration>();
@@ -321,14 +335,16 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
             subscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(subscription.getId());
 
             // CHANGE EOT
+            testListener.setNonExpectedMode();
             testListener.pushExpectedEvent(NextEvent.CHANGE);
             subscription.changePlan("Pistol", BillingPeriod.MONTHLY, "gunclubDiscount", clock.getUTCNow(), context);
-            assertFalse(testListener.isCompleted(2000));
+            assertFalse(testListener.isCompleted(3000));
+            testListener.reset();
 
             // CHANGE
             testListener.pushExpectedEvent(NextEvent.CHANGE);
             subscription.changePlan("Assault-Rifle", BillingPeriod.ANNUAL, "gunclubDiscount", clock.getUTCNow(), context);
-            assertFalse(testListener.isCompleted(2000));
+            assertTrue(testListener.isCompleted(5000));
 
             Plan currentPlan = subscription.getCurrentPlan();
             assertNotNull(currentPlan);
@@ -339,7 +355,8 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
             PlanPhase currentPhase = subscription.getCurrentPhase();
             assertNotNull(currentPhase);
             assertEquals(currentPhase.getPhaseType(), PhaseType.DISCOUNT);
-
+            
+            assertListenerStatus();
         } catch (EntitlementUserApiException e) {
             Assert.fail(e.getMessage());
         }
@@ -347,6 +364,7 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
 
     protected void testMultipleChangeLastEOT() throws EntitlementBillingApiException {
 
+        log.info("Starting testMultipleChangeLastEOT");
         try {
 
             SubscriptionData subscription = createSubscription("Assault-Rifle", BillingPeriod.ANNUAL, "gunclubDiscount");
@@ -354,8 +372,9 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
             assertEquals(trialPhase.getPhaseType(), PhaseType.TRIAL);
 
             testListener.pushExpectedEvent(NextEvent.PHASE);
-            clock.setDeltaFromReality(trialPhase.getDuration(), DAY_IN_MS);
-            assertTrue(testListener.isCompleted(2000));
+            Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(31));
+            clock.addDeltaFromReality(it.toDurationMillis());
+            assertTrue(testListener.isCompleted(5000));
 
             // SET CTD
             List<Duration> durationList = new ArrayList<Duration>();
@@ -367,15 +386,17 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
             subscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(subscription.getId());
 
             // CHANGE EOT
+            testListener.setNonExpectedMode();
             testListener.pushExpectedEvent(NextEvent.CHANGE);
             subscription.changePlan("Shotgun", BillingPeriod.MONTHLY, "gunclubDiscount", clock.getUTCNow(), context);
-            assertFalse(testListener.isCompleted(2000));
+            assertFalse(testListener.isCompleted(3000));
             testListener.reset();
 
             // CHANGE EOT
+            testListener.setNonExpectedMode();
             testListener.pushExpectedEvent(NextEvent.CHANGE);
             subscription.changePlan("Pistol", BillingPeriod.ANNUAL, "gunclubDiscount", clock.getUTCNow(), context);
-            assertFalse(testListener.isCompleted(2000));
+            assertFalse(testListener.isCompleted(3000));
             testListener.reset();
 
             // CHECK NO CHANGE OCCURED YET
@@ -391,8 +412,10 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
 
             // ACTIVATE CHNAGE BY MOVING AFTER CTD
             testListener.pushExpectedEvent(NextEvent.CHANGE);
-            clock.addDeltaFromReality(ctd);
-            assertTrue(testListener.isCompleted(3000));
+            it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusMonths(1));
+            clock.addDeltaFromReality(it.toDurationMillis());
+
+            assertTrue(testListener.isCompleted(5000));
 
             currentPlan = subscription.getCurrentPlan();
             assertNotNull(currentPlan);
@@ -408,8 +431,9 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
 
             // MOVE TO NEXT PHASE
             testListener.pushExpectedEvent(NextEvent.PHASE);
-            clock.addDeltaFromReality(currentPhase.getDuration());
-            assertTrue(testListener.isCompleted(3000));
+            it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusMonths(6));
+            clock.addDeltaFromReality(it.toDurationMillis());
+            assertTrue(testListener.isCompleted(5000));
             subscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(subscription.getId());
 
             currentPlan = subscription.getCurrentPlan();
@@ -422,7 +446,7 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
             assertNotNull(currentPhase);
             assertEquals(currentPhase.getPhaseType(), PhaseType.EVERGREEN);
 
-
+            assertListenerStatus();
         } catch (EntitlementUserApiException e) {
             Assert.fail(e.getMessage());
         }
@@ -430,6 +454,9 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
 
 
     protected void testCorrectPhaseAlignmentOnChange() {
+        
+        log.info("Starting testCorrectPhaseAlignmentOnChange");
+        
         try {
 
             SubscriptionData subscription = createSubscription("Shotgun", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME);
@@ -437,13 +464,15 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
             assertEquals(trialPhase.getPhaseType(), PhaseType.TRIAL);
 
             // MOVE 2 DAYS AHEAD
-            clock.setDeltaFromReality(getDurationDay(1), DAY_IN_MS);
+            Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(2));
+            clock.addDeltaFromReality(it.toDurationMillis());
+    
 
             // CHANGE IMMEDIATE TO A 3 PHASES PLAN
             testListener.reset();
             testListener.pushExpectedEvent(NextEvent.CHANGE);
             subscription.changePlan("Assault-Rifle", BillingPeriod.ANNUAL, "gunclubDiscount", clock.getUTCNow(), context);
-            assertTrue(testListener.isCompleted(3000));
+            assertTrue(testListener.isCompleted(5000));
             testListener.reset();
 
             // CHECK EVERYTHING LOOKS CORRECT
@@ -458,8 +487,10 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
 
             // MOVE AFTER TRIAL PERIOD -> DISCOUNT
             testListener.pushExpectedEvent(NextEvent.PHASE);
-            clock.addDeltaFromReality(trialPhase.getDuration());
-            assertTrue(testListener.isCompleted(3000));
+            it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(30));
+            clock.addDeltaFromReality(it.toDurationMillis());
+
+            assertTrue(testListener.isCompleted(5000));
 
             trialPhase = subscription.getCurrentPhase();
             assertEquals(trialPhase.getPhaseType(), PhaseType.DISCOUNT);
@@ -467,11 +498,12 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
             subscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(subscription.getId());
 
             DateTime expectedNextPhaseDate =  subscription.getStartDate().plusDays(30).plusMonths(6);
-            SubscriptionTransition nextPhase = subscription.getPendingTransition();
+            SubscriptionEvent nextPhase = subscription.getPendingTransition();
             DateTime nextPhaseEffectiveDate = nextPhase.getEffectiveTransitionTime();
 
             assertEquals(nextPhaseEffectiveDate, expectedNextPhaseDate);
 
+            assertListenerStatus();
 
         } catch (EntitlementUserApiException e) {
             Assert.fail(e.getMessage());
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlanMemory.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlanMemory.java
index 0351ea1..0da1169 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlanMemory.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlanMemory.java
@@ -16,12 +16,13 @@
 
 package com.ning.billing.entitlement.api.user;
 
+import org.testng.annotations.Test;
+
 import com.google.inject.Guice;
 import com.google.inject.Injector;
 import com.google.inject.Stage;
 import com.ning.billing.entitlement.api.billing.EntitlementBillingApiException;
 import com.ning.billing.entitlement.glue.MockEngineModuleMemory;
-import org.testng.annotations.Test;
 
 public class TestUserApiChangePlanMemory extends TestUserApiChangePlan {
 
@@ -32,46 +33,38 @@ public class TestUserApiChangePlanMemory extends TestUserApiChangePlan {
 
 
     @Override
-    @Test(enabled=true, groups={"fast-disabled"})
+    @Test(enabled=true, groups={"fast"})
     public void testChangePlanBundleAlignEOTWithNoChargeThroughDate() {
          super.testChangePlanBundleAlignEOTWithNoChargeThroughDate();
     }
 
     @Override
-    @Test(enabled=true, groups={"fast-disabled"})
+    @Test(enabled=true, groups={"fast"})
     public void testChangePlanBundleAlignEOTWithChargeThroughDate() throws EntitlementBillingApiException {
         super.testChangePlanBundleAlignEOTWithChargeThroughDate();
     }
 
     @Override
-    @Test(enabled=true, groups={"fast-disabled"})
+    @Test(enabled=true, groups={"fast"})
     public void testChangePlanBundleAlignIMM() {
         super.testChangePlanBundleAlignIMM();
     }
 
     @Override
-    @Test(enabled=true, groups={"fast-disabled"})
+    @Test(enabled=true, groups={"fast"})
     public void testMultipleChangeLastIMM() throws EntitlementBillingApiException {
         super.testMultipleChangeLastIMM();
     }
 
     @Override
-    @Test(enabled=true, groups={"fast-disabled"})
+    @Test(enabled=true, groups={"fast"})
     public void testMultipleChangeLastEOT() throws EntitlementBillingApiException {
         super.testMultipleChangeLastEOT();
     }
 
-    /*
-    // Set to false until we implement rescue example.
-    @Override
-    @Test(enabled=false, groups={"fast"})
-    public void testChangePlanChangePlanAlignEOTWithChargeThroughDate() throws EntitlementBillingApiException {
-        super.testChangePlanChangePlanAlignEOTWithChargeThroughDate();
-    }
-    */
 
     @Override
-    @Test(enabled=true, groups={"fast-disabled"})
+    @Test(enabled=true, groups={"fast"})
     public void testCorrectPhaseAlignmentOnChange() {
         super.testCorrectPhaseAlignmentOnChange();
     }
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlanSql.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlanSql.java
index 735099c..1039b85 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlanSql.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlanSql.java
@@ -16,12 +16,13 @@
 
 package com.ning.billing.entitlement.api.user;
 
+import org.testng.annotations.Test;
+
 import com.google.inject.Guice;
 import com.google.inject.Injector;
 import com.google.inject.Stage;
 import com.ning.billing.entitlement.api.billing.EntitlementBillingApiException;
 import com.ning.billing.entitlement.glue.MockEngineModuleSql;
-import org.testng.annotations.Test;
 
 public class TestUserApiChangePlanSql extends TestUserApiChangePlan {
 
@@ -32,8 +33,8 @@ public class TestUserApiChangePlanSql extends TestUserApiChangePlan {
         return Guice.createInjector(Stage.DEVELOPMENT, new MockEngineModuleSql());
     }
 
-    @Test(enabled= true, groups={"stress"})
-    public void stressTest() throws EntitlementBillingApiException {
+    @Test(enabled= false, groups={"stress"})
+    public void stressTest() throws Exception {
         for (int i = 0; i < MAX_STRESS_ITERATIONS; i++) {
             cleanupTest();
             setupTest();
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCreate.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCreate.java
index a8d6e0c..6517044 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCreate.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCreate.java
@@ -22,19 +22,21 @@ import static org.testng.Assert.assertNotNull;
 import static org.testng.Assert.assertTrue;
 
 import java.util.List;
+
 import org.joda.time.DateTime;
+import org.joda.time.Interval;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.testng.Assert;
 
+import com.ning.billing.api.TestApiListener.NextEvent;
 import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.PhaseType;
 import com.ning.billing.catalog.api.Plan;
 import com.ning.billing.catalog.api.PlanPhase;
 import com.ning.billing.catalog.api.PriceListSet;
-import com.ning.billing.catalog.api.PhaseType;
 import com.ning.billing.catalog.api.ProductCategory;
 import com.ning.billing.entitlement.api.TestApiBase;
-import com.ning.billing.entitlement.api.ApiTestListener.NextEvent;
 import com.ning.billing.entitlement.events.EntitlementEvent;
 import com.ning.billing.entitlement.events.phase.PhaseEvent;
 import com.ning.billing.util.clock.DefaultClock;
@@ -70,6 +72,8 @@ public abstract class TestUserApiCreate extends TestApiBase {
 
             assertTrue(testListener.isCompleted(5000));
 
+            assertListenerStatus();
+            
         } catch (EntitlementUserApiException e) {
         	log.error("Unexpected exception",e);
             Assert.fail(e.getMessage());
@@ -110,6 +114,8 @@ public abstract class TestUserApiCreate extends TestApiBase {
             assertNotNull(currentPhase);
             assertEquals(currentPhase.getPhaseType(), PhaseType.EVERGREEN);
 
+            assertListenerStatus();
+            
         } catch (EntitlementUserApiException e) {
             Assert.fail(e.getMessage());
         }
@@ -160,14 +166,15 @@ public abstract class TestUserApiCreate extends TestApiBase {
             assertEquals(nextPhaseChange, nextExpectedPhaseChange);
 
             testListener.pushExpectedEvent(NextEvent.PHASE);
-
-            clock.setDeltaFromReality(currentPhase.getDuration(), DAY_IN_MS);
+            Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(31));
+            clock.addDeltaFromReality(it.toDurationMillis());
 
             DateTime futureNow = clock.getUTCNow();
             assertTrue(futureNow.isAfter(nextPhaseChange));
 
             assertTrue(testListener.isCompleted(5000));
 
+            assertListenerStatus();
         } catch (EntitlementUserApiException e) {
             Assert.fail(e.getMessage());
         }
@@ -197,23 +204,26 @@ public abstract class TestUserApiCreate extends TestApiBase {
 
             // MOVE TO DISCOUNT PHASE
             testListener.pushExpectedEvent(NextEvent.PHASE);
-            clock.setDeltaFromReality(currentPhase.getDuration(), DAY_IN_MS);
+            Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(31));
+            clock.addDeltaFromReality(it.toDurationMillis());
+            assertTrue(testListener.isCompleted(5000));
             currentPhase = subscription.getCurrentPhase();
             assertNotNull(currentPhase);
             assertEquals(currentPhase.getPhaseType(), PhaseType.DISCOUNT);
-            assertTrue(testListener.isCompleted(2000));
+            
 
             // MOVE TO EVERGREEN PHASE + RE-READ SUBSCRIPTION
             testListener.pushExpectedEvent(NextEvent.PHASE);
-            clock.addDeltaFromReality(currentPhase.getDuration());
-            assertTrue(testListener.isCompleted(2000));
+            it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusYears(1));
+            clock.addDeltaFromReality(it.toDurationMillis());
+            assertTrue(testListener.isCompleted(5000));
 
             subscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(subscription.getId());
             currentPhase = subscription.getCurrentPhase();
             assertNotNull(currentPhase);
             assertEquals(currentPhase.getPhaseType(), PhaseType.EVERGREEN);
 
-
+            assertListenerStatus();
         } catch (EntitlementUserApiException e) {
             Assert.fail(e.getMessage());
         }
@@ -234,11 +244,9 @@ public abstract class TestUserApiCreate extends TestApiBase {
                     getProductSpecifier(productName, planSetName, term, null), clock.getUTCNow(), context);
             assertNotNull(subscription);
 
+            assertListenerStatus();
         } catch (EntitlementUserApiException e) {
             Assert.fail(e.getMessage());
         }
     }
-
-
-
 }
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCreateMemory.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCreateMemory.java
index b674e34..e4969f1 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCreateMemory.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCreateMemory.java
@@ -16,11 +16,12 @@
 
 package com.ning.billing.entitlement.api.user;
 
+import org.testng.annotations.Test;
+
 import com.google.inject.Guice;
 import com.google.inject.Injector;
 import com.google.inject.Stage;
 import com.ning.billing.entitlement.glue.MockEngineModuleMemory;
-import org.testng.annotations.Test;
 
 public class TestUserApiCreateMemory extends TestUserApiCreate {
 
@@ -31,31 +32,31 @@ public class TestUserApiCreateMemory extends TestUserApiCreate {
     }
 
     @Override
-    @Test(enabled=true, groups={"fast-disabled"})
+    @Test(enabled=true, groups={"fast"})
     public void testCreateWithRequestedDate() {
         super.testCreateWithRequestedDate();
     }
 
     @Override
-    @Test(enabled=true, groups={"fast-disabled"})
+    @Test(enabled=true, groups={"fast"})
     public void testCreateWithInitialPhase() {
         super.testSimpleSubscriptionThroughPhases();
     }
 
     @Override
-    @Test(enabled=true, groups={"fast-disabled"})
+    @Test(enabled=true, groups={"fast"})
     public void testSimpleCreateSubscription() {
         super.testSimpleCreateSubscription();
     }
 
     @Override
-    @Test(enabled=true, groups={"fast-disabled"})
+    @Test(enabled=true, groups={"fast"})
     protected void testSimpleSubscriptionThroughPhases() {
         super.testSimpleSubscriptionThroughPhases();
     }
 
     @Override
-    @Test(enabled=true, groups={"fast-disabled"})
+    @Test(enabled=true, groups={"fast"})
     protected void testSubscriptionWithAddOn() {
         super.testSubscriptionWithAddOn();
     }
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCreateSql.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCreateSql.java
index 6820fec..c890da7 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCreateSql.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCreateSql.java
@@ -16,11 +16,12 @@
 
 package com.ning.billing.entitlement.api.user;
 
+import org.testng.annotations.Test;
+
 import com.google.inject.Guice;
 import com.google.inject.Injector;
 import com.google.inject.Stage;
 import com.ning.billing.entitlement.glue.MockEngineModuleSql;
-import org.testng.annotations.Test;
 
 public class TestUserApiCreateSql extends TestUserApiCreate {
 
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiDemos.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiDemos.java
index 74fbef7..0973666 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiDemos.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiDemos.java
@@ -16,31 +16,34 @@
 
 package com.ning.billing.entitlement.api.user;
 
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.joda.time.Interval;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
 import com.google.inject.Guice;
 import com.google.inject.Injector;
 import com.google.inject.Stage;
-
+import com.ning.billing.api.TestApiListener.NextEvent;
 import com.ning.billing.catalog.api.BillingPeriod;
 import com.ning.billing.catalog.api.Duration;
+import com.ning.billing.catalog.api.PhaseType;
 import com.ning.billing.catalog.api.Plan;
 import com.ning.billing.catalog.api.PlanPhase;
-import com.ning.billing.catalog.api.PhaseType;
 import com.ning.billing.catalog.api.ProductCategory;
 import com.ning.billing.entitlement.api.TestApiBase;
-
-import com.ning.billing.entitlement.api.ApiTestListener.NextEvent;
 import com.ning.billing.entitlement.api.billing.EntitlementBillingApiException;
 import com.ning.billing.entitlement.glue.MockEngineModuleSql;
 import com.ning.billing.util.clock.DefaultClock;
-import org.joda.time.DateTime;
-import org.testng.Assert;
-import org.testng.annotations.Test;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.UUID;
-
-import static org.testng.Assert.*;
 
 public class TestUserApiDemos extends TestApiBase {
 
@@ -67,6 +70,7 @@ public class TestUserApiDemos extends TestApiBase {
     @Test(enabled=true, groups="demos")
     public void testDemo1() throws EntitlementBillingApiException {
 
+        log.info("Starting testSubscriptionWithAddOn");
         try {
             System.out.println("DEMO 1 START");
 
@@ -80,14 +84,15 @@ public class TestUserApiDemos extends TestApiBase {
             /* STEP 2. CHANGE PLAN WHILE IN TRIAL */
             testListener.pushExpectedEvent(NextEvent.CHANGE);
             subscription.changePlan("Assault-Rifle", BillingPeriod.ANNUAL, "gunclubDiscount", clock.getUTCNow(), context);
-            assertTrue(testListener.isCompleted(3000));
+            assertTrue(testListener.isCompleted(5000));
 
             displayState(subscription.getId(), "STEP 2. CHANGED PLAN WHILE IN TRIAL");
 
             /* STEP 3. MOVE TO DISCOUNT PHASE */
             testListener.pushExpectedEvent(NextEvent.PHASE);
-            clock.setDeltaFromReality(trialPhase.getDuration(), DAY_IN_MS);
-            assertTrue(testListener.isCompleted(3000));
+            Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(31));
+            clock.addDeltaFromReality(it.toDurationMillis());
+            assertTrue(testListener.isCompleted(5000));
 
             displayState(subscription.getId(), "STEP 3. MOVE TO DISCOUNT PHASE");
 
@@ -101,25 +106,28 @@ public class TestUserApiDemos extends TestApiBase {
             billingApi.setChargedThroughDate(subscription.getId(), newChargedThroughDate, context);
             subscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(subscription.getId());
 
+            testListener.setNonExpectedMode();
             testListener.pushExpectedEvent(NextEvent.CHANGE);
             subscription.changePlan("Shotgun", BillingPeriod.ANNUAL, "gunclubDiscount", clock.getUTCNow(), context);
-            assertFalse(testListener.isCompleted(2000));
+            assertFalse(testListener.isCompleted(3000));
             testListener.reset();
 
             displayState(subscription.getId(), "STEP 4. SET CTD AND CHANGE PLAN EOT (Shotgun)");
 
             /* STEP 5. CHANGE AGAIN */
+            testListener.setNonExpectedMode();
             testListener.pushExpectedEvent(NextEvent.CHANGE);
             subscription.changePlan("Pistol", BillingPeriod.ANNUAL, "gunclubDiscount", clock.getUTCNow(), context);
-            assertFalse(testListener.isCompleted(2000));
+            assertFalse(testListener.isCompleted(3000));
             testListener.reset();
 
             displayState(subscription.getId(), "STEP 5. CHANGE AGAIN EOT (Pistol)");
 
             /* STEP 6. MOVE TO EOT AND CHECK CHANGE OCCURED */
             testListener.pushExpectedEvent(NextEvent.CHANGE);
-            clock.addDeltaFromReality(ctd);
-            assertTrue(testListener.isCompleted(2000));
+            it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusMonths(1));
+            clock.addDeltaFromReality(it.toDurationMillis());
+            assertTrue(testListener.isCompleted(5000));
 
             Plan currentPlan = subscription.getCurrentPlan();
             assertNotNull(currentPlan);
@@ -135,7 +143,8 @@ public class TestUserApiDemos extends TestApiBase {
 
             /* STEP 7.  MOVE TO NEXT PHASE */
             testListener.pushExpectedEvent(NextEvent.PHASE);
-            clock.addDeltaFromReality(currentPhase.getDuration());
+            it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusMonths(6));
+            clock.addDeltaFromReality(it.toDurationMillis());
             assertTrue(testListener.isCompleted(5000));
             subscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(subscription.getId());
 
@@ -157,6 +166,7 @@ public class TestUserApiDemos extends TestApiBase {
 
             displayState(subscription.getId(), "STEP 8.  CANCELLATION");
 
+            assertListenerStatus();
         } catch (EntitlementUserApiException e) {
             Assert.fail(e.getMessage());
         }
@@ -168,29 +178,33 @@ public class TestUserApiDemos extends TestApiBase {
 
         System.out.println("");
         System.out.println("******\t STEP " + stepMsg + " **************");
-
-        SubscriptionData subscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(subscriptionId);
+        try {
+            SubscriptionData subscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(subscriptionId);
 
 
-        Plan currentPlan = subscription.getCurrentPlan();
-        PlanPhase currentPhase = subscription.getCurrentPhase();
-        String priceList = subscription.getCurrentPriceList();
-        System.out.println("");
-        System.out.println("\t CURRENT TIME = " + clock.getUTCNow());
-        System.out.println("");
-        System.out.println("\t CURRENT STATE = " +  subscription.getState());
-        System.out.println("\t CURRENT PRODUCT = " +  ((currentPlan == null) ? "NONE" : currentPlan.getProduct().getName()));
-        System.out.println("\t CURRENT TERM = " +  ((currentPlan == null) ? "NONE" : currentPlan.getBillingPeriod().toString()));
-        System.out.println("\t CURRENT PHASE = " +  ((currentPhase == null) ? "NONE" : currentPhase.getPhaseType()));
-        System.out.println("\t CURRENT PRICE LIST = " + ((priceList == null) ? "NONE" : priceList));
-        System.out.println("\t CURRENT \'SLUG\' = " +  ((currentPhase == null) ? "NONE" : currentPhase.getName()));
+            Plan currentPlan = subscription.getCurrentPlan();
+            PlanPhase currentPhase = subscription.getCurrentPhase();
+            String priceList = subscription.getCurrentPriceList().getName();
+
+            System.out.println("");
+            System.out.println("\t CURRENT TIME = " + clock.getUTCNow());
+            System.out.println("");
+            System.out.println("\t CURRENT STATE = " +  subscription.getState());
+            System.out.println("\t CURRENT PRODUCT = " +  ((currentPlan == null) ? "NONE" : currentPlan.getProduct().getName()));
+            System.out.println("\t CURRENT TERM = " +  ((currentPlan == null) ? "NONE" : currentPlan.getBillingPeriod().toString()));
+            System.out.println("\t CURRENT PHASE = " +  ((currentPhase == null) ? "NONE" : currentPhase.getPhaseType()));
+            System.out.println("\t CURRENT PRICE LIST = " + ((priceList == null) ? "NONE" : priceList));
+            System.out.println("\t CURRENT \'SLUG\' = " +  ((currentPhase == null) ? "NONE" : currentPhase.getName()));
 
+        } catch (EntitlementUserApiException e) {
+            System.out.println("No subscription found for id:"  + subscriptionId );
+        }
         System.out.println("");
 
     }
 
     @Test(enabled= true, groups={"stress"})
-    public void stressTest() throws EntitlementBillingApiException {
+    public void stressTest() throws Exception {
         for (int i = 0; i < 100; i++) {
             cleanupTest();
             setupTest();
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiError.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiError.java
index 11103ba..c56e0e0 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiError.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiError.java
@@ -16,28 +16,31 @@
 
 package com.ning.billing.entitlement.api.user;
 
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+
+import java.util.UUID;
+
+import javax.annotation.Nullable;
+
+import org.joda.time.DateTime;
+import org.joda.time.Interval;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
 import com.google.inject.Guice;
 import com.google.inject.Injector;
 import com.google.inject.Stage;
 import com.ning.billing.ErrorCode;
+import com.ning.billing.api.TestApiListener.NextEvent;
 import com.ning.billing.catalog.api.BillingPeriod;
 import com.ning.billing.catalog.api.Duration;
 import com.ning.billing.catalog.api.PlanPhase;
 import com.ning.billing.catalog.api.PriceListSet;
 import com.ning.billing.entitlement.api.TestApiBase;
-import com.ning.billing.entitlement.api.ApiTestListener.NextEvent;
 import com.ning.billing.entitlement.glue.MockEngineModuleMemory;
 import com.ning.billing.util.clock.DefaultClock;
-import org.joda.time.DateTime;
-import org.testng.Assert;
-import org.testng.annotations.Test;
-
-import javax.annotation.Nullable;
-import java.util.UUID;
-
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertFalse;
-import static org.testng.Assert.assertTrue;
 
 public class TestUserApiError extends TestApiBase {
 
@@ -50,6 +53,9 @@ public class TestUserApiError extends TestApiBase {
 
     @Test(enabled=true, groups={"fast"})
     public void testCreateSubscriptionBadCatalog() {
+        
+        log.info("Starting testCreateSubscriptionBadCatalog");
+        
         // WRONG PRODUCTS
         tCreateSubscriptionInternal(bundle.getId(), null, BillingPeriod.ANNUAL, PriceListSet.DEFAULT_PRICELIST_NAME, ErrorCode.CAT_NULL_PRODUCT_NAME);
         tCreateSubscriptionInternal(bundle.getId(), "Whatever", BillingPeriod.ANNUAL, PriceListSet.DEFAULT_PRICELIST_NAME, ErrorCode.CAT_NO_SUCH_PRODUCT);
@@ -66,16 +72,19 @@ public class TestUserApiError extends TestApiBase {
 
     @Test(enabled=true, groups={"fast"})
     public void testCreateSubscriptionNoBundle() {
+        log.info("Starting testCreateSubscriptionNoBundle");
         tCreateSubscriptionInternal(null, "Shotgun", BillingPeriod.ANNUAL, PriceListSet.DEFAULT_PRICELIST_NAME, ErrorCode.ENT_CREATE_NO_BUNDLE);
     }
 
     @Test(enabled=true, groups={"fast"})
     public void testCreateSubscriptionNoBP() {
+        log.info("Starting testCreateSubscriptionNoBP");
         tCreateSubscriptionInternal(bundle.getId(), "Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, ErrorCode.ENT_CREATE_NO_BP);
     }
 
     @Test(enabled=true, groups={"fast"})
     public void testCreateSubscriptionBPExists() {
+        log.info("Starting testCreateSubscriptionBPExists");
         try {
             createSubscription("Shotgun", BillingPeriod.ANNUAL, PriceListSet.DEFAULT_PRICELIST_NAME);
             tCreateSubscriptionInternal(bundle.getId(), "Shotgun", BillingPeriod.ANNUAL, PriceListSet.DEFAULT_PRICELIST_NAME, ErrorCode.ENT_CREATE_BP_EXISTS);
@@ -87,6 +96,7 @@ public class TestUserApiError extends TestApiBase {
 
     @Test(enabled=true, groups={"fast"})
     public void testRecreateSubscriptionBPNotCancelled() {
+        log.info("Starting testRecreateSubscriptionBPNotCancelled");
         try {
             SubscriptionData subscription = createSubscription("Shotgun", BillingPeriod.ANNUAL, PriceListSet.DEFAULT_PRICELIST_NAME);
             try {
@@ -103,10 +113,11 @@ public class TestUserApiError extends TestApiBase {
 
     @Test(enabled=true, groups={"fast"})
     public void testCreateSubscriptionAddOnNotAvailable() {
+        log.info("Starting testCreateSubscriptionAddOnNotAvailable");
         try {
             UUID accountId = UUID.randomUUID();
             SubscriptionBundle aoBundle = entitlementApi.createBundleForAccount(accountId, "myAOBundle", context);
-            createSubscriptionWithBundle(aoBundle.getId(), "Pistol", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME);
+            createSubscriptionWithBundle(aoBundle.getId(), "Pistol", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
             tCreateSubscriptionInternal(aoBundle.getId(), "Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, ErrorCode.ENT_CREATE_AO_NOT_AVAILABLE);
         } catch (Exception e) {
             e.printStackTrace();
@@ -116,10 +127,11 @@ public class TestUserApiError extends TestApiBase {
 
     @Test(enabled=true, groups={"fast"})
     public void testCreateSubscriptionAddOnIncluded() {
+        log.info("Starting testCreateSubscriptionAddOnIncluded");
         try {
             UUID accountId = UUID.randomUUID();
             SubscriptionBundle aoBundle = entitlementApi.createBundleForAccount(accountId, "myAOBundle", context);
-            createSubscriptionWithBundle(aoBundle.getId(), "Assault-Rifle", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME);
+            createSubscriptionWithBundle(aoBundle.getId(), "Assault-Rifle", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
             tCreateSubscriptionInternal(aoBundle.getId(), "Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, ErrorCode.ENT_CREATE_AO_ALREADY_INCLUDED);
         } catch (Exception e) {
             e.printStackTrace();
@@ -148,6 +160,7 @@ public class TestUserApiError extends TestApiBase {
 
     @Test(enabled=true, groups={"fast"})
     public void testChangeSubscriptionNonActive() {
+        log.info("Starting testChangeSubscriptionNonActive");
         try {
             Subscription subscription = createSubscription("Shotgun", BillingPeriod.ANNUAL, PriceListSet.DEFAULT_PRICELIST_NAME);
 
@@ -172,6 +185,7 @@ public class TestUserApiError extends TestApiBase {
 
     @Test(enabled=true, groups={"fast"})
     public void testChangeSubscriptionFutureCancelled() {
+        log.info("Starting testChangeSubscriptionFutureCancelled");
         try {
             Subscription subscription = createSubscription("Shotgun", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME);
             PlanPhase trialPhase = subscription.getCurrentPhase();
@@ -179,8 +193,9 @@ public class TestUserApiError extends TestApiBase {
             // MOVE TO NEXT PHASE
             PlanPhase currentPhase = subscription.getCurrentPhase();
             testListener.pushExpectedEvent(NextEvent.PHASE);
-            clock.setDeltaFromReality(currentPhase.getDuration(), DAY_IN_MS);
-            assertTrue(testListener.isCompleted(3000));
+            Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(31));
+            clock.addDeltaFromReality(it.toDurationMillis());
+            assertTrue(testListener.isCompleted(5000));
 
 
             // SET CTD TO CANCEL IN FUTURE
@@ -202,6 +217,8 @@ public class TestUserApiError extends TestApiBase {
                     assertFalse(true);
                 }
             }
+            
+            assertListenerStatus();
         } catch (Exception e) {
             e.printStackTrace();
             Assert.assertFalse(true);
@@ -211,10 +228,12 @@ public class TestUserApiError extends TestApiBase {
 
     @Test(enabled=false, groups={"fast"})
     public void testCancelBadState() {
+        log.info("Starting testCancelBadState");
     }
 
     @Test(enabled=true, groups={"fast"})
     public void testUncancelBadState() {
+        log.info("Starting testUncancelBadState");
         try {
             Subscription subscription = createSubscription("Shotgun", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME);
 
@@ -228,7 +247,7 @@ public class TestUserApiError extends TestApiBase {
                     assertFalse(true);
                 }
             }
-
+            assertListenerStatus();
         } catch (Exception e) {
             e.printStackTrace();
             Assert.assertFalse(true);
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiRecreate.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiRecreate.java
index 7dbc802..389ac5f 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiRecreate.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiRecreate.java
@@ -25,11 +25,10 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.testng.Assert;
 
-import com.google.inject.Injector;
+import com.ning.billing.api.TestApiListener.NextEvent;
 import com.ning.billing.catalog.api.BillingPeriod;
 import com.ning.billing.catalog.api.PriceListSet;
 import com.ning.billing.entitlement.api.TestApiBase;
-import com.ning.billing.entitlement.api.ApiTestListener.NextEvent;
 
 public abstract class TestUserApiRecreate extends TestApiBase {
 
@@ -37,9 +36,10 @@ public abstract class TestUserApiRecreate extends TestApiBase {
 
 
     protected void testRecreateWithBPCanceledThroughSubscription() {
-        log.info("Starting testRecreateWithBPCanceled");
+        log.info("Starting testRecreateWithBPCanceledThroughSubscription");
         try {
             testCreateAndRecreate(false);
+            assertListenerStatus();
         } catch (EntitlementUserApiException e) {
             log.error("Unexpected exception",e);
             Assert.fail(e.getMessage());
@@ -47,9 +47,10 @@ public abstract class TestUserApiRecreate extends TestApiBase {
     }
 
     protected void testCreateWithBPCanceledFromUserApi() {
-        log.info("Starting testCreateWithBPCanceled");
+        log.info("Starting testCreateWithBPCanceledFromUserApi");
         try {
             testCreateAndRecreate(true);
+            assertListenerStatus();
         } catch (EntitlementUserApiException e) {
             log.error("Unexpected exception",e);
             Assert.fail(e.getMessage());
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiScenarios.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiScenarios.java
index 1ece406..7f874de 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiScenarios.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiScenarios.java
@@ -16,24 +16,27 @@
 
 package com.ning.billing.entitlement.api.user;
 
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+
+import org.joda.time.DateTime;
+import org.joda.time.Interval;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
 import com.google.inject.Guice;
 import com.google.inject.Injector;
 import com.google.inject.Stage;
+import com.ning.billing.api.TestApiListener.NextEvent;
 import com.ning.billing.catalog.api.BillingPeriod;
 import com.ning.billing.catalog.api.Duration;
 import com.ning.billing.catalog.api.PhaseType;
 import com.ning.billing.catalog.api.PlanPhase;
-
-import com.ning.billing.entitlement.api.ApiTestListener.NextEvent;
 import com.ning.billing.entitlement.api.TestApiBase;
 import com.ning.billing.entitlement.api.billing.EntitlementBillingApiException;
 import com.ning.billing.entitlement.glue.MockEngineModuleSql;
 import com.ning.billing.util.clock.DefaultClock;
-import org.joda.time.DateTime;
-import org.testng.Assert;
-import org.testng.annotations.Test;
-
-import static org.testng.Assert.*;
 
 public class TestUserApiScenarios extends TestApiBase {
 
@@ -42,7 +45,7 @@ public class TestUserApiScenarios extends TestApiBase {
         return Guice.createInjector(Stage.DEVELOPMENT, new MockEngineModuleSql());
     }
 
-    @Test(enabled=true)
+    @Test(groups={"slow"}, enabled=true)
     public void testChangeIMMCancelUncancelChangeEOT() throws EntitlementBillingApiException {
 
         log.info("Starting testChangeIMMCancelUncancelChangeEOT");
@@ -54,12 +57,13 @@ public class TestUserApiScenarios extends TestApiBase {
 
             testListener.pushExpectedEvent(NextEvent.CHANGE);
             subscription.changePlan("Pistol", BillingPeriod.ANNUAL, "gunclubDiscount", clock.getUTCNow(), context);
-            testListener.isCompleted(3000);
+            testListener.isCompleted(5000);
 
             // MOVE TO NEXT PHASE
             testListener.pushExpectedEvent(NextEvent.PHASE);
-            clock.setDeltaFromReality(trialPhase.getDuration(), DAY_IN_MS);
-            assertTrue(testListener.isCompleted(2000));
+            Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(31));
+            clock.addDeltaFromReality(it.toDurationMillis());
+            assertTrue(testListener.isCompleted(5000));
 
             // SET CTD
             Duration ctd = getDurationMonth(1);
@@ -69,22 +73,28 @@ public class TestUserApiScenarios extends TestApiBase {
             subscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(subscription.getId());
 
             // CANCEL EOT
+            testListener.setNonExpectedMode();
             testListener.pushExpectedEvent(NextEvent.CANCEL);
             subscription.cancel(clock.getUTCNow(), false, context);
-            assertFalse(testListener.isCompleted(2000));
+            assertFalse(testListener.isCompleted(5000));
             testListener.reset();
 
             // UNCANCEL
             subscription.uncancel(context);
 
             // CHANGE EOT
+            testListener.setNonExpectedMode();            
             testListener.pushExpectedEvent(NextEvent.CHANGE);
             subscription.changePlan("Pistol", BillingPeriod.MONTHLY, "gunclubDiscount", clock.getUTCNow(), context);
-            assertFalse(testListener.isCompleted(2000));
-
-            clock.addDeltaFromReality(ctd);
-            assertTrue(testListener.isCompleted(3000));
+            assertFalse(testListener.isCompleted(5000));
+            testListener.reset();
+            
+            testListener.pushExpectedEvent(NextEvent.CHANGE);
+            it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusMonths(1));
+            clock.addDeltaFromReality(it.toDurationMillis());
+            assertTrue(testListener.isCompleted(5000));
 
+            assertListenerStatus();
 
         } catch (EntitlementUserApiException e) {
             Assert.fail(e.getMessage());
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserCustomFieldsSql.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserCustomFieldsSql.java
index bbfd42a..c208521 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserCustomFieldsSql.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserCustomFieldsSql.java
@@ -16,16 +16,11 @@
 
 package com.ning.billing.entitlement.api.user;
 
-import java.util.List;
-
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertNotNull;
 
-import com.ning.billing.util.callcontext.CallContext;
-import com.ning.billing.util.callcontext.CallOrigin;
-import com.ning.billing.util.callcontext.UserType;
-import com.ning.billing.util.callcontext.DefaultCallContextFactory;
-import com.ning.billing.util.clock.DefaultClock;
+import java.util.List;
+
 import org.joda.time.DateTime;
 import org.testng.Assert;
 import org.testng.annotations.Test;
@@ -33,11 +28,16 @@ import org.testng.annotations.Test;
 import com.google.inject.Guice;
 import com.google.inject.Injector;
 import com.google.inject.Stage;
+import com.ning.billing.api.TestApiListener.NextEvent;
 import com.ning.billing.catalog.api.BillingPeriod;
 import com.ning.billing.catalog.api.PriceListSet;
 import com.ning.billing.entitlement.api.TestApiBase;
-import com.ning.billing.entitlement.api.ApiTestListener.NextEvent;
 import com.ning.billing.entitlement.glue.MockEngineModuleSql;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.callcontext.CallOrigin;
+import com.ning.billing.util.callcontext.DefaultCallContextFactory;
+import com.ning.billing.util.callcontext.UserType;
+import com.ning.billing.util.clock.DefaultClock;
 import com.ning.billing.util.customfield.CustomField;
 
 
@@ -51,7 +51,7 @@ public class TestUserCustomFieldsSql extends TestApiBase {
     }
 
     @Test(enabled=false, groups={"slow"})
-    public void stress() {
+    public void stress() throws Exception {
         cleanupTest();
         for (int i = 0; i < 20; i++) {
             setupTest();
@@ -66,7 +66,7 @@ public class TestUserCustomFieldsSql extends TestApiBase {
 
     @Test(enabled=true, groups={"slow"})
     public void testOverwriteCustomFields() {
-        log.info("Starting testCreateWithRequestedDate");
+        log.info("Starting testOverwriteCustomFields");
         try {
 
             DateTime init = clock.getUTCNow();
@@ -122,7 +122,7 @@ public class TestUserCustomFieldsSql extends TestApiBase {
 
     @Test(enabled=true, groups={"slow"})
     public void testBasicCustomFields() {
-        log.info("Starting testCreateWithRequestedDate");
+        log.info("Starting testBasicCustomFields");
         try {
 
             DateTime init = clock.getUTCNow();
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/engine/core/TestEntitlementNotificationKey.java b/entitlement/src/test/java/com/ning/billing/entitlement/engine/core/TestEntitlementNotificationKey.java
new file mode 100644
index 0000000..dbd7098
--- /dev/null
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/engine/core/TestEntitlementNotificationKey.java
@@ -0,0 +1,45 @@
+/* 
+ * Copyright 2010-2011 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.entitlement.engine.core;
+
+import java.util.UUID;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+public class TestEntitlementNotificationKey {
+
+	@Test(groups="fast")
+	public void testKeyWithSeqId() {
+		UUID id = UUID.randomUUID();
+		int seq = 4;
+		EntitlementNotificationKey input = new EntitlementNotificationKey(id, seq);
+		Assert.assertEquals(id.toString() + ":" + seq, input.toString());
+		EntitlementNotificationKey output = new EntitlementNotificationKey(input.toString());
+		Assert.assertEquals(output, input);
+	}
+
+	@Test(groups="fast")
+	public void testKeyWithoutSeqId() {
+		UUID id = UUID.randomUUID();
+		int seq = 0;
+		EntitlementNotificationKey input = new EntitlementNotificationKey(id, seq);
+		Assert.assertEquals(input.toString(), id.toString());
+		EntitlementNotificationKey output = new EntitlementNotificationKey(input.toString());
+		Assert.assertEquals(output, input);
+	}
+
+}
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/MockEntitlementDaoMemory.java b/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/MockEntitlementDaoMemory.java
index 94836a8..5eb760c 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/MockEntitlementDaoMemory.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/MockEntitlementDaoMemory.java
@@ -21,10 +21,10 @@ import java.util.Collections;
 import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Map;
 import java.util.TreeSet;
 import java.util.UUID;
 
-import com.ning.billing.util.callcontext.CallContext;
 import org.apache.commons.lang.NotImplementedException;
 import org.joda.time.DateTime;
 import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
@@ -36,20 +36,22 @@ import com.ning.billing.catalog.api.CatalogService;
 import com.ning.billing.catalog.api.ProductCategory;
 import com.ning.billing.catalog.api.TimeUnit;
 import com.ning.billing.config.EntitlementConfig;
+import com.ning.billing.entitlement.api.SubscriptionFactory;
 import com.ning.billing.entitlement.api.migration.AccountMigrationData;
 import com.ning.billing.entitlement.api.migration.AccountMigrationData.BundleMigrationData;
 import com.ning.billing.entitlement.api.migration.AccountMigrationData.SubscriptionMigrationData;
+import com.ning.billing.entitlement.api.timeline.SubscriptionDataRepair;
 import com.ning.billing.entitlement.api.user.Subscription;
 import com.ning.billing.entitlement.api.user.SubscriptionBundle;
 import com.ning.billing.entitlement.api.user.SubscriptionBundleData;
 import com.ning.billing.entitlement.api.user.SubscriptionData;
-import com.ning.billing.entitlement.api.user.SubscriptionFactory;
-import com.ning.billing.entitlement.api.user.SubscriptionFactory.SubscriptionBuilder;
+import com.ning.billing.entitlement.api.user.DefaultSubscriptionFactory.SubscriptionBuilder;
 import com.ning.billing.entitlement.engine.core.Engine;
 import com.ning.billing.entitlement.events.EntitlementEvent;
 import com.ning.billing.entitlement.events.EntitlementEvent.EventType;
 import com.ning.billing.entitlement.events.user.ApiEvent;
 import com.ning.billing.entitlement.events.user.ApiEventType;
+import com.ning.billing.util.callcontext.CallContext;
 import com.ning.billing.util.clock.Clock;
 import com.ning.billing.util.notificationq.NotificationKey;
 import com.ning.billing.util.notificationq.NotificationQueue;
@@ -156,7 +158,6 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
     @Override
     public void createSubscription(final SubscriptionData subscription, final List<EntitlementEvent> initialEvents,
                                    final CallContext context) {
-
         synchronized(events) {
             events.addAll(initialEvents);
             for (final EntitlementEvent cur : initialEvents) {
@@ -263,7 +264,7 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
     }
 
     @Override
-    public void updateSubscription(final SubscriptionData subscription, final CallContext context) {
+    public void updateChargedThroughDate(final SubscriptionData subscription, final CallContext context) {
 
         boolean found = false;
         Iterator<Subscription> it = subscriptions.iterator();
@@ -282,8 +283,8 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
 
     @Override
     public void cancelSubscription(final UUID subscriptionId, final EntitlementEvent cancelEvent,
-                                   final CallContext context) {
-        synchronized (cancelEvent) {
+                                   final CallContext context, final int seqId) {
+        synchronized(events) {
             cancelNextPhaseEvent(subscriptionId);
             insertEvent(cancelEvent);
         }
@@ -446,4 +447,15 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
     public void saveCustomFields(SubscriptionData subscription, CallContext context) {
         throw new NotImplementedException();
     }
+
+    @Override
+    public Map<UUID, List<EntitlementEvent>> getEventsForBundle(UUID bundleId) {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public void repair(UUID accountId, UUID bundleId, List<SubscriptionDataRepair> inRepair,
+            CallContext context) {
+    }
 }
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/MockEntitlementDaoSql.java b/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/MockEntitlementDaoSql.java
index 4733f4a..d52c55a 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/MockEntitlementDaoSql.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/MockEntitlementDaoSql.java
@@ -16,8 +16,9 @@
 
 package com.ning.billing.entitlement.engine.dao;
 
+
+import com.ning.billing.util.bus.Bus;
 import com.ning.billing.util.customfield.dao.CustomFieldDao;
-import com.ning.billing.util.tag.dao.TagDao;
 import org.skife.jdbi.v2.IDBI;
 import org.skife.jdbi.v2.Transaction;
 import org.skife.jdbi.v2.TransactionStatus;
@@ -26,19 +27,19 @@ import org.skife.jdbi.v2.sqlobject.mixins.CloseMe;
 import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
 
 import com.google.inject.Inject;
-import com.ning.billing.entitlement.api.user.SubscriptionFactory;
 import com.ning.billing.entitlement.engine.addon.AddonUtils;
 import com.ning.billing.util.clock.Clock;
+
 import com.ning.billing.util.notificationq.NotificationQueueService;
 
-public class MockEntitlementDaoSql extends EntitlementSqlDao implements MockEntitlementDao {
+public class MockEntitlementDaoSql extends AuditedEntitlementDao implements MockEntitlementDao {
 
     private final ResetSqlDao resetDao;
 
     @Inject
     public MockEntitlementDaoSql(IDBI dbi, Clock clock, AddonUtils addonUtils, NotificationQueueService notificationQueueService,
-                                 CustomFieldDao customFieldDao) {
-        super(dbi, clock, addonUtils, notificationQueueService, customFieldDao);
+                                 CustomFieldDao customFieldDao, final Bus eventBus) {
+        super(dbi, clock, addonUtils, notificationQueueService, customFieldDao, eventBus);
         this.resetDao = dbi.onDemand(ResetSqlDao.class);
     }
 
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/glue/MockEngineModule.java b/entitlement/src/test/java/com/ning/billing/entitlement/glue/MockEngineModule.java
index acb1a2a..31e0da3 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/glue/MockEngineModule.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/glue/MockEngineModule.java
@@ -23,12 +23,12 @@ import com.ning.billing.util.clock.MockClockModule;
 import com.ning.billing.util.glue.BusModule;
 import com.ning.billing.util.glue.CallContextModule;
 
-public class MockEngineModule extends EntitlementModule {
+public class MockEngineModule extends DefaultEntitlementModule {
 
+   
     @Override
     protected void configure() {
         super.configure();
-        install(new BusModule());
         install(new CatalogModule());
         bind(AccountUserApi.class).toInstance(BrainDeadProxyFactory.createBrainDeadProxyFor(AccountUserApi.class));
         install(new MockClockModule());
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/glue/MockEngineModuleMemory.java b/entitlement/src/test/java/com/ning/billing/entitlement/glue/MockEngineModuleMemory.java
index 8b78c2d..148b62d 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/glue/MockEngineModuleMemory.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/glue/MockEngineModuleMemory.java
@@ -17,8 +17,13 @@
 package com.ning.billing.entitlement.glue;
 
 
+import com.google.inject.name.Names;
+import com.ning.billing.entitlement.api.timeline.RepairEntitlementLifecycleDao;
 import com.ning.billing.entitlement.engine.dao.EntitlementDao;
 import com.ning.billing.entitlement.engine.dao.MockEntitlementDaoMemory;
+import com.ning.billing.entitlement.engine.dao.RepairEntitlementDao;
+import com.ning.billing.util.glue.BusModule;
+import com.ning.billing.util.glue.BusModule.BusType;
 import com.ning.billing.util.notificationq.MockNotificationQueueService;
 import com.ning.billing.util.notificationq.NotificationQueueService;
 
@@ -27,6 +32,9 @@ public class MockEngineModuleMemory extends MockEngineModule {
     @Override
     protected void installEntitlementDao() {
         bind(EntitlementDao.class).to(MockEntitlementDaoMemory.class).asEagerSingleton();
+        bind(EntitlementDao.class).annotatedWith(Names.named(REPAIR_NAMED)).to(RepairEntitlementDao.class);
+        bind(RepairEntitlementLifecycleDao.class).annotatedWith(Names.named(REPAIR_NAMED)).to(RepairEntitlementDao.class);
+        bind(RepairEntitlementDao.class).asEagerSingleton();
     }
 
     private void installNotificationQueue() {
@@ -36,6 +44,7 @@ public class MockEngineModuleMemory extends MockEngineModule {
     @Override
     protected void configure() {
         super.configure();
+        install(new BusModule(BusType.MEMORY));
         installNotificationQueue();
     }
 }
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/glue/MockEngineModuleSql.java b/entitlement/src/test/java/com/ning/billing/entitlement/glue/MockEngineModuleSql.java
index f774e58..e1b3742 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/glue/MockEngineModuleSql.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/glue/MockEngineModuleSql.java
@@ -16,18 +16,26 @@
 
 package com.ning.billing.entitlement.glue;
 
+
+import com.google.inject.name.Names;
+
+import org.skife.config.ConfigurationObjectFactory;
+import org.skife.jdbi.v2.IDBI;
+
+
 import com.ning.billing.dbi.DBIProvider;
 import com.ning.billing.dbi.DbiConfig;
 import com.ning.billing.dbi.MysqlTestingHelper;
+import com.ning.billing.entitlement.api.timeline.RepairEntitlementLifecycleDao;
 import com.ning.billing.entitlement.engine.dao.EntitlementDao;
 import com.ning.billing.entitlement.engine.dao.MockEntitlementDaoSql;
-import com.ning.billing.util.clock.Clock;
-import com.ning.billing.util.clock.ClockMock;
+
+import com.ning.billing.entitlement.engine.dao.RepairEntitlementDao;
+
+import com.ning.billing.util.glue.BusModule;
 import com.ning.billing.util.glue.FieldStoreModule;
 import com.ning.billing.util.glue.NotificationQueueModule;
-
-import org.skife.config.ConfigurationObjectFactory;
-import org.skife.jdbi.v2.IDBI;
+import com.ning.billing.util.glue.BusModule.BusType;
 
 public class MockEngineModuleSql extends MockEngineModule {
 
@@ -35,7 +43,11 @@ public class MockEngineModuleSql extends MockEngineModule {
     @Override
     protected void installEntitlementDao() {
         bind(EntitlementDao.class).to(MockEntitlementDaoSql.class).asEagerSingleton();
+        bind(EntitlementDao.class).annotatedWith(Names.named(REPAIR_NAMED)).to(RepairEntitlementDao.class);
+        bind(RepairEntitlementLifecycleDao.class).annotatedWith(Names.named(REPAIR_NAMED)).to(RepairEntitlementDao.class);
+        bind(RepairEntitlementDao.class).asEagerSingleton();
     }
+    
 
     protected void installDBI() {
         final MysqlTestingHelper helper = new MysqlTestingHelper();
@@ -55,6 +67,7 @@ public class MockEngineModuleSql extends MockEngineModule {
         installDBI();
         install(new NotificationQueueModule());
         install(new FieldStoreModule());
+        install(new BusModule(BusType.PERSISTENT));
         super.configure();
     }
 }
diff --git a/entitlement/src/test/resources/entitlement.properties b/entitlement/src/test/resources/entitlement.properties
index af1c3fc..58f4f95 100644
--- a/entitlement/src/test/resources/entitlement.properties
+++ b/entitlement/src/test/resources/entitlement.properties
@@ -1,6 +1,8 @@
 killbill.catalog.uri=file:src/test/resources/testInput.xml
 killbill.entitlement.dao.claim.time=60000
 killbill.entitlement.dao.ready.max=1
-killbill.entitlement.engine.notifications.sleep=500
+killbill.entitlement.engine.notifications.sleep=100
+killbill.billing.util.persistent.bus.sleep=100
+killbill.billing.util.persistent.bus.nbThreads=1
 user.timezone=UTC
 
diff --git a/entitlement/src/test/resources/testInput.xml b/entitlement/src/test/resources/testInput.xml
index 8a97d57..bdc73db 100644
--- a/entitlement/src/test/resources/testInput.xml
+++ b/entitlement/src/test/resources/testInput.xml
@@ -1,41 +1,21 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<!--
-  ~ Copyright 2010-2011 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.
-  -->
+<!-- ~ Copyright 2010-2011 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. -->
 
-<!-- 
-Use cases covered so far:
-	Tiered Product (Pistol/Shotgun/Assault-Rifle)
-	Multiple changeEvent plan policies
-	Multiple PlanAlignment (see below, trial add-on alignments and rescue discount package)
-	Product transition rules
-	Add on (Scopes, Hoster)
-	Multi-pack addon (Extra-Ammo)
-	Addon Trial aligned to base plan (holster-monthly-regular)
-	Addon Trial aligned to creation (holster-monthly-special)
-	Rescue discount package (assault-rifle-annual-rescue)
-	Plan phase with a reccurring and a one off (refurbish-maintenance)
-	Phan with more than 2 phase (gunclub discount plans)
-		
-Use Cases to do:
-	Tiered Add On
-	Riskfree period
-	
-
-
- -->
+<!-- Use cases covered so far: Tiered Product (Pistol/Shotgun/Assault-Rifle) 
+	Multiple changeEvent plan policies Multiple PlanAlignment (see below, trial 
+	add-on alignments and rescue discount package) Product transition rules Add 
+	on (Scopes, Hoster) Multi-pack addon (Extra-Ammo) Addon Trial aligned to 
+	base plan (holster-monthly-regular) Addon Trial aligned to creation (holster-monthly-special) 
+	Rescue discount package (assault-rifle-annual-rescue) Plan phase with a reccurring 
+	and a one off (refurbish-maintenance) Phan with more than 2 phase (gunclub 
+	discount plans) Use Cases to do: Tiered Add On Riskfree period -->
 <catalog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 	xsi:noNamespaceSchemaLocation="CatalogSchema.xsd ">
 
@@ -47,21 +27,21 @@ Use Cases to do:
 		<currency>EUR</currency>
 		<currency>GBP</currency>
 	</currencies>
-	
+
 	<products>
 		<product name="Pistol">
 			<category>BASE</category>
 		</product>
 		<product name="Shotgun">
 			<category>BASE</category>
-            <available>
-                <addonProduct>Telescopic-Scope</addonProduct>
-                <addonProduct>Laser-Scope</addonProduct>
-            </available>
+			<available>
+				<addonProduct>Telescopic-Scope</addonProduct>
+				<addonProduct>Laser-Scope</addonProduct>
+			</available>
 		</product>
 		<product name="Assault-Rifle">
 			<category>BASE</category>
-			<included> 
+			<included>
 				<addonProduct>Telescopic-Scope</addonProduct>
 			</included>
 			<available>
@@ -84,37 +64,37 @@ Use Cases to do:
 			<category>ADD_ON</category>
 		</product>
 	</products>
-	 
+
 	<rules>
 		<changePolicy>
-			<changePolicyCase> 
+			<changePolicyCase>
 				<phaseType>TRIAL</phaseType>
 				<policy>IMMEDIATE</policy>
 			</changePolicyCase>
-            <changePolicyCase> 
-                <toProduct>Assault-Rifle</toProduct>
-                <policy>IMMEDIATE</policy>
-            </changePolicyCase>
-            <changePolicyCase> 
-                <fromProduct>Pistol</fromProduct>            
-                <toProduct>Shotgun</toProduct>
-                <policy>IMMEDIATE</policy>
-            </changePolicyCase>
-			<changePolicyCase> 
+			<changePolicyCase>
+				<toProduct>Assault-Rifle</toProduct>
+				<policy>IMMEDIATE</policy>
+			</changePolicyCase>
+			<changePolicyCase>
+				<fromProduct>Pistol</fromProduct>
+				<toProduct>Shotgun</toProduct>
+				<policy>IMMEDIATE</policy>
+			</changePolicyCase>
+			<changePolicyCase>
 				<toPriceList>rescue</toPriceList>
 				<policy>END_OF_TERM</policy>
 			</changePolicyCase>
-			<changePolicyCase> 
+			<changePolicyCase>
 				<fromBillingPeriod>MONTHLY</fromBillingPeriod>
 				<toBillingPeriod>ANNUAL</toBillingPeriod>
 				<policy>IMMEDIATE</policy>
 			</changePolicyCase>
-			<changePolicyCase> 
+			<changePolicyCase>
 				<fromBillingPeriod>ANNUAL</fromBillingPeriod>
 				<toBillingPeriod>MONTHLY</toBillingPeriod>
 				<policy>END_OF_TERM</policy>
 			</changePolicyCase>
-			<changePolicyCase> 
+			<changePolicyCase>
 				<policy>END_OF_TERM</policy>
 			</changePolicyCase>
 		</changePolicy>
@@ -128,27 +108,27 @@ Use Cases to do:
 				<toPriceList>rescue</toPriceList>
 				<alignment>CHANGE_OF_PRICELIST</alignment>
 			</changeAlignmentCase>
-            <changeAlignmentCase>
-                <alignment>START_OF_SUBSCRIPTION</alignment>
-            </changeAlignmentCase>
+			<changeAlignmentCase>
+				<alignment>START_OF_SUBSCRIPTION</alignment>
+			</changeAlignmentCase>
 		</changeAlignment>
 		<cancelPolicy>
 			<cancelPolicyCase>
 				<phaseType>TRIAL</phaseType>
 				<policy>IMMEDIATE</policy>
 			</cancelPolicyCase>
-            <cancelPolicyCase>
-                <policy>END_OF_TERM</policy>
-            </cancelPolicyCase>
+			<cancelPolicyCase>
+				<policy>END_OF_TERM</policy>
+			</cancelPolicyCase>
 		</cancelPolicy>
 		<createAlignment>
-		    <createAlignmentCase>
-		        <product>Laser-Scope</product>
-                <alignment>START_OF_SUBSCRIPTION</alignment>
-            </createAlignmentCase>
-            <createAlignmentCase>
-                <alignment>START_OF_BUNDLE</alignment>
-            </createAlignmentCase>
+			<createAlignmentCase>
+				<product>Laser-Scope</product>
+				<alignment>START_OF_SUBSCRIPTION</alignment>
+			</createAlignmentCase>
+			<createAlignmentCase>
+				<alignment>START_OF_BUNDLE</alignment>
+			</createAlignmentCase>
 		</createAlignment>
 		<billingAlignment>
 			<billingAlignmentCase>
@@ -191,9 +171,18 @@ Use Cases to do:
 				</duration>
 				<billingPeriod>MONTHLY</billingPeriod>
 				<recurringPrice>
-					<price><currency>GBP</currency><value>29.95</value></price>
-					<price><currency>EUR</currency><value>29.95</value></price> 
-					<price><currency>USD</currency><value>29.95</value></price>								
+					<price>
+						<currency>GBP</currency>
+						<value>29.95</value>
+					</price>
+					<price>
+						<currency>EUR</currency>
+						<value>29.95</value>
+					</price>
+					<price>
+						<currency>USD</currency>
+						<value>29.95</value>
+					</price>
 				</recurringPrice>
 			</finalPhase>
 		</plan>
@@ -207,7 +196,7 @@ Use Cases to do:
 					</duration>
 					<billingPeriod>NO_BILLING_PERIOD</billingPeriod>
 					<fixedPrice></fixedPrice>
-				    <!-- no price implies $0 -->
+					<!-- no price implies $0 -->
 				</phase>
 			</initialPhases>
 			<finalPhase type="EVERGREEN">
@@ -217,9 +206,18 @@ Use Cases to do:
 				</duration>
 				<billingPeriod>MONTHLY</billingPeriod>
 				<recurringPrice>
-					<price><currency>USD</currency><value>249.95</value></price>								
-					<price><currency>EUR</currency><value>149.95</value></price>
-					<price><currency>GBP</currency><value>169.95</value></price>
+					<price>
+						<currency>USD</currency>
+						<value>249.95</value>
+					</price>
+					<price>
+						<currency>EUR</currency>
+						<value>149.95</value>
+					</price>
+					<price>
+						<currency>GBP</currency>
+						<value>169.95</value>
+					</price>
 				</recurringPrice>
 			</finalPhase>
 		</plan>
@@ -242,9 +240,18 @@ Use Cases to do:
 				</duration>
 				<billingPeriod>MONTHLY</billingPeriod>
 				<recurringPrice>
-					<price><currency>USD</currency><value>599.95</value></price>								
-					<price><currency>EUR</currency><value>349.95</value></price>
-					<price><currency>GBP</currency><value>399.95</value></price>
+					<price>
+						<currency>USD</currency>
+						<value>599.95</value>
+					</price>
+					<price>
+						<currency>EUR</currency>
+						<value>349.95</value>
+					</price>
+					<price>
+						<currency>GBP</currency>
+						<value>399.95</value>
+					</price>
 				</recurringPrice>
 			</finalPhase>
 		</plan>
@@ -267,9 +274,18 @@ Use Cases to do:
 				</duration>
 				<billingPeriod>ANNUAL</billingPeriod>
 				<recurringPrice>
-					<price><currency>USD</currency><value>199.95</value></price>								
-					<price><currency>EUR</currency><value>199.95</value></price>
-					<price><currency>GBP</currency><value>199.95</value></price>
+					<price>
+						<currency>USD</currency>
+						<value>199.95</value>
+					</price>
+					<price>
+						<currency>EUR</currency>
+						<value>199.95</value>
+					</price>
+					<price>
+						<currency>GBP</currency>
+						<value>199.95</value>
+					</price>
 				</recurringPrice>
 			</finalPhase>
 		</plan>
@@ -292,9 +308,18 @@ Use Cases to do:
 				</duration>
 				<billingPeriod>ANNUAL</billingPeriod>
 				<recurringPrice>
-					<price><currency>USD</currency><value>2399.95</value></price>								
-					<price><currency>EUR</currency><value>1499.95</value></price>
-					<price><currency>GBP</currency><value>1699.95</value></price>
+					<price>
+						<currency>USD</currency>
+						<value>2399.95</value>
+					</price>
+					<price>
+						<currency>EUR</currency>
+						<value>1499.95</value>
+					</price>
+					<price>
+						<currency>GBP</currency>
+						<value>1699.95</value>
+					</price>
 				</recurringPrice>
 			</finalPhase>
 		</plan>
@@ -317,9 +342,18 @@ Use Cases to do:
 				</duration>
 				<billingPeriod>ANNUAL</billingPeriod>
 				<recurringPrice>
-					<price><currency>USD</currency><value>5999.95</value></price>								
-					<price><currency>EUR</currency><value>3499.95</value></price>
-					<price><currency>GBP</currency><value>3999.95</value></price>
+					<price>
+						<currency>USD</currency>
+						<value>5999.95</value>
+					</price>
+					<price>
+						<currency>EUR</currency>
+						<value>3499.95</value>
+					</price>
+					<price>
+						<currency>GBP</currency>
+						<value>3999.95</value>
+					</price>
 				</recurringPrice>
 			</finalPhase>
 		</plan>
@@ -342,9 +376,18 @@ Use Cases to do:
 					</duration>
 					<billingPeriod>MONTHLY</billingPeriod>
 					<recurringPrice>
-						<price><currency>USD</currency><value>9.95</value></price>								
-						<price><currency>EUR</currency><value>9.95</value></price>
-						<price><currency>GBP</currency><value>9.95</value></price>
+						<price>
+							<currency>USD</currency>
+							<value>9.95</value>
+						</price>
+						<price>
+							<currency>EUR</currency>
+							<value>9.95</value>
+						</price>
+						<price>
+							<currency>GBP</currency>
+							<value>9.95</value>
+						</price>
 					</recurringPrice>
 				</phase>
 			</initialPhases>
@@ -354,9 +397,18 @@ Use Cases to do:
 				</duration>
 				<billingPeriod>ANNUAL</billingPeriod>
 				<recurringPrice>
-					<price><currency>USD</currency><value>199.95</value></price>								
-					<price><currency>EUR</currency><value>199.95</value></price>
-					<price><currency>GBP</currency><value>199.95</value></price>
+					<price>
+						<currency>USD</currency>
+						<value>199.95</value>
+					</price>
+					<price>
+						<currency>EUR</currency>
+						<value>199.95</value>
+					</price>
+					<price>
+						<currency>GBP</currency>
+						<value>199.95</value>
+					</price>
 				</recurringPrice>
 			</finalPhase>
 		</plan>
@@ -379,9 +431,18 @@ Use Cases to do:
 					</duration>
 					<billingPeriod>MONTHLY</billingPeriod>
 					<recurringPrice>
-						<price><currency>USD</currency><value>19.95</value></price>								
-						<price><currency>EUR</currency><value>49.95</value></price>
-						<price><currency>GBP</currency><value>69.95</value></price>
+						<price>
+							<currency>USD</currency>
+							<value>19.95</value>
+						</price>
+						<price>
+							<currency>EUR</currency>
+							<value>49.95</value>
+						</price>
+						<price>
+							<currency>GBP</currency>
+							<value>69.95</value>
+						</price>
 					</recurringPrice>
 				</phase>
 			</initialPhases>
@@ -391,9 +452,18 @@ Use Cases to do:
 				</duration>
 				<billingPeriod>ANNUAL</billingPeriod>
 				<recurringPrice>
-					<price><currency>USD</currency><value>2399.95</value></price>								
-					<price><currency>EUR</currency><value>1499.95</value></price>
-					<price><currency>GBP</currency><value>1699.95</value></price>
+					<price>
+						<currency>USD</currency>
+						<value>2399.95</value>
+					</price>
+					<price>
+						<currency>EUR</currency>
+						<value>1499.95</value>
+					</price>
+					<price>
+						<currency>GBP</currency>
+						<value>1699.95</value>
+					</price>
 				</recurringPrice>
 			</finalPhase>
 		</plan>
@@ -416,10 +486,19 @@ Use Cases to do:
 					</duration>
 					<billingPeriod>MONTHLY</billingPeriod>
 					<recurringPrice>
-						<price><currency>USD</currency><value>99.95</value></price>								
-						<price><currency>EUR</currency><value>99.95</value></price>
-						<price><currency>GBP</currency><value>99.95</value></price>
-						</recurringPrice>
+						<price>
+							<currency>USD</currency>
+							<value>99.95</value>
+						</price>
+						<price>
+							<currency>EUR</currency>
+							<value>99.95</value>
+						</price>
+						<price>
+							<currency>GBP</currency>
+							<value>99.95</value>
+						</price>
+					</recurringPrice>
 				</phase>
 			</initialPhases>
 			<finalPhase type="EVERGREEN">
@@ -428,65 +507,110 @@ Use Cases to do:
 				</duration>
 				<billingPeriod>ANNUAL</billingPeriod>
 				<recurringPrice>
-					<price><currency>USD</currency><value>5999.95</value></price>								
-					<price><currency>EUR</currency><value>3499.95</value></price>
-					<price><currency>GBP</currency><value>3999.95</value></price>
+					<price>
+						<currency>USD</currency>
+						<value>5999.95</value>
+					</price>
+					<price>
+						<currency>EUR</currency>
+						<value>3499.95</value>
+					</price>
+					<price>
+						<currency>GBP</currency>
+						<value>3999.95</value>
+					</price>
 				</recurringPrice>
 			</finalPhase>
 		</plan>
 		<plan name="laser-scope-monthly">
-		<product>Laser-Scope</product>
-           <initialPhases>
-              <phase type="DISCOUNT">
-                 <duration>
-                    <unit>MONTHS</unit>
-                    <number>1</number>
-                  </duration>
-                 <billingPeriod>MONTHLY</billingPeriod>
-                    <recurringPrice>
-                      <price><currency>USD</currency><value>999.95</value></price>                             
-                      <price><currency>EUR</currency><value>499.95</value></price>
-                      <price><currency>GBP</currency><value>999.95</value></price>
-                      </recurringPrice>
-                </phase>
-            </initialPhases>
+			<product>Laser-Scope</product>
+			<initialPhases>
+				<phase type="DISCOUNT">
+					<duration>
+						<unit>MONTHS</unit>
+						<number>1</number>
+					</duration>
+					<billingPeriod>MONTHLY</billingPeriod>
+					<recurringPrice>
+						<price>
+							<currency>USD</currency>
+							<value>999.95</value>
+						</price>
+						<price>
+							<currency>EUR</currency>
+							<value>499.95</value>
+						</price>
+						<price>
+							<currency>GBP</currency>
+							<value>999.95</value>
+						</price>
+					</recurringPrice>
+				</phase>
+			</initialPhases>
 			<finalPhase type="EVERGREEN">
 				<duration>
 					<unit>UNLIMITED</unit>
 				</duration>
 				<billingPeriod>MONTHLY</billingPeriod>
 				<recurringPrice>
-					<price><currency>USD</currency><value>1999.95</value></price>								
-					<price><currency>EUR</currency><value>1499.95</value></price>
-					<price><currency>GBP</currency><value>1999.95</value></price>
+					<price>
+						<currency>USD</currency>
+						<value>1999.95</value>
+					</price>
+					<price>
+						<currency>EUR</currency>
+						<value>1499.95</value>
+					</price>
+					<price>
+						<currency>GBP</currency>
+						<value>1999.95</value>
+					</price>
 				</recurringPrice>
 			</finalPhase>
 		</plan>
 		<plan name="telescopic-scope-monthly">
 			<product>Telescopic-Scope</product>
 			<initialPhases>
-              <phase type="DISCOUNT">
-                 <duration>
-                    <unit>MONTHS</unit>
-                    <number>1</number>
-                  </duration>
-                 <billingPeriod>MONTHLY</billingPeriod>
-                    <recurringPrice>
-                      <price><currency>USD</currency><value>399.95</value></price>                             
-                      <price><currency>EUR</currency><value>299.95</value></price>
-                      <price><currency>GBP</currency><value>399.95</value></price>
-                      </recurringPrice>
-                </phase>
-            </initialPhases>
+				<phase type="DISCOUNT">
+					<duration>
+						<unit>MONTHS</unit>
+						<number>1</number>
+					</duration>
+					<billingPeriod>MONTHLY</billingPeriod>
+					<recurringPrice>
+						<price>
+							<currency>USD</currency>
+							<value>399.95</value>
+						</price>
+						<price>
+							<currency>EUR</currency>
+							<value>299.95</value>
+						</price>
+						<price>
+							<currency>GBP</currency>
+							<value>399.95</value>
+						</price>
+					</recurringPrice>
+				</phase>
+			</initialPhases>
 			<finalPhase type="EVERGREEN">
 				<duration>
 					<unit>UNLIMITED</unit>
 				</duration>
 				<billingPeriod>MONTHLY</billingPeriod>
 				<recurringPrice>
-					<price><currency>USD</currency><value>999.95</value></price>								
-					<price><currency>EUR</currency><value>499.95</value></price>
-					<price><currency>GBP</currency><value>999.95</value></price>
+					<price>
+						<currency>USD</currency>
+						<value>999.95</value>
+					</price>
+					<price>
+						<currency>EUR</currency>
+						<value>499.95</value>
+					</price>
+					<price>
+						<currency>GBP</currency>
+						<value>999.95</value>
+					</price>
 				</recurringPrice>
 			</finalPhase>
 		</plan>
@@ -498,9 +622,18 @@ Use Cases to do:
 				</duration>
 				<billingPeriod>MONTHLY</billingPeriod>
 				<recurringPrice>
-					<price><currency>USD</currency><value>999.95</value></price>								
-					<price><currency>EUR</currency><value>499.95</value></price>
-					<price><currency>GBP</currency><value>999.95</value></price>
+					<price>
+						<currency>USD</currency>
+						<value>999.95</value>
+					</price>
+					<price>
+						<currency>EUR</currency>
+						<value>499.95</value>
+					</price>
+					<price>
+						<currency>GBP</currency>
+						<value>999.95</value>
+					</price>
 				</recurringPrice>
 			</finalPhase>
 			<plansAllowedInBundle>-1</plansAllowedInBundle> <!-- arbitrary number of these (multipack) -->
@@ -524,9 +657,18 @@ Use Cases to do:
 				</duration>
 				<billingPeriod>ANNUAL</billingPeriod>
 				<recurringPrice>
-					<price><currency>USD</currency><value>199.95</value></price>								
-					<price><currency>EUR</currency><value>199.95</value></price>
-					<price><currency>GBP</currency><value>199.95</value></price>
+					<price>
+						<currency>USD</currency>
+						<value>199.95</value>
+					</price>
+					<price>
+						<currency>EUR</currency>
+						<value>199.95</value>
+					</price>
+					<price>
+						<currency>GBP</currency>
+						<value>199.95</value>
+					</price>
 				</recurringPrice>
 			</finalPhase>
 		</plan>
@@ -549,9 +691,18 @@ Use Cases to do:
 				</duration>
 				<billingPeriod>ANNUAL</billingPeriod>
 				<recurringPrice>
-					<price><currency>USD</currency><value>199.95</value></price>								
-					<price><currency>EUR</currency><value>199.95</value></price>
-					<price><currency>GBP</currency><value>199.95</value></price>
+					<price>
+						<currency>USD</currency>
+						<value>199.95</value>
+					</price>
+					<price>
+						<currency>EUR</currency>
+						<value>199.95</value>
+					</price>
+					<price>
+						<currency>GBP</currency>
+						<value>199.95</value>
+					</price>
 				</recurringPrice>
 			</finalPhase>
 		</plan>
@@ -565,9 +716,18 @@ Use Cases to do:
 					</duration>
 					<billingPeriod>ANNUAL</billingPeriod>
 					<recurringPrice>
-						<price><currency>USD</currency><value>5999.95</value></price>								
-						<price><currency>EUR</currency><value>3499.95</value></price>
-						<price><currency>GBP</currency><value>3999.95</value></price>
+						<price>
+							<currency>USD</currency>
+							<value>5999.95</value>
+						</price>
+						<price>
+							<currency>EUR</currency>
+							<value>3499.95</value>
+						</price>
+						<price>
+							<currency>GBP</currency>
+							<value>3999.95</value>
+						</price>
 					</recurringPrice>
 				</phase>
 			</initialPhases>
@@ -577,9 +737,18 @@ Use Cases to do:
 				</duration>
 				<billingPeriod>ANNUAL</billingPeriod>
 				<recurringPrice>
-					<price><currency>USD</currency><value>5999.95</value></price>								
-					<price><currency>EUR</currency><value>3499.95</value></price>
-					<price><currency>GBP</currency><value>3999.95</value></price>
+					<price>
+						<currency>USD</currency>
+						<value>5999.95</value>
+					</price>
+					<price>
+						<currency>EUR</currency>
+						<value>3499.95</value>
+					</price>
+					<price>
+						<currency>GBP</currency>
+						<value>3999.95</value>
+					</price>
 				</recurringPrice>
 			</finalPhase>
 		</plan>
@@ -592,20 +761,38 @@ Use Cases to do:
 				</duration>
 				<billingPeriod>MONTHLY</billingPeriod>
 				<recurringPrice>
-					<price><currency>USD</currency><value>199.95</value></price>								
-					<price><currency>EUR</currency><value>199.95</value></price>
-					<price><currency>GBP</currency><value>199.95</value></price>
+					<price>
+						<currency>USD</currency>
+						<value>199.95</value>
+					</price>
+					<price>
+						<currency>EUR</currency>
+						<value>199.95</value>
+					</price>
+					<price>
+						<currency>GBP</currency>
+						<value>199.95</value>
+					</price>
 				</recurringPrice>
 				<fixedPrice>
-					<price><currency>USD</currency><value>599.95</value></price>								
-					<price><currency>EUR</currency><value>599.95</value></price>
-					<price><currency>GBP</currency><value>599.95</value></price>
+					<price>
+						<currency>USD</currency>
+						<value>599.95</value>
+					</price>
+					<price>
+						<currency>EUR</currency>
+						<value>599.95</value>
+					</price>
+					<price>
+						<currency>GBP</currency>
+						<value>599.95</value>
+					</price>
 				</fixedPrice>
 			</finalPhase>
 		</plan>
 	</plans>
 	<priceLists>
-		<defaultPriceList name="DEFAULT"> 
+		<defaultPriceList name="DEFAULT">
 			<plans>
 				<plan>pistol-monthly</plan>
 				<plan>shotgun-monthly</plan>

index.html 120(+120 -0)

diff --git a/index.html b/index.html
new file mode 100644
index 0000000..f907b08
--- /dev/null
+++ b/index.html
@@ -0,0 +1,120 @@
+<!-- ~ Copyright 2010-2011 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. -->
+
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="utf-8">
+    <title>Killbill: Subscription Management/Billing System</title>
+    <meta name="description" content="Killbill is a new open source subscription management/billing system">
+    <meta name="author" content="Stephane Brossier">
+
+    <!--[if lt IE 9]>
+    <script src="http://html5shim.googlecode.com/svn/trunk/html5.js" type="text/javascript"></script>
+    <![endif]-->
+
+    <link rel=stylesheet type="text/css" href="doc/css/bootstrap.min.css">
+    <link rel=stylesheet type="text/css" href="doc/css/prettify.css">
+    <link rel=stylesheet type="text/css" href="doc/css/killbill.css">
+    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
+    <script src="js/prettify.js"></script>
+    <script src="js/bootstrap-dropdown.js"></script>
+
+    <script type="text/javascript">
+      var _gaq = _gaq || [];
+      _gaq.push(['_setAccount', 'UA-19297396-1']);
+      _gaq.push(['_trackPageview']);
+      (function() {
+        var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
+        ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
+        var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
+      })();
+    </script>
+</head>
+
+<body>
+
+<div class="topbar" data-dropdown="dropdown">
+    <div class="topbar-inner">
+        <div class="container-fluid">
+            <a class="brand" href="index.html">Killbill</a>
+            <ul class="nav">
+                <li><a href="doc/user.html">User Guide</a></li>
+                <li><a href="doc/setup.html">Setup/Installation</a></li>
+                <li><a href="doc/api.html">APIs</a></li>
+                <li><a href="doc/design.html">Design</a></li>                                                
+                <li><a href="http://groups.google.com/group/killbill-users">Mailing List</a></li>
+            </ul>
+            <ul class="nav secondary-nav">
+                <li class="dropdown">
+                    <a href="#" class="dropdown-toggle">Maven sites</a>
+                    <ul class="dropdown-menu">
+                        <li><a href="http://sbrossie.github.com/killbill" target="_blank">Killbill</a></li>
+                    </ul>
+                </li>
+            </ul>
+            <ul class="nav secondary-nav">
+                <li class="dropdown">
+                    <a href="#" class="dropdown-toggle">Github projects</a>
+                    <ul class="dropdown-menu">
+                        <li><a href="http://github.com/ning/killbill" target="_blank">Killbill</a></li>
+                    </ul>
+                </li>
+            </ul>
+        </div>
+    </div>
+</div>
+
+
+<div class="container-fluid">
+    <div class="sidebar">
+        <div class="well">
+            <h5>Quick Presentation</h5>
+            <ul>
+                <li><a href="#motivation">Motivation</a></li>
+                <li><a href="#overview">Overview</a></li>
+                <li><a href="#goals">Goals</a></li>
+                <li><a href="#feature-list">Feature List</a></li>
+                <li><a href="#dependencies">Dependencies</a></li>
+                <li><a href="#state-project">State of the Project</a></li>
+            </ul>
+        </div>
+    </div>
+
+    <div class="content">
+
+        <h2 id="motivation">Motivation</h2>
+    
+            This is the motivation; why we are building it
+
+        <h2 id="overview">Overview</h2>
+    
+            This is the overview; describes what it does / (does not ?)
+
+
+        <h2 id="goals">Goals</h2>
+
+            This is the goals
+
+       <h2 id="feature-list">Feature List</h2>
+
+            This is the feature list
+
+       <h2 id="dependencies">Dependencies</h2>
+
+            This is the dependencies
+            
+        <h2 id="state-project">State of the Project</h2>
+
+            This is the state of the project
+            
+    </div>
+</div>
+</body>
+</html>

invoice/pom.xml 42(+13 -29)

diff --git a/invoice/pom.xml b/invoice/pom.xml
index 43f45c9..e03ae9a 100644
--- a/invoice/pom.xml
+++ b/invoice/pom.xml
@@ -33,20 +33,24 @@
             <groupId>com.ning.billing</groupId>
             <artifactId>killbill-util</artifactId>
         </dependency>
+         <dependency>
+            <groupId>org.jdbi</groupId>
+            <artifactId>jdbi</artifactId>
+        </dependency>
         <dependency>
-            <groupId>com.ning.billing</groupId>
-            <artifactId>killbill-util</artifactId>
-            <type>test-jar</type>
-            <scope>test</scope>
+            <groupId>org.antlr</groupId>
+            <artifactId>stringtemplate</artifactId>
+            <scope>runtime</scope>
         </dependency>
-            <dependency>
-            <groupId>com.ning.billing</groupId>
-            <artifactId>killbill-entitlement</artifactId>
-            <scope>test</scope>
+        <dependency>
+            <groupId>com.google.inject</groupId>
+            <artifactId>guice</artifactId>
+            <scope>provided</scope>
         </dependency>
+       <!-- TEST SCOPE -->
         <dependency>
             <groupId>com.ning.billing</groupId>
-            <artifactId>killbill-entitlement</artifactId>
+            <artifactId>killbill-util</artifactId>
             <type>test-jar</type>
             <scope>test</scope>
         </dependency>
@@ -62,12 +66,6 @@
             <scope>test</scope>
         </dependency>
         <dependency>
-            <groupId>com.ning.billing</groupId>
-            <artifactId>killbill-account</artifactId>
-            <type>test-jar</type>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
             <groupId>org.testng</groupId>
             <artifactId>testng</artifactId>
             <scope>test</scope>
@@ -78,20 +76,6 @@
             <scope>test</scope>
         </dependency>
         <dependency>
-            <groupId>org.jdbi</groupId>
-            <artifactId>jdbi</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.antlr</groupId>
-            <artifactId>stringtemplate</artifactId>
-            <scope>runtime</scope>
-        </dependency>
-        <dependency>
-            <groupId>com.google.inject</groupId>
-            <artifactId>guice</artifactId>
-            <scope>provided</scope>
-        </dependency>
-        <dependency>
             <groupId>com.jayway.awaitility</groupId>
             <artifactId>awaitility</artifactId>
             <scope>test</scope>
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 6f033db..da41e77 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
@@ -26,18 +26,14 @@ import com.ning.billing.util.bus.Bus;
 public class DefaultInvoiceService implements InvoiceService {
 
     public static final String INVOICE_SERVICE_NAME = "invoice-service";
-    private final InvoiceUserApi userApi;
-    private final InvoicePaymentApi paymentApi;
     private final NextBillingDateNotifier dateNotifier;
     private final InvoiceListener invoiceListener;
     private final Bus eventBus;
 
     @Inject
-    public DefaultInvoiceService(InvoiceListener invoiceListener, Bus eventBus, InvoiceUserApi userApi, InvoicePaymentApi paymentApi, NextBillingDateNotifier dateNotifier) {
+    public DefaultInvoiceService(InvoiceListener invoiceListener, Bus eventBus, NextBillingDateNotifier dateNotifier) {
         this.invoiceListener = invoiceListener;
         this.eventBus = eventBus;
-        this.userApi = userApi;
-        this.paymentApi = paymentApi;
         this.dateNotifier = dateNotifier;
     }
 
@@ -47,16 +43,6 @@ public class DefaultInvoiceService implements InvoiceService {
         return INVOICE_SERVICE_NAME;
     }
 
-    @Override
-    public InvoiceUserApi getUserApi() {
-        return userApi;
-    }
-
-    @Override
-    public InvoicePaymentApi getPaymentApi() {
-        return paymentApi;
-    }
-
     @LifecycleHandlerType(LifecycleHandlerType.LifecycleLevel.INIT_SERVICE)
     public void initialize() {
         dateNotifier.initialize();
diff --git a/invoice/src/main/java/com/ning/billing/invoice/api/migration/MigrationInvoice.java b/invoice/src/main/java/com/ning/billing/invoice/api/migration/MigrationInvoice.java
index 84877eb..6627982 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/api/migration/MigrationInvoice.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/api/migration/MigrationInvoice.java
@@ -24,6 +24,6 @@ import java.util.UUID;
 
 public class MigrationInvoice extends DefaultInvoice {
     public MigrationInvoice(UUID accountId, DateTime invoiceDate, DateTime targetDate, Currency currency) {
-        super(UUID.randomUUID(), accountId, null, invoiceDate, targetDate, currency, true, null, null);
+        super(UUID.randomUUID(), accountId, null, invoiceDate, targetDate, currency, true);
     }
 }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/api/user/DefaultEmptyInvoiceEvent.java b/invoice/src/main/java/com/ning/billing/invoice/api/user/DefaultEmptyInvoiceEvent.java
new file mode 100644
index 0000000..6ce30f6
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/api/user/DefaultEmptyInvoiceEvent.java
@@ -0,0 +1,104 @@
+/* 
+ * Copyright 2010-2011 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.invoice.api.user;
+
+import java.util.UUID;
+
+import org.codehaus.jackson.annotate.JsonCreator;
+import org.codehaus.jackson.annotate.JsonIgnore;
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.joda.time.DateTime;
+
+import com.ning.billing.invoice.api.EmptyInvoiceEvent;
+
+public class DefaultEmptyInvoiceEvent implements EmptyInvoiceEvent {
+
+    private final UUID accountId;
+    private final DateTime processingDate;
+    private final UUID userToken;
+
+    
+    @JsonCreator
+    public DefaultEmptyInvoiceEvent(@JsonProperty("accountId") final UUID accountId,
+            @JsonProperty("processingDate") final DateTime processingDate,
+            @JsonProperty("userToken") final UUID userToken) {
+        super();
+        this.accountId = accountId;
+        this.processingDate = processingDate;
+        this.userToken = userToken;
+    }
+
+    @JsonIgnore
+    @Override
+    public BusEventType getBusEventType() {
+        return BusEventType.INVOICE_EMPTY;
+    }
+
+    @Override
+    public UUID getUserToken() {
+        return userToken;
+    }
+
+    public UUID getAccountId() {
+        return accountId;
+    }
+
+    public DateTime getProcessingDate() {
+        return processingDate;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result
+                + ((accountId == null) ? 0 : accountId.hashCode());
+        result = prime * result
+                + ((processingDate == null) ? 0 : processingDate.hashCode());
+        result = prime * result
+                + ((userToken == null) ? 0 : userToken.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        DefaultEmptyInvoiceEvent other = (DefaultEmptyInvoiceEvent) obj;
+        if (accountId == null) {
+            if (other.accountId != null)
+                return false;
+        } else if (!accountId.equals(other.accountId))
+            return false;
+        if (processingDate == null) {
+            if (other.processingDate != null)
+                return false;
+        } else if (processingDate.compareTo(other.processingDate) != 0)
+            return false;
+        if (userToken == null) {
+            if (other.userToken != null)
+                return false;
+        } else if (!userToken.equals(other.userToken))
+            return false;
+        return true;
+    }
+    
+    
+}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/dao/DefaultInvoiceDao.java b/invoice/src/main/java/com/ning/billing/invoice/dao/DefaultInvoiceDao.java
index cf46d15..5178a29 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/dao/DefaultInvoiceDao.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/DefaultInvoiceDao.java
@@ -18,61 +18,46 @@ package com.ning.billing.invoice.dao;
 
 import java.math.BigDecimal;
 import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 import java.util.UUID;
 
-import com.ning.billing.util.ChangeType;
-import com.ning.billing.util.audit.dao.AuditSqlDao;
-import com.ning.billing.util.callcontext.CallContext;
-import com.ning.billing.util.customfield.CustomField;
-import com.ning.billing.util.customfield.dao.CustomFieldSqlDao;
-import com.ning.billing.util.tag.ControlTagType;
-import com.ning.billing.util.tag.Tag;
-import com.ning.billing.util.tag.dao.TagDao;
 import org.joda.time.DateTime;
 import org.skife.jdbi.v2.IDBI;
 import org.skife.jdbi.v2.Transaction;
 import org.skife.jdbi.v2.TransactionStatus;
 import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import com.google.inject.Inject;
-import com.ning.billing.entitlement.api.billing.EntitlementBillingApi;
 import com.ning.billing.invoice.api.Invoice;
-import com.ning.billing.invoice.api.InvoiceCreationNotification;
 import com.ning.billing.invoice.api.InvoiceItem;
 import com.ning.billing.invoice.api.InvoicePayment;
-import com.ning.billing.invoice.api.user.DefaultInvoiceCreationNotification;
 import com.ning.billing.invoice.model.FixedPriceInvoiceItem;
 import com.ning.billing.invoice.model.RecurringInvoiceItem;
 import com.ning.billing.invoice.notification.NextBillingDatePoster;
-import com.ning.billing.util.bus.Bus;
+import com.ning.billing.util.ChangeType;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.customfield.CustomField;
+import com.ning.billing.util.customfield.dao.CustomFieldSqlDao;
+import com.ning.billing.util.dao.EntityAudit;
+import com.ning.billing.util.dao.ObjectType;
+import com.ning.billing.util.dao.TableName;
+import com.ning.billing.util.tag.ControlTagType;
+import com.ning.billing.util.tag.Tag;
+import com.ning.billing.util.tag.dao.TagDao;
 
 public class DefaultInvoiceDao implements InvoiceDao {
-    private final static Logger log = LoggerFactory.getLogger(DefaultInvoiceDao.class);
-
     private final InvoiceSqlDao invoiceSqlDao;
     private final InvoicePaymentSqlDao invoicePaymentSqlDao;
-    private final EntitlementBillingApi entitlementBillingApi;
     private final TagDao tagDao;
 
-    private final Bus eventBus;
-
 	private final NextBillingDatePoster nextBillingDatePoster;
 
     @Inject
-    public DefaultInvoiceDao(final IDBI dbi, final Bus eventBus,
-                             final EntitlementBillingApi entitlementBillingApi,
+    public DefaultInvoiceDao(final IDBI dbi,
                              final NextBillingDatePoster nextBillingDatePoster,
                              final TagDao tagDao) {
         this.invoiceSqlDao = dbi.onDemand(InvoiceSqlDao.class);
         this.invoicePaymentSqlDao = dbi.onDemand(InvoicePaymentSqlDao.class);
-        this.eventBus = eventBus;
-        this.entitlementBillingApi = entitlementBillingApi;
         this.nextBillingDatePoster = nextBillingDatePoster;
         this.tagDao = tagDao;
     }
@@ -151,75 +136,58 @@ public class DefaultInvoiceDao implements InvoiceDao {
 
     @Override
     public void create(final Invoice invoice, final CallContext context) {
+        
         invoiceSqlDao.inTransaction(new Transaction<Void, InvoiceSqlDao>() {
             @Override
-            public Void inTransaction(final InvoiceSqlDao invoiceDao, final TransactionStatus status) throws Exception {
+            public Void inTransaction(final InvoiceSqlDao transactional, final TransactionStatus status) throws Exception {
 
                 // STEPH this seems useless
-                Invoice currentInvoice = invoiceDao.getById(invoice.getId().toString());
+                Invoice currentInvoice = transactional.getById(invoice.getId().toString());
 
                 if (currentInvoice == null) {
-                    invoiceDao.create(invoice, context);
+                    List<EntityAudit> audits = new ArrayList<EntityAudit>();
+
+                    transactional.create(invoice, context);
+                    Long recordId = transactional.getRecordId(invoice.getId().toString());
+                    audits.add(new EntityAudit(TableName.INVOICES, recordId, ChangeType.INSERT));
+
+                    List<Long> recordIdList;
 
                     List<InvoiceItem> recurringInvoiceItems = invoice.getInvoiceItems(RecurringInvoiceItem.class);
-                    RecurringInvoiceItemSqlDao recurringInvoiceItemDao = invoiceDao.become(RecurringInvoiceItemSqlDao.class);
+                    RecurringInvoiceItemSqlDao recurringInvoiceItemDao = transactional.become(RecurringInvoiceItemSqlDao.class);
                     recurringInvoiceItemDao.batchCreateFromTransaction(recurringInvoiceItems, context);
+                    recordIdList = recurringInvoiceItemDao.getRecordIds(invoice.getId().toString());
+                    audits.addAll(createAudits(TableName.RECURRING_INVOICE_ITEMS, recordIdList));
 
-                    notifyOfFutureBillingEvents(invoiceSqlDao, recurringInvoiceItems);
+                    notifyOfFutureBillingEvents(transactional, recurringInvoiceItems);
 
                     List<InvoiceItem> fixedPriceInvoiceItems = invoice.getInvoiceItems(FixedPriceInvoiceItem.class);
-                    FixedPriceInvoiceItemSqlDao fixedPriceInvoiceItemDao = invoiceDao.become(FixedPriceInvoiceItemSqlDao.class);
+                    FixedPriceInvoiceItemSqlDao fixedPriceInvoiceItemDao = transactional.become(FixedPriceInvoiceItemSqlDao.class);
                     fixedPriceInvoiceItemDao.batchCreateFromTransaction(fixedPriceInvoiceItems, context);
+                    recordIdList = fixedPriceInvoiceItemDao.getRecordIds(invoice.getId().toString());
+                    audits.addAll(createAudits(TableName.FIXED_INVOICE_ITEMS, recordIdList));
 
-                    setChargedThroughDates(invoiceSqlDao, fixedPriceInvoiceItems, recurringInvoiceItems, context);
-
-                    // STEPH Why do we need that? Are the payments not always null at this point?
                     List<InvoicePayment> invoicePayments = invoice.getPayments();
-                    InvoicePaymentSqlDao invoicePaymentSqlDao = invoiceDao.become(InvoicePaymentSqlDao.class);
+                    InvoicePaymentSqlDao invoicePaymentSqlDao = transactional.become(InvoicePaymentSqlDao.class);
                     invoicePaymentSqlDao.batchCreateFromTransaction(invoicePayments, context);
+                    recordIdList = invoicePaymentSqlDao.getRecordIds(invoice.getId().toString());
+                    audits.addAll(createAudits(TableName.INVOICE_PAYMENTS, recordIdList));
 
-                    AuditSqlDao auditSqlDao = invoiceDao.become(AuditSqlDao.class);
-                    auditSqlDao.insertAuditFromTransaction("invoices", invoice.getId().toString(), ChangeType.INSERT, context);
-                    auditSqlDao.insertAuditFromTransaction("recurring_invoice_items", getIdsFromInvoiceItems(recurringInvoiceItems), ChangeType.INSERT, context);
-                    auditSqlDao.insertAuditFromTransaction("fixed_invoice_items", getIdsFromInvoiceItems(fixedPriceInvoiceItems), ChangeType.INSERT, context);
-                    auditSqlDao.insertAuditFromTransaction("invoice_payments", getIdsFromInvoicePayments(invoicePayments), ChangeType.INSERT, context);
-
+                    transactional.insertAuditFromTransaction(audits, context);
                 }
 
                 return null;
             }
         });
-
-        // TODO: move this inside the transaction once the bus is persistent
-        InvoiceCreationNotification event;
-        event = new DefaultInvoiceCreationNotification(invoice.getId(), invoice.getAccountId(),
-                                                      invoice.getBalance(), invoice.getCurrency(),
-                                                      invoice.getInvoiceDate());
-        try {
-            eventBus.post(event);
-        } catch (Bus.EventBusException e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    private List<String> getIdsFromInvoiceItems(List<InvoiceItem> invoiceItems) {
-        List<String> ids = new ArrayList<String>();
-
-        for (InvoiceItem item : invoiceItems) {
-            ids.add(item.getId().toString());
-        }
-
-        return ids;
     }
 
-    private List<String> getIdsFromInvoicePayments(List<InvoicePayment> invoicePayments) {
-        List<String> ids = new ArrayList<String>();
-
-        for (InvoicePayment payment : invoicePayments) {
-            ids.add(payment.getId().toString());
+    private List<EntityAudit> createAudits(TableName tableName, List<Long> recordIdList) {
+        List<EntityAudit> entityAuditList = new ArrayList<EntityAudit>();
+        for (Long recordId : recordIdList) {
+            entityAuditList.add(new EntityAudit(tableName, recordId, ChangeType.INSERT));
         }
 
-        return ids;
+        return entityAuditList;
     }
 
     @Override
@@ -248,9 +216,10 @@ public class DefaultInvoiceDao implements InvoiceDao {
             public Void inTransaction(InvoicePaymentSqlDao transactional, TransactionStatus status) throws Exception {
                 transactional.notifyOfPaymentAttempt(invoicePayment, context);
 
-                AuditSqlDao auditSqlDao = transactional.become(AuditSqlDao.class);
                 String invoicePaymentId = invoicePayment.getId().toString();
-                auditSqlDao.insertAuditFromTransaction("invoice_payments", invoicePaymentId, ChangeType.INSERT, context);
+                Long recordId = transactional.getRecordId(invoicePaymentId);
+                EntityAudit audit = new EntityAudit(TableName.INVOICE_PAYMENTS, recordId, ChangeType.INSERT);
+                transactional.insertAuditFromTransaction(audit, context);
 
                 return null;
             }
@@ -283,12 +252,12 @@ public class DefaultInvoiceDao implements InvoiceDao {
 
     @Override
     public void addControlTag(ControlTagType controlTagType, UUID invoiceId, CallContext context) {
-        tagDao.addTag(controlTagType.toString(), invoiceId, Invoice.ObjectType, context);
+        tagDao.addTag(controlTagType.toString(), invoiceId, ObjectType.INVOICE, context);
     }
 
     @Override
     public void removeControlTag(ControlTagType controlTagType, UUID invoiceId, CallContext context) {
-        tagDao.removeTag(controlTagType.toString(), invoiceId, Invoice.ObjectType, context);
+        tagDao.removeTag(controlTagType.toString(), invoiceId, ObjectType.INVOICE, context);
     }
 
     @Override
@@ -346,7 +315,7 @@ public class DefaultInvoiceDao implements InvoiceDao {
     }
 
     private void getTagsWithinTransaction(final Invoice invoice, final Transmogrifier dao) {
-        List<Tag> tags = tagDao.loadTagsFromTransaction(dao, invoice.getId(), Invoice.ObjectType);
+        List<Tag> tags = tagDao.loadEntitiesFromTransaction(dao, invoice.getId(), ObjectType.INVOICE);
         invoice.addTags(tags);
     }
 
@@ -359,7 +328,7 @@ public class DefaultInvoiceDao implements InvoiceDao {
     private void getFieldsWithinTransaction(final Invoice invoice, final InvoiceSqlDao invoiceSqlDao) {
         CustomFieldSqlDao customFieldSqlDao = invoiceSqlDao.become(CustomFieldSqlDao.class);
         String invoiceId = invoice.getId().toString();
-        List<CustomField> customFields = customFieldSqlDao.load(invoiceId, Invoice.ObjectType);
+        List<CustomField> customFields = customFieldSqlDao.load(invoiceId, ObjectType.INVOICE);
         invoice.setFields(customFields);
     }
 
@@ -375,32 +344,4 @@ public class DefaultInvoiceDao implements InvoiceDao {
             }
         }
     }
-    
-    private void setChargedThroughDates(final InvoiceSqlDao dao, final Collection<InvoiceItem> fixedPriceItems,
-                                        final Collection<InvoiceItem> recurringItems, CallContext context) {
-        Map<UUID, DateTime> chargeThroughDates = new HashMap<UUID, DateTime>();
-        addInvoiceItemsToChargeThroughDates(chargeThroughDates, fixedPriceItems);
-        addInvoiceItemsToChargeThroughDates(chargeThroughDates, recurringItems);
-
-        for (UUID subscriptionId : chargeThroughDates.keySet()) {
-            DateTime chargeThroughDate = chargeThroughDates.get(subscriptionId);
-            log.info("Setting CTD for subscription {} to {}", subscriptionId.toString(), chargeThroughDate.toString());
-            entitlementBillingApi.setChargedThroughDateFromTransaction(dao, subscriptionId, chargeThroughDate, context);
-        }
-    }
-
-    private void addInvoiceItemsToChargeThroughDates(Map<UUID, DateTime> chargeThroughDates, Collection<InvoiceItem> items) {
-        for (InvoiceItem item : items) {
-            UUID subscriptionId = item.getSubscriptionId();
-            DateTime endDate = item.getEndDate();
-
-            if (chargeThroughDates.containsKey(subscriptionId)) {
-                if (chargeThroughDates.get(subscriptionId).isBefore(endDate)) {
-                    chargeThroughDates.put(subscriptionId, endDate);
-                }
-            } else {
-                chargeThroughDates.put(subscriptionId, endDate);
-            }
-        }
-    }
 }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/dao/FixedPriceInvoiceItemSqlDao.java b/invoice/src/main/java/com/ning/billing/invoice/dao/FixedPriceInvoiceItemSqlDao.java
index f564658..d4fa5ee 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/dao/FixedPriceInvoiceItemSqlDao.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/FixedPriceInvoiceItemSqlDao.java
@@ -16,12 +16,18 @@
 
 package com.ning.billing.invoice.dao;
 
-import com.ning.billing.catalog.api.Currency;
-import com.ning.billing.invoice.api.InvoiceItem;
-import com.ning.billing.invoice.model.FixedPriceInvoiceItem;
-import com.ning.billing.util.callcontext.CallContext;
-import com.ning.billing.util.callcontext.CallContextBinder;
-import com.ning.billing.util.entity.EntityDao;
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.math.BigDecimal;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.List;
+import java.util.UUID;
+
+import com.ning.billing.util.entity.dao.EntitySqlDao;
 import org.joda.time.DateTime;
 import org.skife.jdbi.v2.SQLStatement;
 import org.skife.jdbi.v2.StatementContext;
@@ -36,20 +42,18 @@ import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
 import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
 import org.skife.jdbi.v2.tweak.ResultSetMapper;
 
-import java.lang.annotation.Annotation;
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-import java.math.BigDecimal;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.util.List;
-import java.util.UUID;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.model.FixedPriceInvoiceItem;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.callcontext.CallContextBinder;
 
 @ExternalizedSqlViaStringTemplate3()
 @RegisterMapper(FixedPriceInvoiceItemSqlDao.FixedPriceInvoiceItemMapper.class)
-public interface FixedPriceInvoiceItemSqlDao extends EntityDao<InvoiceItem> {
+public interface FixedPriceInvoiceItemSqlDao extends EntitySqlDao<InvoiceItem> {
+    @SqlQuery
+    List<Long> getRecordIds(@Bind("invoiceId") final String invoiceId);
+
     @SqlQuery
     List<InvoiceItem> getInvoiceItemsByInvoice(@Bind("invoiceId") final String invoiceId);
 
@@ -78,9 +82,10 @@ public interface FixedPriceInvoiceItemSqlDao extends EntityDao<InvoiceItem> {
                 return new Binder<FixedPriceInvoiceItemBinder, FixedPriceInvoiceItem>() {
                     public void bind(SQLStatement q, FixedPriceInvoiceItemBinder bind, FixedPriceInvoiceItem item) {
                         q.bind("id", item.getId().toString());
-                        q.bind("invoiceId", item.getInvoiceId().toString());
                         q.bind("accountId", item.getAccountId().toString());
-                        q.bind("subscriptionId", item.getSubscriptionId().toString());
+                        q.bind("invoiceId", item.getInvoiceId().toString());
+                        q.bind("bundleId", item.getBundleId() == null ? null : item.getBundleId().toString());
+                        q.bind("subscriptionId", item.getSubscriptionId() == null ? null : item.getSubscriptionId().toString());
                         q.bind("planName", item.getPlanName());
                         q.bind("phaseName", item.getPhaseName());
                         q.bind("startDate", item.getStartDate().toDate());
@@ -99,18 +104,17 @@ public interface FixedPriceInvoiceItemSqlDao extends EntityDao<InvoiceItem> {
             UUID id = UUID.fromString(result.getString("id"));
             UUID invoiceId = UUID.fromString(result.getString("invoice_id"));
             UUID accountId = UUID.fromString(result.getString("account_id"));
-            UUID subscriptionId = UUID.fromString(result.getString("subscription_id"));
+            UUID bundleId = result.getString("bundle_id") == null ? null :UUID.fromString(result.getString("bundle_id"));
+            UUID subscriptionId = result.getString("subscription_id") == null ? null : UUID.fromString(result.getString("subscription_id"));
             String planName = result.getString("plan_name");
             String phaseName = result.getString("phase_name");
             DateTime startDate = new DateTime(result.getTimestamp("start_date"));
             DateTime endDate = new DateTime(result.getTimestamp("end_date"));
             BigDecimal amount = result.getBigDecimal("amount");
             Currency currency = Currency.valueOf(result.getString("currency"));
-            String createdBy = result.getString("created_by");
-            DateTime createdDate = new DateTime(result.getTimestamp("created_date"));
 
-            return new FixedPriceInvoiceItem(id, invoiceId, accountId, subscriptionId, planName, phaseName,
-                                            startDate, endDate, amount, currency, createdBy, createdDate);
+            return new FixedPriceInvoiceItem(id, invoiceId, accountId, bundleId, subscriptionId, planName, phaseName,
+                                            startDate, endDate, amount, currency);
         }
     }
 }
\ No newline at end of file
diff --git a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoicePaymentSqlDao.java b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoicePaymentSqlDao.java
index c51d65d..a11c8e0 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoicePaymentSqlDao.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoicePaymentSqlDao.java
@@ -28,9 +28,11 @@ import java.util.List;
 import java.util.UUID;
 
 import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.util.dao.AuditSqlDao;
 import com.ning.billing.util.callcontext.CallContext;
 import com.ning.billing.util.callcontext.CallContextBinder;
 import com.ning.billing.util.dao.MapperBase;
+import com.ning.billing.util.entity.dao.EntitySqlDao;
 import org.joda.time.DateTime;
 import org.skife.jdbi.v2.SQLStatement;
 import org.skife.jdbi.v2.StatementContext;
@@ -51,7 +53,10 @@ import com.ning.billing.invoice.api.InvoicePayment;
 
 @ExternalizedSqlViaStringTemplate3
 @RegisterMapper(InvoicePaymentSqlDao.InvoicePaymentMapper.class)
-public interface InvoicePaymentSqlDao extends Transactional<InvoicePaymentSqlDao>, Transmogrifier {
+public interface InvoicePaymentSqlDao extends EntitySqlDao<InvoicePayment>, Transactional<InvoicePaymentSqlDao>, AuditSqlDao, Transmogrifier {
+    @SqlQuery
+    List<Long> getRecordIds(@Bind("invoiceId") final String invoiceId);
+    
     @SqlQuery
     public InvoicePayment getByPaymentAttemptId(@Bind("paymentAttempt") final String paymentAttemptId);
 
@@ -81,15 +86,13 @@ public interface InvoicePaymentSqlDao extends Transactional<InvoicePaymentSqlDao
     public static class InvoicePaymentMapper extends MapperBase implements ResultSetMapper<InvoicePayment> {
         @Override
         public InvoicePayment map(int index, ResultSet result, StatementContext context) throws SQLException {
-            final UUID id = UUID.fromString(result.getString("id"));
-            final UUID paymentAttemptId = UUID.fromString(result.getString("payment_attempt_id"));
-            final UUID invoiceId = UUID.fromString(result.getString("invoice_id"));
+            final UUID id = getUUID(result, "id");
+            final UUID paymentAttemptId = getUUID(result, "payment_attempt_id");
+            final UUID invoiceId = getUUID(result, "invoice_id");
             final DateTime paymentAttemptDate = getDate(result, "payment_attempt_date");
             final BigDecimal amount = result.getBigDecimal("amount");
             final String currencyString = result.getString("currency");
             final Currency currency = (currencyString == null) ? null : Currency.valueOf(currencyString);
-            final String createdBy = result.getString("created_by");
-            final DateTime createdDate = getDate(result, "created_date");
 
             return new InvoicePayment() {
                 @Override
@@ -116,14 +119,6 @@ public interface InvoicePaymentSqlDao extends Transactional<InvoicePaymentSqlDao
                 public Currency getCurrency() {
                     return currency;
                 }
-                @Override
-                public String getCreatedBy() {
-                    return createdBy;
-                }
-                @Override
-                public DateTime getCreatedDate() {
-                    return createdDate ;
-                }
             };
         }
     }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceSqlDao.java b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceSqlDao.java
index dc8e303..af611bc 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceSqlDao.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceSqlDao.java
@@ -20,9 +20,10 @@ import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.invoice.api.Invoice;
 import com.ning.billing.invoice.model.DefaultInvoice;
 import com.ning.billing.util.callcontext.CallContext;
-import com.ning.billing.util.UuidMapper;
+import com.ning.billing.util.dao.AuditSqlDao;
+import com.ning.billing.util.dao.UuidMapper;
 import com.ning.billing.util.callcontext.CallContextBinder;
-import com.ning.billing.util.entity.EntityDao;
+import com.ning.billing.util.entity.dao.EntitySqlDao;
 import org.joda.time.DateTime;
 import org.skife.jdbi.v2.SQLStatement;
 import org.skife.jdbi.v2.StatementContext;
@@ -53,7 +54,7 @@ import java.util.UUID;
 
 @ExternalizedSqlViaStringTemplate3()
 @RegisterMapper(InvoiceSqlDao.InvoiceMapper.class)
-public interface InvoiceSqlDao extends EntityDao<Invoice>, Transactional<InvoiceSqlDao>, Transmogrifier, CloseMe {
+public interface InvoiceSqlDao extends EntitySqlDao<Invoice>, AuditSqlDao, Transactional<InvoiceSqlDao>, Transmogrifier, CloseMe {
     @Override
     @SqlUpdate
     void create(@InvoiceBinder Invoice invoice, @CallContextBinder final CallContext context);
@@ -117,10 +118,8 @@ public interface InvoiceSqlDao extends EntityDao<Invoice>, Transactional<Invoice
             DateTime targetDate = new DateTime(result.getTimestamp("target_date"));
             Currency currency = Currency.valueOf(result.getString("currency"));
             boolean isMigrationInvoice = result.getBoolean("migrated");
-            String createdBy = result.getString("created_by");
-            DateTime createdDate = new DateTime(result.getTimestamp("created_date"));
 
-            return new DefaultInvoice(id, accountId, invoiceNumber, invoiceDate, targetDate, currency, isMigrationInvoice, createdBy, createdDate);
+            return new DefaultInvoice(id, accountId, invoiceNumber, invoiceDate, targetDate, currency, isMigrationInvoice);
         }
     }
 
diff --git a/invoice/src/main/java/com/ning/billing/invoice/dao/RecurringInvoiceItemSqlDao.java b/invoice/src/main/java/com/ning/billing/invoice/dao/RecurringInvoiceItemSqlDao.java
index 29dfb9e..4daa560 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/dao/RecurringInvoiceItemSqlDao.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/RecurringInvoiceItemSqlDao.java
@@ -16,12 +16,18 @@
 
 package com.ning.billing.invoice.dao;
 
-import com.ning.billing.catalog.api.Currency;
-import com.ning.billing.invoice.api.InvoiceItem;
-import com.ning.billing.invoice.model.RecurringInvoiceItem;
-import com.ning.billing.util.callcontext.CallContext;
-import com.ning.billing.util.callcontext.CallContextBinder;
-import com.ning.billing.util.entity.EntityDao;
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.math.BigDecimal;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.List;
+import java.util.UUID;
+
+import com.ning.billing.util.dao.MapperBase;
 import org.joda.time.DateTime;
 import org.skife.jdbi.v2.SQLStatement;
 import org.skife.jdbi.v2.StatementContext;
@@ -36,20 +42,19 @@ import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
 import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
 import org.skife.jdbi.v2.tweak.ResultSetMapper;
 
-import java.lang.annotation.Annotation;
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-import java.math.BigDecimal;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.util.List;
-import java.util.UUID;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.model.RecurringInvoiceItem;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.callcontext.CallContextBinder;
+import com.ning.billing.util.entity.dao.EntitySqlDao;
 
 @ExternalizedSqlViaStringTemplate3()
 @RegisterMapper(RecurringInvoiceItemSqlDao.RecurringInvoiceItemMapper.class)
-public interface RecurringInvoiceItemSqlDao extends EntityDao<InvoiceItem> {
+public interface RecurringInvoiceItemSqlDao extends EntitySqlDao<InvoiceItem> {
+    @SqlQuery
+    List<Long> getRecordIds(@Bind("invoiceId") final String invoiceId);
+
     @SqlQuery
     List<InvoiceItem> getInvoiceItemsByInvoice(@Bind("invoiceId") final String invoiceId);
 
@@ -79,7 +84,8 @@ public interface RecurringInvoiceItemSqlDao extends EntityDao<InvoiceItem> {
                         q.bind("id", item.getId().toString());
                         q.bind("invoiceId", item.getInvoiceId().toString());
                         q.bind("accountId", item.getAccountId().toString());
-                        q.bind("subscriptionId", item.getSubscriptionId().toString());
+                        q.bind("bundleId", item.getBundleId() == null ? null : item.getBundleId().toString());
+                        q.bind("subscriptionId", item.getSubscriptionId() == null ? null : item.getSubscriptionId().toString());
                         q.bind("planName", item.getPlanName());
                         q.bind("phaseName", item.getPhaseName());
                         q.bind("startDate", item.getStartDate().toDate());
@@ -94,27 +100,26 @@ public interface RecurringInvoiceItemSqlDao extends EntityDao<InvoiceItem> {
         }
     }
 
-    public static class RecurringInvoiceItemMapper implements ResultSetMapper<InvoiceItem> {
+    public static class RecurringInvoiceItemMapper extends MapperBase implements ResultSetMapper<InvoiceItem> {
         @Override
         public InvoiceItem map(int index, ResultSet result, StatementContext context) throws SQLException {
-            UUID id = UUID.fromString(result.getString("id"));
-            UUID invoiceId = UUID.fromString(result.getString("invoice_id"));
-            UUID accountId = UUID.fromString(result.getString("account_id"));
-            UUID subscriptionId = UUID.fromString(result.getString("subscription_id"));
+            UUID id = getUUID(result, "id");
+            UUID invoiceId = getUUID(result, "invoice_id");
+            UUID accountId = getUUID(result, "account_id");
+            UUID subscriptionId = getUUID(result, "subscription_id");
+            UUID bundleId = getUUID(result, "bundle_id");
             String planName = result.getString("plan_name");
             String phaseName = result.getString("phase_name");
-            DateTime startDate = new DateTime(result.getTimestamp("start_date"));
-            DateTime endDate = new DateTime(result.getTimestamp("end_date"));
+            DateTime startDate = getDate(result, "start_date");
+            DateTime endDate = getDate(result, "end_date");
             BigDecimal amount = result.getBigDecimal("amount");
             BigDecimal rate = result.getBigDecimal("rate");
             Currency currency = Currency.valueOf(result.getString("currency"));
-            String reversedItemString = result.getString("reversed_item_id");
-            UUID reversedItemId = (reversedItemString == null) ? null : UUID.fromString(reversedItemString);
-            String createdBy = result.getString("created_by");
-            DateTime createdDate = new DateTime(result.getTimestamp("created_date"));
+            UUID reversedItemId = getUUID(result, "reversed_item_id");
+
+            return new RecurringInvoiceItem(id, invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate,
+                    amount, rate, currency, reversedItemId);
 
-            return new RecurringInvoiceItem(id, invoiceId, accountId, subscriptionId, planName, phaseName, startDate, endDate,
-                    amount, rate, currency, reversedItemId, createdBy, createdDate);
         }
     }
 }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/InvoiceDispatcher.java b/invoice/src/main/java/com/ning/billing/invoice/InvoiceDispatcher.java
index 32c625c..32d444d 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/InvoiceDispatcher.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/InvoiceDispatcher.java
@@ -17,11 +17,12 @@
 package com.ning.billing.invoice;
 
 import java.util.Collection;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.SortedSet;
 import java.util.UUID;
 
-import com.ning.billing.util.callcontext.CallContext;
 import org.joda.time.DateTime;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -29,51 +30,73 @@ import org.slf4j.LoggerFactory;
 import com.google.inject.Inject;
 import com.ning.billing.ErrorCode;
 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.Currency;
 import com.ning.billing.entitlement.api.billing.BillingEvent;
-import com.ning.billing.entitlement.api.billing.EntitlementBillingApi;
-import com.ning.billing.entitlement.api.user.SubscriptionTransition;
+import com.ning.billing.entitlement.api.billing.EntitlementBillingApiException;
+import com.ning.billing.entitlement.api.user.SubscriptionEvent;
 import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceCreationEvent;
+import com.ning.billing.invoice.api.InvoiceNotifier;
 import com.ning.billing.invoice.api.InvoiceApiException;
 import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.api.user.DefaultEmptyInvoiceEvent;
+import com.ning.billing.invoice.api.user.DefaultInvoiceCreationEvent;
 import com.ning.billing.invoice.dao.InvoiceDao;
 import com.ning.billing.invoice.model.BillingEventSet;
+import com.ning.billing.invoice.model.FixedPriceInvoiceItem;
 import com.ning.billing.invoice.model.InvoiceGenerator;
+import com.ning.billing.invoice.model.RecurringInvoiceItem;
+import com.ning.billing.junction.api.BillingApi;
+import com.ning.billing.util.bus.Bus;
+import com.ning.billing.util.bus.Bus.EventBusException;
+import com.ning.billing.util.bus.BusEvent;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.clock.Clock;
 import com.ning.billing.util.globallocker.GlobalLock;
 import com.ning.billing.util.globallocker.GlobalLocker;
-import com.ning.billing.util.globallocker.LockFailedException;
 import com.ning.billing.util.globallocker.GlobalLocker.LockerService;
+import com.ning.billing.util.globallocker.LockFailedException;
 
 public class InvoiceDispatcher {
-	private final static Logger log = LoggerFactory.getLogger(InvoiceDispatcher.class);
+    private final static Logger log = LoggerFactory.getLogger(InvoiceDispatcher.class);
     private final static int NB_LOCK_TRY = 5;
 
     private final InvoiceGenerator generator;
-    private final EntitlementBillingApi entitlementBillingApi;
+    private final BillingApi billingApi;
     private final AccountUserApi accountUserApi;
     private final InvoiceDao invoiceDao;
+    private final InvoiceNotifier invoiceNotifier;
     private final GlobalLocker locker;
+    private final Bus eventBus;
+    private final Clock clock;
 
     private final boolean VERBOSE_OUTPUT;
 
     @Inject
     public InvoiceDispatcher(final InvoiceGenerator generator, final AccountUserApi accountUserApi,
-                             final EntitlementBillingApi entitlementBillingApi,
-                             final InvoiceDao invoiceDao,
-                             final GlobalLocker locker) {
+            final BillingApi billingApi,
+            final InvoiceDao invoiceDao,
+            final InvoiceNotifier invoiceNotifier,
+            final GlobalLocker locker,
+            final Bus eventBus,
+            final Clock clock) {
         this.generator = generator;
-        this.entitlementBillingApi = entitlementBillingApi;
+        this.billingApi = billingApi;
         this.accountUserApi = accountUserApi;
         this.invoiceDao = invoiceDao;
+        this.invoiceNotifier = invoiceNotifier;
         this.locker = locker;
+        this.eventBus = eventBus;
+        this.clock = clock;
 
         String verboseOutputValue = System.getProperty("VERBOSE_OUTPUT");
         VERBOSE_OUTPUT = (verboseOutputValue != null) && Boolean.parseBoolean(verboseOutputValue);
     }
 
-    public void processSubscription(final SubscriptionTransition transition,
-                                    final CallContext context) throws InvoiceApiException {
+    public void processSubscription(final SubscriptionEvent transition,
+            final CallContext context) throws InvoiceApiException {
         UUID subscriptionId = transition.getSubscriptionId();
         DateTime targetDate = transition.getEffectiveTransitionTime();
         log.info("Got subscription transition from InvoiceListener. id: " + subscriptionId.toString() + "; targetDate: " + targetDate.toString());
@@ -82,25 +105,24 @@ public class InvoiceDispatcher {
     }
 
     public void processSubscription(final UUID subscriptionId, final DateTime targetDate,
-                                    final CallContext context) throws InvoiceApiException {
-        if (subscriptionId == null) {
-            log.error("Failed handling entitlement change.", new InvoiceApiException(ErrorCode.INVOICE_INVALID_TRANSITION));
-            return;
-        }
-
-        UUID accountId = entitlementBillingApi.getAccountIdFromSubscriptionId(subscriptionId);
-        if (accountId == null) {
+            final CallContext context) throws InvoiceApiException {
+        try {
+            if (subscriptionId == null) {
+                log.error("Failed handling entitlement change.", new InvoiceApiException(ErrorCode.INVOICE_INVALID_TRANSITION));
+                return;
+            }
+            UUID accountId = billingApi.getAccountIdFromSubscriptionId(subscriptionId);
+            processAccount(accountId, targetDate, false, context);
+        } catch (EntitlementBillingApiException e) {
             log.error("Failed handling entitlement change.",
                     new InvoiceApiException(ErrorCode.INVOICE_NO_ACCOUNT_ID_FOR_SUBSCRIPTION_ID, subscriptionId.toString()));
-            return;
         }
-
-        processAccount(accountId, targetDate, false, context);
+        return;
     }
-    
+
     public Invoice processAccount(final UUID accountId, final DateTime targetDate,
-                                  final boolean dryRun, final CallContext context) throws InvoiceApiException {
-		GlobalLock lock = null;
+            final boolean dryRun, final CallContext context) throws InvoiceApiException {
+        GlobalLock lock = null;
         try {
             lock = locker.lockWithNumberOfTries(LockerService.INVOICE, accountId.toString(), NB_LOCK_TRY);
 
@@ -118,46 +140,103 @@ public class InvoiceDispatcher {
         return null;
     }
 
+   
     private Invoice processAccountWithLock(final UUID accountId, final DateTime targetDate,
-                                           final boolean dryRun, final CallContext context) throws InvoiceApiException {
+            final boolean dryRun, final CallContext context) throws InvoiceApiException {
+        try {
+            Account account = accountUserApi.getAccountById(accountId);
+            SortedSet<BillingEvent> events = billingApi.getBillingEventsForAccountAndUpdateAccountBCD(accountId);
+            BillingEventSet billingEvents = new BillingEventSet(events);
 
-        Account account = accountUserApi.getAccountById(accountId);
-        if (account == null) {
-            log.error("Failed handling entitlement change.",
-                    new InvoiceApiException(ErrorCode.INVOICE_ACCOUNT_ID_INVALID, accountId.toString()));
-            return null;
-        }
+            Currency targetCurrency = account.getCurrency();
 
-        SortedSet<BillingEvent> events = entitlementBillingApi.getBillingEventsForAccount(accountId);
-        BillingEventSet billingEvents = new BillingEventSet(events);
+            List<Invoice> invoices = invoiceDao.getInvoicesByAccount(accountId);
+            Invoice invoice = generator.generateInvoice(accountId, billingEvents, invoices, targetDate, targetCurrency);
 
-        Currency targetCurrency = account.getCurrency();
+            if (invoice == null) {
+                log.info("Generated null invoice.");
+                outputDebugData(events, invoices);
+                if (!dryRun) {
+                    BusEvent event = new DefaultEmptyInvoiceEvent(accountId, clock.getUTCNow(), context.getUserToken());
+                    postEvent(event, accountId);
+                }
+            } else {
+                log.info("Generated invoice {} with {} items.", invoice.getId().toString(), invoice.getNumberOfItems());
+                if (VERBOSE_OUTPUT) {
+                    log.info("New items");
+                    for (InvoiceItem item : invoice.getInvoiceItems()) {
+                        log.info(item.toString());
+                    }
+                }
+                outputDebugData(events, invoices);
+                if (!dryRun) {
+                    invoiceDao.create(invoice, context);
 
-        List<Invoice> invoices = invoiceDao.getInvoicesByAccount(accountId);
-        Invoice invoice = generator.generateInvoice(accountId, billingEvents, invoices, targetDate, targetCurrency);
+                    List<InvoiceItem> fixedPriceInvoiceItems = invoice.getInvoiceItems(FixedPriceInvoiceItem.class);
+                    List<InvoiceItem> recurringInvoiceItems = invoice.getInvoiceItems(RecurringInvoiceItem.class);
+                    setChargedThroughDates(fixedPriceInvoiceItems, recurringInvoiceItems, context);
 
-        if (invoice == null) {
-            log.info("Generated null invoice.");
-            outputDebugData(events, invoices);
-        } else {
-            log.info("Generated invoice {} with {} items.", invoice.getId().toString(), invoice.getNumberOfItems());
+                    final InvoiceCreationEvent event = new DefaultInvoiceCreationEvent(invoice.getId(), invoice.getAccountId(),
+                            invoice.getBalance(), invoice.getCurrency(),
+                            invoice.getInvoiceDate(),
+                            context.getUserToken());
 
-            if (VERBOSE_OUTPUT) {
-                log.info("New items");
-                for (InvoiceItem item : invoice.getInvoiceItems()) {
-                    log.info(item.toString());
+                    postEvent(event, accountId);
                 }
             }
-            outputDebugData(events, invoices);
 
-            if (invoice.getNumberOfItems() > 0 && !dryRun) {
-                invoiceDao.create(invoice, context);
+            if (account.isNotifiedForInvoices()) {
+                invoiceNotifier.notify(account, invoice);
             }
+
+            return invoice;
+        } catch(AccountApiException e) {
+            log.error("Failed handling entitlement change.",e);
+            return null;    
         }
-        
-        return invoice;
     }
 
+    private void setChargedThroughDates(final Collection<InvoiceItem> fixedPriceItems,
+            final Collection<InvoiceItem> recurringItems, CallContext context) {
+
+        Map<UUID, DateTime> chargeThroughDates = new HashMap<UUID, DateTime>();
+        addInvoiceItemsToChargeThroughDates(chargeThroughDates, fixedPriceItems);
+        addInvoiceItemsToChargeThroughDates(chargeThroughDates, recurringItems);
+
+        for (UUID subscriptionId : chargeThroughDates.keySet()) {
+            if(subscriptionId != null) {
+                DateTime chargeThroughDate = chargeThroughDates.get(subscriptionId);
+                log.info("Setting CTD for subscription {} to {}", subscriptionId.toString(), chargeThroughDate.toString());
+                billingApi.setChargedThroughDate(subscriptionId, chargeThroughDate, context);
+            }
+        }
+    }
+    
+    private void postEvent(final BusEvent event, final UUID accountId) {
+        try {
+            eventBus.post(event);
+        } catch (EventBusException e){
+            log.error(String.format("Failed to post event {} for account {} ", event.getBusEventType(), accountId), e);
+        }
+    }
+
+
+    private void addInvoiceItemsToChargeThroughDates(Map<UUID, DateTime> chargeThroughDates, Collection<InvoiceItem> items) {
+        for (InvoiceItem item : items) {
+            UUID subscriptionId = item.getSubscriptionId();
+            DateTime endDate = item.getEndDate();
+
+            if (chargeThroughDates.containsKey(subscriptionId)) {
+                if (chargeThroughDates.get(subscriptionId).isBefore(endDate)) {
+                    chargeThroughDates.put(subscriptionId, endDate);
+                }
+            } else {
+                chargeThroughDates.put(subscriptionId, endDate);
+            }
+        }
+    }
+
+
     private void outputDebugData(Collection<BillingEvent> events, Collection<Invoice> invoices) {
         if (VERBOSE_OUTPUT) {
             log.info("Events");
diff --git a/invoice/src/main/java/com/ning/billing/invoice/InvoiceListener.java b/invoice/src/main/java/com/ning/billing/invoice/InvoiceListener.java
index ff23965..66f423e 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/InvoiceListener.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/InvoiceListener.java
@@ -28,7 +28,9 @@ import org.slf4j.LoggerFactory;
 
 import com.google.common.eventbus.Subscribe;
 import com.google.inject.Inject;
-import com.ning.billing.entitlement.api.user.SubscriptionTransition;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
+import com.ning.billing.entitlement.api.timeline.RepairEntitlementEvent;
+import com.ning.billing.entitlement.api.user.SubscriptionEvent;
 import com.ning.billing.invoice.api.InvoiceApiException;
 
 public class InvoiceListener {
@@ -43,9 +45,26 @@ public class InvoiceListener {
     }
 
     @Subscribe
-    public void handleSubscriptionTransition(final SubscriptionTransition transition) {
+    public void handleRepairEntitlementEvent(final RepairEntitlementEvent repairEvent) {
         try {
-            CallContext context = factory.createCallContext("Transition", CallOrigin.INTERNAL, UserType.SYSTEM);
+            CallContext context = factory.createCallContext("RepairBundle", CallOrigin.INTERNAL, UserType.SYSTEM, repairEvent.getUserToken());
+            dispatcher.processAccount(repairEvent.getAccountId(), repairEvent.getEffectiveDate(), false, context);
+        } catch (InvoiceApiException e) {
+            log.error(e.getMessage());
+        }
+    }
+    
+    @Subscribe
+    public void handleSubscriptionTransition(final SubscriptionEvent transition) {
+        try {
+            //  Skip future uncancel event
+            //  Skip events which are marked as not being the last one
+            if (transition.getTransitionType() == SubscriptionTransitionType.UNCANCEL 
+                    || transition.getRemainingEventsForUserOperation() > 0) {
+                return;
+            }
+
+            CallContext context = factory.createCallContext("Transition", CallOrigin.INTERNAL, UserType.SYSTEM, transition.getUserToken());
         	dispatcher.processSubscription(transition, context);
         } catch (InvoiceApiException e) {
             log.error(e.getMessage());
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/BillingEventSet.java b/invoice/src/main/java/com/ning/billing/invoice/model/BillingEventSet.java
index da95559..8cf1e1e 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/BillingEventSet.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/BillingEventSet.java
@@ -28,7 +28,10 @@ public class BillingEventSet extends ArrayList<BillingEvent> {
 
     public BillingEventSet(Collection<BillingEvent> events) {
         super();
-        addAll(events);
+        if(events != null) {
+            addAll(events);
+            
+        }
     }
 
     public boolean isLast(final BillingEvent event) {
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/CreditInvoiceItem.java b/invoice/src/main/java/com/ning/billing/invoice/model/CreditInvoiceItem.java
new file mode 100644
index 0000000..ac800a7
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/CreditInvoiceItem.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2010-2011 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.invoice.model;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.InvoiceItem;
+import org.joda.time.DateTime;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+public class CreditInvoiceItem extends InvoiceItemBase {
+    public CreditInvoiceItem(UUID invoiceId, UUID accountId, DateTime date, BigDecimal amount, Currency currency) {
+        this(UUID.randomUUID(), invoiceId, accountId, date, amount, currency);
+    }
+
+    public CreditInvoiceItem(UUID id, UUID invoiceId, UUID accountId, DateTime date, BigDecimal amount, Currency currency) {
+        super(id, invoiceId, accountId, null, null, null, null, date, date, amount, currency);
+    }
+
+    @Override
+    public InvoiceItem asReversingItem() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getDescription() {
+        return "Credit";
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        CreditInvoiceItem that = (CreditInvoiceItem) o;
+
+        if (accountId.compareTo(that.accountId) != 0) return false;
+        if (amount.compareTo(that.amount) != 0) return false;
+        if (currency != that.currency) return false;
+        if (startDate.compareTo(that.startDate) != 0) return false;
+        if (endDate.compareTo(that.endDate) != 0) return false;
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = accountId.hashCode();
+        result = 31 * result + startDate.hashCode();
+        result = 31 * result + endDate.hashCode();
+        result = 31 * result + amount.hashCode();
+        result = 31 * result + currency.hashCode();
+        return result;
+    }
+
+    @Override
+    public int compareTo(InvoiceItem item) {
+        if (!(item instanceof CreditInvoiceItem)) {
+            return 1;
+        }
+
+        CreditInvoiceItem that = (CreditInvoiceItem) item;
+        return id.compareTo(that.getId());
+    }
+}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoice.java b/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoice.java
index 1467415..8f38437 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoice.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoice.java
@@ -25,6 +25,7 @@ import javax.annotation.Nullable;
 
 import com.ning.billing.util.callcontext.CallContext;
 import com.ning.billing.util.customfield.CustomField;
+import com.ning.billing.util.dao.ObjectType;
 import com.ning.billing.util.entity.ExtendedEntityBase;
 import org.joda.time.DateTime;
 
@@ -45,13 +46,13 @@ public class DefaultInvoice extends ExtendedEntityBase implements Invoice {
 
     // used to create a new invoice
     public DefaultInvoice(UUID accountId, DateTime invoiceDate, DateTime targetDate, Currency currency) {
-        this(UUID.randomUUID(), accountId, null, invoiceDate, targetDate, currency, false, null, null);
+        this(UUID.randomUUID(), accountId, null, invoiceDate, targetDate, currency, false);
     }
 
     // used to hydrate invoice from persistence layer
     public DefaultInvoice(UUID invoiceId, UUID accountId, @Nullable Integer invoiceNumber, DateTime invoiceDate,
-                          DateTime targetDate, Currency currency, boolean isMigrationInvoice, @Nullable String createdBy, @Nullable DateTime createdDate) {
-        super(invoiceId, createdBy, createdDate);
+                          DateTime targetDate, Currency currency, boolean isMigrationInvoice) {
+        super(invoiceId);
         this.accountId = accountId;
         this.invoiceNumber = invoiceNumber;
         this.invoiceDate = invoiceDate;
@@ -112,11 +113,6 @@ public class DefaultInvoice extends ExtendedEntityBase implements Invoice {
     }
 
     @Override
-    public UUID getId() {
-        return id;
-    }
-
-    @Override
     public UUID getAccountId() {
         return accountId;
     }
@@ -196,11 +192,7 @@ public class DefaultInvoice extends ExtendedEntityBase implements Invoice {
         }
 
         DateTime lastPaymentAttempt = getLastPaymentAttempt();
-        if (lastPaymentAttempt == null) {
-            return true;
-        }
-
-        return !lastPaymentAttempt.plusDays(numberOfDays).isAfter(targetDate);
+        return (lastPaymentAttempt == null) || lastPaymentAttempt.plusDays(numberOfDays).isAfter(targetDate);
     }
 
     @Override
@@ -209,8 +201,8 @@ public class DefaultInvoice extends ExtendedEntityBase implements Invoice {
     }
 
     @Override
-    public String getObjectName() {
-        return Invoice.ObjectType;
+    public ObjectType getObjectType() {
+        return ObjectType.INVOICE;
     }
 
     @Override
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoiceGenerator.java b/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoiceGenerator.java
index 389010c..c24215c 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoiceGenerator.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoiceGenerator.java
@@ -16,6 +16,20 @@
 
 package com.ning.billing.invoice.model;
 
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import javax.annotation.Nullable;
+
+import org.joda.time.DateTime;
+import org.joda.time.Months;
+
 import com.google.inject.Inject;
 import com.ning.billing.ErrorCode;
 import com.ning.billing.catalog.api.BillingPeriod;
@@ -28,16 +42,6 @@ import com.ning.billing.invoice.api.Invoice;
 import com.ning.billing.invoice.api.InvoiceApiException;
 import com.ning.billing.invoice.api.InvoiceItem;
 import com.ning.billing.util.clock.Clock;
-import org.joda.time.DateTime;
-import org.joda.time.Months;
-
-import java.math.BigDecimal;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-import java.util.UUID;
-import javax.annotation.Nullable;
 
 public class DefaultInvoiceGenerator implements InvoiceGenerator {
     private static final int ROUNDING_MODE = InvoicingConfiguration.getRoundingMode();
@@ -57,9 +61,9 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
     */
     @Override
     public Invoice generateInvoice(final UUID accountId, @Nullable final BillingEventSet events,
-                                   @Nullable final List<Invoice> existingInvoices,
-                                   DateTime targetDate,
-                                   final Currency targetCurrency) throws InvoiceApiException {
+                                         @Nullable final List<Invoice> existingInvoices,
+                                         DateTime targetDate,
+                                         final Currency targetCurrency) throws InvoiceApiException {
         if ((events == null) || (events.size() == 0)) {
             return null;
         }
@@ -89,18 +93,92 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
         for (InvoiceItem existingItem : existingItems) {
             if (existingItem instanceof RecurringInvoiceItem) {
                 RecurringInvoiceItem recurringItem = (RecurringInvoiceItem) existingItem;
-                proposedItems.add(recurringItem.asCredit());
+                proposedItems.add(recurringItem.asReversingItem());
             }
         }
 
+        //addCreditItems(accountId, proposedItems, existingInvoices, targetCurrency);
+
         if (proposedItems == null || proposedItems.size() == 0) {
             return null;
         } else {
             invoice.addInvoiceItems(proposedItems);
+
             return invoice;
         }
     }
 
+   /*
+    * ensures that the balance of all invoices are zero or positive, adding an adjusting credit item if needed
+    */
+    private void addCreditItems(UUID accountId, List<InvoiceItem> invoiceItems, List<Invoice> invoices, Currency currency) {
+        Map<UUID, BigDecimal> invoiceBalances = new HashMap<UUID, BigDecimal>();
+
+        updateInvoiceBalance(invoiceItems, invoiceBalances);
+
+        // add all existing items and payments
+        if (invoices != null) {
+            for (Invoice invoice : invoices) {
+                updateInvoiceBalance(invoice.getInvoiceItems(), invoiceBalances);
+            }
+
+            for (Invoice invoice : invoices) {
+                UUID invoiceId = invoice.getId();
+                invoiceBalances.put(invoiceId, invoiceBalances.get(invoiceId).subtract(invoice.getAmountPaid()));
+            }
+        }
+
+        BigDecimal creditTotal = BigDecimal.ZERO;
+
+        for (UUID invoiceId : invoiceBalances.keySet()) {
+            BigDecimal balance = invoiceBalances.get(invoiceId);
+            if (balance.compareTo(BigDecimal.ZERO) < 0) {
+                creditTotal = creditTotal.add(balance.negate());
+                invoiceItems.add(new CreditInvoiceItem(invoiceId, accountId, clock.getUTCNow(), balance, currency));
+            }
+        }
+
+        if (creditTotal.compareTo(BigDecimal.ZERO) != 0) {
+            // create a single credit item to cover all credits
+            //invoiceItems.add(new CreditInvoiceItem());
+        }
+    }
+
+    private void updateInvoiceBalance(List<InvoiceItem> items, Map<UUID, BigDecimal> invoiceBalances) {
+        for (InvoiceItem item : items) {
+            UUID invoiceId = item.getInvoiceId();
+
+            if (!invoiceBalances.containsKey(invoiceId)) {
+                invoiceBalances.put(invoiceId, BigDecimal.ZERO);
+            }
+
+            invoiceBalances.put(invoiceId, invoiceBalances.get(invoiceId).add(item.getAmount()));
+        }
+    }
+
+    @Override
+    public void distributeItems(List<Invoice> invoices) {
+        Map<UUID, Invoice> invoiceMap = new HashMap<UUID, Invoice>();
+
+        for (Invoice invoice : invoices) {
+            invoiceMap.put(invoice.getId(), invoice);
+        }
+
+        for (final Invoice invoice: invoices) {
+            Iterator<InvoiceItem> itemIterator = invoice.getInvoiceItems().iterator();
+            final UUID invoiceId = invoice.getId();
+
+            while (itemIterator.hasNext()) {
+                InvoiceItem item = itemIterator.next();
+
+                if (!item.getInvoiceId().equals(invoiceId)) {
+                    invoiceMap.get(item.getInvoiceId()).addInvoiceItem(item);
+                    itemIterator.remove();
+                }
+            }
+        }
+    }
+
     private void validateTargetDate(DateTime targetDate) throws InvoiceApiException {
         int maximumNumberOfMonths = config.getNumberOfMonthsInFuture();
 
@@ -211,7 +289,9 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
                     if (rate != null) {
                         BigDecimal amount = itemDatum.getNumberOfCycles().multiply(rate).setScale(NUMBER_OF_DECIMALS, ROUNDING_MODE);
 
-                        RecurringInvoiceItem recurringItem = new RecurringInvoiceItem(invoiceId, accountId,
+                        RecurringInvoiceItem recurringItem = new RecurringInvoiceItem(invoiceId, 
+                                accountId,
+                                thisEvent.getSubscription().getBundleId(), 
                                 thisEvent.getSubscription().getId(),
                                 thisEvent.getPlan().getName(),
                                 thisEvent.getPlanPhase().getName(),
@@ -246,7 +326,8 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
                 Duration duration = thisEvent.getPlanPhase().getDuration();
                 DateTime endDate = duration.addToDateTime(thisEvent.getEffectiveDate());
 
-                return new FixedPriceInvoiceItem(invoiceId, accountId, thisEvent.getSubscription().getId(),
+                return new FixedPriceInvoiceItem(invoiceId, accountId, thisEvent.getSubscription().getBundleId(),
+                                                 thisEvent.getSubscription().getId(),
                                                  thisEvent.getPlan().getName(), thisEvent.getPlanPhase().getName(),
                                                  thisEvent.getEffectiveDate(), endDate, fixedPrice, currency);
             } else {
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoicePayment.java b/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoicePayment.java
index 22eb5e6..ba02039 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoicePayment.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoicePayment.java
@@ -33,18 +33,17 @@ public class DefaultInvoicePayment extends EntityBase implements InvoicePayment 
     private final Currency currency;
 
     public DefaultInvoicePayment(final UUID paymentAttemptId, final UUID invoiceId, final DateTime paymentDate) {
-        this(UUID.randomUUID(), paymentAttemptId, invoiceId, paymentDate, null, null, null, null);
+        this(UUID.randomUUID(), paymentAttemptId, invoiceId, paymentDate, null, null);
     }
 
     public DefaultInvoicePayment(final UUID paymentAttemptId, final UUID invoiceId, final DateTime paymentDate,
                                  final BigDecimal amount, final Currency currency) {
-        this(UUID.randomUUID(), paymentAttemptId, invoiceId, paymentDate, amount, currency, null, null);
+        this(UUID.randomUUID(), paymentAttemptId, invoiceId, paymentDate, amount, currency);
     }
 
     public DefaultInvoicePayment(final UUID id, final UUID paymentAttemptId, final UUID invoiceId, final DateTime paymentDate,
-                                 @Nullable final BigDecimal amount, @Nullable final Currency currency,
-                                 @Nullable final String createdBy, @Nullable final DateTime createdDate) {
-        super(id, createdBy, createdDate);
+                                 @Nullable final BigDecimal amount, @Nullable final Currency currency) {
+        super(id);
         this.paymentAttemptId = paymentAttemptId;
         this.amount = amount;
         this.invoiceId = invoiceId;
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/FixedPriceInvoiceItem.java b/invoice/src/main/java/com/ning/billing/invoice/model/FixedPriceInvoiceItem.java
index 91e49fa..b5a79ab 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/FixedPriceInvoiceItem.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/FixedPriceInvoiceItem.java
@@ -16,27 +16,28 @@
 
 package com.ning.billing.invoice.model;
 
-import com.ning.billing.catalog.api.Currency;
-import com.ning.billing.invoice.api.InvoiceItem;
-import org.joda.time.DateTime;
-
 import java.math.BigDecimal;
 import java.util.UUID;
 
+import org.joda.time.DateTime;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.InvoiceItem;
+
 public class FixedPriceInvoiceItem extends InvoiceItemBase {
-    public FixedPriceInvoiceItem(UUID invoiceId, UUID accountId, UUID subscriptionId, String planName, String phaseName,
+
+    public FixedPriceInvoiceItem(UUID invoiceId, UUID accountId, UUID bundleId, UUID subscriptionId, String planName, String phaseName,
                                  DateTime startDate, DateTime endDate, BigDecimal amount, Currency currency) {
-        super(invoiceId, accountId, subscriptionId, planName, phaseName, startDate, endDate, amount, currency);
+        super(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, amount, currency);
     }
 
-    public FixedPriceInvoiceItem(UUID id, UUID invoiceId, UUID accountId, UUID subscriptionId, String planName, String phaseName,
-                                 DateTime startDate, DateTime endDate, BigDecimal amount, Currency currency,
-                                 String createdBy, DateTime createdDate) {
-        super(id, invoiceId, accountId, subscriptionId, planName, phaseName, startDate, endDate, amount, currency, createdBy, createdDate);
+    public FixedPriceInvoiceItem(UUID id, UUID invoiceId, UUID accountId, UUID bundleId, UUID subscriptionId, String planName, String phaseName,
+                                 DateTime startDate, DateTime endDate, BigDecimal amount, Currency currency) {
+        super(id, invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, amount, currency);
     }
 
     @Override
-    public InvoiceItem asCredit() {
+    public InvoiceItem asReversingItem() {
         throw new UnsupportedOperationException();
     }
 
@@ -49,6 +50,7 @@ public class FixedPriceInvoiceItem extends InvoiceItemBase {
     public int hashCode() {
         int result = accountId.hashCode();
         result = 31 * result + (subscriptionId != null ? subscriptionId.hashCode() : 0);
+        result = 31 * result + (bundleId != null ? bundleId.hashCode() : 0);
         result = 31 * result + (planName != null ? planName.hashCode() : 0);
         result = 31 * result + (phaseName != null ? phaseName.hashCode() : 0);
         result = 31 * result + (startDate != null ? startDate.hashCode() : 0);
@@ -66,12 +68,17 @@ public class FixedPriceInvoiceItem extends InvoiceItemBase {
 
         FixedPriceInvoiceItem that = (FixedPriceInvoiceItem) item;
         int compareAccounts = getAccountId().compareTo(that.getAccountId());
-        if (compareAccounts == 0) {
-            int compareSubscriptions = getSubscriptionId().compareTo(that.getSubscriptionId());
-            if (compareSubscriptions == 0) {
-                return getStartDate().compareTo(that.getStartDate());
+        if (compareAccounts == 0 && bundleId != null) {
+            int compareBundles = getBundleId().compareTo(that.getBundleId());
+            if (compareBundles == 0 && subscriptionId != null) {
+                int compareSubscriptions = getSubscriptionId().compareTo(that.getSubscriptionId());
+                if (compareSubscriptions == 0) {
+                    return getStartDate().compareTo(that.getStartDate());
+                } else {
+                    return compareSubscriptions;
+                }
             } else {
-                return compareSubscriptions;
+                return compareBundles;
             }
         } else {
             return compareAccounts;
@@ -84,7 +91,8 @@ public class FixedPriceInvoiceItem extends InvoiceItemBase {
         sb.append("InvoiceItem = {").append("id = ").append(id.toString()).append(", ");
         sb.append("invoiceId = ").append(invoiceId.toString()).append(", ");
         sb.append("accountId = ").append(accountId.toString()).append(", ");
-        sb.append("subscriptionId = ").append(subscriptionId.toString()).append(", ");
+        sb.append("subscriptionId = ").append(subscriptionId == null ? null : subscriptionId.toString()).append(", ");
+        sb.append("bundleId = ").append(bundleId == null ? null : bundleId.toString()).append(", ");
         sb.append("planName = ").append(planName).append(", ");
         sb.append("phaseName = ").append(phaseName).append(", ");
         sb.append("startDate = ").append(startDate.toString()).append(", ");
@@ -110,6 +118,8 @@ public class FixedPriceInvoiceItem extends InvoiceItemBase {
         if (accountId.compareTo(that.accountId) != 0) return false;
         if (subscriptionId != null ? !subscriptionId.equals(that.subscriptionId) : that.subscriptionId != null)
             return false;
+        if (bundleId != null ? !bundleId.equals(that.bundleId) : that.bundleId != null)
+            return false;
         if (amount != null ? amount.compareTo(that.amount) != 0 : that.amount != null) return false;
         if (currency != that.currency) return false;
         if (startDate != null ? startDate.compareTo(that.startDate) != 0 : that.startDate != null) return false;
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceGenerator.java b/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceGenerator.java
index 01ebf34..f903087 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceGenerator.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceGenerator.java
@@ -19,6 +19,7 @@ package com.ning.billing.invoice.model;
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.invoice.api.Invoice;
 import com.ning.billing.invoice.api.InvoiceApiException;
+import com.ning.billing.invoice.api.InvoiceItem;
 import org.joda.time.DateTime;
 
 import javax.annotation.Nullable;
@@ -26,5 +27,8 @@ import java.util.List;
 import java.util.UUID;
 
 public interface InvoiceGenerator {
-    public Invoice generateInvoice(UUID accountId, @Nullable BillingEventSet events, @Nullable List<Invoice> existingInvoices, DateTime targetDate, Currency targetCurrency) throws InvoiceApiException;
+    public Invoice generateInvoice(UUID accountId, @Nullable BillingEventSet events, @Nullable List<Invoice> existingInvoices,
+                                   DateTime targetDate, Currency targetCurrency) throws InvoiceApiException;
+
+    public void distributeItems(List<Invoice> invoices);
 }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceItemBase.java b/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceItemBase.java
index 2f16468..5ccaa8c 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceItemBase.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceItemBase.java
@@ -29,6 +29,7 @@ public abstract class InvoiceItemBase extends EntityBase implements InvoiceItem 
     protected final UUID invoiceId;
     protected final UUID accountId;
     protected final UUID subscriptionId;
+    protected final UUID bundleId;
     protected final String planName;
     protected final String phaseName;
     protected final DateTime startDate;
@@ -36,19 +37,20 @@ public abstract class InvoiceItemBase extends EntityBase implements InvoiceItem 
     protected final BigDecimal amount;
     protected final Currency currency;
 
-    public InvoiceItemBase(UUID invoiceId, UUID accountId, @Nullable UUID subscriptionId, String planName, String phaseName,
-                           DateTime startDate, DateTime endDate, BigDecimal amount, Currency currency) {
-        this(UUID.randomUUID(), invoiceId, accountId,  subscriptionId, planName, phaseName,
-                startDate, endDate, amount, currency, null, null);
+    public InvoiceItemBase(UUID invoiceId, UUID accountId, UUID bundleId, UUID subscriptionId, String planName, String phaseName,
+            DateTime startDate, DateTime endDate, BigDecimal amount, Currency currency) {
+        this(UUID.randomUUID(), invoiceId, accountId, bundleId, subscriptionId, planName, phaseName,
+                startDate, endDate, amount, currency);
     }
 
-    public InvoiceItemBase(UUID id, UUID invoiceId, UUID accountId, @Nullable UUID subscriptionId, String planName, String phaseName,
-                           DateTime startDate, DateTime endDate, BigDecimal amount, Currency currency,
-                           @Nullable String createdBy, @Nullable DateTime createdDate) {
-        super(id, createdBy, createdDate);
+    public InvoiceItemBase(UUID id, UUID invoiceId, UUID accountId, @Nullable UUID bundleId,
+                           @Nullable UUID subscriptionId, String planName, String phaseName,
+                           DateTime startDate, DateTime endDate, BigDecimal amount, Currency currency) {
+        super(id);
         this.invoiceId = invoiceId;
         this.accountId = accountId;
         this.subscriptionId = subscriptionId;
+        this.bundleId = bundleId;
         this.planName = planName;
         this.phaseName = phaseName;
         this.startDate = startDate;
@@ -57,21 +59,16 @@ public abstract class InvoiceItemBase extends EntityBase implements InvoiceItem 
         this.currency = currency;
     }
 
-    public DateTime getCreatedDate() {
-        return createdDate;
-    }
-
-    @Override
-    public UUID getId() {
-        return id;
-    }
-
     @Override
     public UUID getInvoiceId() {
         return invoiceId;
     }
 
     @Override
+    public UUID getBundleId() {
+        return bundleId;
+    }
+    
     public UUID getAccountId() {
         return accountId;
     }
@@ -112,7 +109,7 @@ public abstract class InvoiceItemBase extends EntityBase implements InvoiceItem 
     }
 
     @Override
-    public abstract InvoiceItem asCredit();
+    public abstract InvoiceItem asReversingItem();
 
     @Override
     public abstract String getDescription();
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/MigrationInvoiceItem.java b/invoice/src/main/java/com/ning/billing/invoice/model/MigrationInvoiceItem.java
index 01c1296..ec0962b 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/MigrationInvoiceItem.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/MigrationInvoiceItem.java
@@ -25,10 +25,9 @@ import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.catalog.api.MigrationPlan;
 
 public class MigrationInvoiceItem extends FixedPriceInvoiceItem {
-	private final static UUID MIGRATION_SUBSCRIPTION_ID = UUID.fromString("ed25f954-3aa2-4422-943b-c3037ad7257c"); //new UUID(0L,0L);
 
 	public MigrationInvoiceItem(UUID invoiceId, UUID accountId, DateTime startDate, BigDecimal amount, Currency currency) {
-		super(invoiceId, accountId, MIGRATION_SUBSCRIPTION_ID, MigrationPlan.MIGRATION_PLAN_NAME, MigrationPlan.MIGRATION_PLAN_PHASE_NAME,
+		super(invoiceId, accountId, null, null, MigrationPlan.MIGRATION_PLAN_NAME, MigrationPlan.MIGRATION_PLAN_PHASE_NAME,
               startDate, startDate, amount, currency);
 	}
 }
\ No newline at end of file
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/RecurringInvoiceItem.java b/invoice/src/main/java/com/ning/billing/invoice/model/RecurringInvoiceItem.java
index f63599d..bc813fc 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/RecurringInvoiceItem.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/RecurringInvoiceItem.java
@@ -27,51 +27,50 @@ public class RecurringInvoiceItem extends InvoiceItemBase {
     private final BigDecimal rate;
     private final UUID reversedItemId;
 
-    public RecurringInvoiceItem(UUID invoiceId, UUID accountId, UUID subscriptionId, String planName, String phaseName,
+    public RecurringInvoiceItem(UUID invoiceId, UUID accountId, UUID bundleId, UUID subscriptionId, String planName, String phaseName,
                                 DateTime startDate, DateTime endDate,
                                 BigDecimal amount, BigDecimal rate,
-                                Currency currency) {
-        super(invoiceId, accountId, subscriptionId, planName, phaseName, startDate, endDate, amount, currency);
+                                Currency currency) { 
+        super(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, amount, currency);
         this.rate = rate;
         this.reversedItemId = null;
     }
 
-    public RecurringInvoiceItem(UUID invoiceId, UUID accountId, UUID subscriptionId, String planName, String phaseName,
+    public RecurringInvoiceItem(UUID invoiceId, UUID accountId, UUID bundleId, UUID subscriptionId, String planName, String phaseName,
                                 DateTime startDate, DateTime endDate,
                                 BigDecimal amount, BigDecimal rate,
                                 Currency currency, UUID reversedItemId) {
-        super(invoiceId, accountId, subscriptionId, planName, phaseName, startDate, endDate,
+        super(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate,
                 amount, currency);
         this.rate = rate;
         this.reversedItemId = reversedItemId;
     }
 
-    public RecurringInvoiceItem(UUID id, UUID invoiceId, UUID accountId, UUID subscriptionId, String planName, String phaseName,
+    public RecurringInvoiceItem(UUID id, UUID invoiceId, UUID accountId, UUID bundleId, UUID subscriptionId,
+                                String planName, String phaseName,
                                 DateTime startDate, DateTime endDate,
                                 BigDecimal amount, BigDecimal rate,
-                                Currency currency,
-                                String createdBy, DateTime createdDate) {
-        super(id, invoiceId, accountId, subscriptionId, planName, phaseName, startDate, endDate, amount, currency, createdBy, createdDate);
-
+                                Currency currency) {
+        super(id, invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, amount, currency);
         this.rate = rate;
         this.reversedItemId = null;
     }
 
-    public RecurringInvoiceItem(UUID id, UUID invoiceId, UUID accountId, UUID subscriptionId, String planName, String phaseName,
+    public RecurringInvoiceItem(UUID id, UUID invoiceId, UUID accountId, UUID bundleId, UUID subscriptionId,
+                                String planName, String phaseName,
                                 DateTime startDate, DateTime endDate,
                                 BigDecimal amount, BigDecimal rate,
-                                Currency currency, UUID reversedItemId,
-                                String createdBy, DateTime createdDate) {
-        super(id, invoiceId, accountId, subscriptionId, planName, phaseName, startDate, endDate, amount, currency, createdBy, createdDate);
-
+                                Currency currency, UUID reversedItemId) {
+        super(id, invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, amount, currency);
         this.rate = rate;
         this.reversedItemId = reversedItemId;
     }
 
     @Override
-    public InvoiceItem asCredit() {
+    public InvoiceItem asReversingItem() {
         BigDecimal amountNegated = amount == null ? null : amount.negate();
-        return new RecurringInvoiceItem(invoiceId, accountId, subscriptionId, planName, phaseName, startDate, endDate,
+
+        return new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate,
                 amountNegated, rate, currency, id);
     }
 
@@ -103,17 +102,23 @@ public class RecurringInvoiceItem extends InvoiceItemBase {
 
         RecurringInvoiceItem that = (RecurringInvoiceItem) item;
         int compareAccounts = getAccountId().compareTo(that.getAccountId());
-        if (compareAccounts == 0) {
-            int compareSubscriptions = getSubscriptionId().compareTo(that.getSubscriptionId());
-            if (compareSubscriptions == 0) {
-                int compareStartDates = getStartDate().compareTo(that.getStartDate());
-                if (compareStartDates == 0) {
-                    return getEndDate().compareTo(that.getEndDate());
+        if (compareAccounts == 0 && bundleId != null) {
+            int compareBundles = getBundleId().compareTo(that.getBundleId());
+            if (compareBundles == 0 && subscriptionId != null) {
+
+                int compareSubscriptions = getSubscriptionId().compareTo(that.getSubscriptionId());
+                if (compareSubscriptions == 0) {
+                    int compareStartDates = getStartDate().compareTo(that.getStartDate());
+                    if (compareStartDates == 0) {
+                        return getEndDate().compareTo(that.getEndDate());
+                    } else {
+                        return compareStartDates;
+                    }
                 } else {
-                    return compareStartDates;
+                    return compareSubscriptions;
                 }
             } else {
-                return compareSubscriptions;
+                return compareBundles;
             }
         } else {
             return compareAccounts;
@@ -137,7 +142,10 @@ public class RecurringInvoiceItem extends InvoiceItemBase {
         if (rate.compareTo(that.rate) != 0) return false;
         if (reversedItemId != null ? !reversedItemId.equals(that.reversedItemId) : that.reversedItemId != null)
             return false;
-        if (!subscriptionId.equals(that.subscriptionId)) return false;
+        if (subscriptionId != null ? !subscriptionId.equals(that.subscriptionId) : that.subscriptionId != null)
+            return false;
+        if (bundleId != null ? !bundleId.equals(that.bundleId) : that.bundleId != null)
+            return false;
 
         return true;
     }
@@ -146,6 +154,7 @@ public class RecurringInvoiceItem extends InvoiceItemBase {
     public int hashCode() {
         int result = accountId.hashCode();
         result = 31 * result + (subscriptionId != null ? subscriptionId.hashCode() : 0);
+        result = 31 * result + (bundleId != null ? bundleId.hashCode() : 0);
         result = 31 * result + planName.hashCode();
         result = 31 * result + phaseName.hashCode();
         result = 31 * result + startDate.hashCode();
@@ -165,6 +174,8 @@ public class RecurringInvoiceItem extends InvoiceItemBase {
         sb.append(startDate.toString()).append(", ");
         sb.append(endDate.toString()).append(", ");
         sb.append(amount.toString()).append(", ");
+        sb.append("subscriptionId = ").append(subscriptionId == null ? null : subscriptionId.toString()).append(", ");
+        sb.append("bundleId = ").append(bundleId == null ? null : bundleId.toString()).append(", ");
 
         return sb.toString();
     }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/notification/DefaultNextBillingDateNotifier.java b/invoice/src/main/java/com/ning/billing/invoice/notification/DefaultNextBillingDateNotifier.java
index 03dc211..d61e206 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/notification/DefaultNextBillingDateNotifier.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/notification/DefaultNextBillingDateNotifier.java
@@ -19,19 +19,17 @@ package com.ning.billing.invoice.notification;
 import java.util.UUID;
 
 import org.joda.time.DateTime;
-import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import com.google.inject.Inject;
 import com.ning.billing.config.InvoiceConfig;
+import com.ning.billing.config.NotificationConfig;
 import com.ning.billing.entitlement.api.user.EntitlementUserApi;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
 import com.ning.billing.entitlement.api.user.Subscription;
 import com.ning.billing.invoice.InvoiceListener;
 import com.ning.billing.invoice.api.DefaultInvoiceService;
-import com.ning.billing.util.bus.Bus;
-import com.ning.billing.util.notificationq.NotificationConfig;
-import com.ning.billing.util.notificationq.NotificationKey;
 import com.ning.billing.util.notificationq.NotificationQueue;
 import com.ning.billing.util.notificationq.NotificationQueueService;
 import com.ning.billing.util.notificationq.NotificationQueueService.NotificationQueueAlreadyExists;
@@ -68,15 +66,19 @@ public class DefaultNextBillingDateNotifier implements  NextBillingDateNotifier 
                     new NotificationQueueHandler() {
                 @Override
                 public void handleReadyNotification(String notificationKey, DateTime eventDate) {
-                	try {
-                 		UUID key = UUID.fromString(notificationKey);
-                        Subscription subscription = entitlementUserApi.getSubscriptionFromId(key);
-                        if (subscription == null) {
-                            log.warn("Next Billing Date Notification Queue handled spurious notification (key: " + key + ")" );
-                        } else {
-                            processEvent(key , eventDate);
+                    try {
+                        UUID key = UUID.fromString(notificationKey);
+                        try {
+                            Subscription subscription = entitlementUserApi.getSubscriptionFromId(key);
+                            if (subscription == null) {
+                                log.warn("Next Billing Date Notification Queue handled spurious notification (key: " + key + ")" );
+                            } else {
+                                processEvent(key , eventDate);
+                            }
+                        } catch (EntitlementUserApiException e) {
+                            log.warn("Next Billing Date Notification Queue handled spurious notification (key: " + key + ")", e );
                         }
-                	} catch (IllegalArgumentException e) {
+                    } catch (IllegalArgumentException e) {
                 		log.error("The key returned from the NextBillingNotificationQueue is not a valid UUID", e);
                 		return;
                 	}
@@ -84,21 +86,15 @@ public class DefaultNextBillingDateNotifier implements  NextBillingDateNotifier 
                 }
             },
             new NotificationConfig() {
+                
                 @Override
-                public boolean isNotificationProcessingOff() {
-                    return config.isEventProcessingOff();
-                }
-                @Override
-                public long getNotificationSleepTimeMs() {
-                    return config.getNotificationSleepTimeMs();
+                public long getSleepTimeMs() {
+                    return config.getSleepTimeMs();
                 }
+                
                 @Override
-                public int getDaoMaxReadyEvents() {
-                    return config.getDaoMaxReadyEvents();
-                }
-                @Override
-                public long getDaoClaimTimeMs() {
-                    return config.getDaoClaimTimeMs();
+                public boolean isNotificationProcessingOff() {
+                    return config.isNotificationProcessingOff();
                 }
             });
         } catch (NotificationQueueAlreadyExists e) {
diff --git a/invoice/src/main/java/com/ning/billing/invoice/notification/DefaultNextBillingDatePoster.java b/invoice/src/main/java/com/ning/billing/invoice/notification/DefaultNextBillingDatePoster.java
index 8ddb29d..4aad036 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/notification/DefaultNextBillingDatePoster.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/notification/DefaultNextBillingDatePoster.java
@@ -31,7 +31,8 @@ import com.ning.billing.util.notificationq.NotificationQueueService;
 import com.ning.billing.util.notificationq.NotificationQueueService.NoSuchNotificationQueue;
 
 public class DefaultNextBillingDatePoster implements NextBillingDatePoster {
-    private final static Logger log = LoggerFactory.getLogger(DefaultNextBillingDateNotifier.class);
+    
+    private final static Logger log = LoggerFactory.getLogger(DefaultNextBillingDatePoster.class);
 
 	private final NotificationQueueService notificationQueueService;
 
diff --git a/invoice/src/main/java/com/ning/billing/invoice/notification/EmailInvoiceNotifier.java b/invoice/src/main/java/com/ning/billing/invoice/notification/EmailInvoiceNotifier.java
new file mode 100644
index 0000000..58026b2
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/notification/EmailInvoiceNotifier.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2010-2011 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.invoice.notification;
+
+import com.google.inject.Inject;
+import com.ning.billing.ErrorCode;
+import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.AccountEmail;
+import com.ning.billing.account.api.AccountUserApi;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceApiException;
+import com.ning.billing.invoice.api.InvoiceNotifier;
+import com.ning.billing.invoice.template.HtmlInvoiceGenerator;
+import com.ning.billing.util.email.DefaultEmailSender;
+import com.ning.billing.util.email.EmailApiException;
+import com.ning.billing.util.email.EmailConfig;
+import com.ning.billing.util.email.EmailSender;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+public class EmailInvoiceNotifier implements InvoiceNotifier {
+    private final AccountUserApi accountUserApi;
+    private final HtmlInvoiceGenerator generator;
+    private final EmailConfig config;
+
+    @Inject
+    public EmailInvoiceNotifier(AccountUserApi accountUserApi, HtmlInvoiceGenerator generator, EmailConfig config) {
+        this.accountUserApi = accountUserApi;
+        this.generator = generator;
+        this.config = config;
+    }
+
+    @Override
+    public void notify(Account account, Invoice invoice) throws InvoiceApiException {
+        List<String> to = new ArrayList<String>();
+        to.add(account.getEmail());
+
+        List<AccountEmail> accountEmailList = accountUserApi.getEmails(account.getId());
+        List<String> cc = new ArrayList<String>();
+        for (AccountEmail email : accountEmailList) {
+            cc.add(email.getEmail());
+        }
+
+        String htmlBody = null;
+        try {
+            htmlBody = generator.generateInvoice(account, invoice, "HtmlInvoiceTemplate");
+        } catch (IOException e) {
+            throw new InvoiceApiException(e, ErrorCode.EMAIL_SENDING_FAILED);
+        }
+
+        // TODO: get subject
+        String subject = "";
+
+        EmailSender sender = new DefaultEmailSender(config);
+        try {
+            sender.sendSecureEmail(to, cc, subject, htmlBody);
+        } catch (EmailApiException e) {
+            throw new InvoiceApiException(e, ErrorCode.EMAIL_SENDING_FAILED);
+        } catch (IOException e) {
+            throw new InvoiceApiException(e, ErrorCode.EMAIL_SENDING_FAILED);
+        }
+    }
+}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/notification/NullInvoiceNotifier.java b/invoice/src/main/java/com/ning/billing/invoice/notification/NullInvoiceNotifier.java
new file mode 100644
index 0000000..460e779
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/notification/NullInvoiceNotifier.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2010-2011 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.invoice.notification;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceNotifier;
+
+public class NullInvoiceNotifier implements InvoiceNotifier {
+    @Override
+    public void notify(Account account, Invoice invoice) {
+        // deliberate no-op
+    }
+}
\ No newline at end of file
diff --git a/invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceFormatter.java b/invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceFormatter.java
new file mode 100644
index 0000000..92d1a63
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceFormatter.java
@@ -0,0 +1,277 @@
+/*
+ * Copyright 2010-2011 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.
+ */
+
+/*
+ * Copyright 2010-2011 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.invoice.template.formatters;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.api.InvoicePayment;
+import com.ning.billing.invoice.api.formatters.InvoiceFormatter;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.customfield.CustomField;
+import com.ning.billing.util.dao.ObjectType;
+import com.ning.billing.util.tag.ControlTagType;
+import com.ning.billing.util.template.translation.TranslatorConfig;
+import com.ning.billing.util.tag.Tag;
+import com.ning.billing.util.tag.TagDefinition;
+import org.joda.time.DateTime;
+import org.joda.time.format.DateTimeFormat;
+import org.joda.time.format.DateTimeFormatter;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.UUID;
+
+public class DefaultInvoiceFormatter implements InvoiceFormatter {
+    private final TranslatorConfig config;
+    private final Invoice invoice;
+    private final DateTimeFormatter dateFormatter;
+    private final Locale locale;
+
+    public DefaultInvoiceFormatter(TranslatorConfig config, Invoice invoice, Locale locale) {
+        this.config = config;
+        this.invoice = invoice;
+        dateFormatter = DateTimeFormat.mediumDate().withLocale(locale);
+        this.locale = locale;
+    }
+
+    @Override
+    public Integer getInvoiceNumber() {
+        return invoice.getInvoiceNumber();
+    }
+
+    @Override
+    public List<InvoiceItem> getInvoiceItems() {
+        List<InvoiceItem> formatters = new ArrayList<InvoiceItem>();
+        for (InvoiceItem item : invoice.getInvoiceItems()) {
+            formatters.add(new DefaultInvoiceItemFormatter(config, item, dateFormatter, locale));
+        }
+        return formatters;
+    }
+
+    @Override
+    public boolean addInvoiceItem(InvoiceItem item) {
+        return invoice.addInvoiceItem(item);
+    }
+
+    @Override
+    public boolean addInvoiceItems(List<InvoiceItem> items) {
+        return invoice.addInvoiceItems(items);
+    }
+
+    @Override
+    public <T extends InvoiceItem> List<InvoiceItem> getInvoiceItems(Class<T> clazz) {
+        return invoice.getInvoiceItems(clazz);
+    }
+
+    @Override
+    public int getNumberOfItems() {
+        return invoice.getNumberOfItems();
+    }
+
+    @Override
+    public boolean addPayment(InvoicePayment payment) {
+        return invoice.addPayment(payment);
+    }
+
+    @Override
+    public boolean addPayments(List<InvoicePayment> payments) {
+        return invoice.addPayments(payments);
+    }
+
+    @Override
+    public List<InvoicePayment> getPayments() {
+        return invoice.getPayments();
+    }
+
+    @Override
+    public int getNumberOfPayments() {
+        return invoice.getNumberOfPayments();
+    }
+
+    @Override
+    public UUID getAccountId() {
+        return invoice.getAccountId();
+    }
+
+    @Override
+    public BigDecimal getTotalAmount() {
+        return invoice.getTotalAmount();
+    }
+
+    @Override
+    public BigDecimal getBalance() {
+        return invoice.getBalance();
+    }
+
+    @Override
+    public boolean isDueForPayment(DateTime targetDate, int numberOfDays) {
+        return invoice.isDueForPayment(targetDate, numberOfDays);
+    }
+
+    @Override
+    public boolean isMigrationInvoice() {
+        return invoice.isMigrationInvoice();
+    }
+
+    @Override
+    public DateTime getInvoiceDate() {
+        return invoice.getInvoiceDate();
+    }
+
+    @Override
+    public DateTime getTargetDate() {
+        return invoice.getTargetDate();
+    }
+
+    @Override
+    public Currency getCurrency() {
+        return invoice.getCurrency();
+    }
+
+    @Override
+    public DateTime getLastPaymentAttempt() {
+        return invoice.getLastPaymentAttempt();
+    }
+
+    @Override
+    public BigDecimal getAmountPaid() {
+        return invoice.getAmountPaid();
+    }
+
+    @Override
+    public String getFormattedInvoiceDate() {
+        return invoice.getInvoiceDate().toString(dateFormatter);
+    }
+
+    @Override
+    public String getFieldValue(String fieldName) {
+        return invoice.getFieldValue(fieldName);
+    }
+
+    @Override
+    public void setFieldValue(String fieldName, String fieldValue) {
+        invoice.setFieldValue(fieldName, fieldValue);
+    }
+
+    @Override
+    public void saveFieldValue(String fieldName, String fieldValue, CallContext context) {
+        invoice.saveFieldValue(fieldName, fieldValue, context);
+    }
+
+    @Override
+    public List<CustomField> getFieldList() {
+        return invoice.getFieldList();
+    }
+
+    @Override
+    public void setFields(List<CustomField> fields) {
+        invoice.setFields(fields);
+    }
+
+    @Override
+    public void saveFields(List<CustomField> fields, CallContext context) {
+        invoice.saveFields(fields, context);
+    }
+
+    @Override
+    public void clearFields() {
+        invoice.clearFields();
+    }
+
+    @Override
+    public void clearPersistedFields(CallContext context) {
+        invoice.clearPersistedFields(context);
+    }
+
+    @Override
+    public ObjectType getObjectType() {
+        return invoice.getObjectType();
+    }
+
+    @Override
+    public UUID getId() {
+        return invoice.getId();
+    }
+
+    @Override
+    public List<Tag> getTagList() {
+        return invoice.getTagList();
+    }
+
+    @Override
+    public boolean hasTag(TagDefinition tagDefinition) {
+        return invoice.hasTag(tagDefinition);
+    }
+
+    @Override
+    public boolean hasTag(ControlTagType controlTagType) {
+        return invoice.hasTag(controlTagType);
+    }
+
+    @Override
+    public void addTag(TagDefinition definition) {
+        invoice.addTag(definition);
+    }
+
+    @Override
+    public void addTags(List<Tag> tags) {
+        invoice.addTags(tags);
+    }
+
+    @Override
+    public void addTagsFromDefinitions(List<TagDefinition> tagDefinitions) {
+        invoice.addTagsFromDefinitions(tagDefinitions);
+    }
+
+    @Override
+    public void clearTags() {
+        invoice.clearTags();
+    }
+
+    @Override
+    public void removeTag(TagDefinition definition) {
+        invoice.removeTag(definition);
+    }
+
+    @Override
+    public boolean generateInvoice() {
+        return invoice.generateInvoice();
+    }
+
+    @Override
+    public boolean processPayment() {
+        return invoice.processPayment();
+    }
+}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceFormatterFactory.java b/invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceFormatterFactory.java
new file mode 100644
index 0000000..83c0619
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceFormatterFactory.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2010-2011 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.invoice.template.formatters;
+
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.formatters.InvoiceFormatter;
+import com.ning.billing.invoice.api.formatters.InvoiceFormatterFactory;
+import com.ning.billing.util.template.translation.TranslatorConfig;
+
+import java.util.Locale;
+
+public class DefaultInvoiceFormatterFactory implements InvoiceFormatterFactory {
+    @Override
+    public InvoiceFormatter createInvoiceFormatter(TranslatorConfig config, Invoice invoice, Locale locale) {
+        return new DefaultInvoiceFormatter(config, invoice, locale);
+    }
+}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceItemFormatter.java b/invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceItemFormatter.java
new file mode 100644
index 0000000..f1a2e8d
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceItemFormatter.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2010-2011 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.invoice.template.formatters;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.api.formatters.InvoiceItemFormatter;
+import com.ning.billing.util.template.translation.DefaultCatalogTranslator;
+import com.ning.billing.util.template.translation.Translator;
+import com.ning.billing.util.template.translation.TranslatorConfig;
+import org.joda.time.DateTime;
+import org.joda.time.format.DateTimeFormatter;
+
+import java.math.BigDecimal;
+import java.util.Locale;
+import java.util.UUID;
+
+public class DefaultInvoiceItemFormatter implements InvoiceItemFormatter {
+    private final Translator translator;
+
+    private final InvoiceItem item;
+    private final DateTimeFormatter dateFormatter;
+    private final Locale locale;
+
+    public DefaultInvoiceItemFormatter(TranslatorConfig config, InvoiceItem item, DateTimeFormatter dateFormatter, Locale locale) {
+        this.item = item;
+        this.dateFormatter = dateFormatter;
+        this.locale = locale;
+
+        this.translator = new DefaultCatalogTranslator(config);
+    }
+
+    @Override
+    public BigDecimal getAmount() {
+        return item.getAmount();
+    }
+
+    @Override
+    public Currency getCurrency() {
+        return item.getCurrency();
+    }
+
+    @Override
+    public InvoiceItem asReversingItem() {
+        return item.asReversingItem();
+    }
+
+    @Override
+    public String getDescription() {
+        return item.getDescription();
+    }
+
+    @Override
+    public DateTime getStartDate() {
+        return item.getStartDate();
+    }
+
+    @Override
+    public DateTime getEndDate() {
+        return item.getEndDate();
+    }
+
+    @Override
+    public String getFormattedStartDate() {
+        return item.getStartDate().toString(dateFormatter);
+    }
+
+    @Override
+    public String getFormattedEndDate() {
+        return item.getEndDate().toString(dateFormatter);
+    }
+
+    @Override
+    public UUID getInvoiceId() {
+        return item.getInvoiceId();
+    }
+
+    @Override
+    public UUID getAccountId() {
+        return item.getAccountId();
+    }
+
+    @Override
+    public UUID getBundleId() {
+        return item.getBundleId();
+    }
+
+    @Override
+    public UUID getSubscriptionId() {
+        return item.getSubscriptionId();
+    }
+
+    @Override
+    public String getPlanName() {
+        return translator.getTranslation(locale, item.getPlanName());
+    }
+
+    @Override
+    public String getPhaseName() {
+        return translator.getTranslation(locale, item.getPhaseName());
+    }
+
+    @Override
+    public int compareTo(InvoiceItem invoiceItem) {
+        return item.compareTo(invoiceItem);
+    }
+
+    @Override
+    public UUID getId() {
+        return item.getId();
+    }
+}
\ No newline at end of file
diff --git a/invoice/src/main/java/com/ning/billing/invoice/template/HtmlInvoiceGenerator.java b/invoice/src/main/java/com/ning/billing/invoice/template/HtmlInvoiceGenerator.java
new file mode 100644
index 0000000..2a006d8
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/template/HtmlInvoiceGenerator.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2010-2011 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.invoice.template;
+
+import com.google.inject.Inject;
+import com.ning.billing.account.api.Account;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.formatters.InvoiceFormatter;
+import com.ning.billing.invoice.api.formatters.InvoiceFormatterFactory;
+import com.ning.billing.invoice.template.translator.DefaultInvoiceTranslator;
+import com.ning.billing.util.email.templates.TemplateEngine;
+import com.ning.billing.util.template.translation.TranslatorConfig;
+
+import java.io.IOException;
+import java.lang.String;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+public class HtmlInvoiceGenerator {
+    private final InvoiceFormatterFactory factory;
+    private final TemplateEngine templateEngine;
+    private final TranslatorConfig config;
+
+    @Inject
+    public HtmlInvoiceGenerator(InvoiceFormatterFactory factory, TemplateEngine templateEngine, TranslatorConfig config) {
+        this.factory = factory;
+        this.templateEngine = templateEngine;
+        this.config = config;
+    }
+
+    public String generateInvoice(Account account, Invoice invoice, String templateName) throws IOException {
+        Map<String, Object> data = new HashMap<String, Object>();
+        DefaultInvoiceTranslator invoiceTranslator = new DefaultInvoiceTranslator(config);
+        Locale locale = new Locale(account.getLocale());
+        invoiceTranslator.setLocale(locale);
+        data.put("text", invoiceTranslator);
+        data.put("account", account);
+
+        InvoiceFormatter formattedInvoice = factory.createInvoiceFormatter(config, invoice, locale);
+        data.put("invoice", formattedInvoice);
+
+        return templateEngine.executeTemplate(templateName, data);
+    }
+}
\ No newline at end of file
diff --git a/invoice/src/main/java/com/ning/billing/invoice/template/translator/DefaultInvoiceTranslator.java b/invoice/src/main/java/com/ning/billing/invoice/template/translator/DefaultInvoiceTranslator.java
new file mode 100644
index 0000000..f238ea7
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/template/translator/DefaultInvoiceTranslator.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2010-2011 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.invoice.template.translator;
+
+import com.google.inject.Inject;
+import com.ning.billing.util.template.translation.DefaultTranslatorBase;
+import com.ning.billing.util.template.translation.TranslatorConfig;
+
+import java.util.Locale;
+
+public class DefaultInvoiceTranslator extends DefaultTranslatorBase implements InvoiceStrings {
+    private Locale locale;
+
+    @Inject
+    public DefaultInvoiceTranslator(TranslatorConfig config) {
+        super(config);
+    }
+
+    public void setLocale(Locale locale) {
+        this.locale = locale;
+    }
+
+    @Override
+    protected String getBundlePath() {
+        return "com/ning/billing/util/email/translation/InvoiceTranslation";
+    }
+
+    @Override
+    protected String getTranslationType() {
+        return "invoice";
+    }
+
+    @Override
+    public String getInvoiceTitle() {
+        return getTranslation(locale, "invoiceTitle");
+    }
+
+    @Override
+    public String getInvoiceDate() {
+        return getTranslation(locale, "invoiceDate");
+    }
+
+    @Override
+    public String getInvoiceNumber() {
+        return getTranslation(locale, "invoiceNumber");
+    }
+
+    @Override
+    public String getAccountOwnerName() {
+        return getTranslation(locale, "accountOwnerName");
+    }
+
+    @Override
+    public String getAccountOwnerEmail() {
+        return getTranslation(locale, "accountOwnerEmail");
+    }
+
+    @Override
+    public String getAccountOwnerPhone() {
+        return getTranslation(locale, "accountOwnerPhone");
+    }
+
+    @Override
+    public String getCompanyName() {
+        return getTranslation(locale, "companyName");
+    }
+
+    @Override
+    public String getCompanyAddress() {
+        return getTranslation(locale, "companyAddress");
+    }
+
+    @Override
+    public String getCompanyCityProvincePostalCode() {
+        return getTranslation(locale, "");
+    }
+
+    @Override
+    public String getCompanyCountry() {
+        return getTranslation(locale, "companyCountry");
+    }
+
+    @Override
+    public String getCompanyUrl() {
+        return getTranslation(locale, "companyUrl");
+    }
+
+    @Override
+    public String getInvoiceItemBundleName() {
+        return getTranslation(locale, "invoiceItemBundleName");
+    }
+
+    @Override
+    public String getInvoiceItemDescription() {
+        return getTranslation(locale, "invoiceItemDescription");
+    }
+
+    @Override
+    public String getInvoiceItemServicePeriod() {
+        return getTranslation(locale, "invoiceItemServicePeriod");
+    }
+
+    @Override
+    public String getInvoiceItemAmount() {
+        return getTranslation(locale, "invoiceItemAmount");
+    }
+
+    @Override
+    public String getInvoiceAmount() {
+        return getTranslation(locale, "invoiceAmount");
+    }
+
+    @Override
+    public String getInvoiceAmountPaid() {
+        return getTranslation(locale, "invoiceAmountPaid");
+    }
+
+    @Override
+    public String getInvoiceBalance() {
+        return getTranslation(locale, "invoiceBalance");
+    }
+}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/template/translator/InvoiceStrings.java b/invoice/src/main/java/com/ning/billing/invoice/template/translator/InvoiceStrings.java
new file mode 100644
index 0000000..cc19d5f
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/template/translator/InvoiceStrings.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2010-2011 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.invoice.template.translator;
+
+public interface InvoiceStrings {
+    String getInvoiceTitle();
+    String getInvoiceDate();
+    String getInvoiceNumber();
+    String getAccountOwnerName();
+    String getAccountOwnerEmail();
+    String getAccountOwnerPhone();
+
+    // company name and address
+    String getCompanyName();
+    String getCompanyAddress();
+    String getCompanyCityProvincePostalCode();
+    String getCompanyCountry();
+    String getCompanyUrl();
+
+    String getInvoiceItemBundleName();
+    String getInvoiceItemDescription();
+    String getInvoiceItemServicePeriod();
+    String getInvoiceItemAmount();
+
+    String getInvoiceAmount();
+    String getInvoiceAmountPaid();
+    String getInvoiceBalance();
+}
diff --git a/invoice/src/main/resources/com/ning/billing/invoice/dao/FixedPriceInvoiceItemSqlDao.sql.stg b/invoice/src/main/resources/com/ning/billing/invoice/dao/FixedPriceInvoiceItemSqlDao.sql.stg
index 100182f..d7aed8c 100644
--- a/invoice/src/main/resources/com/ning/billing/invoice/dao/FixedPriceInvoiceItemSqlDao.sql.stg
+++ b/invoice/src/main/resources/com/ning/billing/invoice/dao/FixedPriceInvoiceItemSqlDao.sql.stg
@@ -4,6 +4,7 @@ fields(prefix) ::= <<
   <prefix>id,
   <prefix>invoice_id,
   <prefix>account_id,
+  <prefix>bundle_id,
   <prefix>subscription_id,
   <prefix>plan_name,
   <prefix>phase_name,
@@ -42,16 +43,38 @@ getInvoiceItemsBySubscription() ::= <<
 
 create() ::= <<
   INSERT INTO fixed_invoice_items(<fields()>)
-  VALUES(:id, :invoiceId, :accountId, :subscriptionId, :planName, :phaseName,
+  VALUES(:id, :invoiceId, :accountId, :bundleId, :subscriptionId, :planName, :phaseName,
          :startDate, :endDate, :amount, :currency, :userName, :createdDate);
 >>
 
 batchCreateFromTransaction() ::= <<
   INSERT INTO fixed_invoice_items(<fields()>)
-  VALUES(:id, :invoiceId, :accountId, :subscriptionId, :planName, :phaseName,
+  VALUES(:id, :invoiceId, :accountId, :bundleId, :subscriptionId, :planName, :phaseName,
          :startDate, :endDate, :amount, :currency, :userName, :createdDate);
 >>
 
+getRecordIds() ::= <<
+    SELECT record_id, id
+    FROM fixed_invoice_items
+    WHERE invoice_id = :invoiceId;
+>>
+
+auditFields(prefix) ::= <<
+    <prefix>table_name,
+    <prefix>record_id,
+    <prefix>change_type,
+    <prefix>change_date,
+    <prefix>changed_by,
+    <prefix>reason_code,
+    <prefix>comments,
+    <prefix>user_token
+>>
+
+insertAuditFromTransaction() ::= <<
+    INSERT INTO audit_log(<auditFields()>)
+    VALUES(:tableName, :recordId, :changeType, :createdDate, :userName, NULL, NULL, NULL);
+>>
+
 test() ::= <<
   SELECT 1
   FROM fixed_invoice_items;
diff --git a/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoicePaymentSqlDao.sql.stg b/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoicePaymentSqlDao.sql.stg
index a673b81..6967da0 100644
--- a/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoicePaymentSqlDao.sql.stg
+++ b/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoicePaymentSqlDao.sql.stg
@@ -49,6 +49,34 @@ getInvoicePayment() ::= <<
     WHERE payment_id = :payment_id;
 >>
 
+getRecordId() ::= <<
+    SELECT record_id
+    FROM invoice_payments
+    WHERE id = :id;
+>>
+
+getRecordIds() ::= <<
+    SELECT record_id, id
+    FROM invoice_payments
+    WHERE invoice_id = :invoiceId;
+>>
+
+auditFields(prefix) ::= <<
+    <prefix>table_name,
+    <prefix>record_id,
+    <prefix>change_type,
+    <prefix>change_date,
+    <prefix>changed_by,
+    <prefix>reason_code,
+    <prefix>comments,
+    <prefix>user_token
+>>
+
+insertAuditFromTransaction() ::= <<
+    INSERT INTO audit_log(<auditFields()>)
+    VALUES(:tableName, :recordId, :changeType, :createdDate, :userName, NULL, NULL, NULL);
+>>
+
 test() ::= <<
   SELECT 1 FROM invoice_payments;
 >>
diff --git a/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoiceSqlDao.sql.stg b/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoiceSqlDao.sql.stg
index b83dc82..f8efa27 100644
--- a/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoiceSqlDao.sql.stg
+++ b/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoiceSqlDao.sql.stg
@@ -1,18 +1,6 @@
 group InvoiceDao;
 
-invoiceFetchFields(prefix) ::= <<
-    <prefix>invoice_number,
-    <prefix>id,
-    <prefix>account_id,
-    <prefix>invoice_date,
-    <prefix>target_date,
-    <prefix>currency,
-  	<prefix>migrated,
-    <prefix>created_by,
-    <prefix>created_date
->>
-
-invoiceSetFields(prefix) ::= <<
+invoiceFields(prefix) ::= <<
     <prefix>id,
     <prefix>account_id,
     <prefix>invoice_date,
@@ -24,42 +12,42 @@ invoiceSetFields(prefix) ::= <<
 >>
 
 get() ::= <<
-  SELECT <invoiceFetchFields()>
+  SELECT record_id as invoice_number, <invoiceFields()>
   FROM invoices
   ORDER BY target_date ASC;
 >>
 
 getInvoicesByAccount() ::= <<
-  SELECT <invoiceFetchFields()>
+  SELECT record_id as invoice_number, <invoiceFields()>
   FROM invoices
   WHERE account_id = :accountId AND migrated = 'FALSE'
   ORDER BY target_date ASC;
 >>
 
 getAllInvoicesByAccount() ::= <<
-  SELECT <invoiceFetchFields()>
+  SELECT record_id as invoice_number, <invoiceFields()>
   FROM invoices
   WHERE account_id = :accountId
   ORDER BY target_date ASC;
 >>
 
 getInvoicesByAccountAfterDate() ::= <<
-  SELECT <invoiceFetchFields()>
+  SELECT record_id as invoice_number, <invoiceFields()>
   FROM invoices
   WHERE account_id = :accountId AND target_date >= :fromDate AND migrated = 'FALSE'
   ORDER BY target_date ASC;
 >>
 
 getInvoicesBySubscription() ::= <<
-  SELECT <invoiceFetchFields("i.")>
+  SELECT record_id as invoice_number, <invoiceFields("i.")>
   FROM invoices i
   LEFT JOIN recurring_invoice_items rii ON i.id = rii.invoice_id
   WHERE rii.subscription_id = :subscriptionId  AND migrated = 'FALSE'
-  GROUP BY <invoiceFetchFields("i.")>;
+  GROUP BY record_id as invoice_number, <invoiceFields("i.")>;
 >>
 
 getById() ::= <<
-  SELECT <invoiceFetchFields()>
+  SELECT record_id as invoice_number, <invoiceFields()>
   FROM invoices
   WHERE id = :id;
 >>
@@ -75,7 +63,7 @@ getAccountBalance() ::= <<
 >>
 
 create() ::= <<
-  INSERT INTO invoices(<invoiceSetFields()>)
+  INSERT INTO invoices(<invoiceFields()>)
   VALUES (:id, :accountId, :invoiceDate, :targetDate, :currency, :migrated, :userName, :createdDate);
 >>
 
@@ -87,7 +75,7 @@ getInvoiceIdByPaymentAttemptId() ::= <<
 >>
 
 getUnpaidInvoicesByAccountId() ::= <<
-  SELECT <invoiceFetchFields("i.")>
+  SELECT record_id as invoice_number, <invoiceFields("i.")>
   FROM invoices i
   LEFT JOIN invoice_payment_summary ips ON i.id = ips.invoice_id
   LEFT JOIN invoice_item_summary iis ON i.id = iis.invoice_id
@@ -97,6 +85,28 @@ getUnpaidInvoicesByAccountId() ::= <<
   ORDER BY i.target_date ASC;
 >>
 
+getRecordId() ::= <<
+    SELECT record_id
+    FROM invoices
+    WHERE id = :id;
+>>
+
+auditFields(prefix) ::= <<
+    <prefix>table_name,
+    <prefix>record_id,
+    <prefix>change_type,
+    <prefix>change_date,
+    <prefix>changed_by,
+    <prefix>reason_code,
+    <prefix>comments,
+    <prefix>user_token
+>>
+
+insertAuditFromTransaction() ::= <<
+    INSERT INTO audit_log(<auditFields()>)
+    VALUES(:tableName, :recordId, :changeType, :createdDate, :userName, NULL, NULL, NULL);
+>>
+
 test() ::= <<
   SELECT 1
   FROM invoices;
diff --git a/invoice/src/main/resources/com/ning/billing/invoice/dao/RecurringInvoiceItemSqlDao.sql.stg b/invoice/src/main/resources/com/ning/billing/invoice/dao/RecurringInvoiceItemSqlDao.sql.stg
index 6b5f504..8afb199 100644
--- a/invoice/src/main/resources/com/ning/billing/invoice/dao/RecurringInvoiceItemSqlDao.sql.stg
+++ b/invoice/src/main/resources/com/ning/billing/invoice/dao/RecurringInvoiceItemSqlDao.sql.stg
@@ -4,6 +4,7 @@ fields(prefix) ::= <<
   <prefix>id,
   <prefix>invoice_id,
   <prefix>account_id,
+  <prefix>bundle_id,
   <prefix>subscription_id,
   <prefix>plan_name,
   <prefix>phase_name,
@@ -44,16 +45,38 @@ getInvoiceItemsBySubscription() ::= <<
 
 create() ::= <<
   INSERT INTO recurring_invoice_items(<fields()>)
-  VALUES(:id, :invoiceId, :accountId, :subscriptionId, :planName, :phaseName, :startDate, :endDate,
+  VALUES(:id, :invoiceId, :accountId, :bundleId, :subscriptionId, :planName, :phaseName, :startDate, :endDate,
          :amount, :rate, :currency, :reversedItemId, :userName, :createdDate);
 >>
 
 batchCreateFromTransaction() ::= <<
   INSERT INTO recurring_invoice_items(<fields()>)
-  VALUES(:id, :invoiceId, :accountId, :subscriptionId, :planName, :phaseName, :startDate, :endDate,
+  VALUES(:id, :invoiceId, :accountId, :bundleId, :subscriptionId, :planName, :phaseName, :startDate, :endDate,
          :amount, :rate, :currency, :reversedItemId, :userName, :createdDate);
 >>
 
+getRecordIds() ::= <<
+    SELECT record_id, id
+    FROM recurring_invoice_items
+    WHERE invoice_id = :invoiceId;
+>>
+
+auditFields(prefix) ::= <<
+    <prefix>table_name,
+    <prefix>record_id,
+    <prefix>change_type,
+    <prefix>change_date,
+    <prefix>changed_by,
+    <prefix>reason_code,
+    <prefix>comments,
+    <prefix>user_token
+>>
+
+insertAuditFromTransaction() ::= <<
+    INSERT INTO audit_log(<auditFields()>)
+    VALUES(:tableName, :recordId, :changeType, :createdDate, :userName, NULL, NULL, NULL);
+>>
+
 test() ::= <<
   SELECT 1
   FROM recurring_invoice_items;
diff --git a/invoice/src/main/resources/com/ning/billing/invoice/ddl.sql b/invoice/src/main/resources/com/ning/billing/invoice/ddl.sql
index 3adb40e..8bda35c 100644
--- a/invoice/src/main/resources/com/ning/billing/invoice/ddl.sql
+++ b/invoice/src/main/resources/com/ning/billing/invoice/ddl.sql
@@ -1,41 +1,47 @@
 DROP TABLE IF EXISTS invoice_items;
 DROP TABLE IF EXISTS recurring_invoice_items;
 CREATE TABLE recurring_invoice_items (
-  id char(36) NOT NULL,
-  invoice_id char(36) NOT NULL,
-  account_id char(36) NOT NULL,
-  subscription_id char(36) NOT NULL,
-  plan_name varchar(50) NOT NULL,
-  phase_name varchar(50) NOT NULL,
-  start_date datetime NOT NULL,
-  end_date datetime NOT NULL,
-  amount numeric(10,4) NULL,
-  rate numeric(10,4) NULL,
-  currency char(3) NOT NULL,
-  reversed_item_id char(36),
-  created_by varchar(50) NOT NULL,
-  created_date datetime NOT NULL,
-  PRIMARY KEY(id)
+    record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
+    id char(36) NOT NULL,
+    invoice_id char(36) NOT NULL,
+    account_id char(36) NOT NULL,
+    bundle_id char(36),
+    subscription_id char(36),
+    plan_name varchar(50) NOT NULL,
+    phase_name varchar(50) NOT NULL,
+    start_date datetime NOT NULL,
+    end_date datetime NOT NULL,
+    amount numeric(10,4) NULL,
+    rate numeric(10,4) NULL,
+    currency char(3) NOT NULL,
+    reversed_item_id char(36),
+    created_by varchar(50) NOT NULL,
+    created_date datetime NOT NULL,
+    PRIMARY KEY(record_id)
 ) ENGINE=innodb;
+CREATE UNIQUE INDEX recurring_invoice_items_id ON recurring_invoice_items(id);
 CREATE INDEX recurring_invoice_items_subscription_id ON recurring_invoice_items(subscription_id ASC);
 CREATE INDEX recurring_invoice_items_invoice_id ON recurring_invoice_items(invoice_id ASC);
 
 DROP TABLE IF EXISTS fixed_invoice_items;
 CREATE TABLE fixed_invoice_items (
-  id char(36) NOT NULL,
-  invoice_id char(36) NOT NULL,
-  account_id char(36) NOT NULL,
-  subscription_id char(36) NOT NULL,
-  plan_name varchar(50) NOT NULL,
-  phase_name varchar(50) NOT NULL,
-  start_date datetime NOT NULL,
-  end_date datetime NOT NULL,
-  amount numeric(10,4) NULL,
-  currency char(3) NOT NULL,
-  created_by varchar(50) NOT NULL,
-  created_date datetime NOT NULL,
-  PRIMARY KEY(id)
+    record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
+    id char(36) NOT NULL,
+    invoice_id char(36) NOT NULL,
+    account_id char(36) NOT NULL,
+    bundle_id char(36),
+    subscription_id char(36),
+    plan_name varchar(50) NOT NULL,
+    phase_name varchar(50) NOT NULL,
+    start_date datetime NOT NULL,
+    end_date datetime NOT NULL,
+    amount numeric(10,4) NULL,
+    currency char(3) NOT NULL,
+    created_by varchar(50) NOT NULL,
+    created_date datetime NOT NULL,
+    PRIMARY KEY(record_id)
 ) ENGINE=innodb;
+CREATE UNIQUE INDEX fixed_invoice_items_id ON fixed_invoice_items(id);
 CREATE INDEX fixed_invoice_items_subscription_id ON fixed_invoice_items(subscription_id ASC);
 CREATE INDEX fixed_invoice_items_invoice_id ON fixed_invoice_items(invoice_id ASC);
 
@@ -43,33 +49,34 @@ DROP TABLE IF EXISTS invoice_locking;
 
 DROP TABLE IF EXISTS invoices;
 CREATE TABLE invoices (
-  invoice_number int NOT NULL AUTO_INCREMENT,
-  id char(36) NOT NULL,
-  account_id char(36) NOT NULL,
-  invoice_date datetime NOT NULL,
-  target_date datetime NOT NULL,
-  currency char(3) NOT NULL,
-  migrated bool NOT NULL,
-  created_by varchar(50) NOT NULL,
-  created_date datetime NOT NULL,
-  PRIMARY KEY(invoice_number)
+    record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
+    id char(36) NOT NULL,
+    account_id char(36) NOT NULL,
+    invoice_date datetime NOT NULL,
+    target_date datetime NOT NULL,
+    currency char(3) NOT NULL,
+    migrated bool NOT NULL,
+    created_by varchar(50) NOT NULL,
+    created_date datetime NOT NULL,
+    PRIMARY KEY(record_id)
 ) ENGINE=innodb;
-CREATE INDEX invoices_invoice_number ON invoices(invoice_number ASC);
-CREATE INDEX invoices_id ON invoices(id ASC);
+CREATE UNIQUE INDEX invoices_id ON invoices(id);
 CREATE INDEX invoices_account_id ON invoices(account_id ASC);
 
 DROP TABLE IF EXISTS invoice_payments;
 CREATE TABLE invoice_payments (
-  id char(36) NOT NULL,
-  invoice_id char(36) NOT NULL,
-  payment_attempt_id char(36) COLLATE utf8_bin NOT NULL,
-  payment_attempt_date datetime,
-  amount numeric(10,4),
-  currency char(3),
-  created_by varchar(50) NOT NULL,
-  created_date datetime NOT NULL,
-  PRIMARY KEY(invoice_id, payment_attempt_id)
+    record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
+    id char(36) NOT NULL,
+    invoice_id char(36) NOT NULL,
+    payment_attempt_id char(36) COLLATE utf8_bin NOT NULL,
+    payment_attempt_date datetime,
+    amount numeric(10,4),
+    currency char(3),
+    created_by varchar(50) NOT NULL,
+    created_date datetime NOT NULL,
+    PRIMARY KEY(record_id)
 ) ENGINE=innodb;
+CREATE UNIQUE INDEX invoice_payments_id ON invoice_payments(id);
 CREATE UNIQUE INDEX invoice_payments_unique ON invoice_payments(invoice_id, payment_attempt_id);
 
 DROP VIEW IF EXISTS invoice_payment_summary;
diff --git a/invoice/src/test/java/com/ning/billing/invoice/api/migration/MockModuleNoEntitlement.java b/invoice/src/test/java/com/ning/billing/invoice/api/migration/MockModuleNoEntitlement.java
index 4701bc8..fe5d886 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/api/migration/MockModuleNoEntitlement.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/api/migration/MockModuleNoEntitlement.java
@@ -16,43 +16,46 @@
 
 package com.ning.billing.invoice.api.migration;
 
-import com.ning.billing.entitlement.api.billing.EntitlementBillingApi;
-import com.ning.billing.entitlement.engine.dao.EntitlementDao;
 import com.ning.billing.invoice.MockModule;
-import com.ning.billing.invoice.glue.InvoiceModule;
-import com.ning.billing.invoice.notification.DefaultNextBillingDateNotifier;
-import com.ning.billing.invoice.notification.DefaultNextBillingDatePoster;
+import com.ning.billing.invoice.api.InvoiceNotifier;
+import com.ning.billing.invoice.glue.DefaultInvoiceModule;
 import com.ning.billing.invoice.notification.NextBillingDateNotifier;
 import com.ning.billing.invoice.notification.NextBillingDatePoster;
+import com.ning.billing.invoice.notification.NullInvoiceNotifier;
 import com.ning.billing.mock.BrainDeadProxyFactory;
 import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
+import com.ning.billing.util.email.templates.TemplateModule;
 
 public class MockModuleNoEntitlement extends MockModule {
-
-	@Override
-	protected void installEntitlementModule() {
-		EntitlementBillingApi entitlementApi = BrainDeadProxyFactory.createBrainDeadProxyFor(EntitlementBillingApi.class);
-		((ZombieControl)entitlementApi).addResult("setChargedThroughDateFromTransaction", BrainDeadProxyFactory.ZOMBIE_VOID);
-		bind(EntitlementBillingApi.class).toInstance(entitlementApi);
-		bind(EntitlementDao.class).toInstance(BrainDeadProxyFactory.createBrainDeadProxyFor(EntitlementDao.class));
-
-	}
+//	@Override
+//	protected void installEntitlementModule() {
+//		BillingApi entitlementApi = BrainDeadProxyFactory.createBrainDeadProxyFor(BillingApi.class);
+//        ((ZombieControl)entitlementApi).addResult("setChargedThroughDateFromTransaction", BrainDeadProxyFactory.ZOMBIE_VOID);
+//        ((ZombieControl)entitlementApi).addResult("getBillingEventsForAccountAndUpdateAccountBCD", BrainDeadProxyFactory.ZOMBIE_VOID);
+//		//bind(BillingApi.class).toInstance(entitlementApi);
+////        bind(EntitlementUserApi.class).toInstance(BrainDeadProxyFactory.createBrainDeadProxyFor(EntitlementUserApi.class));
+//        ChargeThruApi cta = BrainDeadProxyFactory.createBrainDeadProxyFor(ChargeThruApi.class);
+//        bind(ChargeThruApi.class).toInstance(cta);
+//        ((ZombieControl)cta).addResult("setChargedThroughDateFromTransaction", BrainDeadProxyFactory.ZOMBIE_VOID);
+//	}
 
 	@Override
 	protected void installInvoiceModule() {
-		install(new InvoiceModule(){
+		install(new DefaultInvoiceModule(){
 
 			@Override
-			protected void installNotifier() {
+			protected void installNotifiers() {
 				 bind(NextBillingDateNotifier.class).toInstance(BrainDeadProxyFactory.createBrainDeadProxyFor(NextBillingDateNotifier.class));
 				 NextBillingDatePoster poster = BrainDeadProxyFactory.createBrainDeadProxyFor(NextBillingDatePoster.class);
 				 ((ZombieControl)poster).addResult("insertNextBillingNotification",BrainDeadProxyFactory.ZOMBIE_VOID);
 			     bind(NextBillingDatePoster.class).toInstance(poster);
+                bind(InvoiceNotifier.class).to(NullInvoiceNotifier.class).asEagerSingleton();
 			}
 			
 			
 		});
-		
+        install(new TemplateModule());
+
 		
 	}
 
diff --git a/invoice/src/test/java/com/ning/billing/invoice/api/migration/TestDefaultInvoiceMigrationApi.java b/invoice/src/test/java/com/ning/billing/invoice/api/migration/TestDefaultInvoiceMigrationApi.java
index 07c8e54..efedbaf 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/api/migration/TestDefaultInvoiceMigrationApi.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/api/migration/TestDefaultInvoiceMigrationApi.java
@@ -23,11 +23,7 @@ import java.util.SortedSet;
 import java.util.TreeSet;
 import java.util.UUID;
 
-import com.ning.billing.util.callcontext.CallContext;
-import com.ning.billing.util.callcontext.CallOrigin;
-import com.ning.billing.util.callcontext.UserType;
-import com.ning.billing.util.callcontext.DefaultCallContextFactory;
-import com.ning.billing.util.clock.Clock;
+import com.ning.billing.invoice.tests.InvoicingTestBase;
 import org.apache.commons.io.IOUtils;
 import org.joda.time.DateTime;
 import org.slf4j.Logger;
@@ -48,29 +44,35 @@ import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.catalog.api.Plan;
 import com.ning.billing.catalog.api.PlanPhase;
 import com.ning.billing.dbi.MysqlTestingHelper;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
 import com.ning.billing.entitlement.api.billing.BillingEvent;
 import com.ning.billing.entitlement.api.billing.BillingModeType;
-import com.ning.billing.entitlement.api.billing.DefaultBillingEvent;
-import com.ning.billing.entitlement.api.billing.EntitlementBillingApi;
 import com.ning.billing.entitlement.api.user.Subscription;
-import com.ning.billing.entitlement.api.user.SubscriptionTransition.SubscriptionTransitionType;
 import com.ning.billing.invoice.InvoiceDispatcher;
 import com.ning.billing.invoice.TestInvoiceDispatcher;
 import com.ning.billing.invoice.api.Invoice;
 import com.ning.billing.invoice.api.InvoiceMigrationApi;
+import com.ning.billing.invoice.api.InvoiceNotifier;
 import com.ning.billing.invoice.api.InvoicePaymentApi;
 import com.ning.billing.invoice.api.InvoiceUserApi;
 import com.ning.billing.invoice.dao.InvoiceDao;
 import com.ning.billing.invoice.model.InvoiceGenerator;
+import com.ning.billing.invoice.notification.NullInvoiceNotifier;
+import com.ning.billing.junction.api.BillingApi;
 import com.ning.billing.mock.BrainDeadProxyFactory;
 import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
 import com.ning.billing.util.bus.BusService;
 import com.ning.billing.util.bus.DefaultBusService;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.callcontext.CallOrigin;
+import com.ning.billing.util.callcontext.DefaultCallContextFactory;
+import com.ning.billing.util.callcontext.UserType;
+import com.ning.billing.util.clock.Clock;
 import com.ning.billing.util.clock.ClockMock;
 import com.ning.billing.util.globallocker.GlobalLocker;
 
 @Guice(modules = {MockModuleNoEntitlement.class})
-public class TestDefaultInvoiceMigrationApi {
+public class TestDefaultInvoiceMigrationApi extends InvoicingTestBase {
 	Logger log = LoggerFactory.getLogger(TestDefaultInvoiceMigrationApi.class);
 
 	@Inject
@@ -94,8 +96,9 @@ public class TestDefaultInvoiceMigrationApi {
 
 	@Inject
 	private InvoiceMigrationApi migrationApi;
-
-
+	
+	@Inject
+	private BillingApi billingApi;
 
 	private UUID accountId ;
 	private UUID subscriptionId ;
@@ -110,7 +113,7 @@ public class TestDefaultInvoiceMigrationApi {
 
     private final Clock clock = new ClockMock();
 
-	@BeforeClass(alwaysRun = true)
+	@BeforeClass(groups={"slow"})
 	public void setup() throws Exception
 	{
 		log.info("Starting set up");
@@ -129,11 +132,13 @@ public class TestDefaultInvoiceMigrationApi {
 
 		busService.getBus().start();
 
+        ((ZombieControl)billingApi).addResult("setChargedThroughDate", BrainDeadProxyFactory.ZOMBIE_VOID);
 		migrationInvoiceId = createAndCheckMigrationInvoice();
 		regularInvoiceId = generateRegularInvoice();
+
 	}
 
-	@AfterClass(alwaysRun = true)
+	@AfterClass(groups={"slow"})
 	public void tearDown() {
 		try {
 			((DefaultBusService) busService).stopBus();
@@ -169,23 +174,26 @@ public class TestDefaultInvoiceMigrationApi {
 		((ZombieControl)accountUserApi).addResult("getAccountById", account);
 		((ZombieControl)account).addResult("getCurrency", Currency.USD);
 		((ZombieControl)account).addResult("getId", accountId);
+        ((ZombieControl)account).addResult("isNotifiedForInvoices", true);
 
 		Subscription subscription =  BrainDeadProxyFactory.createBrainDeadProxyFor(Subscription.class);
-		((ZombieControl)subscription).addResult("getId", subscriptionId);
+        ((ZombieControl)subscription).addResult("getId", subscriptionId);
+        ((ZombieControl)subscription).addResult("getBundleId", new UUID(0L,0L));
 		SortedSet<BillingEvent> events = new TreeSet<BillingEvent>();
 		Plan plan = MockPlan.createBicycleNoTrialEvergreen1USD();
 		PlanPhase planPhase = MockPlanPhase.create1USDMonthlyEvergreen();
 		DateTime effectiveDate = new DateTime().minusDays(1);
 		Currency currency = Currency.USD;
 		BigDecimal fixedPrice = null;
-		events.add(new DefaultBillingEvent(subscription, effectiveDate,plan, planPhase,
-				fixedPrice, BigDecimal.ONE, currency, BillingPeriod.MONTHLY, 1,
-				BillingModeType.IN_ADVANCE, "", 1L, SubscriptionTransitionType.CREATE));
+		events.add(createMockBillingEvent(account, subscription, effectiveDate, plan, planPhase,
+                fixedPrice, BigDecimal.ONE, currency, BillingPeriod.MONTHLY, 1,
+                BillingModeType.IN_ADVANCE, "", 1L, SubscriptionTransitionType.CREATE));
 
-		EntitlementBillingApi entitlementBillingApi = BrainDeadProxyFactory.createBrainDeadProxyFor(EntitlementBillingApi.class);
-		((ZombieControl)entitlementBillingApi).addResult("getBillingEventsForAccount", events);
+		((ZombieControl)billingApi).addResult("getBillingEventsForAccountAndUpdateAccountBCD", events);
 
-		InvoiceDispatcher dispatcher = new InvoiceDispatcher(generator, accountUserApi, entitlementBillingApi, invoiceDao, locker);
+        InvoiceNotifier invoiceNotifier = new NullInvoiceNotifier();
+		InvoiceDispatcher dispatcher = new InvoiceDispatcher(generator, accountUserApi, billingApi,
+                                                             invoiceDao, invoiceNotifier, locker, busService.getBus(), clock);
 
         CallContext context = new DefaultCallContextFactory(clock).createCallContext("Migration test", CallOrigin.TEST, UserType.TEST);
 		Invoice invoice = dispatcher.processAccount(accountId, date_regular, true, context);
@@ -204,7 +212,7 @@ public class TestDefaultInvoiceMigrationApi {
 	}
 
 	// Check migration invoice is NOT returned for all user api invoice calls
-	@Test(groups={"slow"},enabled=true)
+	@Test(groups={"slow"}, enabled=true)
 	public void testUserApiAccess(){
 		List<Invoice> byAccount = invoiceUserApi.getInvoicesByAccount(accountId);
 		Assert.assertEquals(byAccount.size(),1);
@@ -231,7 +239,7 @@ public class TestDefaultInvoiceMigrationApi {
 	}
 
 
-	// Account balance should reflect total of migration and non-migration invoices
+	// ACCOUNT balance should reflect total of migration and non-migration invoices
 	@Test(groups={"slow"},enabled=true)
 	public void testBalance(){
 		Invoice migrationInvoice = invoiceDao.getById(migrationInvoiceId);
@@ -239,7 +247,7 @@ public class TestDefaultInvoiceMigrationApi {
 		BigDecimal balanceOfAllInvoices = migrationInvoice.getBalance().add(regularInvoice.getBalance());
 
 		BigDecimal accountBalance = invoiceUserApi.getAccountBalance(accountId);
-		System.out.println("Account balance: " + accountBalance + " should equal the Balance Of All Invoices: " + balanceOfAllInvoices);
+		System.out.println("ACCOUNT balance: " + accountBalance + " should equal the Balance Of All Invoices: " + balanceOfAllInvoices);
 		Assert.assertEquals(accountBalance.compareTo(balanceOfAllInvoices), 0);
 
 
diff --git a/invoice/src/test/java/com/ning/billing/invoice/api/MockInvoicePaymentApi.java b/invoice/src/test/java/com/ning/billing/invoice/api/MockInvoicePaymentApi.java
index 3a00c1b..09b809c 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/api/MockInvoicePaymentApi.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/api/MockInvoicePaymentApi.java
@@ -22,11 +22,11 @@ import java.util.List;
 import java.util.UUID;
 import java.util.concurrent.CopyOnWriteArrayList;
 
-import com.ning.billing.util.callcontext.CallContext;
 import org.joda.time.DateTime;
 
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.invoice.model.DefaultInvoicePayment;
+import com.ning.billing.util.callcontext.CallContext;
 
 public class MockInvoicePaymentApi implements InvoicePaymentApi
 {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/api/user/TestEventJson.java b/invoice/src/test/java/com/ning/billing/invoice/api/user/TestEventJson.java
new file mode 100644
index 0000000..eb991dd
--- /dev/null
+++ b/invoice/src/test/java/com/ning/billing/invoice/api/user/TestEventJson.java
@@ -0,0 +1,65 @@
+/* 
+ * Copyright 2010-2011 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.invoice.api.user;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import org.codehaus.jackson.map.ObjectMapper;
+import org.codehaus.jackson.map.SerializationConfig;
+import org.joda.time.DateTime;
+import org.testng.Assert;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Test;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.EmptyInvoiceEvent;
+import com.ning.billing.invoice.api.InvoiceCreationEvent;
+
+public class TestEventJson {
+
+    private ObjectMapper mapper = new ObjectMapper();
+
+    @BeforeTest(groups= {"fast"})
+    public void setup() {
+        mapper = new ObjectMapper();
+        mapper.disable(SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS);
+    }
+
+    @Test(groups= {"fast"})
+    public void testInvoiceCreationEvent() throws Exception {
+
+        InvoiceCreationEvent e = new DefaultInvoiceCreationEvent(UUID.randomUUID(), UUID.randomUUID(), new BigDecimal(12.0), Currency.USD, new DateTime(), UUID.randomUUID());
+
+        String json = mapper.writeValueAsString(e);
+
+        Class<?> claz = Class.forName(DefaultInvoiceCreationEvent.class.getName());
+        Object obj =  mapper.readValue(json, claz);
+        Assert.assertTrue(obj.equals(e));
+    }
+
+    @Test(groups= {"fast"})
+    public void testEmptyInvoiceEvent() throws Exception {
+
+        EmptyInvoiceEvent e = new DefaultEmptyInvoiceEvent(UUID.randomUUID(), new DateTime(), UUID.randomUUID());
+
+        String json = mapper.writeValueAsString(e);
+
+        Class<?> claz = Class.forName(DefaultEmptyInvoiceEvent.class.getName());
+        Object obj =  mapper.readValue(json, claz);
+        Assert.assertTrue(obj.equals(e));
+    }
+}
diff --git a/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceDaoTestBase.java b/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceDaoTestBase.java
index 60a4313..91dbf7d 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceDaoTestBase.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceDaoTestBase.java
@@ -17,33 +17,32 @@
 package com.ning.billing.invoice.dao;
 
 import static org.testng.Assert.assertTrue;
-import static org.testng.Assert.fail;
 
 import java.io.IOException;
 
-import com.ning.billing.config.InvoiceConfig;
-import com.ning.billing.invoice.model.DefaultInvoiceGenerator;
-import com.ning.billing.invoice.model.InvoiceGenerator;
-import com.ning.billing.invoice.tests.InvoicingTestBase;
-import com.ning.billing.util.callcontext.CallContext;
-import com.ning.billing.util.callcontext.CallOrigin;
-import com.ning.billing.util.callcontext.UserType;
-import com.ning.billing.util.callcontext.DefaultCallContextFactory;
-import com.ning.billing.util.clock.Clock;
 import org.apache.commons.io.IOUtils;
 import org.skife.jdbi.v2.Handle;
 import org.skife.jdbi.v2.TransactionCallback;
 import org.skife.jdbi.v2.TransactionStatus;
 import org.testng.annotations.AfterClass;
 import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
 
 import com.google.inject.Guice;
 import com.google.inject.Injector;
 import com.google.inject.Stage;
+import com.ning.billing.config.InvoiceConfig;
 import com.ning.billing.invoice.glue.InvoiceModuleWithEmbeddedDb;
+import com.ning.billing.invoice.model.DefaultInvoiceGenerator;
+import com.ning.billing.invoice.model.InvoiceGenerator;
+import com.ning.billing.invoice.tests.InvoicingTestBase;
 import com.ning.billing.util.bus.BusService;
 import com.ning.billing.util.bus.DefaultBusService;
-import org.testng.annotations.BeforeMethod;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.callcontext.CallOrigin;
+import com.ning.billing.util.callcontext.DefaultCallContextFactory;
+import com.ning.billing.util.callcontext.UserType;
+import com.ning.billing.util.clock.Clock;
 
 public abstract class InvoiceDaoTestBase extends InvoicingTestBase {
     protected InvoiceDao invoiceDao;
@@ -56,27 +55,22 @@ public abstract class InvoiceDaoTestBase extends InvoicingTestBase {
 
     private final InvoiceConfig invoiceConfig = new InvoiceConfig() {
         @Override
-        public long getDaoClaimTimeMs() {throw new UnsupportedOperationException();}
-        @Override
-        public int getDaoMaxReadyEvents() {throw new UnsupportedOperationException();}
-        @Override
-        public long getNotificationSleepTimeMs() {throw new UnsupportedOperationException();}
+        public long getSleepTimeMs() {throw new UnsupportedOperationException();}
         @Override
-        public boolean isEventProcessingOff() {throw new UnsupportedOperationException();}
+        public boolean isNotificationProcessingOff() {throw new UnsupportedOperationException();}
         @Override
         public int getNumberOfMonthsInFuture() {return 36;}
     };
 
     @BeforeClass(alwaysRun = true)
     protected void setup() throws IOException {
-        try {
             module = new InvoiceModuleWithEmbeddedDb();
             final String invoiceDdl = IOUtils.toString(DefaultInvoiceDao.class.getResourceAsStream("/com/ning/billing/invoice/ddl.sql"));
-            final String entitlementDdl = IOUtils.toString(DefaultInvoiceDao.class.getResourceAsStream("/com/ning/billing/entitlement/ddl.sql"));
+            final String utilDdl = IOUtils.toString(DefaultInvoiceDao.class.getResourceAsStream("/com/ning/billing/util/ddl.sql"));
 
             module.startDb();
             module.initDb(invoiceDdl);
-            module.initDb(entitlementDdl);
+            module.initDb(utilDdl);
 
             final Injector injector = Guice.createInjector(Stage.DEVELOPMENT, module);
 
@@ -94,10 +88,7 @@ public abstract class InvoiceDaoTestBase extends InvoicingTestBase {
             ((DefaultBusService) busService).startBus();
 
             assertTrue(true);
-        }
-        catch (Throwable t) {
-            fail(t.toString());
-        }
+       
     }
 
     @BeforeMethod(alwaysRun = true)
@@ -106,21 +97,9 @@ public abstract class InvoiceDaoTestBase extends InvoicingTestBase {
             @Override
             public Void inTransaction(Handle h, TransactionStatus status)
                     throws Exception {
-                h.execute("truncate table accounts");
-                //h.execute("truncate table entitlement_events");
-                //h.execute("truncate table subscriptions");
-                //h.execute("truncate table bundles");
-                //h.execute("truncate table notifications");
-                //h.execute("truncate table claimed_notifications");
                 h.execute("truncate table invoices");
                 h.execute("truncate table fixed_invoice_items");
                 h.execute("truncate table recurring_invoice_items");
-                //h.execute("truncate table tag_definitions");
-                //h.execute("truncate table tags");
-                //h.execute("truncate table custom_fields");
-                //h.execute("truncate table invoice_payments");
-                //h.execute("truncate table payment_attempts");
-                //h.execute("truncate table payments");
 
                 return null;
             }
diff --git a/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceDaoTests.java b/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceDaoTests.java
index ad329bc..bb8313a 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceDaoTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceDaoTests.java
@@ -16,6 +16,21 @@
 
 package com.ning.billing.invoice.dao;
 
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.testng.annotations.Test;
+
 import com.ning.billing.catalog.DefaultPrice;
 import com.ning.billing.catalog.MockInternationalPrice;
 import com.ning.billing.catalog.MockPlan;
@@ -26,11 +41,10 @@ import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.catalog.api.PhaseType;
 import com.ning.billing.catalog.api.Plan;
 import com.ning.billing.catalog.api.PlanPhase;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
 import com.ning.billing.entitlement.api.billing.BillingEvent;
 import com.ning.billing.entitlement.api.billing.BillingModeType;
-import com.ning.billing.entitlement.api.billing.DefaultBillingEvent;
 import com.ning.billing.entitlement.api.user.Subscription;
-import com.ning.billing.entitlement.api.user.SubscriptionTransition.SubscriptionTransitionType;
 import com.ning.billing.invoice.api.Invoice;
 import com.ning.billing.invoice.api.InvoiceApiException;
 import com.ning.billing.invoice.api.InvoiceItem;
@@ -42,22 +56,8 @@ import com.ning.billing.invoice.model.RecurringInvoiceItem;
 import com.ning.billing.mock.BrainDeadProxyFactory;
 import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
 import com.ning.billing.util.tag.ControlTagType;
-import org.joda.time.DateTime;
-import org.testng.annotations.Test;
-
-import java.math.BigDecimal;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.UUID;
-
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertFalse;
-import static org.testng.Assert.assertNotNull;
-import static org.testng.Assert.assertNull;
-import static org.testng.Assert.assertTrue;
 
-@Test(groups = {"invoicing", "invoicing-invoiceDao"})
+@Test(groups = {"slow", "invoicing", "invoicing-invoiceDao"})
 public class InvoiceDaoTests extends InvoiceDaoTestBase {
     @Test
     public void testCreationAndRetrievalByAccount() {
@@ -84,11 +84,12 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         Invoice invoice = new DefaultInvoice(accountId, clock.getUTCNow(), clock.getUTCNow(), Currency.USD);
         UUID invoiceId = invoice.getId();
         UUID subscriptionId = UUID.randomUUID();
+        UUID bundleId = UUID.randomUUID();
         DateTime startDate = new DateTime(2010, 1, 1, 0, 0, 0, 0);
         DateTime endDate = new DateTime(2010, 4, 1, 0, 0, 0, 0);
-        InvoiceItem invoiceItem = new RecurringInvoiceItem(invoiceId, accountId, subscriptionId,
-                "test plan", "test phase", startDate, endDate,
+        InvoiceItem invoiceItem = new RecurringInvoiceItem(invoiceId, accountId, bundleId,subscriptionId, "test plan", "test phase", startDate, endDate,
                 new BigDecimal("21.00"), new BigDecimal("7.00"), Currency.USD);
+
         invoice.addInvoiceItem(invoiceItem);
         invoiceDao.create(invoice, context);
 
@@ -155,6 +156,7 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
     @Test
     public void testGetInvoicesBySubscription() {
         UUID accountId = UUID.randomUUID();
+        UUID bundleId = UUID.randomUUID();
 
         UUID subscriptionId1 = UUID.randomUUID();
         BigDecimal rate1 = new BigDecimal("17.0");
@@ -177,19 +179,20 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         DateTime startDate = new DateTime(2011, 3, 1, 0, 0, 0, 0);
         DateTime endDate = startDate.plusMonths(1);
 
-        RecurringInvoiceItem item1 = new RecurringInvoiceItem(invoiceId1, accountId, subscriptionId1, "test plan", "test A", startDate, endDate,
+
+        RecurringInvoiceItem item1 = new RecurringInvoiceItem(invoiceId1, accountId, bundleId,subscriptionId1, "test plan", "test A", startDate, endDate,
                 rate1, rate1, Currency.USD);
         recurringInvoiceItemDao.create(item1, context);
 
-        RecurringInvoiceItem item2 = new RecurringInvoiceItem(invoiceId1, accountId, subscriptionId2, "test plan", "test B", startDate, endDate,
+        RecurringInvoiceItem item2 = new RecurringInvoiceItem(invoiceId1, accountId, bundleId,subscriptionId2, "test plan", "test B", startDate, endDate,
                 rate2, rate2, Currency.USD);
         recurringInvoiceItemDao.create(item2, context);
 
-        RecurringInvoiceItem item3 = new RecurringInvoiceItem(invoiceId1, accountId, subscriptionId3, "test plan", "test C", startDate, endDate,
+        RecurringInvoiceItem item3 = new RecurringInvoiceItem(invoiceId1, accountId, bundleId,subscriptionId3, "test plan", "test C", startDate, endDate,
                 rate3, rate3, Currency.USD);
         recurringInvoiceItemDao.create(item3, context);
 
-        RecurringInvoiceItem item4 = new RecurringInvoiceItem(invoiceId1, accountId, subscriptionId4, "test plan", "test D", startDate, endDate,
+        RecurringInvoiceItem item4 = new RecurringInvoiceItem(invoiceId1, accountId, bundleId,subscriptionId4, "test plan", "test D", startDate, endDate,
                 rate4, rate4, Currency.USD);
         recurringInvoiceItemDao.create(item4, context);
 
@@ -202,15 +205,16 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         startDate = endDate;
         endDate = startDate.plusMonths(1);
 
-        RecurringInvoiceItem item5 = new RecurringInvoiceItem(invoiceId2, accountId, subscriptionId1, "test plan", "test phase A", startDate, endDate,
+
+        RecurringInvoiceItem item5 = new RecurringInvoiceItem(invoiceId2, accountId, bundleId,subscriptionId1, "test plan", "test phase A", startDate, endDate,
                 rate1, rate1, Currency.USD);
         recurringInvoiceItemDao.create(item5, context);
 
-        RecurringInvoiceItem item6 = new RecurringInvoiceItem(invoiceId2, accountId, subscriptionId2, "test plan", "test phase B", startDate, endDate,
+        RecurringInvoiceItem item6 = new RecurringInvoiceItem(invoiceId2, accountId, bundleId,subscriptionId2, "test plan", "test phase B", startDate, endDate,
                 rate2, rate2, Currency.USD);
         recurringInvoiceItemDao.create(item6, context);
 
-        RecurringInvoiceItem item7 = new RecurringInvoiceItem(invoiceId2, accountId, subscriptionId3, "test plan", "test phase C", startDate, endDate,
+        RecurringInvoiceItem item7 = new RecurringInvoiceItem(invoiceId2, accountId, bundleId,subscriptionId3, "test plan", "test phase C", startDate, endDate,
                 rate3, rate3, Currency.USD);
         recurringInvoiceItemDao.create(item7, context);
 
@@ -260,6 +264,7 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
     @Test
     public void testAccountBalance() {
         UUID accountId = UUID.randomUUID();
+        UUID bundleId = UUID.randomUUID();
         DateTime targetDate1 = new DateTime(2011, 10, 6, 0, 0, 0, 0);
         Invoice invoice1 = new DefaultInvoice(accountId, clock.getUTCNow(), targetDate1, Currency.USD);
         invoiceDao.create(invoice1, context);
@@ -270,11 +275,11 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         BigDecimal rate1 = new BigDecimal("17.0");
         BigDecimal rate2 = new BigDecimal("42.0");
 
-        RecurringInvoiceItem item1 = new RecurringInvoiceItem(invoice1.getId(), accountId, UUID.randomUUID(), "test plan", "test phase A", startDate,
+        RecurringInvoiceItem item1 = new RecurringInvoiceItem(invoice1.getId(), accountId, bundleId,UUID.randomUUID(), "test plan", "test phase A", startDate,
                 endDate, rate1, rate1, Currency.USD);
         recurringInvoiceItemDao.create(item1, context);
 
-        RecurringInvoiceItem item2 = new RecurringInvoiceItem(invoice1.getId(), accountId, UUID.randomUUID(), "test plan", "test phase B", startDate,
+        RecurringInvoiceItem item2 = new RecurringInvoiceItem(invoice1.getId(), accountId, bundleId,UUID.randomUUID(), "test plan", "test phase B", startDate,
                 endDate, rate2, rate2, Currency.USD);
         recurringInvoiceItemDao.create(item2, context);
 
@@ -289,6 +294,7 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
     @Test
     public void testAccountBalanceWithNoPayments() {
         UUID accountId = UUID.randomUUID();
+        UUID bundleId = UUID.randomUUID();
         DateTime targetDate1 = new DateTime(2011, 10, 6, 0, 0, 0, 0);
         Invoice invoice1 = new DefaultInvoice(accountId, clock.getUTCNow(), targetDate1, Currency.USD);
         invoiceDao.create(invoice1, context);
@@ -299,11 +305,11 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         BigDecimal rate1 = new BigDecimal("17.0");
         BigDecimal rate2 = new BigDecimal("42.0");
 
-        RecurringInvoiceItem item1 = new RecurringInvoiceItem(invoice1.getId(), accountId, UUID.randomUUID(), "test plan", "test phase A", startDate, endDate,
+        RecurringInvoiceItem item1 = new RecurringInvoiceItem(invoice1.getId(), accountId, bundleId, UUID.randomUUID(), "test plan", "test phase A", startDate, endDate,
                 rate1, rate1, Currency.USD);
         recurringInvoiceItemDao.create(item1, context);
 
-        RecurringInvoiceItem item2 = new RecurringInvoiceItem(invoice1.getId(), accountId, UUID.randomUUID(), "test plan", "test phase B", startDate, endDate,
+        RecurringInvoiceItem item2 = new RecurringInvoiceItem(invoice1.getId(), accountId, bundleId, UUID.randomUUID(), "test plan", "test phase B", startDate, endDate,
                 rate2, rate2, Currency.USD);
         recurringInvoiceItemDao.create(item2, context);
 
@@ -329,6 +335,7 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
     @Test
     public void testGetUnpaidInvoicesByAccountId() {
         UUID accountId = UUID.randomUUID();
+        UUID bundleId = UUID.randomUUID();
         DateTime targetDate1 = new DateTime(2011, 10, 6, 0, 0, 0, 0);
         Invoice invoice1 = new DefaultInvoice(accountId, clock.getUTCNow(), targetDate1, Currency.USD);
         invoiceDao.create(invoice1, context);
@@ -339,11 +346,12 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         BigDecimal rate1 = new BigDecimal("17.0");
         BigDecimal rate2 = new BigDecimal("42.0");
 
-        RecurringInvoiceItem item1 = new RecurringInvoiceItem(invoice1.getId(), accountId, UUID.randomUUID(), "test plan", "test phase A", startDate, endDate,
+
+        RecurringInvoiceItem item1 = new RecurringInvoiceItem(invoice1.getId(), accountId, bundleId, UUID.randomUUID(), "test plan", "test phase A", startDate, endDate,
                 rate1, rate1, Currency.USD);
         recurringInvoiceItemDao.create(item1, context);
 
-        RecurringInvoiceItem item2 = new RecurringInvoiceItem(invoice1.getId(), accountId, UUID.randomUUID(), "test plan", "test phase B", startDate, endDate,
+        RecurringInvoiceItem item2 = new RecurringInvoiceItem(invoice1.getId(), accountId, bundleId, UUID.randomUUID(), "test plan", "test phase B", startDate, endDate,
                 rate2, rate2, Currency.USD);
         recurringInvoiceItemDao.create(item2, context);
 
@@ -367,7 +375,7 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
 
         BigDecimal rate3 = new BigDecimal("21.0");
 
-        RecurringInvoiceItem item3 = new RecurringInvoiceItem(invoice2.getId(), accountId, UUID.randomUUID(), "test plan", "test phase C", startDate2, endDate2,
+        RecurringInvoiceItem item3 = new RecurringInvoiceItem(invoice2.getId(), accountId, bundleId, UUID.randomUUID(), "test plan", "test phase C", startDate2, endDate2,
                 rate3, rate3, Currency.USD);
         recurringInvoiceItemDao.create(item3, context);
 
@@ -398,11 +406,10 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         MockPlanPhase phase1 = new MockPlanPhase(recurringPrice, null, BillingPeriod.MONTHLY, PhaseType.TRIAL);
         MockPlan plan1 = new MockPlan(phase1);
 
-        Subscription subscription = BrainDeadProxyFactory.createBrainDeadProxyFor(Subscription.class);
-        ((ZombieControl) subscription).addResult("getId", UUID.randomUUID());
+        Subscription subscription = getZombieSubscription();
 
         DateTime effectiveDate1 = new DateTime(2011, 2, 1, 0, 0, 0, 0);
-        BillingEvent event1 = new DefaultBillingEvent(subscription, effectiveDate1, plan1, phase1, null,
+        BillingEvent event1 = createMockBillingEvent(null, subscription, effectiveDate1, plan1, phase1, null,
                 recurringPrice.getPrice(currency), currency, BillingPeriod.MONTHLY, 1, BillingModeType.IN_ADVANCE,
                 "testEvent1", 1L, SubscriptionTransitionType.CREATE);
 
@@ -420,7 +427,7 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         MockPlan plan2 = new MockPlan(phase2);
 
         DateTime effectiveDate2 = new DateTime(2011, 2, 15, 0, 0, 0, 0);
-        BillingEvent event2 = new DefaultBillingEvent(subscription, effectiveDate2, plan2, phase2, null,
+        BillingEvent event2 = createMockBillingEvent(null, subscription, effectiveDate2, plan2, phase2, null,
                 recurringPrice2.getPrice(currency), currency, BillingPeriod.MONTHLY, 1, BillingModeType.IN_ADVANCE,
                 "testEvent2", 2L, SubscriptionTransitionType.CREATE);
         events.add(event2);
@@ -449,11 +456,10 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         MockPlanPhase phase = new MockPlanPhase(recurringPrice, null);
         MockPlan plan = new MockPlan(phase);
 
-        Subscription subscription = BrainDeadProxyFactory.createBrainDeadProxyFor(Subscription.class);
-        ((ZombieControl) subscription).addResult("getId", UUID.randomUUID());
+        Subscription subscription = getZombieSubscription();
         DateTime effectiveDate = buildDateTime(2011, 1, 1);
 
-        BillingEvent event = new DefaultBillingEvent(subscription, effectiveDate, plan, phase, null,
+        BillingEvent event = createMockBillingEvent(null, subscription, effectiveDate, plan, phase, null,
                 recurringPrice.getPrice(currency), currency, BillingPeriod.MONTHLY, 15, BillingModeType.IN_ADVANCE,
                 "testEvent", 1L, SubscriptionTransitionType.CREATE);
         BillingEventSet events = new BillingEventSet();
@@ -467,6 +473,13 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         assertEquals(invoice.getTotalAmount().compareTo(ZERO), 0);
     }
 
+    private Subscription getZombieSubscription() {
+        Subscription subscription = BrainDeadProxyFactory.createBrainDeadProxyFor(Subscription.class);
+        ((ZombieControl) subscription).addResult("getId", UUID.randomUUID());
+        ((ZombieControl) subscription).addResult("getBundleId", UUID.randomUUID());
+        return subscription;
+    }
+
     @Test
     public void testInvoiceForFreeTrialWithRecurringDiscount() throws InvoiceApiException, CatalogApiException {
         Currency currency = Currency.USD;
@@ -482,11 +495,10 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
 
         MockPlan plan = new MockPlan();
 
-        Subscription subscription = BrainDeadProxyFactory.createBrainDeadProxyFor(Subscription.class);
-        ((ZombieControl) subscription).addResult("getId", UUID.randomUUID());
+        Subscription subscription = getZombieSubscription();
         DateTime effectiveDate1 = buildDateTime(2011, 1, 1);
 
-        BillingEvent event1 = new DefaultBillingEvent(subscription, effectiveDate1, plan, phase1, fixedPrice.getPrice(currency),
+        BillingEvent event1 = createMockBillingEvent(null, subscription, effectiveDate1, plan, phase1, fixedPrice.getPrice(currency),
                 null, currency, BillingPeriod.MONTHLY, 1, BillingModeType.IN_ADVANCE,
                 "testEvent1", 1L, SubscriptionTransitionType.CREATE);
         BillingEventSet events = new BillingEventSet();
@@ -502,7 +514,7 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         invoiceList.add(invoice1);
 
         DateTime effectiveDate2 = effectiveDate1.plusDays(30);
-        BillingEvent event2 = new DefaultBillingEvent(subscription, effectiveDate2, plan, phase2, null,
+        BillingEvent event2 = createMockBillingEvent(null, subscription, effectiveDate2, plan, phase2, null,
                 recurringPrice.getPrice(currency), currency, BillingPeriod.MONTHLY, 31, BillingModeType.IN_ADVANCE,
                 "testEvent2", 2L, SubscriptionTransitionType.CHANGE);
         events.add(event2);
@@ -542,11 +554,10 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
 
         MockPlan plan = new MockPlan();
 
-        Subscription subscription = BrainDeadProxyFactory.createBrainDeadProxyFor(Subscription.class);
-        ((ZombieControl) subscription).addResult("getId", UUID.randomUUID());
+        Subscription subscription = getZombieSubscription();
         DateTime effectiveDate1 = buildDateTime(2011, 1, 1);
 
-        BillingEvent event1 = new DefaultBillingEvent(subscription, effectiveDate1, plan, phase1,
+        BillingEvent event1 = createMockBillingEvent(null, subscription, effectiveDate1, plan, phase1,
                 fixedPrice.getPrice(currency), null, currency,
                 BillingPeriod.MONTHLY, 1, BillingModeType.IN_ADVANCE,
                 "testEvent1", 1L, SubscriptionTransitionType.CREATE);
@@ -554,7 +565,7 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         events.add(event1);
 
         DateTime effectiveDate2 = effectiveDate1.plusDays(30);
-        BillingEvent event2 = new DefaultBillingEvent(subscription, effectiveDate2, plan, phase2, null,
+        BillingEvent event2 = createMockBillingEvent(null, subscription, effectiveDate2, plan, phase2, null,
                 recurringPrice.getPrice(currency), currency, BillingPeriod.MONTHLY, 31, BillingModeType.IN_ADVANCE,
                 "testEvent2", 2L, SubscriptionTransitionType.CHANGE);
         events.add(event2);
@@ -578,8 +589,7 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         DateTime targetDate1 = DateTime.now().plusMonths(1);
         DateTime targetDate2 = DateTime.now().plusMonths(2);
 
-        Subscription subscription = BrainDeadProxyFactory.createBrainDeadProxyFor(Subscription.class);
-        ((ZombieControl) subscription).addResult("getId", UUID.randomUUID());
+        Subscription subscription = getZombieSubscription();
 
         Plan plan = BrainDeadProxyFactory.createBrainDeadProxyFor(Plan.class);
         ((ZombieControl) plan).addResult("getName", "plan");
@@ -593,10 +603,10 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         BillingEventSet events = new BillingEventSet();
         List<Invoice> invoices = new ArrayList<Invoice>();
 
-        BillingEvent event1 = new DefaultBillingEvent(subscription, targetDate1, plan, phase1, null,
-                                                      TEN, currency,
-                                                      BillingPeriod.MONTHLY, 31, BillingModeType.IN_ADVANCE,
-                                                      "testEvent1", 1L, SubscriptionTransitionType.CHANGE);
+        BillingEvent event1 = createMockBillingEvent(null, subscription, targetDate1, plan, phase1, null,
+                TEN, currency,
+                BillingPeriod.MONTHLY, 31, BillingModeType.IN_ADVANCE,
+                "testEvent1", 1L, SubscriptionTransitionType.CHANGE);
         events.add(event1);
 
         Invoice invoice1 = generator.generateInvoice(UUID.randomUUID(), events, invoices, targetDate1, Currency.USD);
@@ -605,10 +615,10 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         invoice1 = invoiceDao.getById(invoice1.getId());
         assertNotNull(invoice1.getInvoiceNumber());
 
-        BillingEvent event2 = new DefaultBillingEvent(subscription, targetDate1, plan, phase2, null,
-                                                      TWENTY, currency,
-                                                      BillingPeriod.MONTHLY, 31, BillingModeType.IN_ADVANCE,
-                                                      "testEvent2", 2L, SubscriptionTransitionType.CHANGE);
+        BillingEvent event2 = createMockBillingEvent(null, subscription, targetDate1, plan, phase2, null,
+                TWENTY, currency,
+                BillingPeriod.MONTHLY, 31, BillingModeType.IN_ADVANCE,
+                "testEvent2", 2L, SubscriptionTransitionType.CHANGE);
         events.add(event2);
         Invoice invoice2 = generator.generateInvoice(UUID.randomUUID(), events, invoices, targetDate2, Currency.USD);
         invoiceDao.create(invoice2, context);
@@ -618,8 +628,7 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
 
     @Test
     public void testAddingWrittenOffTag() throws InvoiceApiException {
-        Subscription subscription = BrainDeadProxyFactory.createBrainDeadProxyFor(Subscription.class);
-        ((ZombieControl) subscription).addResult("getId", UUID.randomUUID());
+        Subscription subscription = getZombieSubscription();
 
         Plan plan = BrainDeadProxyFactory.createBrainDeadProxyFor(Plan.class);
         ((ZombieControl) plan).addResult("getName", "plan");
@@ -631,10 +640,10 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         Currency currency = Currency.USD;
 
         // create pseudo-random invoice
-        BillingEvent event1 = new DefaultBillingEvent(subscription, targetDate1, plan, phase1, null,
-                                                      TEN, currency,
-                                                      BillingPeriod.MONTHLY, 31, BillingModeType.IN_ADVANCE,
-                                                      "testEvent1", 1L, SubscriptionTransitionType.CHANGE);
+        BillingEvent event1 = createMockBillingEvent(null, subscription, targetDate1, plan, phase1, null,
+                TEN, currency,
+                BillingPeriod.MONTHLY, 31, BillingModeType.IN_ADVANCE,
+                "testEvent1", 1L, SubscriptionTransitionType.CHANGE);
         BillingEventSet events = new BillingEventSet();
         events.add(event1);
 
@@ -644,13 +653,12 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         invoiceDao.addControlTag(ControlTagType.WRITTEN_OFF, invoice.getId(), context);
 
         Invoice savedInvoice = invoiceDao.getById(invoice.getId());
-        assertTrue(savedInvoice.hasTag(ControlTagType.WRITTEN_OFF.toString()));
+        assertTrue(savedInvoice.hasTag(ControlTagType.WRITTEN_OFF));
     }
 
     @Test
     public void testRemoveWrittenOffTag() throws InvoiceApiException {
-        Subscription subscription = BrainDeadProxyFactory.createBrainDeadProxyFor(Subscription.class);
-        ((ZombieControl) subscription).addResult("getId", UUID.randomUUID());
+        Subscription subscription = getZombieSubscription();
 
         Plan plan = BrainDeadProxyFactory.createBrainDeadProxyFor(Plan.class);
         ((ZombieControl) plan).addResult("getName", "plan");
@@ -662,10 +670,10 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         Currency currency = Currency.USD;
 
         // create pseudo-random invoice
-        BillingEvent event1 = new DefaultBillingEvent(subscription, targetDate1, plan, phase1, null,
-                                                      TEN, currency,
-                                                      BillingPeriod.MONTHLY, 31, BillingModeType.IN_ADVANCE,
-                                                      "testEvent1", 1L, SubscriptionTransitionType.CHANGE);
+        BillingEvent event1 = createMockBillingEvent(null, subscription, targetDate1, plan, phase1, null,
+                TEN, currency,
+                BillingPeriod.MONTHLY, 31, BillingModeType.IN_ADVANCE,
+                "testEvent1", 1L, SubscriptionTransitionType.CHANGE);
         BillingEventSet events = new BillingEventSet();
         events.add(event1);
 
@@ -675,10 +683,10 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         invoiceDao.addControlTag(ControlTagType.WRITTEN_OFF, invoice.getId(), context);
 
         Invoice savedInvoice = invoiceDao.getById(invoice.getId());
-        assertTrue(savedInvoice.hasTag(ControlTagType.WRITTEN_OFF.toString()));
+        assertTrue(savedInvoice.hasTag(ControlTagType.WRITTEN_OFF));
 
         invoiceDao.removeControlTag(ControlTagType.WRITTEN_OFF, invoice.getId(), context);
         savedInvoice = invoiceDao.getById(invoice.getId());
-        assertFalse(savedInvoice.hasTag(ControlTagType.WRITTEN_OFF.toString()));
+        assertFalse(savedInvoice.hasTag(ControlTagType.WRITTEN_OFF));
     }
 }
diff --git a/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceItemDaoTests.java b/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceItemDaoTests.java
index 464ef86..9acde8e 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceItemDaoTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceItemDaoTests.java
@@ -16,19 +16,20 @@
 
 package com.ning.billing.invoice.dao;
 
-import com.ning.billing.catalog.api.Currency;
-import com.ning.billing.invoice.api.InvoiceItem;
-import com.ning.billing.invoice.model.DefaultInvoice;
-import com.ning.billing.invoice.model.RecurringInvoiceItem;
-import org.joda.time.DateTime;
-import org.testng.annotations.Test;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
 
 import java.math.BigDecimal;
 import java.util.List;
 import java.util.UUID;
 
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertNotNull;
+import org.joda.time.DateTime;
+import org.testng.annotations.Test;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.model.DefaultInvoice;
+import com.ning.billing.invoice.model.RecurringInvoiceItem;
 
 @Test(groups = {"invoicing", "invoicing-invoiceDao"})
 public class InvoiceItemDaoTests extends InvoiceDaoTestBase {
@@ -36,12 +37,13 @@ public class InvoiceItemDaoTests extends InvoiceDaoTestBase {
     public void testInvoiceItemCreation() {
         UUID accountId = UUID.randomUUID();
         UUID invoiceId = UUID.randomUUID();
+        UUID bundleId = UUID.randomUUID();
         UUID subscriptionId = UUID.randomUUID();
         DateTime startDate = new DateTime(2011, 10, 1, 0, 0, 0, 0);
         DateTime endDate = new DateTime(2011, 11, 1, 0, 0, 0, 0);
         BigDecimal rate = new BigDecimal("20.00");
 
-        RecurringInvoiceItem item = new RecurringInvoiceItem(invoiceId, accountId, subscriptionId, "test plan", "test phase", startDate, endDate,
+        RecurringInvoiceItem item = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, "test plan", "test phase", startDate, endDate,
                 rate, rate, Currency.USD);
         recurringInvoiceItemDao.create(item, context);
 
@@ -63,12 +65,14 @@ public class InvoiceItemDaoTests extends InvoiceDaoTestBase {
     public void testGetInvoiceItemsBySubscriptionId() {
         UUID accountId = UUID.randomUUID();
         UUID subscriptionId = UUID.randomUUID();
+        UUID bundleId = UUID.randomUUID();
         DateTime startDate = new DateTime(2011, 3, 1, 0, 0, 0, 0);
         BigDecimal rate = new BigDecimal("20.00");
 
         for (int i = 0; i < 3; i++) {
             UUID invoiceId = UUID.randomUUID();
-            RecurringInvoiceItem item = new RecurringInvoiceItem(invoiceId, accountId, subscriptionId,
+
+            RecurringInvoiceItem item = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId,
                     "test plan", "test phase", startDate.plusMonths(i), startDate.plusMonths(i + 1),
                     rate, rate, Currency.USD);
             recurringInvoiceItemDao.create(item, context);
@@ -82,13 +86,15 @@ public class InvoiceItemDaoTests extends InvoiceDaoTestBase {
     public void testGetInvoiceItemsByInvoiceId() {
         UUID accountId = UUID.randomUUID();
         UUID invoiceId = UUID.randomUUID();
+        UUID bundleId = UUID.randomUUID();
         DateTime startDate = new DateTime(2011, 3, 1, 0, 0, 0, 0);
         BigDecimal rate = new BigDecimal("20.00");
 
         for (int i = 0; i < 5; i++) {
             UUID subscriptionId = UUID.randomUUID();
             BigDecimal amount = rate.multiply(new BigDecimal(i + 1));
-            RecurringInvoiceItem item = new RecurringInvoiceItem(invoiceId, accountId, subscriptionId,
+
+            RecurringInvoiceItem item = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId,
                     "test plan", "test phase", startDate, startDate.plusMonths(1),
                     amount, amount, Currency.USD);
             recurringInvoiceItemDao.create(item, context);
@@ -101,6 +107,7 @@ public class InvoiceItemDaoTests extends InvoiceDaoTestBase {
     @Test(groups = "slow")
     public void testGetInvoiceItemsByAccountId() {
         UUID accountId = UUID.randomUUID();
+        UUID bundleId = UUID.randomUUID();
         DateTime targetDate = new DateTime(2011, 5, 23, 0, 0, 0, 0);
         DefaultInvoice invoice = new DefaultInvoice(accountId, clock.getUTCNow(), targetDate, Currency.USD);
 
@@ -111,7 +118,8 @@ public class InvoiceItemDaoTests extends InvoiceDaoTestBase {
         BigDecimal rate = new BigDecimal("20.00");
 
         UUID subscriptionId = UUID.randomUUID();
-        RecurringInvoiceItem item = new RecurringInvoiceItem(invoiceId, accountId, subscriptionId,
+
+        RecurringInvoiceItem item = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId,
                 "test plan", "test phase", startDate, startDate.plusMonths(1),
                 rate, rate, Currency.USD);
         recurringInvoiceItemDao.create(item, context);
diff --git a/invoice/src/test/java/com/ning/billing/invoice/dao/MockInvoiceDao.java b/invoice/src/test/java/com/ning/billing/invoice/dao/MockInvoiceDao.java
index dec7697..ea8345d 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/dao/MockInvoiceDao.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/dao/MockInvoiceDao.java
@@ -23,16 +23,16 @@ import java.util.List;
 import java.util.Map;
 import java.util.UUID;
 
-import com.ning.billing.invoice.api.InvoicePayment;
-import com.ning.billing.util.callcontext.CallContext;
-import com.ning.billing.util.bus.Bus;
-import com.ning.billing.util.tag.ControlTagType;
 import org.joda.time.DateTime;
 
 import com.google.inject.Inject;
 import com.ning.billing.invoice.api.Invoice;
 import com.ning.billing.invoice.api.InvoiceItem;
-import com.ning.billing.invoice.api.user.DefaultInvoiceCreationNotification;
+import com.ning.billing.invoice.api.InvoicePayment;
+import com.ning.billing.invoice.api.user.DefaultInvoiceCreationEvent;
+import com.ning.billing.util.bus.Bus;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.tag.ControlTagType;
 
 public class MockInvoiceDao implements InvoiceDao {
     private final Bus eventBus;
@@ -50,9 +50,9 @@ public class MockInvoiceDao implements InvoiceDao {
             invoices.put(invoice.getId(), invoice);
         }
         try {
-            eventBus.post(new DefaultInvoiceCreationNotification(invoice.getId(), invoice.getAccountId(),
+            eventBus.post(new DefaultInvoiceCreationEvent(invoice.getId(), invoice.getAccountId(),
                                                                  invoice.getBalance(), invoice.getCurrency(),
-                                                                 invoice.getInvoiceDate()));
+                                                                 invoice.getInvoiceDate(), null));
         }
         catch (Bus.EventBusException ex) {
             throw new RuntimeException(ex);
diff --git a/invoice/src/test/java/com/ning/billing/invoice/glue/InvoiceModuleWithEmbeddedDb.java b/invoice/src/test/java/com/ning/billing/invoice/glue/InvoiceModuleWithEmbeddedDb.java
index fd15171..081b21f 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/glue/InvoiceModuleWithEmbeddedDb.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/glue/InvoiceModuleWithEmbeddedDb.java
@@ -16,38 +16,43 @@
 
 package com.ning.billing.invoice.glue;
 
+import static org.testng.Assert.assertNotNull;
+
 import java.io.IOException;
 import java.net.URL;
 
-import com.ning.billing.invoice.api.test.InvoiceTestApi;
+import org.skife.jdbi.v2.IDBI;
+
+import com.ning.billing.account.api.AccountUserApi;
+import com.ning.billing.catalog.glue.CatalogModule;
+import com.ning.billing.dbi.MysqlTestingHelper;
+import com.ning.billing.invoice.api.InvoiceNotifier;
 import com.ning.billing.invoice.api.test.DefaultInvoiceTestApi;
+import com.ning.billing.invoice.api.test.InvoiceTestApi;
 import com.ning.billing.invoice.dao.InvoicePaymentSqlDao;
 import com.ning.billing.invoice.dao.RecurringInvoiceItemSqlDao;
 import com.ning.billing.invoice.notification.MockNextBillingDateNotifier;
 import com.ning.billing.invoice.notification.MockNextBillingDatePoster;
 import com.ning.billing.invoice.notification.NextBillingDateNotifier;
 import com.ning.billing.invoice.notification.NextBillingDatePoster;
+import com.ning.billing.invoice.notification.NullInvoiceNotifier;
+import com.ning.billing.junction.api.BillingApi;
 import com.ning.billing.mock.BrainDeadProxyFactory;
+import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
+import com.ning.billing.mock.glue.MockEntitlementModule;
 import com.ning.billing.util.callcontext.CallContextFactory;
 import com.ning.billing.util.callcontext.DefaultCallContextFactory;
-import com.ning.billing.util.glue.FieldStoreModule;
-import com.ning.billing.util.glue.GlobalLockerModule;
-import com.ning.billing.util.glue.TagStoreModule;
-import org.skife.jdbi.v2.IDBI;
-
-import com.ning.billing.account.api.AccountUserApi;
-import com.ning.billing.catalog.glue.CatalogModule;
-import com.ning.billing.dbi.MysqlTestingHelper;
-import com.ning.billing.entitlement.glue.EntitlementModule;
 import com.ning.billing.util.clock.Clock;
 import com.ning.billing.util.clock.DefaultClock;
+import com.ning.billing.util.email.templates.TemplateModule;
 import com.ning.billing.util.glue.BusModule;
+import com.ning.billing.util.glue.FieldStoreModule;
+import com.ning.billing.util.glue.GlobalLockerModule;
+import com.ning.billing.util.glue.TagStoreModule;
 import com.ning.billing.util.notificationq.MockNotificationQueueService;
 import com.ning.billing.util.notificationq.NotificationQueueService;
 
-import static org.testng.Assert.assertNotNull;
-
-public class InvoiceModuleWithEmbeddedDb extends InvoiceModule {
+public class InvoiceModuleWithEmbeddedDb extends DefaultInvoiceModule {
     private final MysqlTestingHelper helper = new MysqlTestingHelper();
     private IDBI dbi;
 
@@ -80,9 +85,10 @@ public class InvoiceModuleWithEmbeddedDb extends InvoiceModule {
     }
 
     @Override
-    protected void installNotifier() {
+    protected void installNotifiers() {
         bind(NextBillingDateNotifier.class).to(MockNextBillingDateNotifier.class).asEagerSingleton();
         bind(NextBillingDatePoster.class).to(MockNextBillingDatePoster.class).asEagerSingleton();
+        bind(InvoiceNotifier.class).to(NullInvoiceNotifier.class).asEagerSingleton();
     }
 
     @Override
@@ -94,20 +100,27 @@ public class InvoiceModuleWithEmbeddedDb extends InvoiceModule {
 
         bind(Clock.class).to(DefaultClock.class).asEagerSingleton();
         bind(CallContextFactory.class).to(DefaultCallContextFactory.class).asEagerSingleton();
-                install(new FieldStoreModule());
+        install(new FieldStoreModule());
         install(new TagStoreModule());
 
         installNotificationQueue();
 //      install(new AccountModule());
         bind(AccountUserApi.class).toInstance(BrainDeadProxyFactory.createBrainDeadProxyFor(AccountUserApi.class));
+
+        BillingApi billingApi = BrainDeadProxyFactory.createBrainDeadProxyFor(BillingApi.class);
+        ((ZombieControl) billingApi).addResult("setChargedThroughDateFromTransaction", BrainDeadProxyFactory.ZOMBIE_VOID);
+        bind(BillingApi.class).toInstance(billingApi);
+
         install(new CatalogModule());
-        install(new EntitlementModule());
+        install(new MockEntitlementModule());
         install(new GlobalLockerModule());
 
         super.configure();
 
         bind(InvoiceTestApi.class).to(DefaultInvoiceTestApi.class).asEagerSingleton();
         install(new BusModule());
+        install(new TemplateModule());
+
     }
 
     private static void loadSystemPropertiesFromClasspath(final String resource) {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/glue/InvoiceModuleWithMocks.java b/invoice/src/test/java/com/ning/billing/invoice/glue/InvoiceModuleWithMocks.java
index fe4feb4..521d83d 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/glue/InvoiceModuleWithMocks.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/glue/InvoiceModuleWithMocks.java
@@ -16,18 +16,17 @@
 
 package com.ning.billing.invoice.glue;
 
+import com.ning.billing.invoice.api.InvoiceNotifier;
 import com.ning.billing.invoice.dao.InvoiceDao;
 import com.ning.billing.invoice.dao.MockInvoiceDao;
+import com.ning.billing.invoice.notification.NullInvoiceNotifier;
 import com.ning.billing.util.globallocker.GlobalLocker;
 import com.ning.billing.util.globallocker.MockGlobalLocker;
-import com.ning.billing.util.glue.CallContextModule;
 import com.ning.billing.util.glue.FieldStoreModule;
-import com.ning.billing.util.glue.TagStoreModule;
-import org.skife.jdbi.v2.Call;
 
 
-public class InvoiceModuleWithMocks extends InvoiceModule {
-    @Override
+public class InvoiceModuleWithMocks extends DefaultInvoiceModule {
+    @Override 
     protected void installInvoiceDao() {
         bind(MockInvoiceDao.class).asEagerSingleton();
         bind(InvoiceDao.class).to(MockInvoiceDao.class);
@@ -35,18 +34,13 @@ public class InvoiceModuleWithMocks extends InvoiceModule {
     }
 
     @Override
-    protected void installGlobalLocker() {
-        bind(GlobalLocker.class).to(MockGlobalLocker.class).asEagerSingleton();
-    }
-
-    @Override
     protected void installInvoiceListener() {
 
     }
 
     @Override
-    protected void installNotifier() {
-
+    protected void installNotifiers() {
+        bind(InvoiceNotifier.class).to(NullInvoiceNotifier.class).asEagerSingleton();
     }
 
     @Override
@@ -55,15 +49,7 @@ public class InvoiceModuleWithMocks extends InvoiceModule {
     }
 
     @Override
-    protected void installInvoiceMigrationApi() {
-
-    }
-
-    @Override
-    public void configure() {
-        super.configure();
+    public void installInvoiceMigrationApi() {
 
-        install(new FieldStoreModule());
-        //install(new TagStoreModule());
     }
 }
diff --git a/invoice/src/test/java/com/ning/billing/invoice/HtmlInvoiceGeneratorTest.java b/invoice/src/test/java/com/ning/billing/invoice/HtmlInvoiceGeneratorTest.java
new file mode 100644
index 0000000..95f39c9
--- /dev/null
+++ b/invoice/src/test/java/com/ning/billing/invoice/HtmlInvoiceGeneratorTest.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2010-2011 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.invoice;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.api.formatters.InvoiceFormatterFactory;
+import com.ning.billing.invoice.template.HtmlInvoiceGenerator;
+import com.ning.billing.invoice.template.formatters.DefaultInvoiceFormatterFactory;
+import com.ning.billing.mock.BrainDeadProxyFactory;
+import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
+import com.ning.billing.util.email.templates.MustacheTemplateEngine;
+import com.ning.billing.util.email.templates.TemplateEngine;
+import com.ning.billing.util.template.translation.TranslatorConfig;
+import org.joda.time.DateTime;
+import org.skife.config.ConfigurationObjectFactory;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+import static org.testng.Assert.assertNotNull;
+
+@Test(groups = {"fast", "email"})
+public class HtmlInvoiceGeneratorTest {
+    private HtmlInvoiceGenerator g;
+    private final static String TEST_TEMPLATE_NAME = "HtmlInvoiceTemplate";
+
+    @BeforeClass
+    public void setup() {
+        TranslatorConfig config = new ConfigurationObjectFactory(System.getProperties()).build(TranslatorConfig.class);
+        TemplateEngine templateEngine = new MustacheTemplateEngine();
+        InvoiceFormatterFactory factory = new DefaultInvoiceFormatterFactory();
+        g = new HtmlInvoiceGenerator(factory, templateEngine, config);
+    }
+
+    @Test
+    public void testGenerateInvoice() throws Exception {
+        String output = g.generateInvoice(createAccount(), createInvoice(), TEST_TEMPLATE_NAME);
+        assertNotNull(output);
+        System.out.print(output);
+    }
+
+    private Account createAccount() {
+        Account account = BrainDeadProxyFactory.createBrainDeadProxyFor(Account.class);
+        ZombieControl zombieControl = (ZombieControl) account;
+        zombieControl.addResult("getExternalKey", "1234abcd");
+        zombieControl.addResult("getName", "Jim Smith");
+        zombieControl.addResult("getFirstNameLength", 3);
+        zombieControl.addResult("getEmail", "jim.smith@mail.com");
+        zombieControl.addResult("getLocale", Locale.US.toString());
+        zombieControl.addResult("getAddress1", "123 Some Street");
+        zombieControl.addResult("getAddress2", "Apt 456");
+        zombieControl.addResult("getCity", "Some City");
+        zombieControl.addResult("getStateOrProvince", "Some State");
+        zombieControl.addResult("getPostalCode", "12345-6789");
+        zombieControl.addResult("getCountry", "USA");
+        zombieControl.addResult("getPhone", "123-456-7890");
+
+        return account;
+    }
+
+    private Invoice createInvoice() {
+        DateTime startDate = new DateTime().minusMonths(1);
+        DateTime endDate = new DateTime();
+
+        BigDecimal price1 = new BigDecimal("29.95");
+        BigDecimal price2 = new BigDecimal("59.95");
+        Invoice dummyInvoice = BrainDeadProxyFactory.createBrainDeadProxyFor(Invoice.class);
+        ZombieControl zombie = (ZombieControl) dummyInvoice;
+        zombie.addResult("getInvoiceDate", startDate);
+        zombie.addResult("getInvoiceNumber", 42);
+        zombie.addResult("getCurrency", Currency.USD);
+        zombie.addResult("getTotalAmount", price1.add(price2));
+        zombie.addResult("getAmountPaid", BigDecimal.ZERO);
+        zombie.addResult("getBalance", price1.add(price2));
+
+        List<InvoiceItem> items = new ArrayList<InvoiceItem>();
+        items.add(createInvoiceItem(price1, "Domain 1", startDate, endDate, "ning-plus"));
+        items.add(createInvoiceItem(price2, "Domain 2", startDate, endDate, "ning-pro"));
+        zombie.addResult("getInvoiceItems", items);
+
+        return dummyInvoice;
+    }
+
+    private InvoiceItem createInvoiceItem(BigDecimal amount, String networkName, DateTime startDate, DateTime endDate, String planName) {
+        InvoiceItem item = BrainDeadProxyFactory.createBrainDeadProxyFor(InvoiceItem.class);
+        ZombieControl zombie = (ZombieControl) item;
+        zombie.addResult("getAmount", amount);
+        zombie.addResult("getStartDate", startDate);
+        zombie.addResult("getEndDate", endDate);
+        zombie.addResult("getPlanName", planName);
+        zombie.addResult("getDescription", networkName);
+
+
+        return item;
+    }
+}
diff --git a/invoice/src/test/java/com/ning/billing/invoice/MockModule.java b/invoice/src/test/java/com/ning/billing/invoice/MockModule.java
index 99f12b4..c756020 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/MockModule.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/MockModule.java
@@ -16,32 +16,31 @@
 
 package com.ning.billing.invoice;
 
-import com.ning.billing.util.callcontext.CallContextFactory;
-import com.ning.billing.util.callcontext.DefaultCallContextFactory;
-import com.ning.billing.util.glue.FieldStoreModule;
-import com.ning.billing.util.glue.TagStoreModule;
 import org.skife.config.ConfigurationObjectFactory;
 import org.skife.jdbi.v2.IDBI;
 
 import com.google.inject.AbstractModule;
-import com.ning.billing.account.api.AccountUserApi;
 import com.ning.billing.catalog.glue.CatalogModule;
 import com.ning.billing.dbi.DBIProvider;
 import com.ning.billing.dbi.DbiConfig;
 import com.ning.billing.dbi.MysqlTestingHelper;
-import com.ning.billing.entitlement.glue.EntitlementModule;
-import com.ning.billing.invoice.glue.InvoiceModule;
-import com.ning.billing.mock.BrainDeadProxyFactory;
+import com.ning.billing.invoice.api.formatters.InvoiceFormatterFactory;
+import com.ning.billing.invoice.glue.DefaultInvoiceModule;
+import com.ning.billing.invoice.template.formatters.DefaultInvoiceFormatterFactory;
+import com.ning.billing.mock.glue.MockJunctionModule;
+import com.ning.billing.util.callcontext.CallContextFactory;
+import com.ning.billing.util.callcontext.DefaultCallContextFactory;
 import com.ning.billing.util.clock.Clock;
 import com.ning.billing.util.clock.ClockMock;
+import com.ning.billing.util.email.EmailModule;
+import com.ning.billing.util.email.templates.TemplateModule;
 import com.ning.billing.util.glue.BusModule;
+import com.ning.billing.util.glue.FieldStoreModule;
 import com.ning.billing.util.glue.GlobalLockerModule;
 import com.ning.billing.util.glue.NotificationQueueModule;
-
+import com.ning.billing.util.glue.TagStoreModule;
 
 public class MockModule extends AbstractModule {
-    public static final String PLUGIN_NAME = "yoyo";
-
     @Override
     protected void configure() {
         bind(Clock.class).to(ClockMock.class).asEagerSingleton();
@@ -61,24 +60,20 @@ public class MockModule extends AbstractModule {
             bind(IDBI.class).toInstance(dbi);
         }
 
+        bind(InvoiceFormatterFactory.class).to(DefaultInvoiceFormatterFactory.class).asEagerSingleton();
+
+        install(new EmailModule());
         install(new GlobalLockerModule());
         install(new NotificationQueueModule());
-//        install(new AccountModule());
-        bind(AccountUserApi.class).toInstance(BrainDeadProxyFactory.createBrainDeadProxyFor(AccountUserApi.class));
-
-        installEntitlementModule();
         install(new CatalogModule());
         install(new BusModule());
         installInvoiceModule();
+        install(new MockJunctionModule());
+        install(new TemplateModule());
 
     }
-    
-    protected void installEntitlementModule() {
-    	install(new EntitlementModule());
-    }
 
     protected void installInvoiceModule() {
-    	install(new InvoiceModule());
+    	install(new DefaultInvoiceModule());
     }
-
 }
diff --git a/invoice/src/test/java/com/ning/billing/invoice/notification/MockNextBillingDatePoster.java b/invoice/src/test/java/com/ning/billing/invoice/notification/MockNextBillingDatePoster.java
index 493ba5d..88ff62a 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/notification/MockNextBillingDatePoster.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/notification/MockNextBillingDatePoster.java
@@ -16,11 +16,11 @@
 
 package com.ning.billing.invoice.notification;
 
+import java.util.UUID;
+
 import org.joda.time.DateTime;
 import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
 
-import java.util.UUID;
-
 public class MockNextBillingDatePoster implements NextBillingDatePoster {
     @Override
     public void insertNextBillingNotification(Transmogrifier transactionalDao, UUID subscriptionId, DateTime futureNotificationTime) {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/notification/TestNextBillingDateNotifier.java b/invoice/src/test/java/com/ning/billing/invoice/notification/TestNextBillingDateNotifier.java
index 26df2af..a0eb6d5 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/notification/TestNextBillingDateNotifier.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/notification/TestNextBillingDateNotifier.java
@@ -24,23 +24,6 @@ import java.sql.SQLException;
 import java.util.UUID;
 import java.util.concurrent.Callable;
 
-import com.ning.billing.account.api.AccountUserApi;
-import com.ning.billing.account.api.MockAccountUserApi;
-import com.ning.billing.entitlement.api.billing.DefaultEntitlementBillingApi;
-import com.ning.billing.entitlement.api.billing.EntitlementBillingApi;
-import com.ning.billing.invoice.InvoiceDispatcher;
-import com.ning.billing.invoice.dao.DefaultInvoiceDao;
-import com.ning.billing.invoice.dao.InvoiceDao;
-import com.ning.billing.invoice.model.DefaultInvoiceGenerator;
-import com.ning.billing.invoice.model.InvoiceGenerator;
-import com.ning.billing.util.callcontext.CallContextFactory;
-import com.ning.billing.util.callcontext.DefaultCallContextFactory;
-import com.ning.billing.util.customfield.dao.AuditedCustomFieldDao;
-import com.ning.billing.util.customfield.dao.CustomFieldDao;
-import com.ning.billing.util.globallocker.GlobalLocker;
-import com.ning.billing.util.globallocker.MySqlGlobalLocker;
-import com.ning.billing.util.tag.dao.AuditedTagDao;
-import com.ning.billing.util.tag.dao.TagDao;
 import org.apache.commons.io.IOUtils;
 import org.joda.time.DateTime;
 import org.skife.config.ConfigurationObjectFactory;
@@ -50,31 +33,36 @@ import org.skife.jdbi.v2.TransactionStatus;
 import org.testng.Assert;
 import org.testng.annotations.AfterClass;
 import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
 import com.google.inject.AbstractModule;
 import com.google.inject.Guice;
 import com.google.inject.Injector;
 import com.google.inject.Stage;
-import com.ning.billing.catalog.DefaultCatalogService;
-import com.ning.billing.catalog.api.CatalogService;
-import com.ning.billing.config.CatalogConfig;
+import com.ning.billing.catalog.MockCatalogModule;
 import com.ning.billing.config.InvoiceConfig;
+import com.ning.billing.dbi.DBIProvider;
+import com.ning.billing.dbi.DbiConfig;
 import com.ning.billing.dbi.MysqlTestingHelper;
-import com.ning.billing.entitlement.api.user.DefaultEntitlementUserApi;
 import com.ning.billing.entitlement.api.user.EntitlementUserApi;
 import com.ning.billing.entitlement.api.user.Subscription;
-import com.ning.billing.entitlement.engine.dao.EntitlementDao;
-import com.ning.billing.entitlement.engine.dao.EntitlementSqlDao;
+import com.ning.billing.invoice.InvoiceDispatcher;
 import com.ning.billing.invoice.InvoiceListener;
-import com.ning.billing.lifecycle.KillbillService.ServiceException;
+import com.ning.billing.invoice.glue.InvoiceModuleWithMocks;
+import com.ning.billing.lifecycle.KillbillService;
 import com.ning.billing.mock.BrainDeadProxyFactory;
 import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
+import com.ning.billing.mock.glue.MockJunctionModule;
 import com.ning.billing.util.bus.Bus;
-import com.ning.billing.util.bus.InMemoryBus;
+import com.ning.billing.util.callcontext.CallContextFactory;
+import com.ning.billing.util.callcontext.DefaultCallContextFactory;
 import com.ning.billing.util.clock.Clock;
 import com.ning.billing.util.clock.ClockMock;
-import com.ning.billing.util.notificationq.DefaultNotificationQueueService;
+import com.ning.billing.util.clock.MockClockModule;
+import com.ning.billing.util.glue.BusModule;
+import com.ning.billing.util.glue.BusModule.BusType;
+import com.ning.billing.util.glue.NotificationQueueModule;
 import com.ning.billing.util.notificationq.DummySqlTest;
 import com.ning.billing.util.notificationq.NotificationQueueService;
 import com.ning.billing.util.notificationq.dao.NotificationSqlDao;
@@ -88,6 +76,7 @@ public class TestNextBillingDateNotifier {
 	private InvoiceListenerMock listener;
 	private NotificationQueueService notificationQueueService;
 
+	
 	private static final class InvoiceListenerMock extends InvoiceListener {
 		int eventCount = 0;
 		UUID latestSubscriptionId = null;
@@ -113,37 +102,37 @@ public class TestNextBillingDateNotifier {
 
 	}
 
+	@BeforeMethod(groups={"slow"})
+    public void cleanDb() {
+	    helper.cleanupAllTables();
+	}
 
 	@BeforeClass(groups={"slow"})
-	public void setup() throws ServiceException, IOException, ClassNotFoundException, SQLException {
+	public void setup() throws KillbillService.ServiceException, IOException, ClassNotFoundException, SQLException {
 		//TestApiBase.loadSystemPropertiesFromClasspath("/entitlement.properties");
         final Injector g = Guice.createInjector(Stage.PRODUCTION,  new AbstractModule() {
-			@Override
+			
             protected void configure() {
-                bind(Clock.class).to(ClockMock.class).asEagerSingleton();
-                bind(CallContextFactory.class).to(DefaultCallContextFactory.class).asEagerSingleton();
-                bind(Bus.class).to(InMemoryBus.class).asEagerSingleton();
-                bind(NotificationQueueService.class).to(DefaultNotificationQueueService.class).asEagerSingleton();
-                final InvoiceConfig invoiceConfig = new ConfigurationObjectFactory(System.getProperties()).build(InvoiceConfig.class);
-                bind(InvoiceConfig.class).toInstance(invoiceConfig);
-                final CatalogConfig catalogConfig = new ConfigurationObjectFactory(System.getProperties()).build(CatalogConfig.class);
-                bind(CatalogConfig.class).toInstance(catalogConfig);
-                bind(CatalogService.class).to(DefaultCatalogService.class).asEagerSingleton();
+                install(new MockClockModule());
+                install(new BusModule(BusType.MEMORY));
+                install(new InvoiceModuleWithMocks());
+                install(new MockJunctionModule());
+                install(new MockCatalogModule());
+                install(new NotificationQueueModule());
+                
                 final MysqlTestingHelper helper = new MysqlTestingHelper();
                 bind(MysqlTestingHelper.class).toInstance(helper);
-                IDBI dbi = helper.getDBI();
-                bind(IDBI.class).toInstance(dbi);
-                bind(TagDao.class).to(AuditedTagDao.class).asEagerSingleton();
-                bind(EntitlementDao.class).to(EntitlementSqlDao.class).asEagerSingleton();
-                bind(CustomFieldDao.class).to(AuditedCustomFieldDao.class).asEagerSingleton();
-                bind(GlobalLocker.class).to(MySqlGlobalLocker.class).asEagerSingleton();
-                bind(InvoiceGenerator.class).to(DefaultInvoiceGenerator.class).asEagerSingleton();
-                bind(InvoiceDao.class).to(DefaultInvoiceDao.class);
-                bind(NextBillingDatePoster.class).to(DefaultNextBillingDatePoster.class).asEagerSingleton();
-                bind(AccountUserApi.class).to(MockAccountUserApi.class).asEagerSingleton();
-                bind(EntitlementBillingApi.class).to(DefaultEntitlementBillingApi.class).asEagerSingleton();
-                bind(EntitlementUserApi.class).to(DefaultEntitlementUserApi.class).asEagerSingleton();                
-			}
+                if (helper.isUsingLocalInstance()) {
+                    bind(IDBI.class).toProvider(DBIProvider.class).asEagerSingleton();
+                    final DbiConfig config = new ConfigurationObjectFactory(System.getProperties()).build(DbiConfig.class);
+                    bind(DbiConfig.class).toInstance(config);
+                } else {
+                    final IDBI dbi = helper.getDBI();
+                    bind(IDBI.class).toInstance(dbi);
+                }
+                
+            
+            }
         });
 
         clock = g.getInstance(Clock.class);
@@ -167,16 +156,14 @@ public class TestNextBillingDateNotifier {
 	private void startMysql() throws IOException, ClassNotFoundException, SQLException {
 		final String ddl = IOUtils.toString(NotificationSqlDao.class.getResourceAsStream("/com/ning/billing/util/ddl.sql"));
 		final String testDdl = IOUtils.toString(NotificationSqlDao.class.getResourceAsStream("/com/ning/billing/util/ddl_test.sql"));
-		final String entitlementDdl = IOUtils.toString(NotificationSqlDao.class.getResourceAsStream("/com/ning/billing/entitlement/ddl.sql"));
 		helper.startMysql();
 		helper.initDb(ddl);
 		helper.initDb(testDdl);
-        helper.initDb(entitlementDdl);
 	}
 
 
 	@Test(enabled=true, groups="slow")
-	public void test() throws Exception {
+	public void testInvoiceNotifier() throws Exception {
 		final UUID subscriptionId = new UUID(0L,1L);
 		final DateTime now = new DateTime();
 		final DateTime readyTime = now.plusMillis(2000);
@@ -213,8 +200,9 @@ public class TestNextBillingDateNotifier {
 		Assert.assertEquals(listener.getLatestSubscriptionId(), subscriptionId);
 	}
 
-	@AfterClass(alwaysRun = true)
+	@AfterClass(groups="slow")
     public void tearDown() {
+	    notifier.stop();
     	helper.stopMysql();
     }
 
diff --git a/invoice/src/test/java/com/ning/billing/invoice/TestInvoiceDispatcher.java b/invoice/src/test/java/com/ning/billing/invoice/TestInvoiceDispatcher.java
index 7008c90..26403d0 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/TestInvoiceDispatcher.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/TestInvoiceDispatcher.java
@@ -23,11 +23,9 @@ import java.util.SortedSet;
 import java.util.TreeSet;
 import java.util.UUID;
 
-import com.ning.billing.util.callcontext.CallContext;
-import com.ning.billing.util.callcontext.CallOrigin;
-import com.ning.billing.util.callcontext.UserType;
-import com.ning.billing.util.callcontext.DefaultCallContextFactory;
-import com.ning.billing.util.clock.Clock;
+import com.ning.billing.invoice.api.InvoiceNotifier;
+import com.ning.billing.invoice.notification.NullInvoiceNotifier;
+import com.ning.billing.invoice.tests.InvoicingTestBase;
 import org.apache.commons.io.IOUtils;
 import org.joda.time.DateTime;
 import org.slf4j.Logger;
@@ -48,33 +46,34 @@ import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.catalog.api.Plan;
 import com.ning.billing.catalog.api.PlanPhase;
 import com.ning.billing.dbi.MysqlTestingHelper;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
 import com.ning.billing.entitlement.api.billing.BillingEvent;
 import com.ning.billing.entitlement.api.billing.BillingModeType;
-import com.ning.billing.entitlement.api.billing.DefaultBillingEvent;
-import com.ning.billing.entitlement.api.billing.EntitlementBillingApi;
 import com.ning.billing.entitlement.api.user.Subscription;
-import com.ning.billing.entitlement.api.user.SubscriptionTransition.SubscriptionTransitionType;
 import com.ning.billing.invoice.api.Invoice;
 import com.ning.billing.invoice.api.InvoiceApiException;
-import com.ning.billing.invoice.api.InvoiceUserApi;
 import com.ning.billing.invoice.dao.InvoiceDao;
 import com.ning.billing.invoice.model.InvoiceGenerator;
 import com.ning.billing.invoice.notification.NextBillingDateNotifier;
+import com.ning.billing.junction.api.BillingApi;
 import com.ning.billing.mock.BrainDeadProxyFactory;
 import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
+import com.ning.billing.mock.glue.MockJunctionModule;
 import com.ning.billing.util.bus.BusService;
 import com.ning.billing.util.bus.DefaultBusService;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.callcontext.CallOrigin;
+import com.ning.billing.util.callcontext.DefaultCallContextFactory;
+import com.ning.billing.util.callcontext.UserType;
+import com.ning.billing.util.clock.Clock;
 import com.ning.billing.util.globallocker.GlobalLocker;
 
 @Test(groups = "slow")
 @Guice(modules = {MockModule.class})
-public class TestInvoiceDispatcher {
+public class TestInvoiceDispatcher extends InvoicingTestBase {
 	private Logger log = LoggerFactory.getLogger(TestInvoiceDispatcher.class);
 
 	@Inject
-	private InvoiceUserApi invoiceUserApi;
-
-	@Inject
 	private InvoiceGenerator generator;
 
 	@Inject
@@ -89,8 +88,11 @@ public class TestInvoiceDispatcher {
 	@Inject
 	private NextBillingDateNotifier notifier;
 
-	@Inject
-	private BusService busService;
+    @Inject
+    private BusService busService;
+
+    @Inject
+    private BillingApi billingApi;
 
     @Inject
     private Clock clock;
@@ -100,13 +102,11 @@ public class TestInvoiceDispatcher {
     @BeforeSuite(groups = "slow")
     public void setup() throws IOException
     {
-		final String entitlementDdl = IOUtils.toString(TestInvoiceDispatcher.class.getResourceAsStream("/com/ning/billing/entitlement/ddl.sql"));
 		final String invoiceDdl = IOUtils.toString(TestInvoiceDispatcher.class.getResourceAsStream("/com/ning/billing/invoice/ddl.sql"));
 		final String utilDdl = IOUtils.toString(TestInvoiceDispatcher.class.getResourceAsStream("/com/ning/billing/util/ddl.sql"));
 
 		helper.startMysql();
 
-		helper.initDb(entitlementDdl);
 		helper.initDb(invoiceDdl);
 		helper.initDb(utilDdl);
 		notifier.initialize();
@@ -115,6 +115,7 @@ public class TestInvoiceDispatcher {
         context = new DefaultCallContextFactory(clock).createCallContext("Miracle Max", CallOrigin.TEST, UserType.TEST);
 
 		busService.getBus().start();
+		((ZombieControl)billingApi).addResult("setChargedThroughDate", BrainDeadProxyFactory.ZOMBIE_VOID);
 	}
 
 	@AfterClass(alwaysRun = true)
@@ -139,25 +140,28 @@ public class TestInvoiceDispatcher {
 		((ZombieControl)accountUserApi).addResult("getAccountById", account);
 		((ZombieControl)account).addResult("getCurrency", Currency.USD);
 		((ZombieControl)account).addResult("getId", accountId);
+        ((ZombieControl)account).addResult(("isNotifiedForInvoices"), true);
 
 		Subscription subscription =  BrainDeadProxyFactory.createBrainDeadProxyFor(Subscription.class);
-		((ZombieControl)subscription).addResult("getId", subscriptionId);
+        ((ZombieControl)subscription).addResult("getId", subscriptionId);
+        ((ZombieControl)subscription).addResult("getBundleId", new UUID(0L,0L));
 		SortedSet<BillingEvent> events = new TreeSet<BillingEvent>();
 		Plan plan = MockPlan.createBicycleNoTrialEvergreen1USD();
 		PlanPhase planPhase = MockPlanPhase.create1USDMonthlyEvergreen();
 		DateTime effectiveDate = new DateTime().minusDays(1);
 		Currency currency = Currency.USD;
 		BigDecimal fixedPrice = null;
-		events.add(new DefaultBillingEvent(subscription, effectiveDate,plan, planPhase,
-				fixedPrice, BigDecimal.ONE, currency, BillingPeriod.MONTHLY, 1,
-				BillingModeType.IN_ADVANCE, "", 1L, SubscriptionTransitionType.CREATE));
+		events.add(createMockBillingEvent(account, subscription, effectiveDate, plan, planPhase,
+                fixedPrice, BigDecimal.ONE, currency, BillingPeriod.MONTHLY, 1,
+                BillingModeType.IN_ADVANCE, "", 1L, SubscriptionTransitionType.CREATE));
 
-		EntitlementBillingApi entitlementBillingApi = BrainDeadProxyFactory.createBrainDeadProxyFor(EntitlementBillingApi.class);
-		((ZombieControl)entitlementBillingApi).addResult("getBillingEventsForAccount", events);
+		((ZombieControl) billingApi).addResult("getBillingEventsForAccountAndUpdateAccountBCD", events);
 
 		DateTime target = new DateTime();
 
-		InvoiceDispatcher dispatcher = new InvoiceDispatcher(generator, accountUserApi, entitlementBillingApi, invoiceDao, locker);
+        InvoiceNotifier invoiceNotifier = new NullInvoiceNotifier();
+		InvoiceDispatcher dispatcher = new InvoiceDispatcher(generator, accountUserApi, billingApi, invoiceDao,
+                                                             invoiceNotifier, locker, busService.getBus(), clock);
 
 		Invoice invoice = dispatcher.processAccount(accountId, target, true, context);
 		Assert.assertNotNull(invoice);
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/DefaultInvoiceGeneratorTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/DefaultInvoiceGeneratorTests.java
index 6e6b9b3..6c3dd65 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/DefaultInvoiceGeneratorTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/DefaultInvoiceGeneratorTests.java
@@ -16,6 +16,21 @@
 
 package com.ning.billing.invoice.tests;
 
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import javax.annotation.Nullable;
+
+import com.ning.billing.invoice.model.DefaultInvoicePayment;
+import org.joda.time.DateTime;
+import org.testng.annotations.Test;
+
 import com.ning.billing.catalog.DefaultPrice;
 import com.ning.billing.catalog.MockInternationalPrice;
 import com.ning.billing.catalog.MockPlan;
@@ -27,13 +42,10 @@ import com.ning.billing.catalog.api.PhaseType;
 import com.ning.billing.catalog.api.Plan;
 import com.ning.billing.catalog.api.PlanPhase;
 import com.ning.billing.config.InvoiceConfig;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
 import com.ning.billing.entitlement.api.billing.BillingEvent;
 import com.ning.billing.entitlement.api.billing.BillingModeType;
-import com.ning.billing.entitlement.api.billing.DefaultBillingEvent;
 import com.ning.billing.entitlement.api.user.Subscription;
-import com.ning.billing.entitlement.api.user.SubscriptionData;
-import com.ning.billing.entitlement.api.user.SubscriptionFactory.SubscriptionBuilder;
-import com.ning.billing.entitlement.api.user.SubscriptionTransition.SubscriptionTransitionType;
 import com.ning.billing.invoice.api.Invoice;
 import com.ning.billing.invoice.api.InvoiceApiException;
 import com.ning.billing.invoice.model.BillingEventSet;
@@ -43,56 +55,33 @@ import com.ning.billing.invoice.model.InvoiceGenerator;
 import com.ning.billing.invoice.model.RecurringInvoiceItem;
 import com.ning.billing.mock.BrainDeadProxyFactory;
 import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
-
 import com.ning.billing.util.clock.Clock;
 import com.ning.billing.util.clock.DefaultClock;
-import org.joda.time.DateTime;
-import org.testng.annotations.Test;
-
-import javax.annotation.Nullable;
-import java.math.BigDecimal;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.UUID;
-
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertNotNull;
-import static org.testng.Assert.assertNull;
 
 @Test(groups = {"fast", "invoicing", "invoiceGenerator"})
 public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
-    private final Clock clock = new DefaultClock();
-    private final InvoiceConfig invoiceConfig = new InvoiceConfig() {
-        @Override
-        public long getDaoClaimTimeMs() {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public int getDaoMaxReadyEvents() {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public long getNotificationSleepTimeMs() {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public boolean isEventProcessingOff() {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public int getNumberOfMonthsInFuture() {
-            return 36;
-        }
-    };
-
     private final InvoiceGenerator generator;
 
     public DefaultInvoiceGeneratorTests() {
         super();
+
+        Clock clock = new DefaultClock();
+        InvoiceConfig invoiceConfig = new InvoiceConfig() {
+            @Override
+            public long getSleepTimeMs() {
+                throw new UnsupportedOperationException();
+            }
+
+            @Override
+            public int getNumberOfMonthsInFuture() {
+                return 36;
+            }
+
+            @Override
+            public boolean isNotificationProcessingOff() {
+                throw new UnsupportedOperationException();
+            }
+        };
         this.generator = new DefaultInvoiceGenerator(clock, invoiceConfig);
     }
 
@@ -118,7 +107,7 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
     public void testWithSingleMonthlyEvent() throws InvoiceApiException, CatalogApiException {
         BillingEventSet events = new BillingEventSet();
 
-        Subscription sub = new SubscriptionData(new SubscriptionBuilder().setId(UUID.randomUUID()));
+        Subscription sub = createZombieSubscription();
         DateTime startDate = buildDateTime(2011, 9, 1);
 
         Plan plan = new MockPlan();
@@ -138,11 +127,23 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
         assertEquals(invoice.getInvoiceItems().get(0).getSubscriptionId(), sub.getId());
     }
 
+    private Subscription createZombieSubscription() {
+        return createZombieSubscription(UUID.randomUUID());
+    }
+
+    private Subscription createZombieSubscription(UUID subscriptionId) {
+        Subscription sub = BrainDeadProxyFactory.createBrainDeadProxyFor(Subscription.class);
+        ((ZombieControl) sub).addResult("getId", subscriptionId);
+        ((ZombieControl) sub).addResult("getBundleId", UUID.randomUUID());
+
+        return sub;
+    }
+
     @Test
     public void testWithSingleMonthlyEventWithLeadingProRation() throws InvoiceApiException, CatalogApiException {
         BillingEventSet events = new BillingEventSet();
 
-        Subscription sub = new SubscriptionData(new SubscriptionBuilder().setId(UUID.randomUUID()));
+        Subscription sub = createZombieSubscription();
         DateTime startDate = buildDateTime(2011, 9, 1);
 
         Plan plan = new MockPlan();
@@ -176,7 +177,7 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
         BigDecimal rate2 = TEN;
         PlanPhase phase2 = createMockMonthlyPlanPhase(rate2);
 
-        Subscription sub = new SubscriptionData(new SubscriptionBuilder().setId(UUID.randomUUID()));
+        Subscription sub = createZombieSubscription();
 
         BillingEvent event1 = createBillingEvent(sub.getId(), buildDateTime(2011, 9, 1), plan1, phase1, 1);
         events.add(event1);
@@ -201,7 +202,7 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
         BigDecimal rate1 = FIVE;
         PlanPhase phase1 = createMockMonthlyPlanPhase(rate1);
 
-        Subscription sub = new SubscriptionData(new SubscriptionBuilder().setId(UUID.randomUUID()));
+        Subscription sub = createZombieSubscription();
         BillingEvent event1 = createBillingEvent(sub.getId(), buildDateTime(2011, 9, 1), plan1,phase1, 1);
         events.add(event1);
 
@@ -238,7 +239,7 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
         BigDecimal rate1 = FIVE;
         PlanPhase phase1 = createMockMonthlyPlanPhase(rate1);
 
-        Subscription sub = new SubscriptionData(new SubscriptionBuilder().setId(UUID.randomUUID()));
+        Subscription sub = createZombieSubscription();
         BillingEvent event1 = createBillingEvent(sub.getId(), buildDateTime(2011, 9, 1), plan1, phase1, 1);
         events.add(event1);
 
@@ -265,7 +266,7 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
     public void testSingleEventWithExistingInvoice() throws InvoiceApiException, CatalogApiException {
         BillingEventSet events = new BillingEventSet();
 
-        Subscription sub = new SubscriptionData(new SubscriptionBuilder().setId(UUID.randomUUID()));
+        Subscription sub = createZombieSubscription();
         DateTime startDate = buildDateTime(2011, 9, 1);
 
         Plan plan1 = new MockPlan();
@@ -479,8 +480,7 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
     @Test
     public void testFixedPriceLifeCycle() throws InvoiceApiException {
         UUID accountId = UUID.randomUUID();
-        Subscription subscription = BrainDeadProxyFactory.createBrainDeadProxyFor(Subscription.class);
-        ((ZombieControl) subscription).addResult("getId", UUID.randomUUID());
+        Subscription subscription = createZombieSubscription();
 
         Plan plan = new MockPlan("plan 1");
         MockInternationalPrice zeroPrice = new MockInternationalPrice(new DefaultPrice(ZERO, Currency.USD));
@@ -493,17 +493,17 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
 
         BillingEventSet events = new BillingEventSet();
 
-        BillingEvent event1 = new DefaultBillingEvent(subscription, new DateTime("2012-01-1T00:00:00.000-08:00"),
-                                                      plan, phase1,
-                                                      ZERO, null, Currency.USD, BillingPeriod.NO_BILLING_PERIOD, 1,
-                                                      BillingModeType.IN_ADVANCE, "Test Event 1", 1L,
-                                                      SubscriptionTransitionType.CREATE);
+        BillingEvent event1 = createMockBillingEvent(null, subscription, new DateTime("2012-01-1T00:00:00.000-08:00"),
+                plan, phase1,
+                ZERO, null, Currency.USD, BillingPeriod.NO_BILLING_PERIOD, 1,
+                BillingModeType.IN_ADVANCE, "Test Event 1", 1L,
+                SubscriptionTransitionType.CREATE);
 
-        BillingEvent event2 = new DefaultBillingEvent(subscription, changeDate,
-                                                      plan, phase2,
-                                                      ZERO, null, Currency.USD, BillingPeriod.NO_BILLING_PERIOD, 1,
-                                                      BillingModeType.IN_ADVANCE, "Test Event 2", 2L,
-                                                      SubscriptionTransitionType.PHASE);
+        BillingEvent event2 = createMockBillingEvent(null, subscription, changeDate,
+                plan, phase2,
+                ZERO, null, Currency.USD, BillingPeriod.NO_BILLING_PERIOD, 1,
+                BillingModeType.IN_ADVANCE, "Test Event 2", 2L,
+                SubscriptionTransitionType.PHASE);
 
         events.add(event2);
         events.add(event1);
@@ -631,7 +631,7 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
         assertNotNull(invoice2);
         assertEquals(invoice2.getNumberOfItems(), 1);
         assertEquals(invoice2.getInvoiceItems().get(0).getStartDate().compareTo(trialPhaseEndDate), 0);
-        assertEquals(invoice2.getTotalAmount().compareTo(new BigDecimal("3.2097")), 0);
+        assertEquals(invoice2.getTotalAmount().compareTo(new BigDecimal("3.21")), 0);
 
         invoiceList.add(invoice2);
         DateTime targetDate = trialPhaseEndDate.toMutableDateTime().dayOfMonth().set(BILL_CYCLE_DAY).toDateTime();
@@ -686,16 +686,16 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
                                  null, BillingPeriod.ANNUAL, phaseType);
     }
 
-    private DefaultBillingEvent createBillingEvent(final UUID subscriptionId, final DateTime startDate,
+    private BillingEvent createBillingEvent(final UUID subscriptionId, final DateTime startDate,
                                                    final Plan plan, final PlanPhase planPhase, final int billCycleDay) throws CatalogApiException {
-        Subscription sub = new SubscriptionData(new SubscriptionBuilder().setId(subscriptionId));
+        Subscription sub = createZombieSubscription(subscriptionId);
         Currency currency = Currency.USD;
 
-        return new DefaultBillingEvent(sub, startDate, plan, planPhase,
-                                       planPhase.getFixedPrice() == null ? null : planPhase.getFixedPrice().getPrice(currency),
-                                       planPhase.getRecurringPrice() == null ? null : planPhase.getRecurringPrice().getPrice(currency),
-                                       currency, planPhase.getBillingPeriod(),
-                                       billCycleDay, BillingModeType.IN_ADVANCE, "Test", 1L, SubscriptionTransitionType.CREATE);
+        return createMockBillingEvent(null, sub, startDate, plan, planPhase,
+                planPhase.getFixedPrice() == null ? null : planPhase.getFixedPrice().getPrice(currency),
+                planPhase.getRecurringPrice() == null ? null : planPhase.getRecurringPrice().getPrice(currency),
+                currency, planPhase.getBillingPeriod(),
+                billCycleDay, BillingModeType.IN_ADVANCE, "Test", 1L, SubscriptionTransitionType.CREATE);
     }
 
     private void testInvoiceGeneration(final UUID accountId, final BillingEventSet events, final List<Invoice> existingInvoices,
@@ -710,5 +710,116 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
         assertEquals(invoice.getTotalAmount(), expectedAmount);
     }
 
+    @Test(groups = {"fast", "invoicing"})
+    public void testAddOnInvoiceGeneration() throws CatalogApiException, InvoiceApiException {
+        DateTime april25 = new DateTime(2012, 4, 25, 0, 0, 0, 0);
+
+        // create a base plan on April 25th
+        UUID accountId = UUID.randomUUID();
+        Subscription baseSubscription = createZombieSubscription();
+
+        Plan basePlan = new MockPlan("base Plan");
+        MockInternationalPrice price5 = new MockInternationalPrice(new DefaultPrice(FIVE, Currency.USD));
+        MockInternationalPrice price10 = new MockInternationalPrice(new DefaultPrice(TEN, Currency.USD));
+        MockInternationalPrice price20 = new MockInternationalPrice(new DefaultPrice(TWENTY, Currency.USD));
+        PlanPhase basePlanEvergreen = new MockPlanPhase(price10, null, BillingPeriod.MONTHLY, PhaseType.EVERGREEN);
+
+        BillingEventSet events = new BillingEventSet();
+        events.add(createBillingEvent(baseSubscription.getId(), april25, basePlan, basePlanEvergreen, 25));
+
+        // generate invoice
+        Invoice invoice1 = generator.generateInvoice(accountId, events, null, april25, Currency.USD);
+        assertNotNull(invoice1);
+        assertEquals(invoice1.getNumberOfItems(), 1);
+        assertEquals(invoice1.getTotalAmount().compareTo(TEN), 0);
+
+        List<Invoice> invoices = new ArrayList<Invoice>();
+        invoices.add(invoice1);
+
+        // create 2 add ons on April 28th
+        DateTime april28 = new DateTime(2012, 4, 28, 0, 0, 0, 0);
+        Subscription addOnSubscription1 = createZombieSubscription();
+        Plan addOn1Plan = new MockPlan("add on 1");
+        PlanPhase addOn1PlanPhaseEvergreen = new MockPlanPhase(price5, null, BillingPeriod.MONTHLY, PhaseType.EVERGREEN);
+        events.add(createBillingEvent(addOnSubscription1.getId(), april28, addOn1Plan, addOn1PlanPhaseEvergreen, 25));
+
+        Subscription addOnSubscription2 = createZombieSubscription();
+        Plan addOn2Plan = new MockPlan("add on 2");
+        PlanPhase addOn2PlanPhaseEvergreen = new MockPlanPhase(price20, null, BillingPeriod.MONTHLY, PhaseType.EVERGREEN);
+        events.add(createBillingEvent(addOnSubscription2.getId(), april28, addOn2Plan, addOn2PlanPhaseEvergreen, 25));
+
+        // generate invoice
+        Invoice invoice2 = generator.generateInvoice(accountId, events, invoices, april28, Currency.USD);
+        invoices.add(invoice2);
+        assertNotNull(invoice2);
+        assertEquals(invoice2.getNumberOfItems(), 2);
+        assertEquals(invoice2.getTotalAmount().compareTo(TWENTY_FIVE.multiply(new BigDecimal("0.9")).setScale(NUMBER_OF_DECIMALS, ROUNDING_METHOD)), 0);
+
+        // perform a repair (change base plan; remove one add-on)
+        // event stream should include just two plans
+        BillingEventSet newEvents = new BillingEventSet();
+        Plan basePlan2 = new MockPlan("base plan 2");
+        MockInternationalPrice price13 = new MockInternationalPrice(new DefaultPrice(THIRTEEN, Currency.USD));
+        PlanPhase basePlan2Phase = new MockPlanPhase(price13, null, BillingPeriod.MONTHLY, PhaseType.EVERGREEN);
+        newEvents.add(createBillingEvent(baseSubscription.getId(), april25, basePlan2, basePlan2Phase, 25));
+        newEvents.add(createBillingEvent(addOnSubscription1.getId(), april28, addOn1Plan, addOn1PlanPhaseEvergreen, 25));
+
+        // generate invoice
+        DateTime may1 = new DateTime(2012, 5, 1, 0, 0, 0, 0);
+        Invoice invoice3 = generator.generateInvoice(accountId, newEvents, invoices, may1, Currency.USD);
+        assertNotNull(invoice3);
+        assertEquals(invoice3.getNumberOfItems(), 5);
+        // -4.50 -18 - 10 (to correct the previous 2 invoices) + 4.50 + 13
+        assertEquals(invoice3.getTotalAmount().compareTo(FIFTEEN.negate()), 0);
+    }
+
+    @Test(enabled = false)
+    public void testRepairForPaidInvoice() throws CatalogApiException, InvoiceApiException {
+        // create an invoice
+        DateTime april25 = new DateTime(2012, 4, 25, 0, 0, 0, 0);
+
+        // create a base plan on April 25th
+        UUID accountId = UUID.randomUUID();
+        Subscription originalSubscription = createZombieSubscription();
+
+        Plan originalPlan = new MockPlan("original plan");
+        MockInternationalPrice price10 = new MockInternationalPrice(new DefaultPrice(TEN, Currency.USD));
+        PlanPhase originalPlanEvergreen = new MockPlanPhase(price10, null, BillingPeriod.MONTHLY, PhaseType.EVERGREEN);
+
+        BillingEventSet events = new BillingEventSet();
+        events.add(createBillingEvent(originalSubscription.getId(), april25, originalPlan, originalPlanEvergreen, 25));
+
+        Invoice invoice1 = generator.generateInvoice(accountId, events, null, april25, Currency.USD);
+        List<Invoice> invoices = new ArrayList<Invoice>();
+        invoices.add(invoice1);
+
+        // pay the invoice
+        invoice1.addPayment(new DefaultInvoicePayment(UUID.randomUUID(), invoice1.getId(), april25, TEN, Currency.USD));
+        assertEquals(invoice1.getBalance().compareTo(ZERO), 0);
+
+        // change the plan (i.e. repair) on start date
+        events.clear();
+        Subscription newSubscription = createZombieSubscription();
+        Plan newPlan = new MockPlan("new plan");
+        MockInternationalPrice price5 = new MockInternationalPrice(new DefaultPrice(FIVE, Currency.USD));
+        PlanPhase newPlanEvergreen = new MockPlanPhase(price5, null, BillingPeriod.MONTHLY, PhaseType.EVERGREEN);
+        events.add(createBillingEvent(newSubscription.getId(), april25, newPlan, newPlanEvergreen, 25));
+
+        // generate a new invoice
+        Invoice invoice2 = generator.generateInvoice(accountId, events, invoices, april25, Currency.USD);
+        invoices.add(invoice2);
+
+        // move items to the correct invoice (normally, the dao calls will sort that out)
+        generator.distributeItems(invoices);
+
+        // ensure that the original invoice balance is zero
+
+        assertEquals(invoice1.getBalance().compareTo(ZERO), 0);
+
+        // ensure that the account balance is correct
+        assertEquals(invoice2.getBalance().compareTo(FIVE.negate()), 0);
+
+    }
+
     // TODO: Jeff C -- how do we ensure that an annual add-on is properly aligned *at the end* with the base plan?
 }
\ No newline at end of file
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/DoubleProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/DoubleProRationTests.java
index fd37599..c08c8b8 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/DoubleProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/DoubleProRationTests.java
@@ -16,13 +16,14 @@
 
 package com.ning.billing.invoice.tests.inAdvance.annual;
 
-import com.ning.billing.catalog.api.BillingPeriod;
-import com.ning.billing.invoice.model.InvalidDateSequenceException;
-import com.ning.billing.invoice.tests.inAdvance.ProRationInAdvanceTestBase;
+import java.math.BigDecimal;
+
 import org.joda.time.DateTime;
 import org.testng.annotations.Test;
 
-import java.math.BigDecimal;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.invoice.model.InvalidDateSequenceException;
+import com.ning.billing.invoice.tests.inAdvance.ProRationInAdvanceTestBase;
 
 @Test(groups = {"fast", "invoicing", "proRation"})
 public class DoubleProRationTests extends ProRationInAdvanceTestBase {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/GenericProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/GenericProRationTests.java
index 8d0eb06..81d5347 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/GenericProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/GenericProRationTests.java
@@ -16,11 +16,12 @@
 
 package com.ning.billing.invoice.tests.inAdvance.annual;
 
-import com.ning.billing.catalog.api.BillingPeriod;
-import com.ning.billing.invoice.tests.inAdvance.GenericProRationTestBase;
+import java.math.BigDecimal;
+
 import org.testng.annotations.Test;
 
-import java.math.BigDecimal;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.invoice.tests.inAdvance.GenericProRationTestBase;
 
 @Test(groups = {"fast", "invoicing", "proRation"})
 public class GenericProRationTests extends GenericProRationTestBase {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/LeadingProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/LeadingProRationTests.java
index a009fba..68611f7 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/LeadingProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/LeadingProRationTests.java
@@ -16,13 +16,14 @@
 
 package com.ning.billing.invoice.tests.inAdvance.annual;
 
-import com.ning.billing.catalog.api.BillingPeriod;
-import com.ning.billing.invoice.model.InvalidDateSequenceException;
-import com.ning.billing.invoice.tests.inAdvance.ProRationInAdvanceTestBase;
+import java.math.BigDecimal;
+
 import org.joda.time.DateTime;
 import org.testng.annotations.Test;
 
-import java.math.BigDecimal;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.invoice.model.InvalidDateSequenceException;
+import com.ning.billing.invoice.tests.inAdvance.ProRationInAdvanceTestBase;
 
 @Test(groups = {"fast", "invoicing", "proRation"})
 public class LeadingProRationTests extends ProRationInAdvanceTestBase {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/ProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/ProRationTests.java
index f882005..88922e2 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/ProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/ProRationTests.java
@@ -16,13 +16,14 @@
 
 package com.ning.billing.invoice.tests.inAdvance.annual;
 
-import com.ning.billing.catalog.api.BillingPeriod;
-import com.ning.billing.invoice.model.InvalidDateSequenceException;
-import com.ning.billing.invoice.tests.inAdvance.ProRationInAdvanceTestBase;
+import java.math.BigDecimal;
+
 import org.joda.time.DateTime;
 import org.testng.annotations.Test;
 
-import java.math.BigDecimal;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.invoice.model.InvalidDateSequenceException;
+import com.ning.billing.invoice.tests.inAdvance.ProRationInAdvanceTestBase;
 
 @Test(groups = {"fast", "invoicing", "proRation"})
 public class ProRationTests extends ProRationInAdvanceTestBase {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/TrailingProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/TrailingProRationTests.java
index e0216b5..d1c2013 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/TrailingProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/TrailingProRationTests.java
@@ -16,13 +16,14 @@
 
 package com.ning.billing.invoice.tests.inAdvance.annual;
 
-import com.ning.billing.catalog.api.BillingPeriod;
-import com.ning.billing.invoice.model.InvalidDateSequenceException;
-import com.ning.billing.invoice.tests.inAdvance.ProRationInAdvanceTestBase;
+import java.math.BigDecimal;
+
 import org.joda.time.DateTime;
 import org.testng.annotations.Test;
 
-import java.math.BigDecimal;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.invoice.model.InvalidDateSequenceException;
+import com.ning.billing.invoice.tests.inAdvance.ProRationInAdvanceTestBase;
 
 @Test(groups = {"fast", "invoicing", "proRation"})
 public class TrailingProRationTests extends ProRationInAdvanceTestBase {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/GenericProRationTestBase.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/GenericProRationTestBase.java
index 60892e6..70f1f8f 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/GenericProRationTestBase.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/GenericProRationTestBase.java
@@ -16,11 +16,12 @@
 
 package com.ning.billing.invoice.tests.inAdvance;
 
-import com.ning.billing.invoice.model.InvalidDateSequenceException;
+import java.math.BigDecimal;
+
 import org.joda.time.DateTime;
 import org.testng.annotations.Test;
 
-import java.math.BigDecimal;
+import com.ning.billing.invoice.model.InvalidDateSequenceException;
 
 @Test(groups = {"fast", "invoicing", "proRation"})
 public abstract class GenericProRationTestBase extends ProRationInAdvanceTestBase {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/DoubleProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/DoubleProRationTests.java
index 240b8aa..8c9b61d 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/DoubleProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/DoubleProRationTests.java
@@ -16,13 +16,14 @@
 
 package com.ning.billing.invoice.tests.inAdvance.monthly;
 
-import com.ning.billing.catalog.api.BillingPeriod;
-import com.ning.billing.invoice.model.InvalidDateSequenceException;
-import com.ning.billing.invoice.tests.inAdvance.ProRationInAdvanceTestBase;
+import java.math.BigDecimal;
+
 import org.joda.time.DateTime;
 import org.testng.annotations.Test;
 
-import java.math.BigDecimal;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.invoice.model.InvalidDateSequenceException;
+import com.ning.billing.invoice.tests.inAdvance.ProRationInAdvanceTestBase;
 
 @Test(groups = {"fast", "invoicing", "proRation"})
 public class DoubleProRationTests extends ProRationInAdvanceTestBase {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/GenericProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/GenericProRationTests.java
index 8b40db8..b749ab8 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/GenericProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/GenericProRationTests.java
@@ -16,11 +16,12 @@
 
 package com.ning.billing.invoice.tests.inAdvance.monthly;
 
-import com.ning.billing.catalog.api.BillingPeriod;
-import com.ning.billing.invoice.tests.inAdvance.GenericProRationTestBase;
+import java.math.BigDecimal;
+
 import org.testng.annotations.Test;
 
-import java.math.BigDecimal;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.invoice.tests.inAdvance.GenericProRationTestBase;
 
 @Test(groups = {"fast", "invoicing", "proRation"})
 public class GenericProRationTests extends GenericProRationTestBase {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/LeadingProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/LeadingProRationTests.java
index 998e566..f723738 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/LeadingProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/LeadingProRationTests.java
@@ -16,13 +16,14 @@
 
 package com.ning.billing.invoice.tests.inAdvance.monthly;
 
-import com.ning.billing.catalog.api.BillingPeriod;
-import com.ning.billing.invoice.model.InvalidDateSequenceException;
-import com.ning.billing.invoice.tests.inAdvance.ProRationInAdvanceTestBase;
+import java.math.BigDecimal;
+
 import org.joda.time.DateTime;
 import org.testng.annotations.Test;
 
-import java.math.BigDecimal;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.invoice.model.InvalidDateSequenceException;
+import com.ning.billing.invoice.tests.inAdvance.ProRationInAdvanceTestBase;
 
 @Test(groups = {"fast", "invoicing", "proRation"})
 public class LeadingProRationTests extends ProRationInAdvanceTestBase {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/ProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/ProRationTests.java
index c3748d1..78f06b4 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/ProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/ProRationTests.java
@@ -16,13 +16,14 @@
 
 package com.ning.billing.invoice.tests.inAdvance.monthly;
 
-import com.ning.billing.catalog.api.BillingPeriod;
-import com.ning.billing.invoice.model.InvalidDateSequenceException;
-import com.ning.billing.invoice.tests.inAdvance.ProRationInAdvanceTestBase;
+import java.math.BigDecimal;
+
 import org.joda.time.DateTime;
 import org.testng.annotations.Test;
 
-import java.math.BigDecimal;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.invoice.model.InvalidDateSequenceException;
+import com.ning.billing.invoice.tests.inAdvance.ProRationInAdvanceTestBase;
 
 @Test(groups = {"fast", "invoicing", "proRation"})
 public class ProRationTests extends ProRationInAdvanceTestBase {
@@ -214,9 +215,10 @@ public class ProRationTests extends ProRationInAdvanceTestBase {
         DateTime targetDate = buildDateTime(2011, 4, 21);
 
         BigDecimal expectedValue;
-        expectedValue = SEVEN.divide(THIRTY_ONE, NUMBER_OF_DECIMALS, ROUNDING_METHOD);
+        expectedValue = SEVEN.divide(THIRTY_ONE, 2 * NUMBER_OF_DECIMALS, ROUNDING_METHOD);
         expectedValue = expectedValue.add(ONE);
-        expectedValue = expectedValue.add(THREE.divide(THIRTY_ONE, NUMBER_OF_DECIMALS, ROUNDING_METHOD));
+        expectedValue = expectedValue.add(THREE.divide(THIRTY_ONE, 2 * NUMBER_OF_DECIMALS, ROUNDING_METHOD));
+        expectedValue = expectedValue.setScale(NUMBER_OF_DECIMALS, ROUNDING_METHOD);
         testCalculateNumberOfBillingCycles(startDate, planChangeDate, targetDate, 7, expectedValue);
 
         expectedValue = FIVE.divide(TWENTY_EIGHT, NUMBER_OF_DECIMALS, ROUNDING_METHOD).add(TWO);  
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/TrailingProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/TrailingProRationTests.java
index 6a5e5ef..581e8af 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/TrailingProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/TrailingProRationTests.java
@@ -16,13 +16,14 @@
 
 package com.ning.billing.invoice.tests.inAdvance.monthly;
 
-import com.ning.billing.catalog.api.BillingPeriod;
-import com.ning.billing.invoice.model.InvalidDateSequenceException;
-import com.ning.billing.invoice.tests.inAdvance.ProRationInAdvanceTestBase;
+import java.math.BigDecimal;
+
 import org.joda.time.DateTime;
 import org.testng.annotations.Test;
 
-import java.math.BigDecimal;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.invoice.model.InvalidDateSequenceException;
+import com.ning.billing.invoice.tests.inAdvance.ProRationInAdvanceTestBase;
 
 @Test(groups = {"fast", "invoicing", "proRation"})
 public class TrailingProRationTests extends ProRationInAdvanceTestBase {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/ProRationInAdvanceTestBase.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/ProRationInAdvanceTestBase.java
index 18bd096..b8f3848 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/ProRationInAdvanceTestBase.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/ProRationInAdvanceTestBase.java
@@ -16,10 +16,11 @@
 
 package com.ning.billing.invoice.tests.inAdvance;
 
+import org.testng.annotations.Test;
+
 import com.ning.billing.invoice.model.BillingMode;
 import com.ning.billing.invoice.model.InAdvanceBillingMode;
 import com.ning.billing.invoice.tests.ProRationTestBase;
-import org.testng.annotations.Test;
 
 @Test(groups = {"fast", "invoicing", "proRation"})
 public abstract class ProRationInAdvanceTestBase extends ProRationTestBase {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/DoubleProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/DoubleProRationTests.java
index 184f5d5..e6c3cf3 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/DoubleProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/DoubleProRationTests.java
@@ -16,13 +16,14 @@
 
 package com.ning.billing.invoice.tests.inAdvance.quarterly;
 
-import com.ning.billing.catalog.api.BillingPeriod;
-import com.ning.billing.invoice.model.InvalidDateSequenceException;
-import com.ning.billing.invoice.tests.inAdvance.ProRationInAdvanceTestBase;
+import java.math.BigDecimal;
+
 import org.joda.time.DateTime;
 import org.testng.annotations.Test;
 
-import java.math.BigDecimal;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.invoice.model.InvalidDateSequenceException;
+import com.ning.billing.invoice.tests.inAdvance.ProRationInAdvanceTestBase;
 
 @Test(groups = {"fast", "invoicing", "proRation"})
 public class DoubleProRationTests extends ProRationInAdvanceTestBase {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/GenericProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/GenericProRationTests.java
index c4237a6..3351807 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/GenericProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/GenericProRationTests.java
@@ -16,11 +16,12 @@
 
 package com.ning.billing.invoice.tests.inAdvance.quarterly;
 
-import com.ning.billing.catalog.api.BillingPeriod;
-import com.ning.billing.invoice.tests.inAdvance.GenericProRationTestBase;
+import java.math.BigDecimal;
+
 import org.testng.annotations.Test;
 
-import java.math.BigDecimal;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.invoice.tests.inAdvance.GenericProRationTestBase;
 
 @Test(groups = {"fast", "invoicing", "proRation"})
 public class GenericProRationTests extends GenericProRationTestBase {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/LeadingProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/LeadingProRationTests.java
index 04ec683..18bb8af 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/LeadingProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/LeadingProRationTests.java
@@ -16,13 +16,14 @@
 
 package com.ning.billing.invoice.tests.inAdvance.quarterly;
 
-import com.ning.billing.catalog.api.BillingPeriod;
-import com.ning.billing.invoice.model.InvalidDateSequenceException;
-import com.ning.billing.invoice.tests.inAdvance.ProRationInAdvanceTestBase;
+import java.math.BigDecimal;
+
 import org.joda.time.DateTime;
 import org.testng.annotations.Test;
 
-import java.math.BigDecimal;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.invoice.model.InvalidDateSequenceException;
+import com.ning.billing.invoice.tests.inAdvance.ProRationInAdvanceTestBase;
 
 @Test(groups = {"fast", "invoicing", "proRation"})
 public class LeadingProRationTests extends ProRationInAdvanceTestBase {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/ProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/ProRationTests.java
index e13db0d..2988dfe 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/ProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/ProRationTests.java
@@ -16,13 +16,14 @@
 
 package com.ning.billing.invoice.tests.inAdvance.quarterly;
 
-import com.ning.billing.catalog.api.BillingPeriod;
-import com.ning.billing.invoice.model.InvalidDateSequenceException;
-import com.ning.billing.invoice.tests.inAdvance.ProRationInAdvanceTestBase;
+import java.math.BigDecimal;
+
 import org.joda.time.DateTime;
 import org.testng.annotations.Test;
 
-import java.math.BigDecimal;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.invoice.model.InvalidDateSequenceException;
+import com.ning.billing.invoice.tests.inAdvance.ProRationInAdvanceTestBase;
 
 @Test(groups = {"fast", "invoicing", "proRation"})
 public class ProRationTests extends ProRationInAdvanceTestBase {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/TrailingProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/TrailingProRationTests.java
index 8f63010..270518d 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/TrailingProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/TrailingProRationTests.java
@@ -16,13 +16,14 @@
 
 package com.ning.billing.invoice.tests.inAdvance.quarterly;
 
-import com.ning.billing.catalog.api.BillingPeriod;
-import com.ning.billing.invoice.model.InvalidDateSequenceException;
-import com.ning.billing.invoice.tests.inAdvance.ProRationInAdvanceTestBase;
+import java.math.BigDecimal;
+
 import org.joda.time.DateTime;
 import org.testng.annotations.Test;
 
-import java.math.BigDecimal;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.invoice.model.InvalidDateSequenceException;
+import com.ning.billing.invoice.tests.inAdvance.ProRationInAdvanceTestBase;
 
 @Test(groups = {"fast", "invoicing", "proRation"})
 public class TrailingProRationTests extends ProRationInAdvanceTestBase {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/ValidationProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/ValidationProRationTests.java
index bd8a38d..21dd092 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/ValidationProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/ValidationProRationTests.java
@@ -16,17 +16,18 @@
 
 package com.ning.billing.invoice.tests.inAdvance;
 
+import static org.testng.Assert.assertEquals;
+
+import java.math.BigDecimal;
+
+import org.joda.time.DateTime;
+import org.testng.annotations.Test;
+
 import com.ning.billing.catalog.api.BillingPeriod;
 import com.ning.billing.invoice.model.BillingMode;
 import com.ning.billing.invoice.model.InAdvanceBillingMode;
 import com.ning.billing.invoice.model.InvalidDateSequenceException;
 import com.ning.billing.invoice.tests.ProRationTestBase;
-import org.joda.time.DateTime;
-import org.testng.annotations.Test;
-
-import java.math.BigDecimal;
-
-import static org.testng.Assert.assertEquals;
 
 @Test(groups = {"fast", "invoicing", "proRation"})
 public class ValidationProRationTests extends ProRationTestBase {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/InvoicingTestBase.java b/invoice/src/test/java/com/ning/billing/invoice/tests/InvoicingTestBase.java
index 4b47237..25be5e2 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/InvoicingTestBase.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/InvoicingTestBase.java
@@ -16,10 +16,22 @@
 
 package com.ning.billing.invoice.tests;
 
-import com.ning.billing.invoice.model.InvoicingConfiguration;
+import java.math.BigDecimal;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.catalog.api.PlanPhase;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
+import com.ning.billing.entitlement.api.billing.BillingEvent;
+import com.ning.billing.entitlement.api.billing.BillingModeType;
+import com.ning.billing.entitlement.api.user.Subscription;
 import org.joda.time.DateTime;
 
-import java.math.BigDecimal;
+import com.ning.billing.invoice.model.InvoicingConfiguration;
+
+import javax.annotation.Nullable;
 
 public abstract class InvoicingTestBase {
     protected static final int NUMBER_OF_DECIMALS = InvoicingConfiguration.getNumberOfDecimals();
@@ -31,7 +43,7 @@ public abstract class InvoicingTestBase {
     protected static final BigDecimal ONE_AND_A_HALF = new BigDecimal("1.5").setScale(NUMBER_OF_DECIMALS);
     protected static final BigDecimal TWO = new BigDecimal("2.0").setScale(NUMBER_OF_DECIMALS);
     protected static final BigDecimal THREE = new BigDecimal("3.0").setScale(NUMBER_OF_DECIMALS);
-    //protected static final BigDecimal FOUR = new BigDecimal("4.0").setScale(NUMBER_OF_DECIMALS);
+    protected static final BigDecimal FOUR = new BigDecimal("4.0").setScale(NUMBER_OF_DECIMALS);
     protected static final BigDecimal FIVE = new BigDecimal("5.0").setScale(NUMBER_OF_DECIMALS);
     protected static final BigDecimal SIX = new BigDecimal("6.0").setScale(NUMBER_OF_DECIMALS);
     protected static final BigDecimal SEVEN = new BigDecimal("7.0").setScale(NUMBER_OF_DECIMALS);
@@ -48,6 +60,7 @@ public abstract class InvoicingTestBase {
     protected static final BigDecimal TWENTY = new BigDecimal("20.0").setScale(NUMBER_OF_DECIMALS);
 
     protected static final BigDecimal TWENTY_FOUR = new BigDecimal("24.0").setScale(NUMBER_OF_DECIMALS);
+    protected static final BigDecimal TWENTY_FIVE = new BigDecimal("25.0").setScale(NUMBER_OF_DECIMALS);
 
     protected static final BigDecimal TWENTY_EIGHT = new BigDecimal("28.0").setScale(NUMBER_OF_DECIMALS);
     protected static final BigDecimal TWENTY_NINE = new BigDecimal("29.0").setScale(NUMBER_OF_DECIMALS);
@@ -69,4 +82,84 @@ public abstract class InvoicingTestBase {
     protected DateTime buildDateTime(int year, int month, int day) {
         return new DateTime(year, month, day, 0, 0, 0, 0);
     }
+
+    protected BillingEvent createMockBillingEvent(@Nullable final Account account, final Subscription subscription,
+                                                  final DateTime effectiveDate,
+                                                  final Plan plan, final PlanPhase planPhase,
+                                                  @Nullable final BigDecimal fixedPrice, @Nullable final BigDecimal recurringPrice,
+                                                  final Currency currency, final BillingPeriod billingPeriod, final int billCycleDay,
+                                                  final BillingModeType billingModeType, final String description,
+                                                  final long totalOrdering,
+                                                  final SubscriptionTransitionType type) {
+        return new BillingEvent() {
+            @Override
+            public Account getAccount() {
+                return account;
+            }
+            @Override
+            public int getBillCycleDay() {
+                return billCycleDay;
+            }
+            @Override
+            public Subscription getSubscription() {
+                return subscription;
+            }
+            @Override
+            public DateTime getEffectiveDate() {
+                return effectiveDate;
+            }
+            @Override
+            public PlanPhase getPlanPhase() {
+                return planPhase;
+            }
+            @Override
+            public Plan getPlan() {
+                return plan;
+            }
+            @Override
+            public BillingPeriod getBillingPeriod() {
+                return billingPeriod;
+            }
+            @Override
+            public BillingModeType getBillingMode() {
+                return billingModeType;
+            }
+            @Override
+            public String getDescription() {
+                return description;
+            }
+            @Override
+            public BigDecimal getFixedPrice() {
+                return fixedPrice;
+            }
+            @Override
+            public BigDecimal getRecurringPrice() {
+                return recurringPrice;
+            }
+            @Override
+            public Currency getCurrency() {
+                return currency;
+            }
+            @Override
+            public SubscriptionTransitionType getTransitionType() {
+                return type;
+            }
+            @Override
+            public Long getTotalOrdering() {
+                return totalOrdering;
+            }
+            @Override
+            public int compareTo(BillingEvent e1) {
+                if (!getSubscription().getId().equals(e1.getSubscription().getId())) { // First order by subscription
+                    return getSubscription().getId().compareTo(e1.getSubscription().getId());
+                } else { // subscriptions are the same
+                    if (! getEffectiveDate().equals(e1.getEffectiveDate())) { // Secondly order by date
+                        return getEffectiveDate().compareTo(e1.getEffectiveDate());
+                    } else { // dates and subscriptions are the same
+                        return getTotalOrdering().compareTo(e1.getTotalOrdering());
+                    }
+                }
+            }
+        };
+    }
 }
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/ProRationTestBase.java b/invoice/src/test/java/com/ning/billing/invoice/tests/ProRationTestBase.java
index 122b13b..1cd4e2f 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/ProRationTestBase.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/ProRationTestBase.java
@@ -16,17 +16,18 @@
 
 package com.ning.billing.invoice.tests;
 
-import com.ning.billing.catalog.api.BillingPeriod;
-import com.ning.billing.invoice.model.BillingMode;
-import com.ning.billing.invoice.model.InvalidDateSequenceException;
-import com.ning.billing.invoice.model.RecurringInvoiceItemData;
-import org.joda.time.DateTime;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.fail;
 
 import java.math.BigDecimal;
 import java.util.List;
 
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.fail;
+import org.joda.time.DateTime;
+
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.invoice.model.BillingMode;
+import com.ning.billing.invoice.model.InvalidDateSequenceException;
+import com.ning.billing.invoice.model.RecurringInvoiceItemData;
 
 public abstract class ProRationTestBase extends InvoicingTestBase {
     protected abstract BillingMode getBillingMode();

jaxrs/pom.xml 97(+97 -0)

diff --git a/jaxrs/pom.xml b/jaxrs/pom.xml
new file mode 100644
index 0000000..f8472a9
--- /dev/null
+++ b/jaxrs/pom.xml
@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- ~ Copyright 2010-2011 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. -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>com.ning.billing</groupId>
+        <artifactId>killbill</artifactId>
+        <version>0.1.11-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+    <artifactId>killbill-jaxrs</artifactId>
+    <name>killbill-jaxrs</name>
+    <packaging>jar</packaging>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-util</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>javax.ws.rs</groupId>
+            <artifactId>jsr311-api</artifactId>
+            <!-- WHY DO WE NEED VESRION HERE ?? -->
+            <version>1.1.1</version>
+        </dependency>
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.google.inject</groupId>
+            <artifactId>guice</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>joda-time</groupId>
+            <artifactId>joda-time</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.skife.config</groupId>
+            <artifactId>config-magic</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-log4j12</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.codehaus.jackson</groupId>
+            <artifactId>jackson-core-asl</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.codehaus.jackson</groupId>
+            <artifactId>jackson-jaxrs</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.codehaus.jackson</groupId>
+            <artifactId>jackson-mapper-asl</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.testng</groupId>
+            <artifactId>testng</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-jar-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>test-jar</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountJson.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountJson.java
new file mode 100644
index 0000000..82645a5
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountJson.java
@@ -0,0 +1,403 @@
+/*
+ * Copyright 2010-2011 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.jaxrs.json;
+
+import org.codehaus.jackson.annotate.JsonCreator;
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.codehaus.jackson.map.annotate.JsonView;
+import org.joda.time.DateTimeZone;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.AccountData;
+import com.ning.billing.catalog.api.Currency;
+
+public class AccountJson extends AccountJsonSimple {
+
+    // STEPH Missing city, locale, postalCode from https://home.ninginc.com:8443/display/REVINFRA/Killbill+1.0+APIs
+
+    @JsonView(BundleTimelineViews.Base.class)
+    private final String name;
+    
+    @JsonView(BundleTimelineViews.Base.class)
+    private final Integer length;
+        
+    @JsonView(BundleTimelineViews.Base.class)
+    private final String email;
+    
+    @JsonView(BundleTimelineViews.Base.class)
+    private final Integer billCycleDay;
+    
+    @JsonView(BundleTimelineViews.Base.class)
+    private final String currency;
+    
+    @JsonView(BundleTimelineViews.Base.class)
+    private final String paymentProvider;
+    
+    @JsonView(BundleTimelineViews.Base.class)
+    private final String timeZone;
+    
+    @JsonView(BundleTimelineViews.Base.class)
+    private final String address1;
+    
+    @JsonView(BundleTimelineViews.Base.class)
+    private final String address2;
+    
+    @JsonView(BundleTimelineViews.Base.class)
+    private final String company;
+    
+    @JsonView(BundleTimelineViews.Base.class)
+    private final String state;
+    
+    @JsonView(BundleTimelineViews.Base.class)
+    private final String country;
+    
+    @JsonView(BundleTimelineViews.Base.class)
+    private final String phone;
+
+
+    public AccountJson(Account account) {
+        super(account.getId().toString(), account.getExternalKey());
+        this.name = account.getName();
+        this.length = account.getFirstNameLength();
+        this.email = account.getEmail();
+        this.billCycleDay = account.getBillCycleDay();
+        this.currency = account.getCurrency().toString();
+        this.paymentProvider = account.getPaymentProviderName();
+        this.timeZone = account.getTimeZone().toString();
+        this.address1 = account.getAddress1();
+        this.address2 = account.getAddress2();
+        this.company = account.getCompanyName();
+        this.state = account.getStateOrProvince();
+        this.country = account.getCountry();
+        this.phone = account.getPhone();
+    }
+    
+    public AccountData toAccountData() {
+        return new AccountData() {
+            @Override
+            public DateTimeZone getTimeZone() {
+                return (timeZone != null) ? DateTimeZone.forID(timeZone) : null;
+            }
+            @Override
+            public String getStateOrProvince() {
+                return state;
+            }
+            @Override
+            public String getPostalCode() {
+                return null;
+            }
+            @Override
+            public String getPhone() {
+                return phone;
+            }
+
+            @Override
+            public boolean isMigrated() {
+                return false;
+            }
+
+            @Override
+            public boolean isNotifiedForInvoices() {
+                return false;
+            }
+
+            @Override
+            public String getPaymentProviderName() {
+                return paymentProvider;
+            }
+            @Override
+            public String getName() {
+                return name;
+            }
+            @Override
+            public String getLocale() {
+                return null;
+            }
+            @Override
+            public int getFirstNameLength() {
+                return length;
+            }
+            @Override
+            public String getExternalKey() {
+                return externalKey;
+            }
+            @Override
+            public String getEmail() {
+                return email;
+            }
+            @Override
+            public Currency getCurrency() {
+                Currency result =  (currency != null) ? Currency.valueOf(currency) : Currency.USD;
+                return result;
+            }
+            @Override
+            public String getCountry() {
+                return country;
+            }
+            @Override
+            public String getCompanyName() {
+                return company;
+            }
+            @Override
+            public String getCity() {
+                return null;
+            }
+            @Override
+            public int getBillCycleDay() {
+                return billCycleDay;
+            }
+            @Override
+            public String getAddress2() {
+                return address2;
+            }
+            @Override
+            public String getAddress1() {
+                return address1;
+            }
+        };
+    }
+
+    // Seems like Jackson (JacksonJsonProvider.readFrom(Class<Object>, Type, Annotation[], MediaType, MultivaluedMap<String,String>, InputStream) line: 443)
+    // needs us to define a default CTOR to instanciate the class first.
+    public AccountJson() {
+        super();
+        this.name = null;
+        this.length = null;
+        this.email = null;
+        this.billCycleDay = null;
+        this.currency = null;
+        this.paymentProvider = null;
+        this.timeZone = null;
+        this.address1 = null;
+        this.address2 = null;
+        this.company = null;
+        this.state = null;
+        this.country = null;
+        this.phone = null;
+    }
+
+    @JsonCreator
+    public AccountJson(@JsonProperty("accountId") String accountId,
+            @JsonProperty("name") String name,
+            @JsonProperty("firstNameLength") Integer length,
+            @JsonProperty("external_key") String externalKey,
+            @JsonProperty("email") String email,
+            @JsonProperty("billingDay") Integer billCycleDay,
+            @JsonProperty("currency") String currency,
+            @JsonProperty("paymentProvider") String paymentProvider,
+            @JsonProperty("timezone") String timeZone,
+            @JsonProperty("address1") String address1,
+            @JsonProperty("address2") String address2,
+            @JsonProperty("company") String company,
+            @JsonProperty("state") String state,
+            @JsonProperty("country") String country,
+            @JsonProperty("phone") String phone) {
+        super(accountId, externalKey);
+        this.name = name;
+        this.length = length;
+        this.email = email;
+        this.billCycleDay = billCycleDay;
+        this.currency = currency;
+        this.paymentProvider = paymentProvider;
+        this.timeZone = timeZone;
+        this.address1 = address1;
+        this.address2 = address2;
+        this.company = company;
+        this.state = state;
+        this.country = country;
+        this.phone = phone;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public Integer getLength() {
+        return length;
+    }
+
+    public String getEmail() {
+        return email;
+    }
+
+    public Integer getBillCycleDay() {
+        return billCycleDay;
+    }
+
+    public String getCurrency() {
+        return currency;
+    }
+
+    public String getPaymentProvider() {
+        return paymentProvider;
+    }
+
+    public String getTimeZone() {
+        return timeZone;
+    }
+
+    public String getAddress1() {
+        return address1;
+    }
+
+    public String getAddress2() {
+        return address2;
+    }
+
+    public String getCompany() {
+        return company;
+    }
+
+    public String getState() {
+        return state;
+    }
+
+    public String getCountry() {
+        return country;
+    }
+
+    public String getPhone() {
+        return phone;
+    }
+
+	@Override
+	public int hashCode() {
+		final int prime = 31;
+		int result = 1;
+		result = prime * result
+				+ ((accountId == null) ? 0 : accountId.hashCode());
+		result = prime * result
+				+ ((address1 == null) ? 0 : address1.hashCode());
+		result = prime * result
+				+ ((address2 == null) ? 0 : address2.hashCode());
+		result = prime * result
+				+ ((billCycleDay == null) ? 0 : billCycleDay.hashCode());
+		result = prime * result + ((company == null) ? 0 : company.hashCode());
+		result = prime * result + ((country == null) ? 0 : country.hashCode());
+		result = prime * result
+				+ ((currency == null) ? 0 : currency.hashCode());
+		result = prime * result + ((email == null) ? 0 : email.hashCode());
+		result = prime * result
+				+ ((externalKey == null) ? 0 : externalKey.hashCode());
+		result = prime * result + ((length == null) ? 0 : length.hashCode());
+		result = prime * result + ((name == null) ? 0 : name.hashCode());
+		result = prime * result
+				+ ((paymentProvider == null) ? 0 : paymentProvider.hashCode());
+		result = prime * result + ((phone == null) ? 0 : phone.hashCode());
+		result = prime * result + ((state == null) ? 0 : state.hashCode());
+		result = prime * result
+				+ ((timeZone == null) ? 0 : timeZone.hashCode());
+		return result;
+	}
+
+	// Used to check POST versus GET
+	public boolean equalsNoId(Object obj) {
+		if (this == obj)
+			return true;
+		if (obj == null)
+			return false;
+		if (getClass() != obj.getClass())
+			return false;
+		AccountJson other = (AccountJson) obj;
+		if (address1 == null) {
+			if (other.address1 != null)
+				return false;
+		} else if (!address1.equals(other.address1))
+			return false;
+		if (address2 == null) {
+			if (other.address2 != null)
+				return false;
+		} else if (!address2.equals(other.address2))
+			return false;
+		if (billCycleDay == null) {
+			if (other.billCycleDay != null)
+				return false;
+		} else if (!billCycleDay.equals(other.billCycleDay))
+			return false;
+		if (company == null) {
+			if (other.company != null)
+				return false;
+		} else if (!company.equals(other.company))
+			return false;
+		if (country == null) {
+			if (other.country != null)
+				return false;
+		} else if (!country.equals(other.country))
+			return false;
+		if (currency == null) {
+			if (other.currency != null)
+				return false;
+		} else if (!currency.equals(other.currency))
+			return false;
+		if (email == null) {
+			if (other.email != null)
+				return false;
+		} else if (!email.equals(other.email))
+			return false;
+		if (externalKey == null) {
+			if (other.externalKey != null)
+				return false;
+		} else if (!externalKey.equals(other.externalKey))
+			return false;
+		if (length == null) {
+			if (other.length != null)
+				return false;
+		} else if (!length.equals(other.length))
+			return false;
+		if (name == null) {
+			if (other.name != null)
+				return false;
+		} else if (!name.equals(other.name))
+			return false;
+		if (paymentProvider == null) {
+			if (other.paymentProvider != null)
+				return false;
+		} else if (!paymentProvider.equals(other.paymentProvider))
+			return false;
+		if (phone == null) {
+			if (other.phone != null)
+				return false;
+		} else if (!phone.equals(other.phone))
+			return false;
+		if (state == null) {
+			if (other.state != null)
+				return false;
+		} else if (!state.equals(other.state))
+			return false;
+		if (timeZone == null) {
+			if (other.timeZone != null)
+				return false;
+		} else if (!timeZone.equals(other.timeZone))
+			return false;
+		return true;
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (equalsNoId(obj) == false) {
+			return false;
+		} else {
+			AccountJson other = (AccountJson) obj;
+			if (accountId == null) {
+				if (other.accountId != null)
+					return false;
+			} else if (!accountId.equals(other.accountId))
+				return false;
+		}
+		return true;
+	}
+ }
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountJsonSimple.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountJsonSimple.java
new file mode 100644
index 0000000..d7e1b2d
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountJsonSimple.java
@@ -0,0 +1,49 @@
+/* 
+ * Copyright 2010-2011 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.jaxrs.json;
+
+import org.codehaus.jackson.annotate.JsonCreator;
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.codehaus.jackson.map.annotate.JsonView;
+
+public class AccountJsonSimple {
+
+    @JsonView(BundleTimelineViews.Base.class)
+    protected final String accountId;
+    
+    @JsonView(BundleTimelineViews.Base.class)
+    protected final String externalKey;
+    
+    public AccountJsonSimple() {
+        this.accountId = null;
+        this.externalKey = null;
+    }
+
+    @JsonCreator
+    public AccountJsonSimple(@JsonProperty("accountId") String accountId,
+            @JsonProperty("externalKey") String externalKey) {
+        this.accountId = accountId;
+        this.externalKey = externalKey;
+    }
+
+    public String getAccountId() {
+        return accountId;
+    }
+
+    public String getExternalKey() {
+        return externalKey;
+    }
+}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountTimelineJson.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountTimelineJson.java
new file mode 100644
index 0000000..881745a
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountTimelineJson.java
@@ -0,0 +1,138 @@
+/* 
+ * Copyright 2010-2011 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.jaxrs.json;
+
+import java.math.BigDecimal;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+import java.util.UUID;
+
+import org.codehaus.jackson.annotate.JsonCreator;
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.codehaus.jackson.map.annotate.JsonView;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.entitlement.api.timeline.BundleTimeline;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.payment.api.PaymentAttempt;
+
+public class AccountTimelineJson {
+
+    @JsonView(BundleTimelineViews.ReadTimeline.class)
+    private final List<PaymentJsonWithBundleKeys> payments;
+
+    @JsonView(BundleTimelineViews.ReadTimeline.class)
+    private final List<InvoiceJsonWithBundleKeys> invoices;
+    
+    @JsonView(BundleTimelineViews.ReadTimeline.class)
+    private final AccountJsonSimple account;
+    
+    @JsonView(BundleTimelineViews.Timeline.class)
+    private final List<BundleJsonWithSubscriptions> bundles;
+    
+    @JsonCreator
+    public AccountTimelineJson(@JsonProperty("account") AccountJsonSimple account,
+            @JsonProperty("bundles") List<BundleJsonWithSubscriptions> bundles,
+            @JsonProperty("invoices") List<InvoiceJsonWithBundleKeys> invoices,            
+            @JsonProperty("payments") List<PaymentJsonWithBundleKeys> payments) {
+        this.account = account;
+        this.bundles = bundles;
+        this.invoices = invoices;
+        this.payments = payments;
+    }
+    
+    private String getBundleExternalKey(UUID invoiceId,  List<Invoice> invoices, List<BundleTimeline> bundles) {
+        for (Invoice cur : invoices) {
+            if (cur.getId().equals(invoiceId)) {
+                return getBundleExternalKey(cur, bundles);
+            }
+        }
+        return null;
+    }
+    
+    private String getBundleExternalKey(Invoice invoice, List<BundleTimeline> bundles) {
+        Set<UUID> b = new HashSet<UUID>();
+        for (final InvoiceItem cur : invoice.getInvoiceItems()) {
+            b.add(cur.getBundleId());
+        }
+        boolean first = true;
+        StringBuilder tmp = new StringBuilder();
+        for (final UUID cur : b) {
+            for (final BundleTimeline bt : bundles) {
+                if (bt.getBundleId().equals(cur)) {
+                    if (!first) {
+                        tmp.append(",");
+                    }
+                    tmp.append(bt.getExternalKey());
+                    first = false;
+                    break;
+                }
+            }
+        }
+        return tmp.toString();
+    }
+    
+    public AccountTimelineJson(Account account, List<Invoice> invoices, List<PaymentAttempt> payments, List<BundleTimeline> bundles) {
+        this.account = new AccountJsonSimple(account.getId().toString(), account.getExternalKey());
+        this.bundles = new LinkedList<BundleJsonWithSubscriptions>();
+        for (BundleTimeline cur : bundles) {
+            this.bundles.add(new BundleJsonWithSubscriptions(account.getId(), cur));            
+        }
+        this.invoices = new LinkedList<InvoiceJsonWithBundleKeys>();
+        for (Invoice cur : invoices) {
+            this.invoices.add(new InvoiceJsonWithBundleKeys(cur.getTotalAmount(), cur.getId().toString(), cur.getInvoiceDate(), cur.getTargetDate(),
+                    Integer.toString(cur.getInvoiceNumber()), cur.getBalance(),
+                    getBundleExternalKey(cur, bundles)));
+        }
+        this.payments = new LinkedList<PaymentJsonWithBundleKeys>();
+        for (PaymentAttempt cur : payments) {
+            
+
+            String status = cur.getPaymentId() != null ? "Success" : "Failed";
+            BigDecimal paidAmount = cur.getPaymentId() != null ? cur.getAmount() : BigDecimal.ZERO;
+            
+            this.payments.add(new PaymentJsonWithBundleKeys(cur.getAmount(), paidAmount, cur.getInvoiceId(), cur.getPaymentId(), cur.getCreatedDate(), cur.getUpdatedDate(),
+                    cur.getRetryCount(), cur.getCurrency().toString(), status,
+                    getBundleExternalKey(cur.getInvoiceId(), invoices, bundles)));
+          }
+    }
+    
+    public AccountTimelineJson() {
+        this.account = null;
+        this.bundles = null;
+        this.invoices = null;
+        this.payments = null;
+    }
+
+    public List<PaymentJsonWithBundleKeys> getPayments() {
+        return payments;
+    }
+
+    public List<InvoiceJsonWithBundleKeys> getInvoices() {
+        return invoices;
+    }
+
+    public AccountJsonSimple getAccount() {
+        return account;
+    }
+
+    public List<BundleJsonWithSubscriptions> getBundles() {
+        return bundles;
+    }
+}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/BundleJsonNoSubsciptions.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/BundleJsonNoSubsciptions.java
new file mode 100644
index 0000000..c1221b8
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/BundleJsonNoSubsciptions.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2010-2011 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.jaxrs.json;
+
+import java.util.List;
+
+import org.codehaus.jackson.annotate.JsonCreator;
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.codehaus.jackson.map.annotate.JsonView;
+
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+
+public class BundleJsonNoSubsciptions  extends BundleJsonSimple {
+
+    @JsonView(BundleTimelineViews.Base.class)
+    private final String accountId;
+
+
+    @JsonCreator
+    public BundleJsonNoSubsciptions(@JsonProperty("bundleId") String bundleId,
+            @JsonProperty("accountId") String accountId,
+            @JsonProperty("externalKey") String externalKey,
+            @JsonProperty("subscriptions") List<SubscriptionJsonWithEvents> subscriptions) {
+        super(bundleId, externalKey);
+        this.accountId = accountId;
+    }
+
+    public String getAccountId() {
+        return accountId;
+    }
+
+    
+    public BundleJsonNoSubsciptions(SubscriptionBundle bundle) {
+        super(bundle.getId().toString(), bundle.getKey());
+        this.accountId = bundle.getAccountId().toString();
+    }
+    
+    public BundleJsonNoSubsciptions() {
+        super(null, null);        
+        this.accountId = null;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result
+                + ((accountId == null) ? 0 : accountId.hashCode());
+        result = prime * result
+                + ((bundleId == null) ? 0 : bundleId.hashCode());
+        result = prime * result
+                + ((externalKey == null) ? 0 : externalKey.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (equalsNoId(obj) == false) {
+            return false;
+        }
+        BundleJsonNoSubsciptions other = (BundleJsonNoSubsciptions) obj;
+        if (bundleId == null) {
+            if (other.bundleId != null)
+                return false;
+        } else if (!bundleId.equals(other.bundleId))
+            return false;
+        return true;
+    }
+
+    public boolean equalsNoId(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        BundleJsonNoSubsciptions other = (BundleJsonNoSubsciptions) obj;
+        if (accountId == null) {
+            if (other.accountId != null)
+                return false;
+        } else if (!accountId.equals(other.accountId))
+            return false;
+        if (externalKey == null) {
+            if (other.externalKey != null)
+                return false;
+        } else if (!externalKey.equals(other.externalKey))
+            return false;
+        return true;
+    }
+    
+    
+}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/BundleJsonSimple.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/BundleJsonSimple.java
new file mode 100644
index 0000000..8c2d836
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/BundleJsonSimple.java
@@ -0,0 +1,52 @@
+/* 
+ * Copyright 2010-2011 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.jaxrs.json;
+
+import org.codehaus.jackson.annotate.JsonCreator;
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.codehaus.jackson.map.annotate.JsonView;
+
+public class BundleJsonSimple {
+    
+    @JsonView(BundleTimelineViews.Base.class)
+    protected final String bundleId;
+
+    @JsonView(BundleTimelineViews.Base.class)
+    protected final String externalKey;
+
+    @JsonCreator
+    public BundleJsonSimple(@JsonProperty("bundleId") String bundleId,
+            @JsonProperty("externalKey") String externalKey) {
+        super();
+        this.bundleId = bundleId;
+        this.externalKey = externalKey;
+    }
+    
+    public BundleJsonSimple() {
+        this.bundleId = null;
+        this.externalKey = null;
+    }
+
+    @JsonProperty("bundleId")
+    public String getBundleId() {
+        return bundleId;
+    }
+
+    @JsonProperty("externalKey")
+    public String getExternalKey() {
+        return externalKey;
+    }
+}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/BundleJsonWithSubscriptions.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/BundleJsonWithSubscriptions.java
new file mode 100644
index 0000000..fa45cdd
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/BundleJsonWithSubscriptions.java
@@ -0,0 +1,66 @@
+/* 
+ * Copyright 2010-2011 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.jaxrs.json;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.UUID;
+
+import org.codehaus.jackson.annotate.JsonCreator;
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.codehaus.jackson.map.annotate.JsonView;
+
+import com.ning.billing.entitlement.api.timeline.BundleTimeline;
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+
+public class BundleJsonWithSubscriptions extends BundleJsonSimple {
+
+    @JsonView(BundleTimelineViews.Timeline.class)
+    private final List<SubscriptionJsonWithEvents> subscriptions;
+
+    @JsonCreator
+    public BundleJsonWithSubscriptions(@JsonProperty("bundleId") String bundleId,
+            @JsonProperty("externalKey") String externalKey,
+            @JsonProperty("subscriptions") List<SubscriptionJsonWithEvents> subscriptions) {
+        super(bundleId, externalKey);
+        this.subscriptions = subscriptions;
+    }
+
+    @JsonProperty("subscriptions")
+    public List<SubscriptionJsonWithEvents> getSubscriptions() {
+        return subscriptions;
+    }
+
+    public BundleJsonWithSubscriptions(final UUID accountId, final BundleTimeline bundle) {
+        super(bundle.getBundleId().toString(), bundle.getExternalKey());
+        this.subscriptions = new LinkedList<SubscriptionJsonWithEvents>();
+        for (SubscriptionTimeline cur : bundle.getSubscriptions()) {
+            this.subscriptions.add(new SubscriptionJsonWithEvents(bundle.getBundleId(), cur)); 
+        }
+    }
+    
+    public BundleJsonWithSubscriptions(SubscriptionBundle bundle) {
+        super(bundle.getId().toString(), bundle.getKey());
+        this.subscriptions = null;
+    }
+    
+    public BundleJsonWithSubscriptions() {
+        super(null, null);        
+        this.subscriptions = null;
+    }
+
+}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/BundleTimelineJson.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/BundleTimelineJson.java
new file mode 100644
index 0000000..099d837
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/BundleTimelineJson.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2010-2011 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.jaxrs.json;
+
+import java.util.List;
+
+import org.codehaus.jackson.annotate.JsonCreator;
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.codehaus.jackson.map.annotate.JsonView;
+
+public class BundleTimelineJson {
+
+    @JsonView(BundleTimelineViews.Timeline.class)
+    private final String viewId;
+
+    @JsonView(BundleTimelineViews.Timeline.class)
+    private final BundleJsonWithSubscriptions bundle;
+
+    @JsonView(BundleTimelineViews.ReadTimeline.class)
+    private final List<PaymentJsonSimple> payments;
+
+    @JsonView(BundleTimelineViews.ReadTimeline.class)
+    private final List<InvoiceJsonSimple> invoices;
+
+    @JsonView(BundleTimelineViews.WriteTimeline.class)
+    private final String resonForChange;
+
+    @JsonCreator
+    public BundleTimelineJson(@JsonProperty("viewId") String viewId,
+            @JsonProperty("bundle") BundleJsonWithSubscriptions bundle,
+            @JsonProperty("payments") List<PaymentJsonSimple> payments,
+            @JsonProperty("invoices") List<InvoiceJsonSimple> invoices,
+            @JsonProperty("reasonForChange") String reason) {
+        this.viewId = viewId;
+        this.bundle = bundle;
+        this.payments = payments;
+        this.invoices = invoices;
+        this.resonForChange = reason;
+    }
+
+    public String getViewId() {
+        return viewId;
+    }
+
+    public BundleJsonWithSubscriptions getBundle() {
+        return bundle;
+    }
+
+    public List<PaymentJsonSimple> getPayments() {
+        return payments;
+    }
+
+    public List<InvoiceJsonSimple> getInvoices() {
+        return invoices;
+    }
+
+    public String getResonForChange() {
+        return resonForChange;
+    }
+}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/BundleTimelineViews.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/BundleTimelineViews.java
new file mode 100644
index 0000000..0397bec
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/BundleTimelineViews.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2010-2011 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.jaxrs.json;
+
+
+public class BundleTimelineViews {
+    public static class Base {};
+    public static class Timeline extends Base {};
+    public static class ReadTimeline extends Timeline {};
+    public static class WriteTimeline extends Timeline {};
+}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/CustomFieldJson.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/CustomFieldJson.java
new file mode 100644
index 0000000..cb9e36b
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/CustomFieldJson.java
@@ -0,0 +1,51 @@
+/* 
+ * Copyright 2010-2011 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.jaxrs.json;
+
+import org.codehaus.jackson.annotate.JsonCreator;
+
+import com.ning.billing.util.customfield.CustomField;
+
+public class CustomFieldJson {
+
+    private final String name;
+    private final String value;
+    
+    public CustomFieldJson() {
+        this.name = null;
+        this.value = null;
+    }
+    
+    @JsonCreator
+    public CustomFieldJson(String name, String value) {
+        super();
+        this.name = name;
+        this.value = value;
+    }
+    
+    public CustomFieldJson(CustomField input) {
+        this.name = input.getName();
+        this.value = input.getValue();
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getValue() {
+        return value;
+    }
+}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/InvoiceJsonSimple.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/InvoiceJsonSimple.java
new file mode 100644
index 0000000..be2516d
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/InvoiceJsonSimple.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright 2010-2011 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.jaxrs.json;
+
+import java.math.BigDecimal;
+
+import org.codehaus.jackson.annotate.JsonCreator;
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.codehaus.jackson.map.annotate.JsonView;
+import org.joda.time.DateTime;
+
+import com.ning.billing.invoice.api.Invoice;
+
+public class InvoiceJsonSimple {
+
+    @JsonView(BundleTimelineViews.Base.class)
+    private final BigDecimal amount;
+    
+    @JsonView(BundleTimelineViews.Base.class)
+    private final String invoiceId;
+    
+    @JsonView(BundleTimelineViews.Base.class)
+    private final DateTime invoiceDate;
+
+    @JsonView(BundleTimelineViews.Base.class)
+    private final DateTime targetDate;
+
+    @JsonView(BundleTimelineViews.Base.class)
+    private final String invoiceNumber;
+    
+    @JsonView(BundleTimelineViews.Base.class)
+    private final BigDecimal balance;
+
+    
+    public InvoiceJsonSimple() {
+        this.amount = null;
+        this.invoiceId = null;
+        this.invoiceDate = null;
+        this.targetDate = null;        
+        this.invoiceNumber = null;
+        this.balance = null;
+    }
+    
+    @JsonCreator
+    public InvoiceJsonSimple(@JsonProperty("amount") BigDecimal amount,
+            @JsonProperty("invoiceId") String invoiceId,
+            @JsonProperty("invoiceDate") DateTime invoiceDate,
+            @JsonProperty("targetDate") DateTime targetDate,            
+            @JsonProperty("invoiceNumber") String invoiceNumber,
+            @JsonProperty("balance") BigDecimal balance) {
+        super();
+        this.amount = amount;
+        this.invoiceId = invoiceId;
+        this.invoiceDate = invoiceDate;
+        this.targetDate = targetDate;
+        this.invoiceNumber = invoiceNumber;
+        this.balance = balance;
+    }
+
+    public InvoiceJsonSimple(Invoice input) {
+        this.amount = input.getTotalAmount();
+        this.invoiceId = input.getId().toString();
+        this.invoiceDate = input.getInvoiceDate();
+        this.targetDate = input.getTargetDate();
+        this.invoiceNumber = String.valueOf(input.getInvoiceNumber());
+        this.balance = input.getBalance();
+    }
+    
+    public BigDecimal getAmount() {
+        return amount;
+    }
+
+    public String getInvoiceId() {
+        return invoiceId;
+    }
+
+    public DateTime getInvoiceDate() {
+        return invoiceDate;
+    }
+
+    public DateTime getTargetDate() {
+        return targetDate;
+    }
+
+    public String getInvoiceNumber() {
+        return invoiceNumber;
+    }
+
+    public BigDecimal getBalance() {
+        return balance;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((amount == null) ? 0 : amount.hashCode());
+        result = prime * result + ((balance == null) ? 0 : balance.hashCode());
+        result = prime * result
+                + ((invoiceDate == null) ? 0 : invoiceDate.hashCode());
+        result = prime * result
+                + ((invoiceId == null) ? 0 : invoiceId.hashCode());
+        result = prime * result
+                + ((invoiceNumber == null) ? 0 : invoiceNumber.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        InvoiceJsonSimple other = (InvoiceJsonSimple) obj;
+        if (amount == null) {
+            if (other.amount != null)
+                return false;
+        } else if (!amount.equals(other.amount))
+            return false;
+        if (balance == null) {
+            if (other.balance != null)
+                return false;
+        } else if (!balance.equals(other.balance))
+            return false;
+        if (invoiceDate == null) {
+            if (other.invoiceDate != null)
+                return false;
+        } else if (!invoiceDate.equals(other.invoiceDate))
+            return false;
+        if (invoiceId == null) {
+            if (other.invoiceId != null)
+                return false;
+        } else if (!invoiceId.equals(other.invoiceId))
+            return false;
+        if (invoiceNumber == null) {
+            if (other.invoiceNumber != null)
+                return false;
+        } else if (!invoiceNumber.equals(other.invoiceNumber))
+            return false;
+        return true;
+    }
+}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/InvoiceJsonWithBundleKeys.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/InvoiceJsonWithBundleKeys.java
new file mode 100644
index 0000000..cc15072
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/InvoiceJsonWithBundleKeys.java
@@ -0,0 +1,57 @@
+package com.ning.billing.jaxrs.json;
+import java.math.BigDecimal;
+
+import org.codehaus.jackson.annotate.JsonCreator;
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.joda.time.DateTime;
+
+import com.ning.billing.invoice.api.Invoice;
+
+/* 
+ * Copyright 2010-2011 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.
+ */
+
+public class InvoiceJsonWithBundleKeys extends InvoiceJsonSimple {
+    
+    
+    private final String bundleKeys;
+
+
+    public InvoiceJsonWithBundleKeys() {
+        super();
+        this.bundleKeys = null;
+    }
+    
+    @JsonCreator
+    public InvoiceJsonWithBundleKeys(@JsonProperty("amount") BigDecimal amount,
+            @JsonProperty("invoiceId") String invoiceId,
+            @JsonProperty("invoiceDate") DateTime invoiceDate,
+            @JsonProperty("targetDate") DateTime targetDate,            
+            @JsonProperty("invoiceNumber") String invoiceNumber,
+            @JsonProperty("balance") BigDecimal balance,
+            @JsonProperty("externalBundleKeys") String bundleKeys) {
+        super(amount, invoiceId, invoiceDate, targetDate, invoiceNumber, balance);
+        this.bundleKeys = bundleKeys;
+    }
+
+    public InvoiceJsonWithBundleKeys(Invoice input, String bundleKeys) {
+        super(input);
+        this.bundleKeys = bundleKeys;
+    }
+
+    public String getBundleKeys() {
+        return bundleKeys;
+    }
+}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/PaymentJsonSimple.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/PaymentJsonSimple.java
new file mode 100644
index 0000000..c0a6d7c
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/PaymentJsonSimple.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2010-2011 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.jaxrs.json;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import org.codehaus.jackson.annotate.JsonCreator;
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.codehaus.jackson.map.annotate.JsonView;
+import org.joda.time.DateTime;
+
+import com.ning.billing.util.clock.DefaultClock;
+
+public class PaymentJsonSimple {
+
+    private final BigDecimal paidAmount;
+
+    private final BigDecimal amount;
+
+    private final UUID invoiceId;
+    
+    private final UUID paymentId;
+    
+    private final DateTime requestedDate;
+    
+    private final DateTime effectiveDate;
+    
+    private final Integer retryCount;
+    
+    private final String currency;
+    
+    private final String status;
+      
+    public PaymentJsonSimple() {
+        this.amount = null;
+        this.paidAmount = null;
+        this.invoiceId = null;
+        this.paymentId = null;
+        this.requestedDate = null;
+        this.effectiveDate = null;
+        this.currency = null;
+        this.retryCount = null;
+        this.status = null;
+    }
+
+    @JsonCreator
+    public PaymentJsonSimple(@JsonProperty("amount") BigDecimal amount,
+            @JsonProperty("paidAmount") BigDecimal paidAmount,
+            @JsonProperty("invoiceId") UUID invoiceId,
+            @JsonProperty("paymentId") UUID paymentId,
+            @JsonProperty("requestedDate") DateTime requestedDate,
+            @JsonProperty("effectiveDate") DateTime effectiveDate,
+            @JsonProperty("retryCount") Integer retryCount,
+            @JsonProperty("currency") String currency,            
+            @JsonProperty("status") String status) {
+        super();
+        this.amount = amount;
+        this.paidAmount = paidAmount;
+        this.invoiceId = invoiceId;
+        this.paymentId = paymentId;
+        this.requestedDate = DefaultClock.toUTCDateTime(requestedDate);
+        this.effectiveDate = DefaultClock.toUTCDateTime(effectiveDate);
+        this.currency = currency;
+        this.retryCount = retryCount;
+        this.status = status;
+    }
+
+    public BigDecimal getPaidAmount() {
+        return paidAmount;
+    }
+
+    public UUID getInvoiceId() {
+        return invoiceId;
+    }
+
+    public UUID getPaymentId() {
+        return paymentId;
+    }
+
+    public DateTime getRequestedDate() {
+        return DefaultClock.toUTCDateTime(requestedDate);
+    }
+
+    public DateTime getEffectiveDate() {
+        return DefaultClock.toUTCDateTime(effectiveDate);
+    }
+
+    public String getStatus() {
+        return status;
+    }
+
+    public BigDecimal getAmount() {
+        return amount;
+    }
+
+    public Integer getRetryCount() {
+        return retryCount;
+    }
+
+    public String getCurrency() {
+        return currency;
+    }
+}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/PaymentJsonWithBundleKeys.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/PaymentJsonWithBundleKeys.java
new file mode 100644
index 0000000..e322a59
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/PaymentJsonWithBundleKeys.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2010-2011 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.jaxrs.json;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import org.codehaus.jackson.annotate.JsonCreator;
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.joda.time.DateTime;
+
+public class PaymentJsonWithBundleKeys extends PaymentJsonSimple {
+
+    private final String bundleKeys;
+    
+    public PaymentJsonWithBundleKeys() {
+        super();
+        this.bundleKeys = null;
+    }
+
+    @JsonCreator
+    public PaymentJsonWithBundleKeys(@JsonProperty("amount") BigDecimal amount,
+            @JsonProperty("paidAmount") BigDecimal paidAmount,
+            @JsonProperty("invoiceId") UUID invoiceId,
+            @JsonProperty("paymentId") UUID paymentId,
+            @JsonProperty("requestedDt") DateTime requestedDate,
+            @JsonProperty("effectiveDt") DateTime effectiveDate,
+            @JsonProperty("retryCount") Integer retryCount,
+            @JsonProperty("currency") String currency,            
+            @JsonProperty("status") String status,
+            @JsonProperty("externalBundleKeys") String bundleKeys) {
+        super(amount, paidAmount, invoiceId, paymentId, requestedDate, effectiveDate, retryCount, currency, status);
+        this.bundleKeys = bundleKeys;
+    }
+    
+    public String getBundleKeys() {
+        return bundleKeys;
+    }
+}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/SubscriptionJsonNoEvents.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/SubscriptionJsonNoEvents.java
new file mode 100644
index 0000000..95a5e09
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/SubscriptionJsonNoEvents.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright 2010-2011 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.jaxrs.json;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.UUID;
+
+import org.codehaus.jackson.annotate.JsonCreator;
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.codehaus.jackson.map.annotate.JsonView;
+import org.joda.time.DateTime;
+
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline;
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline.ExistingEvent;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.util.clock.DefaultClock;
+
+public class SubscriptionJsonNoEvents extends SubscriptionJsonSimple {
+
+    @JsonView(BundleTimelineViews.Base.class)
+    private final DateTime startDate;
+    
+    @JsonView(BundleTimelineViews.Base.class)
+    private final String bundleId;
+
+    @JsonView(BundleTimelineViews.Base.class)
+    private final String productName;
+
+    @JsonView(BundleTimelineViews.Base.class)
+    private final String productCategory;
+
+    @JsonView(BundleTimelineViews.Base.class)
+    private final String billingPeriod;
+
+    @JsonView(BundleTimelineViews.Base.class)
+    private final String priceList;
+
+    @JsonView(BundleTimelineViews.Base.class)
+    private final DateTime chargedThroughDate;
+    
+
+
+    @JsonCreator
+    public SubscriptionJsonNoEvents(@JsonProperty("subscriptionId") String subscriptionId,
+            @JsonProperty("bundleId") String bundleId,
+            @JsonProperty("startDate") DateTime startDate,
+            @JsonProperty("productName") String productName,
+            @JsonProperty("productCategory") String productCategory,
+            @JsonProperty("billingPeriod") String billingPeriod,
+            @JsonProperty("priceList") String priceList,
+            @JsonProperty("chargedThroughDate") DateTime chargedThroughDate) {
+        super(subscriptionId);
+        this.bundleId = bundleId;
+        this.startDate = startDate;
+        this.productName = productName;
+        this.productCategory = productCategory;
+        this.billingPeriod = billingPeriod;
+        this.priceList = priceList;
+        this.chargedThroughDate = chargedThroughDate;
+    }
+    
+    public SubscriptionJsonNoEvents() {
+        super(null);
+        this.bundleId = null;
+        this.startDate = null;
+        this.productName = null;
+        this.productCategory = null;
+        this.billingPeriod = null;
+        this.priceList = null;
+        this.chargedThroughDate = null;        
+    }
+    
+    public SubscriptionJsonNoEvents(final Subscription data) {
+        super(data.getId().toString());
+        this.bundleId = data.getBundleId().toString();
+        this.startDate = data.getStartDate();
+        this.productName = data.getCurrentPlan().getProduct().getName();
+        this.productCategory = data.getCurrentPlan().getProduct().getCategory().toString();
+        this.billingPeriod = data.getCurrentPlan().getBillingPeriod().toString();
+        this.priceList = data.getCurrentPriceList().getName();
+        this.chargedThroughDate = data.getChargedThroughDate();
+    }
+   
+
+    public String getSubscriptionId() {
+        return subscriptionId;
+    }
+
+    public String getBundleId() {
+        return bundleId;
+    }
+    
+    public DateTime getStartDate() {
+        return startDate;
+    }
+
+    public String getProductName() {
+        return productName;
+    }
+
+    public String getProductCategory() {
+        return productCategory;
+    }
+
+    public String getBillingPeriod() {
+        return billingPeriod;
+    }
+
+    public String getPriceList() {
+        return priceList;
+    }
+    
+    public DateTime getChargedThroughDate() {
+        return chargedThroughDate;
+    }
+
+
+    @Override
+    public String toString() {
+        return "SubscriptionJson [subscriptionId=" + subscriptionId
+                + ", bundleId=" + bundleId + ", productName=" + productName
+                + ", productCategory=" + productCategory + ", billingPeriod="
+                + billingPeriod + ", priceList=" + priceList + "]";
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result
+                + ((billingPeriod == null) ? 0 : billingPeriod.hashCode());
+        result = prime * result
+                + ((bundleId == null) ? 0 : bundleId.hashCode());
+        result = prime * result
+                + ((priceList == null) ? 0 : priceList.hashCode());
+        result = prime * result
+                + ((productCategory == null) ? 0 : productCategory.hashCode());
+        result = prime * result
+                + ((productName == null) ? 0 : productName.hashCode());
+        result = prime * result
+                + ((subscriptionId == null) ? 0 : subscriptionId.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (equalsNoId(obj) == false) {
+            return false;
+        }
+        SubscriptionJsonNoEvents other = (SubscriptionJsonNoEvents) obj;
+        if (subscriptionId == null) {
+            if (other.subscriptionId != null)
+                return false;
+        } else if (!subscriptionId.equals(other.subscriptionId))
+            return false;
+        return true;
+    }
+
+    public boolean equalsNoId(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        SubscriptionJsonNoEvents other = (SubscriptionJsonNoEvents) obj;
+        if (billingPeriod == null) {
+            if (other.billingPeriod != null)
+                return false;
+        } else if (!billingPeriod.equals(other.billingPeriod))
+            return false;
+        if (bundleId == null) {
+            if (other.bundleId != null)
+                return false;
+        } else if (!bundleId.equals(other.bundleId))
+            return false;
+        if (priceList == null) {
+            if (other.priceList != null)
+                return false;
+        } else if (!priceList.equals(other.priceList))
+            return false;
+        if (productCategory == null) {
+            if (other.productCategory != null)
+                return false;
+        } else if (!productCategory.equals(other.productCategory))
+            return false;
+        if (productName == null) {
+            if (other.productName != null)
+                return false;
+        } else if (!productName.equals(other.productName))
+            return false;
+        return true;
+    }
+}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/SubscriptionJsonSimple.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/SubscriptionJsonSimple.java
new file mode 100644
index 0000000..5fb782e
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/SubscriptionJsonSimple.java
@@ -0,0 +1,39 @@
+/* 
+ * Copyright 2010-2011 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.jaxrs.json;
+
+import org.codehaus.jackson.annotate.JsonCreator;
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.codehaus.jackson.map.annotate.JsonView;
+
+public class SubscriptionJsonSimple {
+    
+    @JsonView(BundleTimelineViews.Base.class)
+    protected final String subscriptionId;
+
+    public SubscriptionJsonSimple() {
+        this.subscriptionId = null;
+    }
+
+    @JsonCreator
+    public SubscriptionJsonSimple(@JsonProperty("subscriptionId") String subscriptionId) {
+        this.subscriptionId = subscriptionId;
+    }
+
+    public String getSubscriptionId() {
+        return subscriptionId;
+    }
+}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/SubscriptionJsonWithEvents.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/SubscriptionJsonWithEvents.java
new file mode 100644
index 0000000..be5ba2c
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/SubscriptionJsonWithEvents.java
@@ -0,0 +1,261 @@
+/* 
+ * Copyright 2010-2011 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.jaxrs.json;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.UUID;
+
+import org.codehaus.jackson.annotate.JsonCreator;
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.codehaus.jackson.map.annotate.JsonView;
+import org.joda.time.DateTime;
+
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline;
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline.ExistingEvent;
+import com.ning.billing.entitlement.api.user.Subscription;
+
+import com.ning.billing.util.clock.DefaultClock;
+
+public class SubscriptionJsonWithEvents extends SubscriptionJsonSimple {
+    
+    @JsonView(BundleTimelineViews.ReadTimeline.class)
+    private final List<SubscriptionReadEventJson> events;
+
+    @JsonView(BundleTimelineViews.WriteTimeline.class)
+    private final List<SubscriptionDeletedEventJson> deletedEvents;
+
+    @JsonView(BundleTimelineViews.WriteTimeline.class)
+    private final List<SubscriptionNewEventJson> newEvents;
+
+
+    public static class SubscriptionReadEventJson extends SubscriptionBaseEventJson {
+
+        @JsonView(BundleTimelineViews.Timeline.class)
+        private final String eventId;
+
+        @JsonView(BundleTimelineViews.Timeline.class)
+        private final DateTime effectiveDate;
+
+        public SubscriptionReadEventJson() {
+            super();
+            this.eventId = null;
+            this.effectiveDate = null;
+        }
+ 
+        @JsonCreator
+        public SubscriptionReadEventJson(@JsonProperty("eventId") String eventId,
+                @JsonProperty("billingPeriod") String billingPeriod,
+                @JsonProperty("requestedDt") DateTime requestedDate,
+                @JsonProperty("effectiveDt") DateTime effectiveDate,
+                @JsonProperty("product") String product,
+                @JsonProperty("priceList") String priceList,
+                @JsonProperty("eventType") String eventType,
+                @JsonProperty("phase") String phase) {
+            super(billingPeriod, requestedDate, product, priceList, eventType, phase);
+            this.eventId = eventId;
+            this.effectiveDate = effectiveDate;
+        }
+
+        public String getEventId() {
+            return eventId;
+        }
+
+        public DateTime getEffectiveDate() {
+            return DefaultClock.toUTCDateTime(effectiveDate);
+        }
+
+        @Override
+        public String toString() {
+            return "SubscriptionReadEventJson [eventId=" + eventId
+                    + ", effectiveDate=" + effectiveDate
+                    + ", getBillingPeriod()=" + getBillingPeriod()
+                    + ", getRequestedDate()=" + getRequestedDate()
+                    + ", getProduct()=" + getProduct() + ", getPriceList()="
+                    + getPriceList() + ", getEventType()=" + getEventType()
+                    + ", getPhase()=" + getPhase() + ", getClass()="
+                    + getClass() + ", hashCode()=" + hashCode()
+                    + ", toString()=" + super.toString() + "]";
+        }
+    }
+
+    public static class SubscriptionDeletedEventJson extends SubscriptionReadEventJson {
+        @JsonCreator
+        public SubscriptionDeletedEventJson(@JsonProperty("event_id") String eventId,
+                @JsonProperty("billing_period") String billingPeriod,
+                @JsonProperty("requested_date") DateTime requestedDate,
+                @JsonProperty("effective_date") DateTime effectiveDate,
+                @JsonProperty("product") String product,
+                @JsonProperty("price_list") String priceList,
+                @JsonProperty("event_type") String eventType,
+                @JsonProperty("phase") String phase) {
+            super(eventId, billingPeriod, requestedDate, effectiveDate, product, priceList, eventType, phase);
+
+        }
+    }
+
+
+    public static class SubscriptionNewEventJson extends SubscriptionBaseEventJson {
+        @JsonCreator
+        public SubscriptionNewEventJson(@JsonProperty("billing_period") String billingPeriod,
+                @JsonProperty("requested_date") DateTime requestedDate,
+                @JsonProperty("product") String product,
+                @JsonProperty("price_list") String priceList,
+                @JsonProperty("event_type") String eventType,
+                @JsonProperty("phase") String phase) {
+            super(billingPeriod, requestedDate, product, priceList, eventType, phase);
+        }
+
+        @Override
+        public String toString() {
+            return "SubscriptionNewEventJson [getBillingPeriod()="
+                    + getBillingPeriod() + ", getRequestedDate()="
+                    + getRequestedDate() + ", getProduct()=" + getProduct()
+                    + ", getPriceList()=" + getPriceList()
+                    + ", getEventType()=" + getEventType() + ", getPhase()="
+                    + getPhase() + ", getClass()=" + getClass()
+                    + ", hashCode()=" + hashCode() + ", toString()="
+                    + super.toString() + "]";
+        }
+    }
+
+    public static class SubscriptionBaseEventJson {
+
+        @JsonView(BundleTimelineViews.Timeline.class)
+        private final String billingPeriod;
+
+        @JsonView(BundleTimelineViews.Timeline.class)
+        private final DateTime requestedDate;
+
+
+        @JsonView(BundleTimelineViews.Timeline.class)
+        private final String product;
+
+        @JsonView(BundleTimelineViews.Timeline.class)
+        private final String priceList;
+
+        @JsonView(BundleTimelineViews.Timeline.class)
+        private final String eventType;
+
+        @JsonView(BundleTimelineViews.Timeline.class)
+        private final String phase;
+
+        public SubscriptionBaseEventJson() {
+            this.billingPeriod = null;
+            this.requestedDate = null;
+            this.product = null;
+            this.priceList = null;
+            this.eventType = null;
+            this.phase = null;
+        }
+        
+        @JsonCreator
+        public SubscriptionBaseEventJson(@JsonProperty("billing_period") String billingPeriod,
+                @JsonProperty("requested_date") DateTime requestedDate,
+                @JsonProperty("product") String product,
+                @JsonProperty("price_list") String priceList,
+                @JsonProperty("event_type") String eventType,
+                @JsonProperty("phase") String phase) {
+            super();
+            this.billingPeriod = billingPeriod;
+            this.requestedDate = DefaultClock.toUTCDateTime(requestedDate);
+            this.product = product;
+            this.priceList = priceList;
+            this.eventType = eventType;
+            this.phase = phase;
+        }
+
+        public String getBillingPeriod() {
+            return billingPeriod;
+        }
+
+        public DateTime getRequestedDate() {
+            return DefaultClock.toUTCDateTime(requestedDate);
+        }
+
+        public String getProduct() {
+            return product;
+        }
+
+        public String getPriceList() {
+            return priceList;
+        }
+
+        public String getEventType() {
+            return eventType;
+        }
+
+        public String getPhase() {
+            return phase;
+        }
+    }
+
+
+    @JsonCreator
+    public SubscriptionJsonWithEvents(@JsonProperty("subscription_id") String subscriptionId,
+            @JsonProperty("events") List<SubscriptionReadEventJson> events,
+            @JsonProperty("new_events") List<SubscriptionNewEventJson> newEvents,
+            @JsonProperty("deleted_events") List<SubscriptionDeletedEventJson> deletedEvents) {
+        super(subscriptionId);
+        this.events = events;
+        this.deletedEvents = deletedEvents;
+        this.newEvents = newEvents;
+    }
+    
+    public SubscriptionJsonWithEvents() {
+        super(null);        
+        this.events = null;
+        this.deletedEvents = null;
+        this.newEvents = null;
+    }
+    
+    public SubscriptionJsonWithEvents(final Subscription data,
+            List<SubscriptionReadEventJson> events, List<SubscriptionDeletedEventJson> deletedEvents, List<SubscriptionNewEventJson> newEvents) {
+        super(data.getId().toString());
+        this.events = events;
+        this.deletedEvents = deletedEvents;
+        this.newEvents = newEvents;
+    }
+    
+    public SubscriptionJsonWithEvents(final UUID bundleId, final SubscriptionTimeline input) {
+        super(input.getId().toString());
+        this.events = new LinkedList<SubscriptionReadEventJson>();
+        for (ExistingEvent cur : input.getExistingEvents()) {
+            PlanPhaseSpecifier spec = cur.getPlanPhaseSpecifier();
+            this.events.add(new SubscriptionReadEventJson(cur.getEventId().toString(), spec.getBillingPeriod().toString(), cur.getRequestedDate(), cur.getEffectiveDate(),
+                    spec.getProductName(), spec.getPriceListName(), cur.getSubscriptionTransitionType().toString(), spec.getPhaseType().toString()));
+        }
+        this.deletedEvents = null;
+        this.newEvents = null;
+    }
+
+    public String getSubscriptionId() {
+        return subscriptionId;
+    }
+
+    public List<SubscriptionReadEventJson> getEvents() {
+        return events;
+    }
+
+    public List<SubscriptionNewEventJson> getNewEvents() {
+        return newEvents;
+    }
+
+    public List<SubscriptionDeletedEventJson> getDeletedEvents() {
+        return deletedEvents;
+    }
+}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/TagDefinitionJson.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/TagDefinitionJson.java
new file mode 100644
index 0000000..25ef04c
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/TagDefinitionJson.java
@@ -0,0 +1,77 @@
+/* 
+ * Copyright 2010-2011 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.jaxrs.json;
+
+import org.codehaus.jackson.annotate.JsonCreator;
+import org.codehaus.jackson.annotate.JsonProperty;
+
+public class TagDefinitionJson {
+    
+    private final String name;
+    private final String description;
+
+    public TagDefinitionJson()  {
+        this.name = null;
+        this.description = null;
+    }
+    
+    @JsonCreator
+    public TagDefinitionJson(@JsonProperty("name") String name,
+            @JsonProperty("description") String description) {
+        super();
+        this.name = name;
+        this.description = description;
+    }
+    
+    public String getName() {
+        return name;
+    }
+    public String getDescription() {
+        return description;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result
+                + ((description == null) ? 0 : description.hashCode());
+        result = prime * result + ((name == null) ? 0 : name.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        TagDefinitionJson other = (TagDefinitionJson) obj;
+        if (description == null) {
+            if (other.description != null)
+                return false;
+        } else if (!description.equals(other.description))
+            return false;
+        if (name == null) {
+            if (other.name != null)
+                return false;
+        } else if (!name.equals(other.name))
+            return false;
+        return true;
+    }
+}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/AccountResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/AccountResource.java
new file mode 100644
index 0000000..b8db3f8
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/AccountResource.java
@@ -0,0 +1,450 @@
+/*
+ * Copyright 2010-2011 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.jaxrs.resources;
+
+import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
+
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.UUID;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.HeaderParam;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Function;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Collections2;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import com.ning.billing.ErrorCode;
+import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.AccountApiException;
+import com.ning.billing.account.api.AccountData;
+import com.ning.billing.account.api.AccountUserApi;
+import com.ning.billing.entitlement.api.timeline.BundleTimeline;
+import com.ning.billing.entitlement.api.timeline.EntitlementRepairException;
+import com.ning.billing.entitlement.api.timeline.EntitlementTimelineApi;
+import com.ning.billing.entitlement.api.user.EntitlementUserApi;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceUserApi;
+import com.ning.billing.jaxrs.json.AccountJson;
+import com.ning.billing.jaxrs.json.AccountTimelineJson;
+import com.ning.billing.jaxrs.json.BundleJsonNoSubsciptions;
+import com.ning.billing.jaxrs.json.CustomFieldJson;
+import com.ning.billing.jaxrs.util.Context;
+import com.ning.billing.jaxrs.util.JaxrsUriBuilder;
+import com.ning.billing.jaxrs.util.TagHelper;
+import com.ning.billing.payment.api.PaymentApi;
+import com.ning.billing.payment.api.PaymentApiException;
+import com.ning.billing.payment.api.PaymentAttempt;
+import com.ning.billing.util.api.TagDefinitionApiException;
+import com.ning.billing.util.api.TagUserApi;
+import com.ning.billing.util.customfield.CustomField;
+import com.ning.billing.util.customfield.StringCustomField;
+import com.ning.billing.util.tag.Tag;
+import com.ning.billing.util.tag.TagDefinition;
+
+
+@Singleton
+@Path(BaseJaxrsResource.ACCOUNTS_PATH)
+public class AccountResource implements BaseJaxrsResource {
+
+    private static final Logger log = LoggerFactory.getLogger(AccountResource.class);
+
+    private final AccountUserApi accountApi;
+    private final EntitlementUserApi entitlementApi;
+    private final EntitlementTimelineApi timelineApi;
+    private final InvoiceUserApi invoiceApi;
+    private final PaymentApi paymentApi;
+    private final Context context;
+    private final TagUserApi tagUserApi;
+    private final JaxrsUriBuilder uriBuilder;
+    private final TagHelper tagHelper;
+    
+    @Inject
+    public AccountResource(final JaxrsUriBuilder uriBuilder,
+            final AccountUserApi accountApi,
+            final EntitlementUserApi entitlementApi, 
+            final InvoiceUserApi invoiceApi,
+            final PaymentApi paymentApi,
+            final EntitlementTimelineApi timelineApi,
+            final TagUserApi tagUserApi,
+            final TagHelper tagHelper,
+            final Context context) {
+        this.uriBuilder = uriBuilder;
+    	this.accountApi = accountApi;
+    	this.tagUserApi = tagUserApi;
+        this.entitlementApi = entitlementApi;
+        this.invoiceApi = invoiceApi;
+        this.paymentApi = paymentApi;
+        this.timelineApi = timelineApi;
+        this.context = context;
+        this.tagHelper = tagHelper;
+    }
+
+    @GET
+    @Path("/{accountId:" + UUID_PATTERN + "}")
+    @Produces(APPLICATION_JSON)
+    public Response getAccount(@PathParam("accountId") String accountId) {
+        try {
+            Account account = accountApi.getAccountById(UUID.fromString(accountId));
+
+            AccountJson json = new AccountJson(account);
+            return Response.status(Status.OK).entity(json).build();
+        } catch (AccountApiException e) {
+            return Response.status(Status.NO_CONTENT).build();            
+        }
+        
+    }
+
+    @GET
+    @Path("/{accountId:" + UUID_PATTERN + "}/" + BUNDLES)
+    @Produces(APPLICATION_JSON)
+    public Response getAccountBundles(@PathParam("accountId") String accountId) {
+        try {
+            UUID uuid = UUID.fromString(accountId);
+            accountApi.getAccountById(uuid);
+
+            List<SubscriptionBundle> bundles = entitlementApi.getBundlesForAccount(uuid);
+            Collection<BundleJsonNoSubsciptions> result = Collections2.transform(bundles, new Function<SubscriptionBundle, BundleJsonNoSubsciptions>() {
+                @Override
+                public BundleJsonNoSubsciptions apply(SubscriptionBundle input) {
+                    return new BundleJsonNoSubsciptions(input);
+                }
+            });
+            return Response.status(Status.OK).entity(result).build();
+        } catch (AccountApiException e) {
+            return Response.status(Status.NO_CONTENT).build();
+        }
+    }
+
+    
+    @GET
+    @Produces(APPLICATION_JSON)
+    public Response getAccountByKey(@QueryParam(QUERY_EXTERNAL_KEY) String externalKey) {
+        try {
+            Account account = null;
+            if (externalKey != null) {
+                account = accountApi.getAccountByKey(externalKey);
+            }
+            if (account == null) {
+                return Response.status(Status.NO_CONTENT).build();
+            }
+            AccountJson json = new AccountJson(account);
+            return Response.status(Status.OK).entity(json).build();
+        } catch (AccountApiException e) {
+            return Response.status(Status.NO_CONTENT).build();
+        }
+    }
+
+    
+    @POST
+    @Consumes(APPLICATION_JSON)
+    @Produces(APPLICATION_JSON)
+    public Response createAccount(final AccountJson json,
+            @HeaderParam(HDR_CREATED_BY) final String createdBy,
+            @HeaderParam(HDR_REASON) final String reason,
+            @HeaderParam(HDR_COMMENT) final String comment) {
+
+        try {
+            AccountData data = json.toAccountData();
+            final Account account = accountApi.createAccount(data, null, null, context.createContext(createdBy, reason, comment));
+            Response response = uriBuilder.buildResponse(AccountResource.class, "getAccount", account.getId());
+            return response;
+        } catch (AccountApiException e) {
+            final String error = String.format("Failed to create account %s", json);
+            log.info(error, e);
+            return Response.status(Status.BAD_REQUEST).entity(error).build();
+        } catch (IllegalArgumentException e) {
+            return Response.status(Status.BAD_REQUEST).entity(e.getMessage()).build();
+        }
+    }
+
+    @PUT
+    @Consumes(APPLICATION_JSON)
+    @Produces(APPLICATION_JSON)
+    @Path("/{accountId:" + UUID_PATTERN + "}")
+    public Response updateAccount(final AccountJson json,
+            @PathParam("accountId") final String accountId,
+            @HeaderParam(HDR_CREATED_BY) final String createdBy,
+            @HeaderParam(HDR_REASON) final String reason,
+            @HeaderParam(HDR_COMMENT) final String comment) {
+        try {
+            AccountData data = json.toAccountData();
+            UUID uuid = UUID.fromString(accountId);
+            accountApi.updateAccount(uuid, data, context.createContext(createdBy, reason, comment));
+            return getAccount(accountId);
+        } catch (AccountApiException e) {
+        	if (e.getCode() == ErrorCode.ACCOUNT_DOES_NOT_EXIST_FOR_ID.getCode()) {
+        		return Response.status(Status.NO_CONTENT).build();        		
+        	} else {
+        		log.info(String.format("Failed to update account %s with %s", accountId, json), e);
+        		return Response.status(Status.BAD_REQUEST).build();
+        	}
+        } catch (IllegalArgumentException e) {
+            return Response.status(Status.BAD_REQUEST).entity(e.getMessage()).build();
+        }
+    }
+
+    // Not supported
+    @DELETE
+    @Path("/{accountId:" + UUID_PATTERN + "}")
+    @Produces(APPLICATION_JSON)
+    public Response cancelAccount(@PathParam("accountId") String accountId) {
+        /*
+        try {
+            accountApi.cancelAccount(accountId);
+            return Response.status(Status.NO_CONTENT).build();
+        } catch (AccountApiException e) {
+            log.info(String.format("Failed to cancel account %s", accountId), e);
+            return Response.status(Status.BAD_REQUEST).build();
+        }
+       */
+        return Response.status(Status.INTERNAL_SERVER_ERROR).build();
+    }
+
+    @GET
+    @Path("/{accountId:" + UUID_PATTERN + "}/" + TIMELINE)
+    @Produces(APPLICATION_JSON)
+    public Response getAccountTimeline(@PathParam("accountId") String accountId) {
+        try {
+            Account account = accountApi.getAccountById(UUID.fromString(accountId));
+           
+            List<Invoice> invoices = invoiceApi.getInvoicesByAccount(account.getId());
+
+            List<PaymentAttempt> payments = new LinkedList<PaymentAttempt>();
+
+            if (invoices.size() > 0) {
+                Collection<String> tmp = Collections2.transform(invoices, new Function<Invoice, String>() {
+                    @Override
+                    public String apply(Invoice input) {
+                        return input.getId().toString();
+                    }
+                });
+                List<String> invoicesId = new ArrayList<String>();
+                invoicesId.addAll(tmp);
+                for (String curId : invoicesId) {
+                    payments.addAll(paymentApi.getPaymentAttemptsForInvoiceId(curId));
+                }
+            }
+
+            List<SubscriptionBundle> bundles = entitlementApi.getBundlesForAccount(account.getId());
+            List<BundleTimeline> bundlesTimeline = new LinkedList<BundleTimeline>();
+            for (SubscriptionBundle cur : bundles) {
+                bundlesTimeline.add(timelineApi.getBundleRepair(cur.getId()));
+            }
+            AccountTimelineJson json = new AccountTimelineJson(account, invoices, payments, bundlesTimeline);
+            return Response.status(Status.OK).entity(json).build();
+        } catch (AccountApiException e) {
+            return Response.status(Status.NO_CONTENT).build();
+        } catch (PaymentApiException e) {
+            log.error(e.getMessage());
+            return Response.status(Status.INTERNAL_SERVER_ERROR).build();
+        } catch (EntitlementRepairException e) {
+            log.error(e.getMessage());
+            return Response.status(Status.INTERNAL_SERVER_ERROR).build();
+        }
+    }
+    
+    
+    /****************************      TAGS     ******************************/
+    
+    @GET
+    @Path(BaseJaxrsResource.TAGS + "/{accountId:" + UUID_PATTERN + "}")
+    @Produces(APPLICATION_JSON)
+    public Response getAccountTags(@PathParam("accountId") String accountId) {
+        try {
+            Account account = accountApi.getAccountById(UUID.fromString(accountId));
+            List<Tag> tags = account.getTagList();
+            Collection<String> tagNameList = (tags.size() == 0) ?
+                    Collections.<String>emptyList() :
+                Collections2.transform(tags, new Function<Tag, String>() {
+                @Override
+                public String apply(Tag input) {
+                    return input.getTagDefinitionName();
+                }
+            });
+            return Response.status(Status.OK).entity(tagNameList).build();
+        } catch (AccountApiException e) {
+            return Response.status(Status.NO_CONTENT).build();
+        }
+    }
+
+    
+    @POST
+    @Path(BaseJaxrsResource.TAGS + "/{accountId:" + UUID_PATTERN + "}")    
+    @Consumes(APPLICATION_JSON)
+    @Produces(APPLICATION_JSON)
+    public Response createAccountTag(@PathParam("accountId") final String accountId,
+            @QueryParam(QUERY_TAGS) final String tagList,
+            @HeaderParam(HDR_CREATED_BY) final String createdBy,
+            @HeaderParam(HDR_REASON) final String reason,
+            @HeaderParam(HDR_COMMENT) final String comment) {
+
+        try {
+            Preconditions.checkNotNull(tagList, "Query % list cannot be null", QUERY_TAGS);
+            
+            Account account = accountApi.getAccountById(UUID.fromString(accountId));
+
+            List<TagDefinition> input = tagHelper.getTagDifinitionFromTagList(tagList);
+            account.addTagsFromDefinitions(input);
+            Response response = uriBuilder.buildResponse(AccountResource.class, "getAccountTags", account.getId());
+            return response;
+        } catch (AccountApiException e) {
+            return Response.status(Status.NO_CONTENT).build();
+        } catch (IllegalArgumentException e) {
+            return Response.status(Status.BAD_REQUEST).entity(e.getMessage()).build();
+        } catch (NullPointerException e) {
+            return Response.status(Status.BAD_REQUEST).entity(e.getMessage()).build();
+        } catch (TagDefinitionApiException e) {
+            return Response.status(Status.BAD_REQUEST).entity(e.getMessage()).build();
+        }
+    }
+    
+    @DELETE
+    @Path(BaseJaxrsResource.TAGS +  "/{accountId:" + UUID_PATTERN + "}")    
+    @Consumes(APPLICATION_JSON)
+    @Produces(APPLICATION_JSON)
+    public Response deleteAccountTag(@PathParam("accountId") final String accountId,
+            @QueryParam(QUERY_TAGS) final String tagList,
+            @HeaderParam(HDR_CREATED_BY) final String createdBy,
+            @HeaderParam(HDR_REASON) final String reason,
+            @HeaderParam(HDR_COMMENT) final String comment) {
+
+        try {
+            Account account = accountApi.getAccountById(UUID.fromString(accountId));
+
+            // Tag APIs needs tome rework...
+            String inputTagList = tagList;
+            if (inputTagList == null) {
+                List<Tag> existingTags = account.getTagList();
+                StringBuilder tmp = new StringBuilder();
+                for (Tag cur : existingTags) {
+                    tmp.append(cur.getTagDefinitionName());
+                    tmp.append(",");
+                }
+                inputTagList = tmp.toString();
+            }
+
+            List<TagDefinition> input = tagHelper.getTagDifinitionFromTagList(tagList);   
+            for (TagDefinition cur : input) {
+                account.removeTag(cur);
+            }
+
+            return Response.status(Status.OK).build();
+        } catch (AccountApiException e) {
+            return Response.status(Status.NO_CONTENT).build();
+        } catch (IllegalArgumentException e) {
+            return Response.status(Status.BAD_REQUEST).entity(e.getMessage()).build();
+        } catch (NullPointerException e) {
+            return Response.status(Status.BAD_REQUEST).entity(e.getMessage()).build();
+        } catch (TagDefinitionApiException e) {
+            return Response.status(Status.BAD_REQUEST).entity(e.getMessage()).build();
+        }
+    }
+    
+    /************************   CUSTOM FIELDS   ******************************/
+    
+    @GET
+    @Path(BaseJaxrsResource.CUSTOM_FIELDS + "/{accountId:" + UUID_PATTERN + "}")
+    @Produces(APPLICATION_JSON)
+    public Response getAccountCustomFields(@PathParam("accountId") String accountId) {
+        try {
+            Account account = accountApi.getAccountById(UUID.fromString(accountId));
+            List<CustomField> fields = account.getFieldList();
+            List<CustomFieldJson> result = new LinkedList<CustomFieldJson>();
+            for (CustomField cur : fields) {
+                result.add(new CustomFieldJson(cur));
+            }
+            return Response.status(Status.OK).entity(result).build();
+        } catch (AccountApiException e) {
+            return Response.status(Status.NO_CONTENT).build();
+        }
+    }
+    
+    
+    @POST
+    @Path(BaseJaxrsResource.CUSTOM_FIELDS + "/{accountId:" + UUID_PATTERN + "}")    
+    @Consumes(APPLICATION_JSON)
+    @Produces(APPLICATION_JSON)
+    public Response createCustomField(@PathParam("accountId") final String accountId,
+            List<CustomFieldJson> customFields,
+            @HeaderParam(HDR_CREATED_BY) final String createdBy,
+            @HeaderParam(HDR_REASON) final String reason,
+            @HeaderParam(HDR_COMMENT) final String comment) {
+
+        try {
+            
+            Account account = accountApi.getAccountById(UUID.fromString(accountId));
+            LinkedList<CustomField> input = new LinkedList<CustomField>();
+            for (CustomFieldJson cur : customFields) {
+                input.add(new StringCustomField(cur.getName(), cur.getValue()));
+            }
+            account.saveFields(input, context.createContext(createdBy, reason, comment));
+            Response response = uriBuilder.buildResponse(AccountResource.class, "getAccountCustomFields", account.getId());            
+            return response;
+        } catch (AccountApiException e) {
+            return Response.status(Status.NO_CONTENT).build();
+        } catch (IllegalArgumentException e) {
+            return Response.status(Status.BAD_REQUEST).entity(e.getMessage()).build();
+        } catch (NullPointerException e) {
+            return Response.status(Status.BAD_REQUEST).entity(e.getMessage()).build();
+        }
+    }
+    
+    @DELETE
+    @Path(BaseJaxrsResource.CUSTOM_FIELDS +  "/{accountId:" + UUID_PATTERN + "}")    
+    @Consumes(APPLICATION_JSON)
+    @Produces(APPLICATION_JSON)
+    public Response deleteCustomFields(@PathParam("accountId") final String accountId,
+            @QueryParam(QUERY_CUSTOM_FIELDS) final String cutomFieldList,
+            @HeaderParam(HDR_CREATED_BY) final String createdBy,
+            @HeaderParam(HDR_REASON) final String reason,
+            @HeaderParam(HDR_COMMENT) final String comment) {
+
+        try {
+            Account account = accountApi.getAccountById(UUID.fromString(accountId));
+            // STEPH missing API to delete custom fields
+            return Response.status(Status.OK).build();
+        } catch (AccountApiException e) {
+            return Response.status(Status.NO_CONTENT).build();
+        } catch (IllegalArgumentException e) {
+            return Response.status(Status.BAD_REQUEST).entity(e.getMessage()).build();
+        } catch (NullPointerException e) {
+            return Response.status(Status.BAD_REQUEST).entity(e.getMessage()).build();
+        }
+    }
+    
+}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/BaseJaxrsResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/BaseJaxrsResource.java
new file mode 100644
index 0000000..2a9e2ee
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/BaseJaxrsResource.java
@@ -0,0 +1,73 @@
+/* 
+ * Copyright 2010-2011 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.jaxrs.resources;
+
+public interface BaseJaxrsResource {
+	
+    public static final String API_PREFIX = "";
+    public static final String API_VERSION = "/1.0";
+    public static final String API_POSTFIX = "/kb";
+    
+    public static final String PREFIX = API_PREFIX + API_VERSION + API_POSTFIX;
+	
+	public static final String TIMELINE = "timeline";
+	
+	/*
+	 * Metadata Additional headers 
+	 */
+	public static String HDR_CREATED_BY = "X-Killbill-CreatedBy";
+	public static String HDR_REASON = "X-Killbill-Reason";  
+	public static String HDR_COMMENT = "X-Killbill-Comment";   	
+	
+	/*
+	 * Patterns
+	 */
+	public static String STRING_PATTERN = "\\w+";	
+	public static String UUID_PATTERN = "\\w+-\\w+-\\w+-\\w+-\\w+";
+	
+	/*
+	 * Query parameters
+	 */
+	public static final String QUERY_EXTERNAL_KEY = "external_key";
+	public static final String QUERY_REQUESTED_DT = "requested_date";
+	public static final String QUERY_CALL_COMPLETION = "call_completion";
+	public static final String QUERY_CALL_TIMEOUT = "call_timeout_sec";    
+	public static final String QUERY_DRY_RUN = "dry_run";      
+	public static final String QUERY_TARGET_DATE = "target_date";          
+	public static final String QUERY_ACCOUNT_ID = "account_id";           	
+	
+	public static final String QUERY_TAGS = "tag_list";    
+	public static final String QUERY_CUSTOM_FIELDS = "custom_field_list";    	
+	
+	public static final String ACCOUNTS = "accounts";  
+    public static final String ACCOUNTS_PATH = PREFIX + "/" + ACCOUNTS;
+
+	public static final String BUNDLES = "bundles";		
+	public static final String BUNDLES_PATH = PREFIX + "/" + BUNDLES;
+
+    public static final String SUBSCRIPTIONS = "subscriptions";     
+    public static final String SUBSCRIPTIONS_PATH = PREFIX + "/" + SUBSCRIPTIONS;
+
+    public static final String TAG_DEFINITIONS = "tag_definitions";     
+    public static final String TAG_DEFINITIONS_PATH = PREFIX + "/" + TAG_DEFINITIONS;
+
+    public static final String INVOICES = "invoices";     
+    public static final String INVOICES_PATH = PREFIX + "/" + INVOICES;
+
+    
+    public static final String TAGS = "tags";
+    public static final String CUSTOM_FIELDS = "custom_fields";    
+}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/BundleResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/BundleResource.java
new file mode 100644
index 0000000..e62d48e
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/BundleResource.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2010-2011 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.jaxrs.resources;
+
+import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.UUID;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.HeaderParam;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Function;
+import com.google.common.collect.Collections2;
+import com.google.inject.Inject;
+import com.ning.billing.ErrorCode;
+import com.ning.billing.entitlement.api.user.EntitlementUserApi;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.jaxrs.json.BundleJsonNoSubsciptions;
+import com.ning.billing.jaxrs.json.SubscriptionJsonNoEvents;
+import com.ning.billing.jaxrs.util.Context;
+import com.ning.billing.jaxrs.util.JaxrsUriBuilder;
+
+@Path(BaseJaxrsResource.BUNDLES_PATH)
+public class BundleResource implements BaseJaxrsResource {
+
+    private static final Logger log = LoggerFactory.getLogger(BundleResource.class);
+
+    private final EntitlementUserApi entitlementApi;
+    private final Context context;
+    private final JaxrsUriBuilder uriBuilder;	
+
+    @Inject
+    public BundleResource(final JaxrsUriBuilder uriBuilder, final EntitlementUserApi entitlementApi, final Context context) {
+        this.uriBuilder = uriBuilder;
+        this.entitlementApi = entitlementApi;
+        this.context = context;
+    }
+
+    @GET
+    @Path("/{bundleId:"  + UUID_PATTERN + "}")
+    @Produces(APPLICATION_JSON)
+    public Response getBundle(@PathParam("bundleId") final String bundleId) throws EntitlementUserApiException {
+        try {
+            SubscriptionBundle bundle = entitlementApi.getBundleFromId(UUID.fromString(bundleId));
+            BundleJsonNoSubsciptions json = new BundleJsonNoSubsciptions(bundle);
+            return Response.status(Status.OK).entity(json).build();
+        } catch (EntitlementUserApiException e) {
+            if (e.getCode() == ErrorCode.ENT_GET_INVALID_BUNDLE_ID.getCode()) {
+                return Response.status(Status.NO_CONTENT).build();
+            } else {
+                throw e;
+            }
+
+        }
+    }
+
+    @GET
+    @Produces(APPLICATION_JSON)
+    public Response getBundleByKey(@QueryParam(QUERY_EXTERNAL_KEY) final String externalKey) throws EntitlementUserApiException {
+        try {
+            SubscriptionBundle bundle = entitlementApi.getBundleForKey(externalKey);
+            BundleJsonNoSubsciptions json = new BundleJsonNoSubsciptions(bundle);
+            return Response.status(Status.OK).entity(json).build();
+        } catch (EntitlementUserApiException e) {
+            if (e.getCode() == ErrorCode.ENT_GET_INVALID_BUNDLE_KEY.getCode()) {
+                return Response.status(Status.NO_CONTENT).build();
+            } else {
+                throw e;
+            }
+
+        }
+    }
+
+    @POST
+    @Consumes(APPLICATION_JSON)
+    @Produces(APPLICATION_JSON)
+    public Response createBundle(final BundleJsonNoSubsciptions json,
+            @HeaderParam(HDR_CREATED_BY) final String createdBy,
+            @HeaderParam(HDR_REASON) final String reason,
+            @HeaderParam(HDR_COMMENT) final String comment) {
+        try {
+            UUID accountId = UUID.fromString(json.getAccountId());
+            final SubscriptionBundle bundle = entitlementApi.createBundleForAccount(accountId, json.getExternalKey(),
+                    context.createContext(createdBy, reason, comment));
+            return uriBuilder.buildResponse(BundleResource.class, "getBundle", bundle.getId());
+        } catch (EntitlementUserApiException e) {
+            log.info(String.format("Failed to create bundle %s", json), e);
+            return Response.status(Status.BAD_REQUEST).build();
+        } catch (IllegalArgumentException e) {
+            return Response.status(Status.BAD_REQUEST).entity(e.getMessage()).build();
+        }
+    }
+
+    @GET
+    @Path("/{bundleId:" + UUID_PATTERN + "}/" + SUBSCRIPTIONS)
+    @Produces(APPLICATION_JSON)
+    public Response getBundleSubscriptions(@PathParam("bundleId") final String bundleId) throws EntitlementUserApiException {
+        try {
+            UUID uuid = UUID.fromString(bundleId);
+            SubscriptionBundle bundle = entitlementApi.getBundleFromId(uuid);
+            if (bundle == null) {
+                return Response.status(Status.NO_CONTENT).build();
+            }
+            List<Subscription> bundles = entitlementApi.getSubscriptionsForBundle(uuid);
+            Collection<SubscriptionJsonNoEvents> result =  Collections2.transform(bundles, new Function<Subscription, SubscriptionJsonNoEvents>() {
+                @Override
+                public SubscriptionJsonNoEvents apply(Subscription input) {
+                    return new SubscriptionJsonNoEvents(input);
+                }
+             });
+            return Response.status(Status.OK).entity(result).build();
+        } catch (EntitlementUserApiException e) {
+            if (e.getCode() == ErrorCode.ENT_GET_INVALID_BUNDLE_ID.getCode()) {
+                return Response.status(Status.NO_CONTENT).build();
+            } else {
+                throw e;
+            }
+
+        }
+    }
+}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/InvoiceResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/InvoiceResource.java
new file mode 100644
index 0000000..fd2ec57
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/InvoiceResource.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2010-2011 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.jaxrs.resources;
+
+import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.UUID;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.GET;
+import javax.ws.rs.HeaderParam;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+
+import org.joda.time.DateTime;
+import org.joda.time.format.DateTimeFormatter;
+import org.joda.time.format.ISODateTimeFormat;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Preconditions;
+import com.google.inject.Inject;
+import com.ning.billing.account.api.AccountApiException;
+import com.ning.billing.account.api.AccountUserApi;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceApiException;
+import com.ning.billing.invoice.api.InvoiceUserApi;
+
+import com.ning.billing.jaxrs.json.InvoiceJsonSimple;
+import com.ning.billing.jaxrs.util.Context;
+import com.ning.billing.jaxrs.util.JaxrsUriBuilder;
+
+
+@Path(BaseJaxrsResource.INVOICES_PATH)
+public class InvoiceResource implements BaseJaxrsResource {
+
+
+    private static final Logger log = LoggerFactory.getLogger(InvoiceResource.class);
+
+    private final DateTimeFormatter DATE_TIME_FORMATTER = ISODateTimeFormat.dateTime();
+    
+    private final AccountUserApi accountApi;
+    private final InvoiceUserApi invoiceApi;
+    private final Context context;
+    private final JaxrsUriBuilder uriBuilder;
+    
+    @Inject
+    public InvoiceResource(final AccountUserApi accountApi,
+            final InvoiceUserApi invoiceApi,
+            final Context context,
+            final JaxrsUriBuilder uriBuilder) {
+        this.accountApi = accountApi;
+        this.invoiceApi = invoiceApi;
+        this.context = context;
+        this.uriBuilder = uriBuilder;
+    }
+    
+    @GET
+    @Produces(APPLICATION_JSON)
+    public Response getInvoices(@QueryParam(QUERY_ACCOUNT_ID) final String accountId) {
+        try {
+            
+            Preconditions.checkNotNull(accountId, "% query parameter must be specified", QUERY_ACCOUNT_ID);
+            accountApi.getAccountById(UUID.fromString(accountId));
+            List<Invoice> invoices = invoiceApi.getInvoicesByAccount(UUID.fromString(accountId));
+            List<InvoiceJsonSimple> result = new LinkedList<InvoiceJsonSimple>();
+            for (Invoice cur : invoices) {
+                result.add(new InvoiceJsonSimple(cur));
+            }
+            return Response.status(Status.OK).entity(result).build();
+        } catch (AccountApiException e) {
+            return Response.status(Status.NO_CONTENT).build();            
+        } catch (NullPointerException e) {
+            return Response.status(Status.BAD_REQUEST).build();            
+        }
+    }
+
+    @GET
+    @Path("/{invoiceId:\\w+-\\w+-\\w+-\\w+-\\w+}")
+    @Produces(APPLICATION_JSON)
+    public Response getInvoice(@PathParam("invoiceId") String invoiceId) {
+        Invoice invoice = invoiceApi.getInvoice(UUID.fromString(invoiceId));
+        InvoiceJsonSimple json = new InvoiceJsonSimple(invoice);
+        return Response.status(Status.OK).entity(json).build();
+    }
+
+    @POST
+    @Consumes(APPLICATION_JSON)
+    @Produces(APPLICATION_JSON)
+    public Response createFutureInvoice(final InvoiceJsonSimple invoice,
+            @QueryParam(QUERY_ACCOUNT_ID) final String accountId,
+            @QueryParam(QUERY_TARGET_DATE) final String targetDate,
+            @QueryParam(QUERY_DRY_RUN) @DefaultValue("false") final Boolean dryRun,
+            @HeaderParam(HDR_CREATED_BY) final String createdBy,
+            @HeaderParam(HDR_REASON) final String reason,
+            @HeaderParam(HDR_COMMENT) final String comment) {
+
+        try {
+            
+            Preconditions.checkNotNull(accountId, "% needs to be specified", QUERY_ACCOUNT_ID);
+            Preconditions.checkNotNull(targetDate, "% needs to be specified", QUERY_TARGET_DATE);
+            
+            DateTime inputDate = (targetDate != null) ? DATE_TIME_FORMATTER.parseDateTime(targetDate) : null;        
+            
+            accountApi.getAccountById(UUID.fromString(accountId));
+            Invoice generatedInvoice = invoiceApi.triggerInvoiceGeneration(UUID.fromString(accountId), inputDate, dryRun.booleanValue(),
+                    context.createContext(createdBy, reason, comment));
+            if (dryRun) {
+                return Response.status(Status.OK).entity(new InvoiceJsonSimple(generatedInvoice)).build();
+            } else {
+               return uriBuilder.buildResponse(InvoiceResource.class, "getInvoice", generatedInvoice.getId());
+            }
+        } catch (AccountApiException e) {
+            return Response.status(Status.BAD_REQUEST).entity(e.getMessage()).build();  
+        } catch (InvoiceApiException e) {
+            return Response.status(Status.BAD_REQUEST).entity(e.getMessage()).build();  
+        } catch (NullPointerException e) {
+            return Response.status(Status.BAD_REQUEST).entity(e.getMessage()).build();            
+        }
+    }
+}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/PaymentResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/PaymentResource.java
new file mode 100644
index 0000000..2988f5a
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/PaymentResource.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2010-2011 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.jaxrs.resources;
+
+import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+
+import com.ning.billing.jaxrs.json.PaymentJsonSimple;
+
+
+@Path("/1.0/payment")
+public class PaymentResource {
+
+
+    @GET
+    @Path("/{invoiceId:\\w+-\\w+-\\w+-\\w+-\\w+}")
+    @Produces(APPLICATION_JSON)
+    public Response getPayments(@PathParam("invoiceId") String invoiceId) {
+        return Response.status(Status.INTERNAL_SERVER_ERROR).build();
+    }
+
+    @GET
+    @Path("/account/{accountId:\\w+-\\w+-\\w+-\\w+-\\w+}")
+    @Produces(APPLICATION_JSON)
+    public Response getAllPayments(@PathParam("accountId") String accountId) {
+        return Response.status(Status.INTERNAL_SERVER_ERROR).build();
+    }
+
+    @POST
+    @Produces(APPLICATION_JSON)
+    @Consumes(APPLICATION_JSON)
+    @Path("/{invoiceId:\\w+-\\w+-\\w+-\\w+-\\w+}")
+    public Response createInstantPayment(PaymentJsonSimple payment,
+            @PathParam("invoiceId") String invoiceId,
+            @QueryParam("last4CC") String last4CC,
+            @QueryParam("nameOnCC") String nameOnCC) {
+        return Response.status(Status.INTERNAL_SERVER_ERROR).build();
+    }
+}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/SubscriptionResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/SubscriptionResource.java
new file mode 100644
index 0000000..67c5b5c
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/SubscriptionResource.java
@@ -0,0 +1,345 @@
+/*
+ * Copyright 2010-2011 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.jaxrs.resources;
+
+import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+import java.util.concurrent.TimeoutException;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.GET;
+import javax.ws.rs.HeaderParam;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+
+import org.joda.time.DateTime;
+import org.joda.time.format.DateTimeFormatter;
+import org.joda.time.format.ISODateTimeFormat;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.inject.Inject;
+import com.ning.billing.ErrorCode;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.entitlement.api.user.EntitlementUserApi;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.api.user.SubscriptionEvent;
+import com.ning.billing.invoice.api.EmptyInvoiceEvent;
+import com.ning.billing.invoice.api.InvoiceCreationEvent;
+import com.ning.billing.jaxrs.json.SubscriptionJsonNoEvents;
+import com.ning.billing.jaxrs.util.Context;
+import com.ning.billing.jaxrs.util.JaxrsUriBuilder;
+import com.ning.billing.jaxrs.util.KillbillEventHandler;
+import com.ning.billing.payment.api.PaymentErrorEvent;
+import com.ning.billing.payment.api.PaymentInfoEvent;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.clock.Clock;
+import com.ning.billing.util.userrequest.CompletionUserRequestBase;
+
+@Path(BaseJaxrsResource.SUBSCRIPTIONS_PATH)
+public class SubscriptionResource implements BaseJaxrsResource {
+
+    private static final Logger log = LoggerFactory.getLogger(SubscriptionResource.class);
+
+    private final DateTimeFormatter DATE_TIME_FORMATTER = ISODateTimeFormat.dateTime();
+
+    private final EntitlementUserApi entitlementApi;
+    private final Context context;
+    private final JaxrsUriBuilder uriBuilder;	
+    private final KillbillEventHandler killbillHandler;
+    
+    @Inject
+    public SubscriptionResource(final JaxrsUriBuilder uriBuilder, final EntitlementUserApi entitlementApi,
+            final Clock clock, final Context context, final KillbillEventHandler killbillHandler) {
+        this.uriBuilder = uriBuilder;
+        this.entitlementApi = entitlementApi;
+        this.context = context;
+        this.killbillHandler = killbillHandler;
+    }
+
+    @GET
+    @Path("/{subscriptionId:" + UUID_PATTERN + "}")
+    @Produces(APPLICATION_JSON)
+    public Response getSubscription(@PathParam("subscriptionId") final String subscriptionId) throws EntitlementUserApiException {
+
+        try {
+            UUID uuid = UUID.fromString(subscriptionId);
+            Subscription subscription = entitlementApi.getSubscriptionFromId(uuid);
+            SubscriptionJsonNoEvents json = new SubscriptionJsonNoEvents(subscription);
+            return Response.status(Status.OK).entity(json).build();
+        } catch (EntitlementUserApiException e) {
+            if (e.getCode() == ErrorCode.ENT_INVALID_SUBSCRIPTION_ID.getCode()) {
+                return Response.status(Status.NO_CONTENT).build();
+            } else {
+                throw e;
+            }
+        }
+    }
+  
+
+    @POST
+    @Consumes(APPLICATION_JSON)
+    @Produces(APPLICATION_JSON)
+    public Response createSubscription(final SubscriptionJsonNoEvents subscription,
+            @QueryParam(QUERY_REQUESTED_DT) final String requestedDate,
+            @QueryParam(QUERY_CALL_COMPLETION) @DefaultValue("false") final Boolean callCompletion,
+            @QueryParam(QUERY_CALL_TIMEOUT) @DefaultValue("3") final long timeoutSec,
+            @HeaderParam(HDR_CREATED_BY) final String createdBy,
+            @HeaderParam(HDR_REASON) final String reason,
+            @HeaderParam(HDR_COMMENT) final String comment) {
+
+
+        SubscriptionCallCompletionCallback<Subscription> callback = new SubscriptionCallCompletionCallback<Subscription>() {
+            @Override
+            public Subscription doOperation(final CallContext ctx) throws EntitlementUserApiException, InterruptedException, TimeoutException {
+
+                DateTime inputDate = (requestedDate != null) ? DATE_TIME_FORMATTER.parseDateTime(requestedDate) : null;        
+                UUID uuid = UUID.fromString(subscription.getBundleId());
+
+                PlanPhaseSpecifier spec =  new PlanPhaseSpecifier(subscription.getProductName(),
+                        ProductCategory.valueOf(subscription.getProductCategory()),
+                        BillingPeriod.valueOf(subscription.getBillingPeriod()), subscription.getPriceList(), null);
+                return entitlementApi.createSubscription(uuid, spec, inputDate, ctx);
+            }
+            @Override
+            public boolean isImmOperation() {
+                return true;
+            }
+            @Override
+            public Response doResponseOk(final Subscription createdSubscription) {
+                return uriBuilder.buildResponse(SubscriptionResource.class, "getSubscription", createdSubscription.getId());
+            }
+        };
+        SubscriptionCallCompletion<Subscription> callCompletionCreation = new SubscriptionCallCompletion<Subscription>();
+        return callCompletionCreation.withSynchronization(callback, timeoutSec, callCompletion, createdBy, reason, comment);
+    }
+
+    @PUT
+    @Produces(APPLICATION_JSON)
+    @Consumes(APPLICATION_JSON)
+    @Path("/{subscriptionId:" + UUID_PATTERN + "}")
+    public Response changeSubscriptionPlan(final SubscriptionJsonNoEvents subscription,
+            @PathParam("subscriptionId") final String subscriptionId,
+            @QueryParam(QUERY_REQUESTED_DT) final String requestedDate,
+            @QueryParam(QUERY_CALL_COMPLETION) @DefaultValue("false") final Boolean callCompletion,
+            @QueryParam(QUERY_CALL_TIMEOUT) @DefaultValue("3") final long timeoutSec,
+            @HeaderParam(HDR_CREATED_BY) final String createdBy,
+            @HeaderParam(HDR_REASON) final String reason,
+            @HeaderParam(HDR_COMMENT) final String comment) {
+
+        SubscriptionCallCompletionCallback<Response> callback = new SubscriptionCallCompletionCallback<Response>() {
+
+            private boolean isImmediateOp = true;
+
+            @Override
+            public Response doOperation(CallContext ctx)
+                    throws EntitlementUserApiException, InterruptedException,
+                    TimeoutException {
+                try {
+                    UUID uuid = UUID.fromString(subscriptionId);
+                    Subscription current = entitlementApi.getSubscriptionFromId(uuid);
+                    DateTime inputDate = (requestedDate != null) ? DATE_TIME_FORMATTER.parseDateTime(requestedDate) : null;
+                    isImmediateOp = current.changePlan(subscription.getProductName(),  BillingPeriod.valueOf(subscription.getBillingPeriod()), subscription.getPriceList(), inputDate, ctx);
+                    return Response.status(Status.OK).build();
+                } catch (EntitlementUserApiException e) {
+                    log.warn("Subscription not found: " + subscriptionId , e);
+                    return Response.status(Status.NO_CONTENT).build();
+                }
+            }
+            @Override
+            public boolean isImmOperation() {
+                return isImmediateOp;
+            }
+            @Override
+            public Response doResponseOk(Response operationResponse) {
+                if (operationResponse.getStatus() != Status.OK.getStatusCode()) {
+                    return operationResponse;
+                }
+                try {
+                    return getSubscription(subscriptionId);
+                } catch (EntitlementUserApiException e) {
+                    if (e.getCode() == ErrorCode.ENT_GET_INVALID_BUNDLE_ID.getCode()) {
+                        return Response.status(Status.NO_CONTENT).build();
+                    } else {
+                        return Response.status(Status.INTERNAL_SERVER_ERROR).build();
+                    }
+                }
+            }
+        };
+        SubscriptionCallCompletion<Response> callCompletionCreation = new SubscriptionCallCompletion<Response>();
+        return callCompletionCreation.withSynchronization(callback, timeoutSec, callCompletion, createdBy, reason, comment);
+    }
+
+    @PUT
+    @Path("/{subscriptionId:" + UUID_PATTERN + "}/uncancel")
+    @Produces(APPLICATION_JSON)
+    public Response uncancelSubscriptionPlan(@PathParam("subscriptionId") final String subscriptionId,
+            @HeaderParam(HDR_CREATED_BY) final String createdBy,
+            @HeaderParam(HDR_REASON) final String reason,
+            @HeaderParam(HDR_COMMENT) final String comment) {
+        try {
+            UUID uuid = UUID.fromString(subscriptionId);
+            Subscription current = entitlementApi.getSubscriptionFromId(uuid);
+        
+            current.uncancel(context.createContext(createdBy, reason, comment));
+            return Response.status(Status.OK).build();
+        } catch (EntitlementUserApiException e) {
+            if(e.getCode() == ErrorCode.ENT_INVALID_SUBSCRIPTION_ID.getCode()) {
+                return Response.status(Status.NO_CONTENT).build();
+            } else {
+                log.info(String.format("Failed to uncancel plan for subscription %s", subscriptionId), e);
+                return Response.status(Status.BAD_REQUEST).build();
+            }
+        }
+    }
+
+    @DELETE
+    @Path("/{subscriptionId:" + UUID_PATTERN + "}")
+    @Produces(APPLICATION_JSON)
+    public Response cancelSubscriptionPlan(final @PathParam("subscriptionId") String subscriptionId,
+            @QueryParam(QUERY_REQUESTED_DT) final String requestedDate,
+            @QueryParam(QUERY_CALL_COMPLETION) @DefaultValue("false") final Boolean callCompletion,
+            @QueryParam(QUERY_CALL_TIMEOUT) @DefaultValue("3") final long timeoutSec,
+            @HeaderParam(HDR_CREATED_BY) final String createdBy,
+            @HeaderParam(HDR_REASON) final String reason,
+            @HeaderParam(HDR_COMMENT) final String comment) {
+
+        SubscriptionCallCompletionCallback<Response> callback = new SubscriptionCallCompletionCallback<Response>() {
+
+            private boolean isImmediateOp = true;
+
+            @Override
+            public Response doOperation(CallContext ctx)
+                    throws EntitlementUserApiException, InterruptedException,
+                    TimeoutException {
+                try {
+                    UUID uuid = UUID.fromString(subscriptionId);
+
+                    Subscription current = entitlementApi.getSubscriptionFromId(uuid);
+
+                    DateTime inputDate = (requestedDate != null) ? DATE_TIME_FORMATTER.parseDateTime(requestedDate) : null;
+                    isImmediateOp = current.cancel(inputDate, false, ctx);
+                    return Response.status(Status.OK).build();
+                } catch (EntitlementUserApiException e) {
+                    if(e.getCode() == ErrorCode.ENT_INVALID_SUBSCRIPTION_ID.getCode()) {
+                        return Response.status(Status.NO_CONTENT).build();
+                    } else {
+                        throw e;
+                    }
+                }
+            }
+            @Override
+            public boolean isImmOperation() {
+                return isImmediateOp;
+            }
+            @Override
+            public Response doResponseOk(Response operationResponse) {
+                return operationResponse;
+            }
+        };
+        SubscriptionCallCompletion<Response> callCompletionCreation = new SubscriptionCallCompletion<Response>();
+        return callCompletionCreation.withSynchronization(callback, timeoutSec, callCompletion, createdBy, reason, comment);
+    }
+
+    private final static class CompletionUserRequestSubscription extends CompletionUserRequestBase {
+
+        public CompletionUserRequestSubscription(final UUID userToken) {
+            super(userToken);
+        }
+        @Override
+        public void onSubscriptionTransition(SubscriptionEvent curEvent) {
+            log.debug(String.format("Got event SubscriptionTransition token = %s, type = %s, remaining = %d ", 
+                    curEvent.getUserToken(), curEvent.getTransitionType(),  curEvent.getRemainingEventsForUserOperation())); 
+        }
+        @Override
+        public void onEmptyInvoice(final EmptyInvoiceEvent curEvent) {
+            log.debug(String.format("Got event EmptyInvoiceNotification token = %s ", curEvent.getUserToken())); 
+            notifyForCompletion();
+        }
+        @Override
+        public void onInvoiceCreation(InvoiceCreationEvent curEvent) {
+            log.debug(String.format("Got event InvoiceCreationNotification token = %s ", curEvent.getUserToken())); 
+            if (curEvent.getAmountOwed().compareTo(BigDecimal.ZERO) <= 0) {
+                notifyForCompletion();
+            }
+        }
+        @Override
+        public void onPaymentInfo(PaymentInfoEvent curEvent) {
+            log.debug(String.format("Got event PaymentInfo token = %s ", curEvent.getUserToken()));  
+            notifyForCompletion();
+        }
+        @Override
+        public void onPaymentError(PaymentErrorEvent curEvent) {
+            log.debug(String.format("Got event PaymentError token = %s ", curEvent.getUserToken())); 
+            notifyForCompletion();
+        }
+    }
+
+    private interface SubscriptionCallCompletionCallback<T> {
+        public T doOperation(final CallContext ctx) throws EntitlementUserApiException, InterruptedException, TimeoutException;
+        public boolean isImmOperation();
+        public Response doResponseOk(final T operationResponse);
+    }
+
+    private class SubscriptionCallCompletion<T> {
+
+        public Response withSynchronization(final SubscriptionCallCompletionCallback<T> callback,
+                final long timeoutSec,
+                final boolean callCompletion,
+                final String createdBy,
+                final String reason,
+                final String comment) {
+
+            CallContext ctx = context.createContext(createdBy, reason, comment);
+            CompletionUserRequestSubscription waiter = callCompletion ? new CompletionUserRequestSubscription(ctx.getUserToken()) : null; 
+            try {
+                if (waiter != null) {
+                    killbillHandler.registerCompletionUserRequestWaiter(waiter);    
+                }
+                T operationValue = callback.doOperation(ctx);
+                if (waiter != null && callback.isImmOperation()) {
+                    waiter.waitForCompletion(timeoutSec * 1000);
+                }
+                return callback.doResponseOk(operationValue);
+            } catch (EntitlementUserApiException e) {
+                log.info(String.format("Failed to complete operation"), e);
+                return Response.status(Status.BAD_REQUEST).build();
+            } catch (InterruptedException e) {
+                return Response.status(Status.INTERNAL_SERVER_ERROR).build();
+            } catch (TimeoutException e) {
+                return Response.status(Status.fromStatusCode(408)).build();   
+            } finally {
+                if (waiter != null) {
+                    killbillHandler.unregisterCompletionUserRequestWaiter(waiter);              
+                }
+            }
+        }
+    }
+}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/TagResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/TagResource.java
new file mode 100644
index 0000000..3111240
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/TagResource.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2010-2011 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.jaxrs.resources;
+
+import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.HeaderParam;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import com.ning.billing.jaxrs.json.TagDefinitionJson;
+import com.ning.billing.jaxrs.util.Context;
+import com.ning.billing.jaxrs.util.JaxrsUriBuilder;
+import com.ning.billing.util.api.TagDefinitionApiException;
+import com.ning.billing.util.api.TagUserApi;
+import com.ning.billing.util.tag.TagDefinition;
+
+@Singleton
+@Path(BaseJaxrsResource.TAG_DEFINITIONS_PATH)
+public class TagResource implements BaseJaxrsResource {
+    
+    private final TagUserApi tagUserApi;
+    private final Context context;
+    private final JaxrsUriBuilder uriBuilder;
+    
+    @Inject
+    public TagResource(TagUserApi tagUserApi, final JaxrsUriBuilder uriBuilder, final Context context) {
+        this.tagUserApi = tagUserApi;
+        this.context = context;
+        this.uriBuilder = uriBuilder;
+    }
+    
+    @GET
+    @Produces(APPLICATION_JSON)
+    public Response getTagDefinitions() {
+        
+        List<TagDefinitionJson> result = new LinkedList<TagDefinitionJson>();
+        List<TagDefinition> tagDefinitions = tagUserApi.getTagDefinitions();
+        for (TagDefinition cur : tagDefinitions) {
+            result.add(new TagDefinitionJson(cur.getName(), cur.getDescription()));
+        }
+        return Response.status(Status.OK).entity(result).build();
+    }
+    
+    @GET
+    @Path("/{tagDefinitionName:" + STRING_PATTERN + "}")
+    @Produces(APPLICATION_JSON)
+    public Response getTagDefinition(@PathParam("tagDefinitionName") final String tagDefName) {
+        try {
+            TagDefinition tagDef = tagUserApi.getTagDefinition(tagDefName);
+            TagDefinitionJson json = new TagDefinitionJson(tagDef.getName(), tagDef.getDescription());
+            return Response.status(Status.OK).entity(json).build();
+        } catch (TagDefinitionApiException e) {
+            return Response.status(Status.NO_CONTENT).build(); 
+        }
+    }
+
+
+
+    @POST
+    @Consumes(APPLICATION_JSON)
+    @Produces(APPLICATION_JSON)
+    public Response createTagDefinition(final TagDefinitionJson json,
+            @HeaderParam(HDR_CREATED_BY) final String createdBy,
+            @HeaderParam(HDR_REASON) final String reason,
+            @HeaderParam(HDR_COMMENT) final String comment) {
+        try {
+            TagDefinition createdTagDef =  tagUserApi.create(json.getName(), json.getDescription(), context.createContext(createdBy, reason, comment));
+            return uriBuilder.buildResponse(TagResource.class, "getTagDefinition", createdTagDef.getName());
+        } catch (TagDefinitionApiException e) {
+            return Response.status(Status.NO_CONTENT).build(); 
+        } catch (IllegalArgumentException e) {
+            return Response.status(Status.BAD_REQUEST).entity(e.getMessage()).build();
+        }
+    }
+    
+    @DELETE
+    @Path("/{tagDefinitionName:" + STRING_PATTERN + "}")
+    @Produces(APPLICATION_JSON)
+    public Response deleteTagDefinition(@PathParam("tagDefinitionName") String tagDefName,
+            @HeaderParam(HDR_CREATED_BY) final String createdBy,
+            @HeaderParam(HDR_REASON) final String reason,
+            @HeaderParam(HDR_COMMENT) final String comment) {
+        try {
+            tagUserApi.deleteTagDefinition(tagDefName, context.createContext(createdBy, reason, comment));
+            return Response.status(Status.NO_CONTENT).build();
+        } catch (TagDefinitionApiException e) {
+            return Response.status(Status.NO_CONTENT).build(); 
+        } catch (IllegalArgumentException e) {
+            return Response.status(Status.BAD_REQUEST).entity(e.getMessage()).build();
+        }
+    }
+}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/util/Context.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/util/Context.java
new file mode 100644
index 0000000..729f166
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/util/Context.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2010-2011 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.jaxrs.util;
+
+import java.util.UUID;
+
+import com.google.common.base.Preconditions;
+import com.google.inject.Inject;
+import com.ning.billing.jaxrs.resources.BaseJaxrsResource;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.callcontext.CallContextFactory;
+import com.ning.billing.util.callcontext.CallOrigin;
+import com.ning.billing.util.callcontext.UserType;
+
+public class Context {
+
+    private final CallOrigin origin;
+    private final UserType userType;
+    final CallContextFactory contextFactory;
+
+    @Inject
+    public Context(final CallContextFactory factory) {
+        super();
+        this.origin = CallOrigin.EXTERNAL;
+        this.userType = UserType.CUSTOMER;
+        this.contextFactory = factory;
+    }
+
+    public CallContext createContext(final String createdBy, final String reason, final String comment)
+    throws IllegalArgumentException {
+        try {
+            Preconditions.checkNotNull(createdBy, String.format("Header %s needs to be set", BaseJaxrsResource.HDR_CREATED_BY));
+            return contextFactory.createCallContext(createdBy, origin, userType, UUID.randomUUID());
+        } catch (NullPointerException e) {
+            throw new IllegalArgumentException(e.getMessage());
+        }
+    }
+}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/util/JaxrsUriBuilder.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/util/JaxrsUriBuilder.java
new file mode 100644
index 0000000..b351b4c
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/util/JaxrsUriBuilder.java
@@ -0,0 +1,39 @@
+/* 
+ * Copyright 2010-2011 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.jaxrs.util;
+
+import java.net.URI;
+import java.util.UUID;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+
+import com.ning.billing.jaxrs.resources.BaseJaxrsResource;
+
+public class JaxrsUriBuilder {
+
+	
+	public Response buildResponse(final Class<? extends BaseJaxrsResource> theClass, final String getMethodName, final Object objectId) {
+		URI uri = UriBuilder.fromPath(objectId.toString()).build();
+		Response.ResponseBuilder ri = Response.created(uri);
+		return ri.entity(new Object() {
+			@SuppressWarnings(value = "all")
+			public URI getUri() {
+				return UriBuilder.fromResource(theClass).path(theClass, getMethodName).build(objectId);
+			}
+		}).build();
+	}
+}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/util/KillbillEventHandler.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/util/KillbillEventHandler.java
new file mode 100644
index 0000000..dc18416
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/util/KillbillEventHandler.java
@@ -0,0 +1,70 @@
+/* 
+ * Copyright 2010-2011 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.jaxrs.util;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+import com.google.common.eventbus.Subscribe;
+import com.ning.billing.util.bus.BusEvent;
+import com.ning.billing.util.userrequest.CompletionUserRequest;
+import com.ning.billing.util.userrequest.CompletionUserRequestNotifier;
+
+public class KillbillEventHandler {
+    
+    
+    private final List<CompletionUserRequest> activeWaiters;
+    
+    public KillbillEventHandler() {
+        activeWaiters = new LinkedList<CompletionUserRequest>();
+    }
+    
+    public void registerCompletionUserRequestWaiter(final CompletionUserRequest waiter) {
+        if (waiter == null) {
+            return;
+        }
+        synchronized(activeWaiters) {
+            activeWaiters.add(waiter);
+        }
+    }
+    
+    public void unregisterCompletionUserRequestWaiter(final CompletionUserRequest waiter) {
+        if (waiter == null) {
+            return;
+        }
+        synchronized(activeWaiters) {
+            activeWaiters.remove(waiter);
+        }
+    }
+    
+    /*
+     * IRS event handler for killbill entitlement events
+     */
+    @Subscribe
+    public void handleEntitlementevents(final BusEvent event) {
+        List<CompletionUserRequestNotifier> runningWaiters = new ArrayList<CompletionUserRequestNotifier>();
+        synchronized(activeWaiters) {
+            runningWaiters.addAll(activeWaiters);
+        }
+        if (runningWaiters.size() == 0) {
+            return;
+        }
+        for (CompletionUserRequestNotifier cur : runningWaiters) {
+            cur.onBusEvent(event);
+        }
+    }
+}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/util/TagHelper.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/util/TagHelper.java
new file mode 100644
index 0000000..e965deb
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/util/TagHelper.java
@@ -0,0 +1,49 @@
+/* 
+ * Copyright 2010-2011 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.jaxrs.util;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import com.google.inject.Inject;
+import com.ning.billing.ErrorCode;
+import com.ning.billing.util.api.TagDefinitionApiException;
+import com.ning.billing.util.api.TagUserApi;
+import com.ning.billing.util.tag.TagDefinition;
+
+public class TagHelper {
+
+    private final TagUserApi tagUserApi;
+    
+    @Inject
+    public TagHelper(final TagUserApi tagUserApi) {
+        this.tagUserApi = tagUserApi;
+    }
+    
+    public List<TagDefinition> getTagDifinitionFromTagList(final String tagList) throws TagDefinitionApiException {
+        List<TagDefinition> result = new LinkedList<TagDefinition>();
+        String [] tagParts = tagList.split(",\\s*");
+        for (String cur : tagParts) {
+            TagDefinition curDef = tagUserApi.getTagDefinition(cur);
+            // Yack should throw excption
+            if (curDef == null) {
+                throw new TagDefinitionApiException(ErrorCode.TAG_DEFINITION_DOES_NOT_EXIST, cur);
+            }
+            result.add(curDef);
+        }
+        return result;
+    }
+}
diff --git a/jaxrs/src/main/resources/.dont-let-git-remove-this-directory b/jaxrs/src/main/resources/.dont-let-git-remove-this-directory
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/jaxrs/src/main/resources/.dont-let-git-remove-this-directory
diff --git a/jaxrs/src/test/resources/log4j.xml b/jaxrs/src/test/resources/log4j.xml
new file mode 100644
index 0000000..512d2dc
--- /dev/null
+++ b/jaxrs/src/test/resources/log4j.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright 2010-2011 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.
+  -->
+
+<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
+<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
+    <appender name="stdout" class="org.apache.log4j.ConsoleAppender">
+        <param name="Target" value="System.out"/>
+        <layout class="org.apache.log4j.PatternLayout">
+            <param name="ConversionPattern" value="%p	%d{ISO8601}	%X{trace}	%t	%c	%m%n"/>
+        </layout>
+    </appender>
+
+
+    <logger name="com.ning.billing.jaxrs">
+        <level value="info"/>
+    </logger>
+
+    <root>
+        <priority value="info"/>
+        <appender-ref ref="stdout"/>
+    </root>
+</log4j:configuration>

junction/pom.xml 114(+114 -0)

diff --git a/junction/pom.xml b/junction/pom.xml
new file mode 100644
index 0000000..0375b37
--- /dev/null
+++ b/junction/pom.xml
@@ -0,0 +1,114 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- ~ Copyright 2010-2011 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. -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>com.ning.billing</groupId>
+        <artifactId>killbill</artifactId>
+        <version>0.1.11-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+    <artifactId>killbill-junction</artifactId>
+    <name>killbill-junction</name>
+    <packaging>jar</packaging>
+    <dependencies>
+        <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-util</artifactId>
+        </dependency>
+         <dependency>
+            <groupId>com.google.inject</groupId>
+            <artifactId>guice</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.skife.config</groupId>
+            <artifactId>config-magic</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>joda-time</groupId>
+            <artifactId>joda-time</artifactId>
+        </dependency>
+         <dependency>
+            <groupId>org.jdbi</groupId>
+            <artifactId>jdbi</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.testng</groupId>
+            <artifactId>testng</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>commons-io</groupId>
+            <artifactId>commons-io</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <!-- TEST SCOPE -->
+        <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-util</artifactId>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-catalog</artifactId>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-catalog</artifactId>
+            <scope>test</scope>
+        </dependency>
+         <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+            <scope>runtime</scope>
+        </dependency>
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-mxj</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-mxj-db-files</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <!-- Strangely this is needed in order to run the tests in local db mode -->
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+           <scope>test</scope>
+         </dependency>
+ 
+    </dependencies>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-jar-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>test-jar</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/junction/src/main/java/com/ning/billing/junction/api/blocking/DefaultBlockingApi.java b/junction/src/main/java/com/ning/billing/junction/api/blocking/DefaultBlockingApi.java
new file mode 100644
index 0000000..904d303
--- /dev/null
+++ b/junction/src/main/java/com/ning/billing/junction/api/blocking/DefaultBlockingApi.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2010-2011 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.junction.api.blocking;
+
+import java.util.SortedSet;
+import java.util.UUID;
+
+import com.google.inject.Inject;
+import com.ning.billing.junction.api.Blockable;
+import com.ning.billing.junction.api.BlockingApi;
+import com.ning.billing.junction.api.BlockingState;
+import com.ning.billing.junction.api.DefaultBlockingState;
+import com.ning.billing.junction.dao.BlockingStateDao;
+import com.ning.billing.util.clock.Clock;
+
+public class DefaultBlockingApi implements BlockingApi {
+    private BlockingStateDao dao;
+    private Clock clock;
+
+    @Inject
+    public DefaultBlockingApi(BlockingStateDao dao, Clock clock) {
+        this.dao = dao;
+        this.clock = clock;
+    }
+    
+    @Override
+    public BlockingState getBlockingStateFor(Blockable overdueable) {
+        BlockingState state = dao.getBlockingStateFor(overdueable);
+        if(state == null) {
+            state = DefaultBlockingState.getClearState();
+        }
+        return state;
+        
+    }
+
+    @Override
+    public BlockingState getBlockingStateFor(UUID overdueableId) {
+        return dao.getBlockingStateFor(overdueableId);
+    }
+
+    @Override
+    public SortedSet<BlockingState> getBlockingHistory(Blockable overdueable) {
+        return dao.getBlockingHistoryFor(overdueable); 
+    }
+
+    @Override
+    public SortedSet<BlockingState> getBlockingHistory(UUID overdueableId) {
+        return dao.getBlockingHistoryFor(overdueableId);
+    }
+
+    @Override
+    public <T extends Blockable> void setBlockingState(BlockingState state) {
+       dao.setBlockingState(state, clock);
+        
+    }
+
+}
diff --git a/junction/src/main/java/com/ning/billing/junction/block/BlockingChecker.java b/junction/src/main/java/com/ning/billing/junction/block/BlockingChecker.java
new file mode 100644
index 0000000..04d68f0
--- /dev/null
+++ b/junction/src/main/java/com/ning/billing/junction/block/BlockingChecker.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2010-2011 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.junction.block;
+
+import java.util.UUID;
+
+import com.ning.billing.junction.api.Blockable;
+import com.ning.billing.junction.api.BlockingApiException;
+
+public interface BlockingChecker {
+
+    public void checkBlockedChange(Blockable blockable)  throws BlockingApiException;
+
+    public void checkBlockedEntitlement(Blockable blockable)  throws BlockingApiException;
+
+    public void checkBlockedBilling(Blockable blockable)  throws BlockingApiException;
+
+    public void checkBlockedChange(UUID bundleId, Blockable.Type type) throws BlockingApiException;
+
+    public void checkBlockedEntitlement(UUID bundleId, Blockable.Type type) throws BlockingApiException;
+
+    public void checkBlockedBilling(UUID bundleId, Blockable.Type type) throws BlockingApiException;
+}
diff --git a/junction/src/main/java/com/ning/billing/junction/block/DefaultBlockingChecker.java b/junction/src/main/java/com/ning/billing/junction/block/DefaultBlockingChecker.java
new file mode 100644
index 0000000..8becce5
--- /dev/null
+++ b/junction/src/main/java/com/ning/billing/junction/block/DefaultBlockingChecker.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright 2010-2011 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.junction.block;
+
+import java.util.UUID;
+
+import com.google.inject.Inject;
+import com.ning.billing.ErrorCode;
+import com.ning.billing.account.api.Account;
+import com.ning.billing.entitlement.api.user.EntitlementUserApi;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.junction.api.Blockable;
+import com.ning.billing.junction.api.BlockingApiException;
+import com.ning.billing.junction.api.BlockingState;
+import com.ning.billing.junction.dao.BlockingStateDao;
+
+public class DefaultBlockingChecker implements BlockingChecker {
+
+    private static class BlockingAggregator {
+        private boolean blockChange = false;
+        private boolean blockEntitlement= false;
+        private boolean blockBilling = false;
+
+        public void or(BlockingState state) {
+            if (state == null) { return; }
+            blockChange = blockChange || state.isBlockChange();
+            blockEntitlement = blockEntitlement || state.isBlockEntitlement();
+            blockBilling = blockBilling || state.isBlockBilling();
+        }
+
+        public void or(BlockingAggregator state) {
+            if (state == null) { return; }
+            blockChange = blockChange || state.isBlockChange();
+            blockEntitlement = blockEntitlement || state.isBlockEntitlement();
+            blockBilling = blockBilling || state.isBlockBilling();
+        }
+
+        public boolean isBlockChange() {
+            return blockChange;
+        }
+        public boolean isBlockEntitlement() {
+            return blockEntitlement;
+        }
+        public boolean isBlockBilling() {
+            return blockBilling;
+        }
+
+    }
+
+    private static final Object TYPE_SUBSCRIPTION = "Subscription";
+    private static final Object TYPE_BUNDLE = "Bundle";
+    private static final Object TYPE_ACCOUNT = "ACCOUNT";
+
+    private static final Object ACTION_CHANGE = "Change";
+    private static final Object ACTION_ENTITLEMENT = "Entitlement";
+    private static final Object ACTION_BILLING = "Billing";
+
+    private final EntitlementUserApi entitlementApi;
+    private final BlockingStateDao dao;
+
+    @Inject
+    public DefaultBlockingChecker(EntitlementUserApi entitlementApi, BlockingStateDao dao) {
+        this.entitlementApi = entitlementApi;
+        this.dao = dao;
+    }
+
+    public BlockingAggregator getBlockedStateSubscriptionId(UUID subscriptionId) throws EntitlementUserApiException  {
+        Subscription subscription = entitlementApi.getSubscriptionFromId(subscriptionId);
+        return getBlockedStateSubscription(subscription);
+    }
+
+    public BlockingAggregator getBlockedStateSubscription(Subscription subscription) throws EntitlementUserApiException  {
+        BlockingAggregator result = new BlockingAggregator();
+        if(subscription != null) {
+            BlockingState subscriptionState = subscription.getBlockingState();
+            if(subscriptionState != null) {
+                result.or(subscriptionState);
+            }
+            if(subscription.getBundleId() != null) {
+                result.or(getBlockedStateBundleId(subscription.getBundleId()));
+            } 
+        }
+        return result;
+    }
+
+    public BlockingAggregator getBlockedStateBundleId(UUID bundleId) throws EntitlementUserApiException  {
+        SubscriptionBundle bundle = entitlementApi.getBundleFromId(bundleId);
+        return getBlockedStateBundle(bundle);
+    }
+
+    public BlockingAggregator getBlockedStateBundle(SubscriptionBundle bundle)  {
+        BlockingAggregator result = getBlockedStateAccountId(bundle.getAccountId());
+        BlockingState bundleState = bundle.getBlockingState();
+        if(bundleState != null) {
+            result.or(bundleState);
+        }
+        return result;
+    }
+
+    public BlockingAggregator getBlockedStateAccountId(UUID accountId)  {
+        BlockingAggregator result = new BlockingAggregator();
+        if(accountId != null) {
+            BlockingState accountState = dao.getBlockingStateFor(accountId);
+            result.or(accountState);
+        }
+        return result;
+    }
+
+    public BlockingAggregator getBlockedStateAccount(Account account)  {
+        if(account != null) {
+            return getBlockedStateAccountId(account.getId());
+        }
+        return new BlockingAggregator();
+    }
+    @Override
+    public void checkBlockedChange(Blockable blockable) throws BlockingApiException  {
+        try {
+            if(blockable instanceof Subscription && getBlockedStateSubscription((Subscription) blockable).isBlockChange()) {
+                throw new BlockingApiException(ErrorCode.BLOCK_BLOCKED_ACTION,ACTION_CHANGE, TYPE_SUBSCRIPTION, blockable.getId().toString());
+            } else if(blockable instanceof SubscriptionBundle &&  getBlockedStateBundle((SubscriptionBundle) blockable).isBlockChange()) {
+                throw new BlockingApiException(ErrorCode.BLOCK_BLOCKED_ACTION,ACTION_CHANGE, TYPE_BUNDLE, blockable.getId().toString());
+            } else if(blockable instanceof Account && getBlockedStateAccount((Account) blockable).isBlockChange()) {
+                throw new BlockingApiException(ErrorCode.BLOCK_BLOCKED_ACTION,ACTION_CHANGE, TYPE_ACCOUNT, blockable.getId().toString());
+            }
+        } catch (EntitlementUserApiException e) {
+            throw new BlockingApiException(e, ErrorCode.values()[e.getCode()]);
+        }
+    }
+
+    @Override
+    public void checkBlockedEntitlement(Blockable blockable) throws BlockingApiException  {
+        try {
+            if(blockable instanceof Subscription && getBlockedStateSubscription((Subscription) blockable).isBlockEntitlement()) {
+                throw new BlockingApiException(ErrorCode.BLOCK_BLOCKED_ACTION,ACTION_ENTITLEMENT, TYPE_SUBSCRIPTION, blockable.getId().toString());
+            } else if(blockable instanceof SubscriptionBundle &&  getBlockedStateBundle((SubscriptionBundle) blockable).isBlockEntitlement()) {
+                throw new BlockingApiException(ErrorCode.BLOCK_BLOCKED_ACTION,ACTION_ENTITLEMENT, TYPE_BUNDLE, blockable.getId().toString());
+            } else if(blockable instanceof Account && getBlockedStateAccount((Account) blockable).isBlockEntitlement()) {
+                throw new BlockingApiException(ErrorCode.BLOCK_BLOCKED_ACTION,ACTION_ENTITLEMENT, TYPE_ACCOUNT, blockable.getId().toString());
+            }
+        } catch (EntitlementUserApiException e) {
+            throw new BlockingApiException(e, ErrorCode.values()[e.getCode()]);
+        }
+    }
+
+    @Override
+    public void checkBlockedBilling(Blockable blockable) throws BlockingApiException  {
+        try {
+            if(blockable instanceof Subscription && getBlockedStateSubscription((Subscription) blockable).isBlockBilling()) {
+                throw new BlockingApiException(ErrorCode.BLOCK_BLOCKED_ACTION,ACTION_BILLING, TYPE_SUBSCRIPTION, blockable.getId().toString());
+            } else if(blockable instanceof SubscriptionBundle &&  getBlockedStateBundle((SubscriptionBundle) blockable).isBlockBilling()) {
+                throw new BlockingApiException(ErrorCode.BLOCK_BLOCKED_ACTION,ACTION_BILLING, TYPE_BUNDLE, blockable.getId().toString());
+            } else if(blockable instanceof Account && getBlockedStateAccount((Account) blockable).isBlockBilling()) {
+                throw new BlockingApiException(ErrorCode.BLOCK_BLOCKED_ACTION,ACTION_BILLING, TYPE_ACCOUNT, blockable.getId().toString());
+            }
+        } catch (EntitlementUserApiException e) {
+            throw new BlockingApiException(e, ErrorCode.values()[e.getCode()]);
+        }
+    }
+
+
+    @Override
+    public void checkBlockedChange(UUID blockableId, Blockable.Type type) throws BlockingApiException  {
+        try {
+            if(type == Blockable.Type.SUBSCRIPTION && getBlockedStateSubscriptionId(blockableId).isBlockChange()) {
+                throw new BlockingApiException(ErrorCode.BLOCK_BLOCKED_ACTION,ACTION_CHANGE, TYPE_SUBSCRIPTION, blockableId.toString());
+            } else if(type == Blockable.Type.SUBSCRIPTION_BUNDLE  &&  getBlockedStateBundleId(blockableId).isBlockChange()) {
+                throw new BlockingApiException(ErrorCode.BLOCK_BLOCKED_ACTION,ACTION_CHANGE, TYPE_BUNDLE, blockableId.toString());
+            } else if(type == Blockable.Type.ACCOUNT  && getBlockedStateAccountId(blockableId).isBlockChange()) {
+                throw new BlockingApiException(ErrorCode.BLOCK_BLOCKED_ACTION,ACTION_CHANGE, TYPE_ACCOUNT, blockableId.toString());
+
+            } 
+        } catch (EntitlementUserApiException e) {
+            throw new BlockingApiException(e, ErrorCode.values()[e.getCode()]);
+        }
+    }
+
+    @Override
+    public void checkBlockedEntitlement(UUID blockableId, Blockable.Type type) throws BlockingApiException  {
+        try {
+            if(type == Blockable.Type.SUBSCRIPTION && getBlockedStateSubscriptionId(blockableId).isBlockEntitlement()) {
+                throw new BlockingApiException(ErrorCode.BLOCK_BLOCKED_ACTION,ACTION_ENTITLEMENT, TYPE_SUBSCRIPTION, blockableId.toString());
+            } else if(type == Blockable.Type.SUBSCRIPTION_BUNDLE  &&  getBlockedStateBundleId(blockableId).isBlockEntitlement()) {
+                throw new BlockingApiException(ErrorCode.BLOCK_BLOCKED_ACTION,ACTION_ENTITLEMENT, TYPE_BUNDLE, blockableId.toString());
+            } else if(type == Blockable.Type.ACCOUNT  && getBlockedStateAccountId(blockableId).isBlockEntitlement()) {
+                throw new BlockingApiException(ErrorCode.BLOCK_BLOCKED_ACTION,ACTION_ENTITLEMENT, TYPE_ACCOUNT, blockableId.toString());
+            }
+        } catch (EntitlementUserApiException e) {
+            throw new BlockingApiException(e, ErrorCode.values()[e.getCode()]);
+        }
+    }
+
+    @Override
+    public void checkBlockedBilling(UUID blockableId, Blockable.Type type) throws BlockingApiException  {
+        try {
+            if(type == Blockable.Type.SUBSCRIPTION && getBlockedStateSubscriptionId(blockableId).isBlockBilling()) {
+                throw new BlockingApiException(ErrorCode.BLOCK_BLOCKED_ACTION,ACTION_BILLING, TYPE_SUBSCRIPTION, blockableId.toString());
+            } else if(type == Blockable.Type.SUBSCRIPTION_BUNDLE  &&  getBlockedStateBundleId(blockableId).isBlockBilling()) {
+                throw new BlockingApiException(ErrorCode.BLOCK_BLOCKED_ACTION,ACTION_BILLING, TYPE_BUNDLE, blockableId.toString());
+            } else if(type == Blockable.Type.ACCOUNT  && getBlockedStateAccountId(blockableId).isBlockBilling()) {
+                throw new BlockingApiException(ErrorCode.BLOCK_BLOCKED_ACTION,ACTION_BILLING, TYPE_ACCOUNT, blockableId.toString());
+            }
+        } catch (EntitlementUserApiException e) {
+            throw new BlockingApiException(e, ErrorCode.values()[e.getCode()]);
+        }
+    }
+
+
+
+
+}
diff --git a/junction/src/main/java/com/ning/billing/junction/dao/BlockingStateDao.java b/junction/src/main/java/com/ning/billing/junction/dao/BlockingStateDao.java
new file mode 100644
index 0000000..6f5396d
--- /dev/null
+++ b/junction/src/main/java/com/ning/billing/junction/dao/BlockingStateDao.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2010-2011 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.junction.dao;
+
+import java.util.SortedSet;
+import java.util.UUID;
+
+import com.ning.billing.junction.api.Blockable;
+import com.ning.billing.junction.api.Blockable.Type;
+import com.ning.billing.junction.api.BlockingState;
+import com.ning.billing.util.clock.Clock;
+
+public interface BlockingStateDao {
+
+    //Read
+    public BlockingState getBlockingStateFor(Blockable blockable);
+
+    public BlockingState getBlockingStateFor(UUID blockableId);
+
+    public SortedSet<BlockingState> getBlockingHistoryFor(Blockable blockable);
+
+    public SortedSet<BlockingState> getBlockingHistoryFor(UUID blockableId);
+
+    //Write
+    <T extends Blockable> void  setBlockingState(BlockingState state, Clock clock);
+
+} 
\ No newline at end of file
diff --git a/junction/src/main/java/com/ning/billing/junction/dao/BlockingStateSqlDao.java b/junction/src/main/java/com/ning/billing/junction/dao/BlockingStateSqlDao.java
new file mode 100644
index 0000000..ba241a4
--- /dev/null
+++ b/junction/src/main/java/com/ning/billing/junction/dao/BlockingStateSqlDao.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright 2010-2011 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.junction.dao;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.SortedSet;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.skife.jdbi.v2.SQLStatement;
+import org.skife.jdbi.v2.StatementContext;
+import org.skife.jdbi.v2.sqlobject.Bind;
+import org.skife.jdbi.v2.sqlobject.Binder;
+import org.skife.jdbi.v2.sqlobject.SqlQuery;
+import org.skife.jdbi.v2.sqlobject.SqlUpdate;
+import org.skife.jdbi.v2.sqlobject.customizers.Mapper;
+import org.skife.jdbi.v2.sqlobject.mixins.CloseMe;
+import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
+import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
+import org.skife.jdbi.v2.tweak.ResultSetMapper;
+
+import com.ning.billing.junction.api.Blockable;
+import com.ning.billing.junction.api.Blockable.Type;
+import com.ning.billing.junction.api.BlockingApi;
+import com.ning.billing.junction.api.BlockingApiException;
+import com.ning.billing.junction.api.BlockingState;
+import com.ning.billing.junction.api.DefaultBlockingState;
+import com.ning.billing.overdue.OverdueState;
+import com.ning.billing.util.clock.Clock;
+import com.ning.billing.util.dao.BinderBase;
+import com.ning.billing.util.dao.MapperBase;
+
+@ExternalizedSqlViaStringTemplate3()
+public interface BlockingStateSqlDao extends BlockingStateDao, CloseMe, Transmogrifier {
+
+    @Override
+    @SqlUpdate
+    public abstract <T extends Blockable> void setBlockingState(
+            @Bind(binder = BlockingStateBinder.class) BlockingState state,
+            @Bind(binder = CurrentTimeBinder.class) Clock clock) ;
+
+
+    @Override
+    @SqlQuery
+    @Mapper(BlockingHistorySqlMapper.class)
+    public abstract BlockingState getBlockingStateFor(@Bind(binder = BlockableBinder.class)Blockable overdueable) ;
+    
+    @Override
+    @SqlQuery
+    @Mapper(BlockingHistorySqlMapper.class)
+    public abstract BlockingState getBlockingStateFor(@Bind(binder = UUIDBinder.class) UUID overdueableId);
+
+    @Override
+    @SqlQuery
+    @Mapper(BlockingHistorySqlMapper.class)
+    public abstract SortedSet<BlockingState> getBlockingHistoryFor(@Bind(binder = BlockableBinder.class)Blockable blockable) ;
+    
+    @Override
+    @SqlQuery
+    @Mapper(BlockingHistorySqlMapper.class)
+    public abstract SortedSet<BlockingState> getBlockingHistoryFor(@Bind(binder = UUIDBinder.class) UUID blockableId);
+
+
+    public class BlockingHistorySqlMapper extends MapperBase implements ResultSetMapper<BlockingState> {
+
+        @Override
+        public BlockingState map(int index, ResultSet r, StatementContext ctx)
+                throws SQLException {
+            
+            DateTime timestamp;
+            UUID blockableId;
+            String stateName;
+            String service;
+            boolean blockChange;
+            boolean blockEntitlement;
+            boolean blockBilling;
+            Type type;
+            try {
+                timestamp = new DateTime(r.getDate("created_date"));
+                blockableId = UUID.fromString(r.getString("id"));
+                stateName = r.getString("state") == null ? BlockingApi.CLEAR_STATE_NAME : r.getString("state");
+                type = Type.get(r.getString("type"));
+                service = r.getString("service");
+                blockChange = r.getBoolean("block_change");
+                blockEntitlement = r.getBoolean("block_entitlement");
+                blockBilling = r.getBoolean("block_billing");
+            } catch (BlockingApiException e) {
+                throw new SQLException(e);
+            }
+            return new DefaultBlockingState(blockableId, stateName, type, service, blockChange, blockEntitlement, blockBilling, timestamp);
+        }    
+    }
+    
+    public static class BlockingStateSqlMapper extends MapperBase implements ResultSetMapper<String> {
+
+        @Override
+        public String map(int index, ResultSet r, StatementContext ctx)
+                throws SQLException {
+            return r.getString("state") == null ? BlockingApi.CLEAR_STATE_NAME : r.getString("state");
+        }
+    }
+    
+    public static class BlockingStateBinder extends BinderBase implements Binder<Bind, DefaultBlockingState> {
+        @Override
+        public void bind(@SuppressWarnings("rawtypes") SQLStatement stmt, Bind bind, DefaultBlockingState state) {
+            stmt.bind("id", state.getBlockedId().toString()); 
+            stmt.bind("state", state.getStateName().toString());
+            stmt.bind("type", state.getType().toString());
+            stmt.bind("service", state.getService().toString());
+            stmt.bind("block_change", state.isBlockChange());
+            stmt.bind("block_entitlement", state.isBlockEntitlement());
+            stmt.bind("block_billing", state.isBlockBilling());
+        }
+    }
+    
+    public static class UUIDBinder extends BinderBase implements Binder<Bind, UUID> {
+        @Override
+        public void bind(@SuppressWarnings("rawtypes") SQLStatement stmt, Bind bind, UUID id) {
+            stmt.bind("id", id.toString());
+        }
+    }
+    
+    public static class BlockableBinder extends BinderBase implements Binder<Bind, Blockable> {
+        @Override
+        public void bind(@SuppressWarnings("rawtypes") SQLStatement stmt, Bind bind, Blockable overdueable) {
+            stmt.bind("id", overdueable.getId().toString());
+        }
+    }
+    
+    public static class OverdueStateBinder<T extends Blockable> extends BinderBase implements Binder<Bind, OverdueState<T>> {
+        @Override
+        public void bind(@SuppressWarnings("rawtypes") SQLStatement stmt, Bind bind, OverdueState<T> overdueState) {
+            stmt.bind("state", overdueState.getName());
+        }
+    }
+    
+    public class BlockableTypeBinder extends BinderBase implements Binder<Bind, Blockable.Type>{
+
+        @Override
+        public void bind(@SuppressWarnings("rawtypes") SQLStatement stmt, Bind bind, Type type) {
+            stmt.bind("type", type.name());
+        }
+
+    }
+
+    public static class CurrentTimeBinder extends BinderBase implements Binder<Bind, Clock> {
+        @Override
+        public void bind(@SuppressWarnings("rawtypes") SQLStatement stmt, Bind bind, Clock clock) {
+            stmt.bind("created_date", clock.getUTCNow().toDate());
+        }
+        
+    }
+
+}
diff --git a/junction/src/main/java/com/ning/billing/junction/glue/DefaultJunctionModule.java b/junction/src/main/java/com/ning/billing/junction/glue/DefaultJunctionModule.java
new file mode 100644
index 0000000..0065ab3
--- /dev/null
+++ b/junction/src/main/java/com/ning/billing/junction/glue/DefaultJunctionModule.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2010-2011 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.junction.glue;
+
+import org.skife.jdbi.v2.IDBI;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.ning.billing.account.api.AccountUserApi;
+import com.ning.billing.entitlement.api.user.EntitlementUserApi;
+import com.ning.billing.glue.JunctionModule;
+import com.ning.billing.junction.api.BillingApi;
+import com.ning.billing.junction.api.BlockingApi;
+import com.ning.billing.junction.api.blocking.DefaultBlockingApi;
+import com.ning.billing.junction.block.BlockingChecker;
+import com.ning.billing.junction.block.DefaultBlockingChecker;
+import com.ning.billing.junction.dao.BlockingStateDao;
+import com.ning.billing.junction.dao.BlockingStateSqlDao;
+import com.ning.billing.junction.plumbing.api.BlockingAccountUserApi;
+import com.ning.billing.junction.plumbing.api.BlockingEntitlementUserApi;
+import com.ning.billing.junction.plumbing.billing.BlockingCalculator;
+import com.ning.billing.junction.plumbing.billing.DefaultBillingApi;
+
+public class DefaultJunctionModule extends AbstractModule implements JunctionModule {
+
+    @Override
+    protected void configure() {
+        // External
+        installBlockingApi();
+        installAccountUserApi();
+        installBillingApi();
+        installEntitlementUserApi();
+        installBlockingChecker();
+        
+        // Internal
+        installBlockingCalculator();
+        installBlockingStateDao();
+     }
+
+    public void installBlockingChecker() {
+        bind(BlockingChecker.class).to(DefaultBlockingChecker.class).asEagerSingleton();
+        
+    }
+
+    public void installBillingApi() {
+        bind(BillingApi.class).to(DefaultBillingApi.class).asEagerSingleton();
+    }
+    
+    public void installBlockingStateDao() {
+        bind(BlockingStateDao.class).toProvider(BlockingDaoProvider.class);
+    }
+    
+    public void installAccountUserApi() {
+        bind(AccountUserApi.class).to(BlockingAccountUserApi.class).asEagerSingleton();
+    }
+    
+    public void installEntitlementUserApi() {
+        bind(EntitlementUserApi.class).to(BlockingEntitlementUserApi.class).asEagerSingleton();
+    }
+    
+    public void installBlockingApi() {
+        bind(BlockingApi.class).to(DefaultBlockingApi.class).asEagerSingleton();
+    }
+    
+    public void installBlockingCalculator() {
+        bind(BlockingCalculator.class).asEagerSingleton();
+    }
+
+    public static class BlockingDaoProvider implements Provider<BlockingStateDao>{        
+        private IDBI dbi;
+
+
+        @Inject
+        public BlockingDaoProvider(IDBI dbi){
+            this.dbi = dbi;
+        }
+        @Override
+        public BlockingStateDao get() {
+            return dbi.onDemand(BlockingStateSqlDao.class);
+        }   
+    }
+}
diff --git a/junction/src/main/java/com/ning/billing/junction/plumbing/api/BlockingAccount.java b/junction/src/main/java/com/ning/billing/junction/plumbing/api/BlockingAccount.java
new file mode 100644
index 0000000..83b9aaf
--- /dev/null
+++ b/junction/src/main/java/com/ning/billing/junction/plumbing/api/BlockingAccount.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright 2010-2011 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.junction.plumbing.api;
+
+import java.util.List;
+import java.util.UUID;
+
+import com.ning.billing.util.dao.ObjectType;
+import com.ning.billing.util.tag.ControlTagType;
+import org.joda.time.DateTimeZone;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.MutableAccountData;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.junction.api.BlockingApi;
+import com.ning.billing.junction.api.BlockingState;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.customfield.CustomField;
+import com.ning.billing.util.tag.Tag;
+import com.ning.billing.util.tag.TagDefinition;
+
+public class BlockingAccount implements Account {
+    private final Account account;
+    private BlockingState blockingState = null;
+    private BlockingApi blockingApi;
+
+    public BlockingAccount( Account account, BlockingApi blockingApi) {
+        this.account = account;
+        this.blockingApi = blockingApi;
+    }
+
+    public List<Tag> getTagList() {
+        return account.getTagList();
+    }
+
+    @Override
+    public boolean hasTag(TagDefinition tagDefinition) {
+        return account.hasTag(tagDefinition);
+    }
+
+    public UUID getId() {
+        return account.getId();
+    }
+
+    @Override
+    public boolean hasTag(ControlTagType controlTagType) {
+        return account.hasTag(controlTagType);
+    }
+
+    public void addTag(TagDefinition definition) {
+        account.addTag(definition);
+    }
+
+    public String getFieldValue(String fieldName) {
+        return account.getFieldValue(fieldName);
+    }
+
+    public String getExternalKey() {
+        return account.getExternalKey();
+    }
+
+    public String getName() {
+        return account.getName();
+    }
+
+    public void addTags(List<Tag> tags) {
+        account.addTags(tags);
+    }
+
+    @Override
+    public void addTagsFromDefinitions(List<TagDefinition> tagDefinitions) {
+        account.addTagsFromDefinitions(tagDefinitions);
+    }
+
+    public void setFieldValue(String fieldName, String fieldValue) {
+        account.setFieldValue(fieldName, fieldValue);
+    }
+
+    public int getFirstNameLength() {
+        return account.getFirstNameLength();
+    }
+
+    public void clearTags() {
+        account.clearTags();
+    }
+
+    public String getEmail() {
+        return account.getEmail();
+    }
+
+    public void removeTag(TagDefinition definition) {
+        account.removeTag(definition);
+    }
+
+    public void saveFieldValue(String fieldName, String fieldValue, CallContext context) {
+        account.saveFieldValue(fieldName, fieldValue, context);
+    }
+
+    public int getBillCycleDay() {
+        return account.getBillCycleDay();
+    }
+
+    public boolean generateInvoice() {
+        return account.generateInvoice();
+    }
+
+    public Currency getCurrency() {
+        return account.getCurrency();
+    }
+
+    public boolean processPayment() {
+        return account.processPayment();
+    }
+
+    public List<CustomField> getFieldList() {
+        return account.getFieldList();
+    }
+
+    public String getPaymentProviderName() {
+        return account.getPaymentProviderName();
+    }
+
+    public MutableAccountData toMutableAccountData() {
+        return account.toMutableAccountData();
+    }
+
+    public void setFields(List<CustomField> fields) {
+        account.setFields(fields);
+    }
+
+    public DateTimeZone getTimeZone() {
+        return account.getTimeZone();
+    }
+
+    public String getLocale() {
+        return account.getLocale();
+    }
+
+    public BlockingState getBlockingState() {
+        if(blockingState == null) {
+            blockingState = blockingApi.getBlockingStateFor(account);
+        }
+        return blockingState;
+    }
+
+    public void saveFields(List<CustomField> fields, CallContext context) {
+        account.saveFields(fields, context);
+    }
+
+    public String getAddress1() {
+        return account.getAddress1();
+    }
+
+    public String getAddress2() {
+        return account.getAddress2();
+    }
+
+    public void clearFields() {
+        account.clearFields();
+    }
+
+    public String getCompanyName() {
+        return account.getCompanyName();
+    }
+
+    public void clearPersistedFields(CallContext context) {
+        account.clearPersistedFields(context);
+    }
+
+    public String getCity() {
+        return account.getCity();
+    }
+
+    public String getStateOrProvince() {
+        return account.getStateOrProvince();
+    }
+
+    public ObjectType getObjectType() {
+        return account.getObjectType();
+    }
+
+    public String getPostalCode() {
+        return account.getPostalCode();
+    }
+
+    public String getCountry() {
+        return account.getCountry();
+    }
+
+    public String getPhone() {
+        return account.getPhone();
+    }
+
+    @Override
+    public boolean isMigrated() {
+        return account.isMigrated();
+    }
+
+    @Override
+    public boolean isNotifiedForInvoices() {
+        return account.isNotifiedForInvoices();
+    }
+
+}
diff --git a/junction/src/main/java/com/ning/billing/junction/plumbing/api/BlockingAccountUserApi.java b/junction/src/main/java/com/ning/billing/junction/plumbing/api/BlockingAccountUserApi.java
new file mode 100644
index 0000000..45e3d5c
--- /dev/null
+++ b/junction/src/main/java/com/ning/billing/junction/plumbing/api/BlockingAccountUserApi.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2010-2011 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.junction.plumbing.api;
+
+import java.util.List;
+import java.util.UUID;
+
+import com.google.inject.Inject;
+import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.AccountApiException;
+import com.ning.billing.account.api.AccountData;
+import com.ning.billing.account.api.AccountEmail;
+import com.ning.billing.account.api.AccountUserApi;
+import com.ning.billing.account.api.MigrationAccountData;
+import com.ning.billing.junction.api.BlockingApi;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.customfield.CustomField;
+import com.ning.billing.util.glue.RealImplementation;
+import com.ning.billing.util.tag.TagDefinition;
+
+public class BlockingAccountUserApi implements AccountUserApi { 
+    private AccountUserApi userApi;
+    private BlockingApi blockingApi;
+
+    @Inject
+    public BlockingAccountUserApi(@RealImplementation AccountUserApi userApi, BlockingApi blockingApi) {
+        this.userApi = userApi;
+        this.blockingApi = blockingApi;
+    }
+
+    @Override
+    public Account createAccount(AccountData data, List<CustomField> fields, List<TagDefinition> tagDefinitions, CallContext context)
+            throws AccountApiException {
+        return userApi.createAccount(data, fields, tagDefinitions, context);
+    }
+
+    @Override
+    public Account migrateAccount(MigrationAccountData data, List<CustomField> fields, List<TagDefinition> tagDefinitions,
+            CallContext context) throws AccountApiException {
+        return userApi.migrateAccount(data, fields, tagDefinitions, context);
+    }
+
+    @Override
+    public void updateAccount(Account account, CallContext context) throws AccountApiException {
+        userApi.updateAccount(account, context);
+    }
+
+    @Override
+    public void updateAccount(String key, AccountData accountData, CallContext context) throws AccountApiException {
+        userApi.updateAccount(key, accountData, context);
+    }
+
+    @Override
+    public void updateAccount(UUID accountId, AccountData accountData, CallContext context) throws AccountApiException {
+        userApi.updateAccount(accountId, accountData, context);
+    }
+
+    @Override
+    public Account getAccountByKey(String key) throws AccountApiException {
+        return new BlockingAccount(userApi.getAccountByKey(key), blockingApi);
+    }
+
+    @Override
+    public Account getAccountById(UUID accountId) throws AccountApiException {
+        return userApi.getAccountById(accountId);
+    }
+
+    @Override
+    public List<Account> getAccounts() {
+        return userApi.getAccounts();
+    }
+
+    @Override
+    public UUID getIdFromKey(String externalKey) throws AccountApiException {
+        return userApi.getIdFromKey(externalKey);
+    }
+
+    @Override
+    public List<AccountEmail> getEmails(UUID accountId) {
+        return userApi.getEmails(accountId);
+    }
+
+    @Override
+    public void saveEmails(UUID accountId, List<AccountEmail> emails, CallContext context) {
+        userApi.saveEmails(accountId, emails, context);
+    }
+
+}
diff --git a/junction/src/main/java/com/ning/billing/junction/plumbing/api/BlockingEntitlementUserApi.java b/junction/src/main/java/com/ning/billing/junction/plumbing/api/BlockingEntitlementUserApi.java
new file mode 100644
index 0000000..9452146
--- /dev/null
+++ b/junction/src/main/java/com/ning/billing/junction/plumbing/api/BlockingEntitlementUserApi.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2010-2011 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.junction.plumbing.api;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+
+import com.google.inject.Inject;
+import com.ning.billing.ErrorCode;
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.entitlement.api.user.EntitlementUserApi;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.entitlement.api.user.SubscriptionStatusDryRun;
+import com.ning.billing.junction.api.Blockable;
+import com.ning.billing.junction.api.BlockingApi;
+import com.ning.billing.junction.api.BlockingApiException;
+import com.ning.billing.junction.block.BlockingChecker;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.glue.RealImplementation;
+
+public class BlockingEntitlementUserApi implements EntitlementUserApi {
+    private final EntitlementUserApi entitlementUserApi;
+    private final BlockingApi blockingApi;
+    private final BlockingChecker checker;
+
+    @Inject
+    public BlockingEntitlementUserApi(@RealImplementation EntitlementUserApi userApi, BlockingApi blockingApi, BlockingChecker checker) {
+        this.entitlementUserApi = userApi;
+        this.blockingApi = blockingApi;
+        this.checker = checker;
+    }
+
+    @Override
+    public SubscriptionBundle getBundleFromId(UUID id) throws EntitlementUserApiException {
+        SubscriptionBundle bundle = entitlementUserApi.getBundleFromId(id);
+        return new BlockingSubscriptionBundle(bundle, blockingApi);
+    }
+
+    @Override
+    public Subscription getSubscriptionFromId(UUID id) throws EntitlementUserApiException {
+        Subscription subscription = entitlementUserApi.getSubscriptionFromId(id);
+        return new BlockingSubscription(subscription, blockingApi, checker);
+    }
+
+    @Override
+    public SubscriptionBundle getBundleForKey(String bundleKey) throws EntitlementUserApiException {
+        SubscriptionBundle bundle = entitlementUserApi.getBundleForKey(bundleKey);
+        return new BlockingSubscriptionBundle(bundle, blockingApi);
+    }
+
+    @Override
+    public List<SubscriptionBundle> getBundlesForAccount(UUID accountId) {
+        List<SubscriptionBundle> result = new ArrayList<SubscriptionBundle>();
+        List<SubscriptionBundle> bundles = entitlementUserApi.getBundlesForAccount(accountId);
+        for(SubscriptionBundle bundle : bundles) {
+            result.add(new BlockingSubscriptionBundle(bundle, blockingApi));
+        }
+        return result;
+    }
+
+    @Override
+    public List<Subscription> getSubscriptionsForBundle(UUID bundleId) {
+        List<Subscription> result = new ArrayList<Subscription>();
+        List<Subscription> subscriptions = entitlementUserApi.getSubscriptionsForBundle(bundleId);
+        for(Subscription subscription : subscriptions) {
+            result.add(new BlockingSubscription(subscription, blockingApi, checker));
+        }
+        return result;
+    }
+
+    @Override
+    public List<Subscription> getSubscriptionsForKey(String bundleKey) {
+        List<Subscription> result = new ArrayList<Subscription>();
+        List<Subscription> subscriptions = entitlementUserApi.getSubscriptionsForKey(bundleKey);
+        for(Subscription subscription : subscriptions) {
+            result.add(new BlockingSubscription(subscription, blockingApi, checker));
+        }
+        return result;
+    }
+
+    @Override
+    public List<SubscriptionStatusDryRun> getDryRunChangePlanStatus(
+            UUID subscriptionId, String productName, DateTime requestedDate)
+            throws EntitlementUserApiException {
+        return entitlementUserApi.getDryRunChangePlanStatus(subscriptionId, productName, requestedDate);
+    }
+
+    @Override
+    public Subscription getBaseSubscription(UUID bundleId) throws EntitlementUserApiException {
+        return new BlockingSubscription(entitlementUserApi.getBaseSubscription(bundleId), blockingApi, checker);
+    }
+
+    @Override
+    public SubscriptionBundle createBundleForAccount(UUID accountId, String bundleKey, CallContext context)
+    throws EntitlementUserApiException {
+        try {
+            checker.checkBlockedChange(accountId, Blockable.Type.ACCOUNT);
+            return new BlockingSubscriptionBundle(entitlementUserApi.createBundleForAccount(accountId, bundleKey, context), blockingApi);
+        }catch (BlockingApiException e) {
+            throw new EntitlementUserApiException(e, e.getCode(), e.getMessage());
+        }
+    }
+
+    @Override
+    public Subscription createSubscription(UUID bundleId, PlanPhaseSpecifier spec, DateTime requestedDate,
+            CallContext context) throws EntitlementUserApiException {
+        try {
+            checker.checkBlockedChange(bundleId, Blockable.Type.SUBSCRIPTION_BUNDLE);
+            return new BlockingSubscription(entitlementUserApi.createSubscription(bundleId, spec, requestedDate, context), blockingApi, checker);
+        }catch (BlockingApiException e) {
+            throw new EntitlementUserApiException(e, e.getCode(), e.getMessage());
+        }
+    }
+
+    @Override
+    public DateTime getNextBillingDate(UUID account) {
+        return entitlementUserApi.getNextBillingDate(account);
+    }
+}
diff --git a/junction/src/main/java/com/ning/billing/junction/plumbing/api/BlockingSubscription.java b/junction/src/main/java/com/ning/billing/junction/plumbing/api/BlockingSubscription.java
new file mode 100644
index 0000000..3de3305
--- /dev/null
+++ b/junction/src/main/java/com/ning/billing/junction/plumbing/api/BlockingSubscription.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright 2010-2011 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.junction.plumbing.api;
+
+import java.util.List;
+import java.util.UUID;
+
+import com.ning.billing.util.dao.ObjectType;
+import com.ning.billing.util.tag.ControlTagType;
+import org.joda.time.DateTime;
+
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.catalog.api.PlanPhase;
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.catalog.api.PriceList;
+import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.api.user.SubscriptionEvent;
+import com.ning.billing.junction.api.BlockingApi;
+import com.ning.billing.junction.api.BlockingApiException;
+import com.ning.billing.junction.api.BlockingState;
+import com.ning.billing.junction.block.BlockingChecker;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.customfield.CustomField;
+import com.ning.billing.util.tag.Tag;
+import com.ning.billing.util.tag.TagDefinition;
+
+
+public class BlockingSubscription implements Subscription {
+    private final Subscription subscription;
+    private final BlockingApi blockingApi; 
+    private final BlockingChecker checker;
+    
+    private BlockingState blockingState = null;
+
+    public BlockingSubscription(Subscription subscription, BlockingApi blockingApi, BlockingChecker checker) {
+        this.subscription = subscription;
+        this.blockingApi = blockingApi;
+        this.checker = checker;
+    }
+
+    public List<Tag> getTagList() {
+        return subscription.getTagList();
+    }
+
+    @Override
+    public boolean hasTag(TagDefinition tagDefinition) {
+        return subscription.hasTag(tagDefinition);
+    }
+
+    public UUID getId() {
+        return subscription.getId();
+    }
+
+    public boolean hasTag(ControlTagType controlTagType) {
+        return subscription.hasTag(controlTagType);
+    }
+
+    public void addTag(TagDefinition definition) {
+        subscription.addTag(definition);
+    }
+
+    public String getFieldValue(String fieldName) {
+        return subscription.getFieldValue(fieldName);
+    }
+
+    public void addTags(List<Tag> tags) {
+        subscription.addTags(tags);
+    }
+
+    @Override
+    public void addTagsFromDefinitions(List<TagDefinition> tagDefinitions) {
+        subscription.addTagsFromDefinitions(tagDefinitions);
+    }
+
+    public void setFieldValue(String fieldName, String fieldValue) {
+        subscription.setFieldValue(fieldName, fieldValue);
+    }
+
+    public void clearTags() {
+        subscription.clearTags();
+    }
+
+    public void removeTag(TagDefinition definition) {
+        subscription.removeTag(definition);
+    }
+
+    public void saveFieldValue(String fieldName, String fieldValue, CallContext context) {
+        subscription.saveFieldValue(fieldName, fieldValue, context);
+    }
+
+    public boolean generateInvoice() {
+        return subscription.generateInvoice();
+    }
+
+    public boolean processPayment() {
+        return subscription.processPayment();
+    }
+
+    public List<CustomField> getFieldList() {
+        return subscription.getFieldList();
+    }
+
+    public void setFields(List<CustomField> fields) {
+        subscription.setFields(fields);
+    }
+
+    public void saveFields(List<CustomField> fields, CallContext context) {
+        subscription.saveFields(fields, context);
+    }
+
+    public void clearFields() {
+        subscription.clearFields();
+    }
+
+    public void clearPersistedFields(CallContext context) {
+        subscription.clearPersistedFields(context);
+    }
+
+    public ObjectType getObjectType() {
+        return subscription.getObjectType();
+    }
+
+    public boolean cancel(DateTime requestedDate, boolean eot, CallContext context) throws EntitlementUserApiException {
+        return subscription.cancel(requestedDate, eot, context);
+    }
+
+    public boolean uncancel(CallContext context) throws EntitlementUserApiException {
+        return subscription.uncancel(context);
+    }
+
+    public boolean changePlan(String productName, BillingPeriod term, String planSet, DateTime requestedDate,
+            CallContext context) throws EntitlementUserApiException {
+        try {
+            checker.checkBlockedChange(this);
+        } catch (BlockingApiException e) {
+            throw new EntitlementUserApiException(e, e.getCode(), e.getMessage()); 
+        }
+        return subscription.changePlan(productName, term, planSet, requestedDate, context);
+    }
+
+    public boolean recreate(PlanPhaseSpecifier spec, DateTime requestedDate, CallContext context)
+            throws EntitlementUserApiException {
+        return subscription.recreate(spec, requestedDate, context);
+    }
+
+    public UUID getBundleId() {
+        return subscription.getBundleId();
+    }
+
+    public SubscriptionState getState() {
+        return subscription.getState();
+    }
+
+    public DateTime getStartDate() {
+        return subscription.getStartDate();
+    }
+
+    public DateTime getEndDate() {
+        return subscription.getEndDate();
+    }
+
+    public Plan getCurrentPlan() {
+        return subscription.getCurrentPlan();
+    }
+
+    public PriceList getCurrentPriceList() {
+        return subscription.getCurrentPriceList();
+    }
+
+    public PlanPhase getCurrentPhase() {
+        return subscription.getCurrentPhase();
+    }
+
+    public DateTime getChargedThroughDate() {
+        return subscription.getChargedThroughDate();
+    }
+
+    public DateTime getPaidThroughDate() {
+        return subscription.getPaidThroughDate();
+    }
+
+    public ProductCategory getCategory() {
+        return subscription.getCategory();
+    }
+
+    public SubscriptionEvent getPendingTransition() {
+        return subscription.getPendingTransition();
+    }
+
+    public SubscriptionEvent getPreviousTransition() {
+        return subscription.getPreviousTransition();
+    }
+
+    public List<SubscriptionEvent> getBillingTransitions() {
+        return subscription.getBillingTransitions();
+    }
+
+    public BlockingState getBlockingState() {
+        if(blockingState == null) {
+            blockingState = blockingApi.getBlockingStateFor(this);
+        }
+        return blockingState;
+    }
+    
+    public Subscription getDelegateSubscription() {
+        return subscription;
+    }
+
+
+}
diff --git a/junction/src/main/java/com/ning/billing/junction/plumbing/api/BlockingSubscriptionBundle.java b/junction/src/main/java/com/ning/billing/junction/plumbing/api/BlockingSubscriptionBundle.java
new file mode 100644
index 0000000..fd23104
--- /dev/null
+++ b/junction/src/main/java/com/ning/billing/junction/plumbing/api/BlockingSubscriptionBundle.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2010-2011 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.junction.plumbing.api;
+
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.junction.api.BlockingApi;
+import com.ning.billing.junction.api.BlockingState;
+import com.ning.billing.overdue.OverdueState;
+
+public class BlockingSubscriptionBundle implements SubscriptionBundle {
+    private final SubscriptionBundle subscriptionBundle;
+    private final BlockingApi blockingApi; 
+    
+    private BlockingState blockingState = null;
+
+    public BlockingSubscriptionBundle(SubscriptionBundle subscriptionBundle, BlockingApi blockingApi) {
+        this.subscriptionBundle = subscriptionBundle;
+        this.blockingApi = blockingApi;
+    }
+
+    public UUID getAccountId() {
+        return subscriptionBundle.getAccountId();
+    }
+
+    public UUID getId() {
+        return subscriptionBundle.getId();
+    }
+
+    public DateTime getStartDate() {
+        return subscriptionBundle.getStartDate();
+    }
+
+    public String getKey() {
+        return subscriptionBundle.getKey();
+    }
+
+    public OverdueState<SubscriptionBundle> getOverdueState() {
+        return subscriptionBundle.getOverdueState();
+    }
+
+    @Override
+    public BlockingState getBlockingState() {
+        if(blockingState == null) {
+            blockingState = blockingApi.getBlockingStateFor(this);
+        }
+        return blockingState;
+    }
+
+}
diff --git a/junction/src/main/java/com/ning/billing/junction/plumbing/billing/BillCycleDayCalculator.java b/junction/src/main/java/com/ning/billing/junction/plumbing/billing/BillCycleDayCalculator.java
new file mode 100644
index 0000000..b1c5558
--- /dev/null
+++ b/junction/src/main/java/com/ning/billing/junction/plumbing/billing/BillCycleDayCalculator.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2010-2011 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.junction.plumbing.billing;
+
+import org.joda.time.DateTime;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.inject.Inject;
+import com.ning.billing.ErrorCode;
+import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.AccountApiException;
+import com.ning.billing.catalog.api.BillingAlignment;
+import com.ning.billing.catalog.api.Catalog;
+import com.ning.billing.catalog.api.CatalogApiException;
+import com.ning.billing.catalog.api.CatalogService;
+import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.catalog.api.PlanPhase;
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.catalog.api.Product;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
+import com.ning.billing.entitlement.api.user.EntitlementUserApi;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.entitlement.api.user.SubscriptionEvent;
+
+public class BillCycleDayCalculator {
+	private static final Logger log = LoggerFactory.getLogger(BillCycleDayCalculator.class);
+	
+	private final CatalogService catalogService;
+	private final EntitlementUserApi entitlementApi;
+
+	@Inject
+	public BillCycleDayCalculator(final CatalogService catalogService, final EntitlementUserApi entitlementApi) {
+		super();
+		this.catalogService = catalogService;
+		this.entitlementApi = entitlementApi;
+	}
+
+	protected int calculateBcd(SubscriptionBundle bundle, Subscription subscription, final SubscriptionEvent transition, final Account account)
+	throws CatalogApiException, AccountApiException, EntitlementUserApiException {
+
+	    Catalog catalog = catalogService.getFullCatalog();
+		
+	    Plan prevPlan = (transition.getPreviousPlan() != null) ? catalog.findPlan(transition.getPreviousPlan(), transition.getEffectiveTransitionTime(), transition.getSubscriptionStartDate()) : null;
+	    Plan nextPlan = (transition.getNextPlan() != null) ? catalog.findPlan(transition.getNextPlan(), transition.getEffectiveTransitionTime(), transition.getSubscriptionStartDate()) : null;	    
+		
+		Plan plan =  (transition.getTransitionType() != SubscriptionTransitionType.CANCEL) ? nextPlan : prevPlan;
+		Product product = plan.getProduct();
+
+        PlanPhase prevPhase = (transition.getPreviousPhase() != null) ? catalog.findPhase(transition.getPreviousPhase(), transition.getEffectiveTransitionTime(), transition.getSubscriptionStartDate()) : null;
+        PlanPhase nextPhase = (transition.getNextPhase() != null) ? catalog.findPhase(transition.getNextPhase(), transition.getEffectiveTransitionTime(), transition.getSubscriptionStartDate()) : null;
+        
+		PlanPhase phase = (transition.getTransitionType() != SubscriptionTransitionType.CANCEL) ? nextPhase : prevPhase;
+
+		BillingAlignment alignment = catalog.billingAlignment(
+		        new PlanPhaseSpecifier(product.getName(),
+		                product.getCategory(),
+		                phase.getBillingPeriod(),
+		                transition.getNextPriceList(),
+		                phase.getPhaseType()),
+		                transition.getRequestedTransitionTime());
+
+		int result = -1;
+		switch (alignment) {
+		case ACCOUNT :
+		    result = account.getBillCycleDay();
+		    if(result == 0) {
+		        result = calculateBcdFromSubscription(subscription, plan, account);
+		    }
+		    break;
+		case BUNDLE :
+		    Subscription baseSub = entitlementApi.getBaseSubscription(bundle.getId());
+		    Plan basePlan = baseSub.getCurrentPlan();
+		    result = calculateBcdFromSubscription(baseSub, basePlan, account);
+		    break;
+		case SUBSCRIPTION :
+		    result = calculateBcdFromSubscription(subscription, plan, account);
+		    break;
+		}
+		if(result == -1) {
+		    throw new CatalogApiException(ErrorCode.CAT_INVALID_BILLING_ALIGNMENT, alignment.toString());
+		}
+		return result;
+
+	}
+
+	private int calculateBcdFromSubscription(Subscription subscription, Plan plan, Account account) throws AccountApiException {
+		DateTime date = plan.dateOfFirstRecurringNonZeroCharge(subscription.getStartDate());
+		return date.toDateTime(account.getTimeZone()).getDayOfMonth();
+	}
+
+}
diff --git a/junction/src/main/java/com/ning/billing/junction/plumbing/billing/BlockingCalculator.java b/junction/src/main/java/com/ning/billing/junction/plumbing/billing/BlockingCalculator.java
new file mode 100644
index 0000000..999c5d6
--- /dev/null
+++ b/junction/src/main/java/com/ning/billing/junction/plumbing/billing/BlockingCalculator.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright 2010-2011 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.junction.plumbing.billing;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+
+import com.google.inject.Inject;
+import com.ning.billing.account.api.Account;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.catalog.api.PlanPhase;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
+import com.ning.billing.entitlement.api.billing.BillingEvent;
+import com.ning.billing.entitlement.api.billing.BillingModeType;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.junction.api.Blockable;
+import com.ning.billing.junction.api.BlockingApi;
+import com.ning.billing.junction.api.BlockingState;
+import com.ning.billing.junction.api.DefaultBlockingState;
+
+public class BlockingCalculator {
+    private final BlockingApi blockingApi;
+
+    protected static class DisabledDuration {
+        private final DateTime start;
+        private final DateTime end;
+
+        public DisabledDuration(DateTime start,DateTime end) {
+            this.start = start;
+            this.end = end;
+        }
+        public DateTime getStart() {
+            return start;
+        }
+        public DateTime getEnd() {
+            return end;
+        }
+
+    }
+
+    protected static class MergeEvent extends DefaultBlockingState {
+
+        public MergeEvent(DateTime timestamp) {
+            super(null,null,null,null,false,false,false,timestamp);
+        }
+
+    }
+
+    @Inject
+    public BlockingCalculator(BlockingApi blockingApi) {
+        this.blockingApi = blockingApi;
+    }
+
+    public void insertBlockingEvents(SortedSet<BillingEvent> billingEvents) {
+        if(billingEvents.size() <= 0) { return; }
+
+        Account account = billingEvents.first().getAccount();
+ 
+        Hashtable<UUID,List<Subscription>> bundleMap = createBundleSubscriptionMap(billingEvents);
+
+        SortedSet<BillingEvent> billingEventsToAdd = new TreeSet<BillingEvent>();
+        SortedSet<BillingEvent> billingEventsToRemove = new TreeSet<BillingEvent>();
+
+        for(UUID bundleId : bundleMap.keySet()) {
+            SortedSet<BlockingState> blockingEvents = blockingApi.getBlockingHistory(bundleId);
+            blockingEvents.addAll(blockingApi.getBlockingHistory(account.getId()));
+            List<DisabledDuration>  blockingDurations  = createBlockingDurations(blockingEvents); 
+
+            for (Subscription subscription: bundleMap.get(bundleId)) {
+                billingEventsToAdd.addAll(createNewEvents( blockingDurations, billingEvents, account, subscription));
+                billingEventsToRemove.addAll(eventsToRemove(blockingDurations, billingEvents, subscription));
+            }
+        }
+
+        for(BillingEvent eventToAdd: billingEventsToAdd ) {
+            billingEvents.add(eventToAdd);
+        }
+
+        for(BillingEvent eventToRemove : billingEventsToRemove) {
+            billingEvents.remove(eventToRemove);
+        }
+
+    }
+
+    protected SortedSet<BillingEvent> eventsToRemove(List<DisabledDuration> disabledDuration,
+            SortedSet<BillingEvent> billingEvents, Subscription subscription) {
+        SortedSet<BillingEvent> result = new TreeSet<BillingEvent>();
+
+        SortedSet<BillingEvent> filteredBillingEvents = filter(billingEvents, subscription);
+        for(DisabledDuration duration : disabledDuration) {
+            for(BillingEvent event : filteredBillingEvents) {
+                if(duration.getEnd() == null || event.getEffectiveDate().isBefore(duration.getEnd())) {
+                    if( event.getEffectiveDate().isAfter(duration.getStart()) ) { //between the pair
+                        result.add(event);
+                    }
+                } else { //after the last event of the pair no need to keep checking
+                    break;
+                }
+            }
+        }
+        return result;
+    }
+
+     protected SortedSet<BillingEvent> createNewEvents( List<DisabledDuration> disabledDuration, SortedSet<BillingEvent> billingEvents, Account account, Subscription subscription) {
+        SortedSet<BillingEvent> result = new TreeSet<BillingEvent>();
+        for(DisabledDuration duration : disabledDuration) {
+            BillingEvent precedingInitialEvent = precedingBillingEventForSubscription(duration.getStart(), billingEvents, subscription);
+            BillingEvent precedingFinalEvent = precedingBillingEventForSubscription(duration.getEnd(), billingEvents, subscription);
+
+            if(precedingInitialEvent != null) { // there is a preceding billing event
+                result.add(createNewDisableEvent(duration.getStart(), precedingInitialEvent));
+                if(duration.getEnd() != null) { // no second event in the pair means they are still disabled (no re-enable)
+                    result.add(createNewReenableEvent(duration.getEnd(), precedingFinalEvent));
+                }
+
+            } else if(precedingFinalEvent != null) { // can happen - e.g. phase event
+                //
+                // TODO: check with Jeff that this is going to do something sensible
+                //
+                result.add(createNewReenableEvent(duration.getEnd(), precedingFinalEvent));
+
+            } 
+
+            // N.B. if there's no precedingInitial and no precedingFinal then there's nothing to do
+        }
+        return result;
+    }
+
+    protected BillingEvent precedingBillingEventForSubscription(DateTime datetime, SortedSet<BillingEvent> billingEvents, Subscription subscription) { 
+        if(datetime == null) { //second of a pair can be null if there's no re-enabling
+            return null;
+        }
+
+        SortedSet<BillingEvent> filteredBillingEvents = filter(billingEvents, subscription);
+        BillingEvent result = filteredBillingEvents.first();
+
+        if(datetime.isBefore(result.getEffectiveDate())) {
+            //This case can happen, for example, if we have an add on and the bundle goes into disabled before the add on is created
+            return null;
+        }
+
+        for(BillingEvent event : filteredBillingEvents) {
+            if(event.getEffectiveDate().isAfter(datetime)) { // found it its the previous event
+                return result;
+            } else { // still looking
+                result = event;
+            }
+        }
+        return result;
+    }
+
+    protected SortedSet<BillingEvent> filter(SortedSet<BillingEvent> billingEvents, Subscription subscription) {
+        SortedSet<BillingEvent> result = new TreeSet<BillingEvent>();
+        for(BillingEvent event : billingEvents) {
+            if(event.getSubscription() == subscription) {
+                result.add(event);
+            }
+        }
+        return result;
+    }
+
+    protected BillingEvent createNewDisableEvent(DateTime odEventTime, BillingEvent previousEvent) {
+        final Account account = previousEvent.getAccount();
+        final int billCycleDay = previousEvent.getBillCycleDay();
+        final Subscription subscription = previousEvent.getSubscription();
+        final DateTime effectiveDate = odEventTime;
+        final PlanPhase planPhase = previousEvent.getPlanPhase();
+        final Plan plan = previousEvent.getPlan();
+        final BigDecimal fixedPrice = BigDecimal.ZERO;
+        final BigDecimal recurringPrice = BigDecimal.ZERO;
+        final Currency currency = previousEvent.getCurrency();
+        final String description = "";
+        final BillingModeType billingModeType = previousEvent.getBillingMode();
+        final BillingPeriod billingPeriod = previousEvent.getBillingPeriod();
+        final SubscriptionTransitionType type = SubscriptionTransitionType.CANCEL;
+        final Long totalOrdering = 0L; //TODO
+
+        return new DefaultBillingEvent(account, subscription, effectiveDate, plan, planPhase,
+                fixedPrice, recurringPrice, currency,
+                billingPeriod, billCycleDay, billingModeType,
+                description, totalOrdering, type);
+    }
+
+    protected BillingEvent createNewReenableEvent(DateTime odEventTime, BillingEvent previousEvent) {
+        final Account account = previousEvent.getAccount();
+        final int billCycleDay = previousEvent.getBillCycleDay();
+        final Subscription subscription = previousEvent.getSubscription();
+        final DateTime effectiveDate = odEventTime;
+        final PlanPhase planPhase = previousEvent.getPlanPhase();
+        final Plan plan = previousEvent.getPlan();
+        final BigDecimal fixedPrice = previousEvent.getFixedPrice();
+        final BigDecimal recurringPrice = previousEvent.getRecurringPrice();
+        final Currency currency = previousEvent.getCurrency();
+        final String description = "";
+        final BillingModeType billingModeType = previousEvent.getBillingMode();
+        final BillingPeriod billingPeriod = previousEvent.getBillingPeriod();
+        final SubscriptionTransitionType type = SubscriptionTransitionType.RE_CREATE;
+        final Long totalOrdering = 0L; //TODO
+
+        return new DefaultBillingEvent(account, subscription, effectiveDate, plan, planPhase,
+                fixedPrice, recurringPrice, currency,
+                billingPeriod, billCycleDay, billingModeType,
+                description, totalOrdering, type);
+    }
+
+    protected Hashtable<UUID,List<Subscription>> createBundleSubscriptionMap(SortedSet<BillingEvent> billingEvents) {
+        Hashtable<UUID,List<Subscription>> result = new Hashtable<UUID,List<Subscription>>();
+        for(BillingEvent event : billingEvents) {
+            UUID bundleId = event.getSubscription().getBundleId();
+            List<Subscription> subs = result.get(bundleId);
+            if(subs == null) {
+                subs = new ArrayList<Subscription>();
+                result.put(bundleId,subs);
+            }
+            if(!result.contains(event.getSubscription())) {
+                subs.add(event.getSubscription());        
+            }
+        }
+        return result;
+    }
+
+
+
+    protected List<DisabledDuration> createBlockingDurations(SortedSet<BlockingState> overdueBundleEvents) {
+        List<DisabledDuration> result = new ArrayList<BlockingCalculator.DisabledDuration>();
+        BlockingState first = null;
+
+        for(BlockingState e : overdueBundleEvents) {
+            if(e.isBlockBilling() && first == null) { // found a transition to disabled
+                first = e;
+            } else if(first != null && !e.isBlockBilling()) { // found a transition from disabled
+                result.add(new DisabledDuration(first.getTimestamp(), e.getTimestamp()));
+                first = null;
+            }
+        }
+
+        if(first != null) { // found a transition to disabled with no terminating event
+            result.add(new DisabledDuration(first.getTimestamp(), null));
+        }
+
+        return result;
+    }
+
+}
diff --git a/junction/src/main/java/com/ning/billing/junction/plumbing/billing/DefaultBillingApi.java b/junction/src/main/java/com/ning/billing/junction/plumbing/billing/DefaultBillingApi.java
new file mode 100644
index 0000000..8f88049
--- /dev/null
+++ b/junction/src/main/java/com/ning/billing/junction/plumbing/billing/DefaultBillingApi.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2010-2011 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.junction.plumbing.billing;
+
+import java.util.List;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.inject.Inject;
+import com.ning.billing.ErrorCode;
+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.account.api.MutableAccountData;
+import com.ning.billing.catalog.api.CatalogApiException;
+import com.ning.billing.catalog.api.CatalogService;
+import com.ning.billing.entitlement.api.billing.BillingEvent;
+import com.ning.billing.entitlement.api.billing.ChargeThruApi;
+import com.ning.billing.entitlement.api.billing.EntitlementBillingApiException;
+import com.ning.billing.entitlement.api.user.EntitlementUserApi;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.entitlement.api.user.SubscriptionEvent;
+import com.ning.billing.junction.api.BillingApi;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.callcontext.CallContextFactory;
+import com.ning.billing.util.callcontext.CallOrigin;
+import com.ning.billing.util.callcontext.UserType;
+
+public class DefaultBillingApi implements BillingApi {
+    private static final String API_USER_NAME = "Billing Api";
+    private static final Logger log = LoggerFactory.getLogger(DefaultBillingApi.class);
+    private final ChargeThruApi chargeThruApi;
+    private final CallContextFactory factory;
+    private final AccountUserApi accountApi;
+    private final BillCycleDayCalculator bcdCalculator;
+    private final EntitlementUserApi entitlementUserApi;
+    private final CatalogService catalogService;
+    private final BlockingCalculator blockCalculator;
+
+    @Inject
+    public DefaultBillingApi(final ChargeThruApi chargeThruApi, final CallContextFactory factory, final AccountUserApi accountApi, 
+            final BillCycleDayCalculator bcdCalculator, final EntitlementUserApi entitlementUserApi, final BlockingCalculator blockCalculator,
+            final CatalogService catalogService) {
+
+        this.chargeThruApi = chargeThruApi;
+        this.accountApi = accountApi;
+        this.bcdCalculator = bcdCalculator;
+        this.factory = factory;
+        this.entitlementUserApi = entitlementUserApi;
+        this.catalogService = catalogService;
+        this.blockCalculator = blockCalculator;
+    }
+
+    @Override
+    public SortedSet<BillingEvent> getBillingEventsForAccountAndUpdateAccountBCD(final UUID accountId) {
+        CallContext context = factory.createCallContext(API_USER_NAME, CallOrigin.INTERNAL, UserType.SYSTEM);
+
+        List<SubscriptionBundle> bundles = entitlementUserApi.getBundlesForAccount(accountId);
+        SortedSet<BillingEvent> result = new TreeSet<BillingEvent>();
+
+        try {
+            Account account = accountApi.getAccountById(accountId);
+            for (final SubscriptionBundle bundle: bundles) {
+                List<Subscription> subscriptions = entitlementUserApi.getSubscriptionsForBundle(bundle.getId());
+
+                for (final Subscription subscription: subscriptions) {
+                    for (final SubscriptionEvent transition : subscription.getBillingTransitions()) {
+                        try {
+                            int bcd = bcdCalculator.calculateBcd(bundle, subscription, transition, account);
+
+                            if(account.getBillCycleDay() == 0) {
+                                MutableAccountData modifiedData = account.toMutableAccountData();
+                                modifiedData.setBillCycleDay(bcd);
+                                accountApi.updateAccount(account.getExternalKey(), modifiedData, context);
+                            }
+
+                            BillingEvent event = new DefaultBillingEvent(account, transition, subscription, bcd, account.getCurrency(), catalogService.getFullCatalog());
+                            result.add(event);
+                        } catch (CatalogApiException e) {
+                            log.error("Failing to identify catalog components while creating BillingEvent from transition: " +
+                                    transition.getId().toString(), e);
+                        } catch (Exception e) {
+                            log.warn("Failed while getting BillingEvent", e);
+                        }
+
+                    }
+                }
+            }
+        } catch (AccountApiException e) {
+            log.warn("Failed while getting BillingEvent", e);
+        }
+        blockCalculator.insertBlockingEvents(result);
+        return result;
+    }
+
+
+    @Override
+    public UUID getAccountIdFromSubscriptionId(UUID subscriptionId) throws EntitlementBillingApiException {
+        UUID result = chargeThruApi.getAccountIdFromSubscriptionId(subscriptionId);
+        if (result == null) {
+            throw new EntitlementBillingApiException(ErrorCode.ENT_INVALID_SUBSCRIPTION_ID, subscriptionId.toString());
+        }
+        return result;
+    }
+
+    @Override
+    public void setChargedThroughDate(UUID subscriptionId, DateTime ctd, CallContext context) {
+        chargeThruApi.setChargedThroughDate(subscriptionId, ctd, context);
+    }
+}
diff --git a/junction/src/main/resources/com/ning/billing/junction/dao/BlockingStateSqlDao.sql.stg b/junction/src/main/resources/com/ning/billing/junction/dao/BlockingStateSqlDao.sql.stg
new file mode 100644
index 0000000..0bd6686
--- /dev/null
+++ b/junction/src/main/resources/com/ning/billing/junction/dao/BlockingStateSqlDao.sql.stg
@@ -0,0 +1,56 @@
+group BlockingStateSqlDao;
+
+getBlockingStateFor() ::= <<
+    select
+        id
+      , state
+      , type
+      , service
+      , block_change
+      , block_entitlement
+      , block_billing
+      , created_date   
+    from blocking_states
+    where id = :id 
+    order by created_date desc
+    limit 1
+    ;
+>>
+
+getBlockingHistoryFor() ::= <<
+    select
+       id
+      , state
+      , type
+      , service
+      , block_change
+      , block_entitlement
+      , block_billing
+      , created_date   
+    from blocking_states
+    where id = :id 
+    order by created_date asc
+    ;
+>>
+
+setBlockingState() ::= <<
+    insert into blocking_states (
+       id
+      , state
+      , type
+      , service
+      , block_change
+      , block_entitlement
+      , block_billing
+      , created_date   
+    ) values (
+        :id
+      , :state
+      , :type
+      , :service
+      , :block_change
+      , :block_entitlement
+      , :block_billing   
+      , :created_date 
+    );
+>>
\ No newline at end of file
diff --git a/junction/src/main/resources/com/ning/billing/junction/ddl.sql b/junction/src/main/resources/com/ning/billing/junction/ddl.sql
new file mode 100644
index 0000000..92b3437
--- /dev/null
+++ b/junction/src/main/resources/com/ning/billing/junction/ddl.sql
@@ -0,0 +1,15 @@
+
+DROP TABLE IF EXISTS blocking_states;
+CREATE TABLE blocking_states (
+    record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
+    id char(36) NOT NULL,
+    type varchar(20) NOT NULL,
+    state varchar(50) NOT NULL,
+    service varchar(20) NOT NULL,
+    block_change bool NOT NULL,
+    block_entitlement bool NOT NULL,
+    block_billing bool NOT NULL,
+    created_date datetime NOT NULL,
+    PRIMARY KEY(record_id)
+) ENGINE=innodb;
+CREATE INDEX blocking_states_id ON blocking_states(id);
\ No newline at end of file
diff --git a/junction/src/test/java/com/ning/billing/junction/api/blocking/TestBlockingApi.java b/junction/src/test/java/com/ning/billing/junction/api/blocking/TestBlockingApi.java
new file mode 100644
index 0000000..c6241d8
--- /dev/null
+++ b/junction/src/test/java/com/ning/billing/junction/api/blocking/TestBlockingApi.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2010-2011 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.junction.api.blocking;
+
+import java.io.IOException;
+import java.util.SortedSet;
+import java.util.UUID;
+
+import org.apache.commons.io.IOUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Guice;
+import org.testng.annotations.Test;
+
+import com.google.inject.Inject;
+import com.ning.billing.dbi.MysqlTestingHelper;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.junction.MockModule;
+import com.ning.billing.junction.api.Blockable;
+import com.ning.billing.junction.api.BlockingApi;
+import com.ning.billing.junction.api.BlockingState;
+import com.ning.billing.junction.api.DefaultBlockingState;
+import com.ning.billing.junction.dao.TestBlockingDao;
+import com.ning.billing.mock.BrainDeadProxyFactory;
+import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
+import com.ning.billing.mock.glue.MockEntitlementModule;
+import com.ning.billing.util.clock.ClockMock;
+
+@Guice(modules = { MockModule.class, MockEntitlementModule.class })
+public class TestBlockingApi {
+    private Logger log = LoggerFactory.getLogger(TestBlockingDao.class);
+    
+    @Inject
+    private MysqlTestingHelper helper;
+    
+    @Inject 
+    private BlockingApi api;
+    
+    @Inject
+    private ClockMock clock;
+
+    @BeforeClass(groups={"slow"})
+    public void setup() throws IOException {
+        log.info("Starting set up TestBlockingApi");
+
+        final String utilDdl = IOUtils.toString(TestBlockingDao.class.getResourceAsStream("/com/ning/billing/junction/ddl.sql"));
+
+        helper.startMysql();
+        helper.initDb(utilDdl);
+     
+    }
+    
+    @BeforeMethod(groups={"slow"})
+    public void clean() {       
+        helper.cleanupTable("blocking_states");
+        clock.resetDeltaFromReality();
+    }
+    
+    @AfterClass(groups = "slow")
+    public void stopMysql()
+    {
+        helper.stopMysql();
+    }
+
+    @Test(groups={"slow"}, enabled=true)
+    public void testApi() { 
+
+        UUID uuid = UUID.randomUUID();
+        String overdueStateName = "WayPassedItMan";
+        String service = "TEST";
+        
+        boolean blockChange = true;
+        boolean blockEntitlement = false;
+        boolean blockBilling = false;
+
+        BlockingState state1 = new DefaultBlockingState(uuid, overdueStateName, Blockable.Type.SUBSCRIPTION_BUNDLE, service, blockChange, blockEntitlement,blockBilling);
+        api.setBlockingState(state1);
+        clock.setDeltaFromReality(1000 * 3600 * 24);
+        
+        String overdueStateName2 = "NoReallyThisCantGoOn";
+        BlockingState state2 = new DefaultBlockingState(uuid, overdueStateName2, Blockable.Type.SUBSCRIPTION_BUNDLE, service, blockChange, blockEntitlement,blockBilling);
+        api.setBlockingState(state2);
+        
+        SubscriptionBundle bundle = BrainDeadProxyFactory.createBrainDeadProxyFor(SubscriptionBundle.class);
+        ((ZombieControl)bundle).addResult("getId", uuid);
+        
+        Assert.assertEquals(api.getBlockingStateFor(bundle).getStateName(), overdueStateName2);
+        Assert.assertEquals(api.getBlockingStateFor(bundle.getId()).getStateName(), overdueStateName2);
+        
+    }
+    
+    @Test(groups={"slow"}, enabled=true)
+    public void testApiHistory() throws Exception { 
+        UUID uuid = UUID.randomUUID();
+        String overdueStateName = "WayPassedItMan";
+        String service = "TEST";
+        
+        boolean blockChange = true;
+        boolean blockEntitlement = false;
+        boolean blockBilling = false;
+
+        BlockingState state1 = new DefaultBlockingState(uuid, overdueStateName, Blockable.Type.SUBSCRIPTION_BUNDLE, service, blockChange, blockEntitlement,blockBilling);
+        api.setBlockingState(state1);
+        
+        clock.setDeltaFromReality(1000 * 3600 * 24);
+
+        String overdueStateName2 = "NoReallyThisCantGoOn";
+        BlockingState state2 = new DefaultBlockingState(uuid, overdueStateName2, Blockable.Type.SUBSCRIPTION_BUNDLE, service, blockChange, blockEntitlement,blockBilling);
+        api.setBlockingState(state2);
+        
+        SubscriptionBundle bundle = BrainDeadProxyFactory.createBrainDeadProxyFor(SubscriptionBundle.class);
+        ((ZombieControl)bundle).addResult("getId", uuid);
+        
+     
+        SortedSet<BlockingState> history1 = api.getBlockingHistory(bundle);
+        SortedSet<BlockingState> history2 = api.getBlockingHistory(bundle.getId());
+        
+        Assert.assertEquals(history1.size(), 2);
+        Assert.assertEquals(history1.first().getStateName(), overdueStateName);
+        Assert.assertEquals(history1.last().getStateName(), overdueStateName2);
+        
+        Assert.assertEquals(history2.size(), 2);
+        Assert.assertEquals(history2.first().getStateName(), overdueStateName);
+        Assert.assertEquals(history2.last().getStateName(), overdueStateName2);
+       
+    }
+    
+}
diff --git a/junction/src/test/java/com/ning/billing/junction/blocking/MockBlockingChecker.java b/junction/src/test/java/com/ning/billing/junction/blocking/MockBlockingChecker.java
new file mode 100644
index 0000000..bf131ae
--- /dev/null
+++ b/junction/src/test/java/com/ning/billing/junction/blocking/MockBlockingChecker.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2010-2011 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.junction.blocking;
+
+import java.util.UUID;
+
+import com.ning.billing.junction.api.Blockable;
+import com.ning.billing.junction.api.Blockable.Type;
+import com.ning.billing.junction.api.BlockingApiException;
+import com.ning.billing.junction.block.BlockingChecker;
+
+public class MockBlockingChecker implements BlockingChecker {
+
+    @Override
+    public void checkBlockedChange(Blockable blockable) throws BlockingApiException {
+        // Intentionally blank
+        
+    }
+
+    @Override
+    public void checkBlockedEntitlement(Blockable blockable) throws BlockingApiException {
+     // Intentionally blank
+    }
+
+    @Override
+    public void checkBlockedBilling(Blockable blockable) throws BlockingApiException {
+     // Intentionally blank
+    }
+
+    @Override
+    public void checkBlockedChange(UUID bundleId, Type type) throws BlockingApiException {
+     // Intentionally blank 
+    }
+
+    @Override
+    public void checkBlockedEntitlement(UUID bundleId, Type type) throws BlockingApiException {
+     // Intentionally blank
+    }
+
+    @Override
+    public void checkBlockedBilling(UUID bundleId, Type type) throws BlockingApiException {
+     // Intentionally blank 
+    }
+
+}
diff --git a/junction/src/test/java/com/ning/billing/junction/blocking/TestBlockingChecker.java b/junction/src/test/java/com/ning/billing/junction/blocking/TestBlockingChecker.java
new file mode 100644
index 0000000..0bff4da
--- /dev/null
+++ b/junction/src/test/java/com/ning/billing/junction/blocking/TestBlockingChecker.java
@@ -0,0 +1,380 @@
+/*
+ * Copyright 2010-2011 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.junction.blocking;
+
+import java.util.SortedSet;
+import java.util.UUID;
+
+import org.apache.commons.lang.NotImplementedException;
+import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.ning.billing.account.api.Account;
+import com.ning.billing.entitlement.api.user.EntitlementUserApi;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.junction.api.Blockable;
+import com.ning.billing.junction.api.BlockingApiException;
+import com.ning.billing.junction.api.BlockingState;
+import com.ning.billing.junction.api.DefaultBlockingState;
+import com.ning.billing.junction.block.BlockingChecker;
+import com.ning.billing.junction.block.DefaultBlockingChecker;
+import com.ning.billing.junction.dao.BlockingStateDao;
+import com.ning.billing.mock.BrainDeadProxyFactory;
+import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
+import com.ning.billing.util.clock.Clock;
+
+
+public class TestBlockingChecker {
+   
+    private BlockingState bundleState;
+    private BlockingState subscriptionState;
+    private BlockingState accountState;
+    
+    private BlockingStateDao dao = new BlockingStateDao() {
+
+        @Override
+        public BlockingState getBlockingStateFor(Blockable blockable) {
+            if(blockable.getId() == account.getId()) {
+                return accountState;
+            } else  if(blockable.getId() == subscription.getId()) {
+                return subscriptionState;
+            } else {
+                return bundleState;
+            }
+        }
+
+        @Override
+        public BlockingState getBlockingStateFor(UUID blockableId) {
+            if(blockableId == account.getId()) {
+                return accountState;
+            } else  if(blockableId == subscription.getId()) {
+                return subscriptionState;
+            } else {
+                return bundleState;
+            }
+        }
+
+        @Override
+        public SortedSet<BlockingState> getBlockingHistoryFor(Blockable overdueable) {
+            throw new NotImplementedException();
+        }
+
+        @Override
+        public SortedSet<BlockingState> getBlockingHistoryFor(UUID overdueableId) {
+            throw new NotImplementedException();
+        }
+
+        @Override
+        public <T extends Blockable> void setBlockingState(BlockingState state, Clock clock) {
+            throw new NotImplementedException();
+        }
+        
+    };
+    private BlockingChecker checker;
+    private Subscription subscription;
+    private Account account;
+    private SubscriptionBundle bundle;
+    
+    @BeforeClass(groups={"fast"})
+    public void setup() {
+        account = BrainDeadProxyFactory.createBrainDeadProxyFor(Account.class);
+        ((ZombieControl) account).addResult("getId", UUID.randomUUID());
+        
+        bundle = BrainDeadProxyFactory.createBrainDeadProxyFor(SubscriptionBundle.class);
+        ((ZombieControl) bundle).addResult("getAccountId", account.getId());
+        ((ZombieControl) bundle).addResult("getId", UUID.randomUUID());
+        ((ZombieControl) bundle).addResult("getKey", "key");
+
+        subscription = BrainDeadProxyFactory.createBrainDeadProxyFor(Subscription.class);
+        ((ZombieControl) subscription).addResult("getId", UUID.randomUUID());
+        ((ZombieControl) subscription).addResult("getBundleId", bundle.getId());
+
+        
+        Injector i = Guice.createInjector(new AbstractModule() {
+
+            @Override
+            protected void configure() {
+                bind(BlockingChecker.class).to(DefaultBlockingChecker.class).asEagerSingleton();
+                
+                bind(BlockingStateDao.class).toInstance(dao);             
+                
+                EntitlementUserApi entitlementUserApi = BrainDeadProxyFactory.createBrainDeadProxyFor(EntitlementUserApi.class);
+                bind(EntitlementUserApi.class).toInstance(entitlementUserApi);
+                ((ZombieControl) entitlementUserApi).addResult("getBundleFromId",bundle);
+                
+            }
+            
+        });
+        checker = i.getInstance(BlockingChecker.class);
+    }
+
+
+    private void setStateBundle(boolean bC, boolean bE, boolean bB) {
+        bundleState = new DefaultBlockingState(UUID.randomUUID(), "state", Blockable.Type.SUBSCRIPTION_BUNDLE, "test-service", bC, bE,bB);
+        ((ZombieControl) bundle).addResult("getBlockingState", bundleState);
+    }
+
+    private void setStateAccount(boolean bC, boolean bE, boolean bB) {
+        accountState = new DefaultBlockingState(UUID.randomUUID(), "state", Blockable.Type.SUBSCRIPTION_BUNDLE, "test-service", bC, bE,bB);
+    }
+
+    private void setStateSubscription(boolean bC, boolean bE, boolean bB) {
+        subscriptionState = new DefaultBlockingState(UUID.randomUUID(), "state", Blockable.Type.SUBSCRIPTION_BUNDLE, "test-service", bC, bE,bB);
+        ((ZombieControl) subscription).addResult("getBlockingState", subscriptionState);
+    }
+
+    @Test(groups={"fast"}, enabled = true)
+    public void testSubscriptionChecker() throws Exception {
+        setStateAccount(false, false, false);
+        setStateBundle(false, false, false);
+        setStateSubscription(false, false, false);
+        checker.checkBlockedChange(subscription);
+        checker.checkBlockedEntitlement(subscription);
+        checker.checkBlockedBilling(subscription);
+        
+        //BLOCKED SUBSCRIPTION
+        setStateSubscription(true, false, false);
+        checker.checkBlockedEntitlement(subscription);
+        checker.checkBlockedBilling(subscription);
+        try {
+            checker.checkBlockedChange(subscription);
+            Assert.fail("The call should have been blocked!");
+        } catch (BlockingApiException e) {
+            //Expected behavior
+        }
+        
+        setStateSubscription(false, true, false);
+        checker.checkBlockedChange(subscription);
+        checker.checkBlockedBilling(subscription);
+        try {
+            checker.checkBlockedEntitlement(subscription);
+            Assert.fail("The call should have been blocked!");
+        } catch (BlockingApiException e) {
+            //Expected behavior
+        }
+        
+        setStateSubscription(false, false, true);
+        checker.checkBlockedChange(subscription);
+        checker.checkBlockedEntitlement(subscription);
+        try {
+           checker.checkBlockedBilling(subscription);
+           Assert.fail("The call should have been blocked!");
+        } catch (BlockingApiException e) {
+            //Expected behavior
+        }
+
+        //BLOCKED BUNDLE
+        setStateSubscription(false, false, false);
+        setStateBundle(true, false, false);
+        checker.checkBlockedEntitlement(subscription);
+        checker.checkBlockedBilling(subscription);
+        try {
+            checker.checkBlockedChange(subscription);
+            Assert.fail("The call should have been blocked!");
+        } catch (BlockingApiException e) {
+            //Expected behavior
+        }
+        
+        setStateBundle(false, true, false);
+        checker.checkBlockedChange(subscription);
+        checker.checkBlockedBilling(subscription);
+        try {
+            checker.checkBlockedEntitlement(subscription);
+            Assert.fail("The call should have been blocked!");
+        } catch (BlockingApiException e) {
+            //Expected behavior
+        }
+        
+        setStateBundle(false, false, true);
+        checker.checkBlockedChange(subscription);
+        checker.checkBlockedEntitlement(subscription);
+        try {
+           checker.checkBlockedBilling(subscription);
+           Assert.fail("The call should have been blocked!");
+        } catch (BlockingApiException e) {
+            //Expected behavior
+        }
+        
+        
+        //BLOCKED ACCOUNT
+        setStateSubscription(false, false, false);
+        setStateBundle(false, false, false);
+        setStateAccount(true, false, false);
+        checker.checkBlockedEntitlement(subscription);
+        checker.checkBlockedBilling(subscription);
+        try {
+            checker.checkBlockedChange(subscription);
+            Assert.fail("The call should have been blocked!");
+        } catch (BlockingApiException e) {
+            //Expected behavior
+        }
+        
+        setStateAccount(false, true, false);
+        checker.checkBlockedChange(subscription);
+        checker.checkBlockedBilling(subscription);
+        try {
+            checker.checkBlockedEntitlement(subscription);
+            Assert.fail("The call should have been blocked!");
+        } catch (BlockingApiException e) {
+            //Expected behavior
+        }
+        
+        setStateAccount(false, false, true);
+        checker.checkBlockedChange(subscription);
+        checker.checkBlockedEntitlement(subscription);
+        try {
+           checker.checkBlockedBilling(subscription);
+           Assert.fail("The call should have been blocked!");
+        } catch (BlockingApiException e) {
+            //Expected behavior
+        }
+        
+        
+    }
+    
+    @Test(groups={"fast"}, enabled = true)
+    public void testBundleChecker() throws Exception {
+        setStateAccount(false, false, false);
+        setStateBundle(false, false, false);
+        setStateSubscription(false, false, false);
+        checker.checkBlockedChange(bundle);
+        checker.checkBlockedEntitlement(bundle);
+        checker.checkBlockedBilling(bundle);
+
+        //BLOCKED BUNDLE
+        setStateSubscription(false, false, false);
+        setStateBundle(true, false, false);
+        checker.checkBlockedEntitlement(bundle);
+        checker.checkBlockedBilling(bundle);
+        try {
+            checker.checkBlockedChange(bundle);
+            Assert.fail("The call should have been blocked!");
+        } catch (BlockingApiException e) {
+            //Expected behavior
+        }
+        
+        setStateBundle(false, true, false);
+        checker.checkBlockedChange(bundle);
+        checker.checkBlockedBilling(bundle);
+        try {
+            checker.checkBlockedEntitlement(bundle);
+            Assert.fail("The call should have been blocked!");
+        } catch (BlockingApiException e) {
+            //Expected behavior
+        }
+        
+        setStateBundle(false, false, true);
+        checker.checkBlockedChange(bundle);
+        checker.checkBlockedEntitlement(bundle);
+        try {
+           checker.checkBlockedBilling(bundle);
+           Assert.fail("The call should have been blocked!");
+        } catch (BlockingApiException e) {
+            //Expected behavior
+        }
+        
+        
+        //BLOCKED ACCOUNT
+        setStateSubscription(false, false, false);
+        setStateBundle(false, false, false);
+        setStateAccount(true, false, false);
+        checker.checkBlockedEntitlement(bundle);
+        checker.checkBlockedBilling(bundle);
+        try {
+            checker.checkBlockedChange(bundle);
+            Assert.fail("The call should have been blocked!");
+        } catch (BlockingApiException e) {
+            //Expected behavior
+        }
+        
+        setStateAccount(false, true, false);
+        checker.checkBlockedChange(bundle);
+        checker.checkBlockedBilling(bundle);
+        try {
+            checker.checkBlockedEntitlement(bundle);
+            Assert.fail("The call should have been blocked!");
+        } catch (BlockingApiException e) {
+            //Expected behavior
+        }
+        
+        setStateAccount(false, false, true);
+        checker.checkBlockedChange(bundle);
+        checker.checkBlockedEntitlement(bundle);
+        try {
+           checker.checkBlockedBilling(bundle);
+           Assert.fail("The call should have been blocked!");
+        } catch (BlockingApiException e) {
+            //Expected behavior
+        }
+        
+  
+    }
+    
+    @Test(groups={"fast"}, enabled = true)
+    public void testAccountChecker() throws Exception {
+        setStateAccount(false, false, false);
+        setStateBundle(false, false, false);
+        setStateSubscription(false, false, false);
+        checker.checkBlockedChange(account);
+        checker.checkBlockedEntitlement(account);
+        checker.checkBlockedBilling(account);
+
+        //BLOCKED ACCOUNT
+        setStateSubscription(false, false, false);
+        setStateBundle(false, false, false);
+        setStateAccount(true, false, false);
+        checker.checkBlockedEntitlement(account);
+        checker.checkBlockedBilling(account);
+        try {
+            checker.checkBlockedChange(account);
+            Assert.fail("The call should have been blocked!");
+        } catch (BlockingApiException e) {
+            //Expected behavior
+        }
+        
+        setStateAccount(false, true, false);
+        checker.checkBlockedChange(account);
+        checker.checkBlockedBilling(account);
+        try {
+            checker.checkBlockedEntitlement(account);
+            Assert.fail("The call should have been blocked!");
+        } catch (BlockingApiException e) {
+            //Expected behavior
+        }
+        
+        setStateAccount(false, false, true);
+        checker.checkBlockedChange(account);
+        checker.checkBlockedEntitlement(account);
+        try {
+           checker.checkBlockedBilling(account);
+           Assert.fail("The call should have been blocked!");
+        } catch (BlockingApiException e) {
+            //Expected behavior
+        }
+        
+
+        
+    }
+    
+
+     
+}
diff --git a/junction/src/test/java/com/ning/billing/junction/dao/TestBlockingDao.java b/junction/src/test/java/com/ning/billing/junction/dao/TestBlockingDao.java
new file mode 100644
index 0000000..8e02e55
--- /dev/null
+++ b/junction/src/test/java/com/ning/billing/junction/dao/TestBlockingDao.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2010-2011 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.junction.dao;
+
+import java.io.IOException;
+import java.util.SortedSet;
+import java.util.UUID;
+
+import org.apache.commons.io.IOUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Guice;
+import org.testng.annotations.Test;
+
+import com.google.inject.Inject;
+import com.ning.billing.dbi.MysqlTestingHelper;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.junction.MockModule;
+import com.ning.billing.junction.api.Blockable;
+import com.ning.billing.junction.api.BlockingState;
+import com.ning.billing.junction.api.DefaultBlockingState;
+import com.ning.billing.mock.BrainDeadProxyFactory;
+import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
+import com.ning.billing.mock.glue.MockEntitlementModule;
+import com.ning.billing.util.clock.ClockMock;
+
+@Guice(modules = {MockModule.class, MockEntitlementModule.class})
+public class TestBlockingDao {
+    private Logger log = LoggerFactory.getLogger(TestBlockingDao.class);
+    
+    @Inject
+    private MysqlTestingHelper helper;
+    
+    @Inject
+    private BlockingStateDao dao;
+
+    @BeforeClass(groups={"slow"})
+    public void setup() throws IOException {
+        log.info("Starting set up TestBlockingDao");
+
+        final String utilDdl = IOUtils.toString(TestBlockingDao.class.getResourceAsStream("/com/ning/billing/junction/ddl.sql"));
+
+        helper.startMysql();
+        helper.initDb(utilDdl);
+
+    }
+    
+    @AfterClass(groups = "slow")
+    public void stopMysql()
+    {
+        if (helper != null) {
+            helper.stopMysql();
+        }
+    }
+
+    @Test(groups={"slow"}, enabled=true)
+    public void testDao() { 
+        ClockMock clock = new ClockMock();
+        UUID uuid = UUID.randomUUID();
+        String overdueStateName = "WayPassedItMan";
+        String service = "TEST";
+        
+        boolean blockChange = true;
+        boolean blockEntitlement = false;
+        boolean blockBilling = false;
+
+        BlockingState state1 = new DefaultBlockingState(uuid, overdueStateName, Blockable.Type.SUBSCRIPTION_BUNDLE, service, blockChange, blockEntitlement,blockBilling);
+        dao.setBlockingState(state1, clock);
+        clock.setDeltaFromReality(1000 * 3600 * 24);
+        
+        String overdueStateName2 = "NoReallyThisCantGoOn";
+        BlockingState state2 = new DefaultBlockingState(uuid, overdueStateName2, Blockable.Type.SUBSCRIPTION_BUNDLE, service, blockChange, blockEntitlement,blockBilling);
+        dao.setBlockingState(state2, clock);
+        
+        SubscriptionBundle bundle = BrainDeadProxyFactory.createBrainDeadProxyFor(SubscriptionBundle.class);
+        ((ZombieControl)bundle).addResult("getId", uuid);
+        
+        Assert.assertEquals(dao.getBlockingStateFor(bundle).getStateName(), state2.getStateName());
+        Assert.assertEquals(dao.getBlockingStateFor(bundle.getId()).getStateName(), overdueStateName2);
+        
+    }
+    
+    @Test(groups={"slow"}, enabled=true)
+    public void testDaoHistory() throws Exception { 
+        ClockMock clock = new ClockMock();
+        UUID uuid = UUID.randomUUID();
+        String overdueStateName = "WayPassedItMan";
+        String service = "TEST";
+        
+        boolean blockChange = true;
+        boolean blockEntitlement = false;
+        boolean blockBilling = false;
+
+        BlockingState state1 = new DefaultBlockingState(uuid, overdueStateName, Blockable.Type.SUBSCRIPTION_BUNDLE, service, blockChange, blockEntitlement,blockBilling);
+        dao.setBlockingState(state1, clock);
+        clock.setDeltaFromReality(1000 * 3600 * 24);
+        
+        String overdueStateName2 = "NoReallyThisCantGoOn";
+        BlockingState state2 = new DefaultBlockingState(uuid, overdueStateName2, Blockable.Type.SUBSCRIPTION_BUNDLE, service, blockChange, blockEntitlement,blockBilling);
+        dao.setBlockingState(state2, clock);
+        
+        SubscriptionBundle bundle = BrainDeadProxyFactory.createBrainDeadProxyFor(SubscriptionBundle.class);
+        ((ZombieControl)bundle).addResult("getId", uuid);
+        
+     
+        SortedSet<BlockingState> history1 = dao.getBlockingHistoryFor(bundle);
+        SortedSet<BlockingState> history2 = dao.getBlockingHistoryFor(bundle.getId());
+        
+        Assert.assertEquals(history1.size(), 2);
+        Assert.assertEquals(history1.first().getStateName(), overdueStateName);
+        Assert.assertEquals(history1.last().getStateName(), overdueStateName2);
+        
+        Assert.assertEquals(history2.size(), 2);
+        Assert.assertEquals(history2.first().getStateName(), overdueStateName);
+        Assert.assertEquals(history2.last().getStateName(), overdueStateName2);
+       
+    }
+    
+}
diff --git a/junction/src/test/java/com/ning/billing/junction/MockBlockingModule.java b/junction/src/test/java/com/ning/billing/junction/MockBlockingModule.java
new file mode 100644
index 0000000..75814aa
--- /dev/null
+++ b/junction/src/test/java/com/ning/billing/junction/MockBlockingModule.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2010-2011 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.junction;
+
+import com.google.inject.AbstractModule;
+import com.ning.billing.junction.api.BlockingApi;
+import com.ning.billing.junction.dao.BlockingStateDao;
+import com.ning.billing.mock.BrainDeadProxyFactory;
+import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
+
+public class MockBlockingModule extends AbstractModule {
+    public static final String CLEAR_STATE="Clear";
+
+    @Override
+    protected void configure() {
+        BlockingApi BlockingApi = BrainDeadProxyFactory.createBrainDeadProxyFor(BlockingApi.class);
+        ((ZombieControl) BlockingApi).addResult("getOverdueStateNameFor", MockBlockingModule.CLEAR_STATE);
+        bind(BlockingStateDao.class).toInstance(BrainDeadProxyFactory.createBrainDeadProxyFor(BlockingStateDao.class));
+        bind(BlockingApi.class).toInstance(BlockingApi);
+    }
+}
diff --git a/junction/src/test/java/com/ning/billing/junction/plumbing/billing/MockSubscription.java b/junction/src/test/java/com/ning/billing/junction/plumbing/billing/MockSubscription.java
new file mode 100644
index 0000000..8c85596
--- /dev/null
+++ b/junction/src/test/java/com/ning/billing/junction/plumbing/billing/MockSubscription.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright 2010-2011 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.junction.plumbing.billing;
+
+import java.util.List;
+import java.util.UUID;
+
+import com.ning.billing.util.dao.ObjectType;
+import org.joda.time.DateTime;
+
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.catalog.api.PlanPhase;
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.catalog.api.PriceList;
+import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.api.user.SubscriptionEvent;
+import com.ning.billing.junction.api.BlockingState;
+import com.ning.billing.mock.BrainDeadProxyFactory;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.customfield.CustomField;
+import com.ning.billing.util.tag.ControlTagType;
+import com.ning.billing.util.tag.Tag;
+import com.ning.billing.util.tag.TagDefinition;
+
+
+public class MockSubscription implements Subscription {
+    Subscription sub = BrainDeadProxyFactory.createBrainDeadProxyFor(Subscription.class);
+
+    public List<Tag> getTagList() {
+        return sub.getTagList();
+    }
+
+    public UUID getId() {
+        return sub.getId();
+    }
+
+    public boolean hasTag(TagDefinition tagDefinition) {
+        return sub.hasTag(tagDefinition);
+    }
+
+    public String getFieldValue(String fieldName) {
+        return sub.getFieldValue(fieldName);
+    }
+
+    public boolean hasTag(ControlTagType controlTagType) {
+        return sub.hasTag(controlTagType);
+    }
+
+    public void setFieldValue(String fieldName, String fieldValue) {
+        sub.setFieldValue(fieldName, fieldValue);
+    }
+
+    public void addTag(TagDefinition definition) {
+        sub.addTag(definition);
+    }
+
+    public void addTags(List<Tag> tags) {
+        sub.addTags(tags);
+    }
+
+    public void saveFieldValue(String fieldName, String fieldValue, CallContext context) {
+        sub.saveFieldValue(fieldName, fieldValue, context);
+    }
+
+    public void addTagsFromDefinitions(List<TagDefinition> tagDefinitions) {
+        sub.addTagsFromDefinitions(tagDefinitions);
+    }
+
+    public List<CustomField> getFieldList() {
+        return sub.getFieldList();
+    }
+
+    public void clearTags() {
+        sub.clearTags();
+    }
+
+    public void setFields(List<CustomField> fields) {
+        sub.setFields(fields);
+    }
+
+    public void removeTag(TagDefinition definition) {
+        sub.removeTag(definition);
+    }
+
+    public void saveFields(List<CustomField> fields, CallContext context) {
+        sub.saveFields(fields, context);
+    }
+
+    public boolean generateInvoice() {
+        return sub.generateInvoice();
+    }
+
+    public boolean processPayment() {
+        return sub.processPayment();
+    }
+
+    public void clearFields() {
+        sub.clearFields();
+    }
+
+    public void clearPersistedFields(CallContext context) {
+        sub.clearPersistedFields(context);
+    }
+
+    @Override
+    public ObjectType getObjectType() {
+        return sub.getObjectType();
+    }
+
+    public boolean cancel(DateTime requestedDate, boolean eot, CallContext context) throws EntitlementUserApiException {
+        return sub.cancel(requestedDate, eot, context);
+    }
+
+    public boolean uncancel(CallContext context) throws EntitlementUserApiException {
+        return sub.uncancel(context);
+    }
+
+    public boolean changePlan(String productName, BillingPeriod term, String planSet, DateTime requestedDate,
+            CallContext context) throws EntitlementUserApiException {
+        return sub.changePlan(productName, term, planSet, requestedDate, context);
+    }
+
+    public boolean recreate(PlanPhaseSpecifier spec, DateTime requestedDate, CallContext context)
+            throws EntitlementUserApiException {
+        return sub.recreate(spec, requestedDate, context);
+    }
+
+    public UUID getBundleId() {
+        return sub.getBundleId();
+    }
+
+    public SubscriptionState getState() {
+        return sub.getState();
+    }
+
+    public DateTime getStartDate() {
+        return sub.getStartDate();
+    }
+
+    public DateTime getEndDate() {
+        return sub.getEndDate();
+    }
+
+    public Plan getCurrentPlan() {
+        return sub.getCurrentPlan();
+    }
+
+    public BlockingState getBlockingState() {
+        return sub.getBlockingState();
+    }
+
+    public PriceList getCurrentPriceList() {
+        return sub.getCurrentPriceList();
+    }
+
+    public PlanPhase getCurrentPhase() {
+        return sub.getCurrentPhase();
+    }
+
+    public DateTime getChargedThroughDate() {
+        return sub.getChargedThroughDate();
+    }
+
+    public DateTime getPaidThroughDate() {
+        return sub.getPaidThroughDate();
+    }
+
+    public ProductCategory getCategory() {
+        return sub.getCategory();
+    }
+
+    public SubscriptionEvent getPendingTransition() {
+        return sub.getPendingTransition();
+    }
+
+    public SubscriptionEvent getPreviousTransition() {
+        return sub.getPreviousTransition();
+    }
+
+    public List<SubscriptionEvent> getBillingTransitions() {
+        return sub.getBillingTransitions();
+    }
+    
+    
+}
diff --git a/junction/src/test/java/com/ning/billing/junction/plumbing/billing/MockSubscriptionEvent.java b/junction/src/test/java/com/ning/billing/junction/plumbing/billing/MockSubscriptionEvent.java
new file mode 100644
index 0000000..cbba255
--- /dev/null
+++ b/junction/src/test/java/com/ning/billing/junction/plumbing/billing/MockSubscriptionEvent.java
@@ -0,0 +1,333 @@
+/* 
+ * Copyright 2010-2011 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.junction.plumbing.billing;
+
+import java.util.UUID;
+
+import org.codehaus.jackson.annotate.JsonCreator;
+import org.codehaus.jackson.annotate.JsonIgnore;
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.joda.time.DateTime;
+
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
+import com.ning.billing.entitlement.api.user.Subscription.SubscriptionState;
+import com.ning.billing.entitlement.api.user.SubscriptionEvent;
+import com.ning.billing.util.bus.BusEvent.BusEventType;
+
+public class MockSubscriptionEvent implements SubscriptionEvent {
+
+    private final Long totalOrdering;
+    private final UUID subscriptionId;
+    private final UUID bundleId;
+    private final UUID eventId;
+    private final DateTime requestedTransitionTime;
+    private final DateTime effectiveTransitionTime;
+    private final SubscriptionState previousState;
+    private final String previousPriceList;
+    private final String previousPlan;
+    private final String previousPhase;
+    private final SubscriptionState nextState;
+    private final String nextPriceList;
+    private final String nextPlan;
+    private final String nextPhase;
+    private final Integer remainingEventsForUserOperation;
+    private final UUID userToken;
+    private final SubscriptionTransitionType transitionType;
+
+    private final DateTime startDate;
+   
+    @JsonCreator
+    public MockSubscriptionEvent(@JsonProperty("eventId") UUID eventId,
+            @JsonProperty("subscriptionId") UUID subscriptionId,
+            @JsonProperty("bundleId") UUID bundleId,
+            @JsonProperty("requestedTransitionTime") DateTime requestedTransitionTime,
+            @JsonProperty("effectiveTransitionTime") DateTime effectiveTransitionTime,
+            @JsonProperty("previousState") SubscriptionState previousState,
+            @JsonProperty("previousPlan") String previousPlan,
+            @JsonProperty("previousPhase") String previousPhase,
+            @JsonProperty("previousPriceList") String previousPriceList,
+            @JsonProperty("nextState") SubscriptionState nextState,
+            @JsonProperty("nextPlan") String nextPlan,
+            @JsonProperty("nextPhase") String nextPhase,
+            @JsonProperty("nextPriceList") String nextPriceList,
+            @JsonProperty("totalOrdering") Long totalOrdering,
+            @JsonProperty("userToken") UUID userToken,
+            @JsonProperty("transitionType") SubscriptionTransitionType transitionType,
+            @JsonProperty("remainingEventsForUserOperation") Integer remainingEventsForUserOperation,
+            @JsonProperty("startDate") DateTime startDate) {
+        super();
+        this.eventId = eventId;
+        this.subscriptionId = subscriptionId;
+        this.bundleId = bundleId;
+        this.requestedTransitionTime = requestedTransitionTime;
+        this.effectiveTransitionTime = effectiveTransitionTime;
+        this.previousState = previousState;
+        this.previousPriceList = previousPriceList;
+        this.previousPlan = previousPlan;
+        this.previousPhase = previousPhase;
+        this.nextState = nextState;
+        this.nextPlan = nextPlan;
+        this.nextPriceList = nextPriceList;
+        this.nextPhase = nextPhase;
+        this.totalOrdering = totalOrdering;
+        this.userToken = userToken;
+        this.transitionType = transitionType;
+        this.remainingEventsForUserOperation = remainingEventsForUserOperation;
+        this.startDate = startDate;
+    }
+    
+    @JsonIgnore
+    @Override
+    public BusEventType getBusEventType() {
+        return BusEventType.SUBSCRIPTION_TRANSITION;
+    }
+
+    @JsonProperty("eventId")
+    @Override
+    public UUID getId() {
+        return eventId;
+    }
+
+    @Override
+    public UUID getSubscriptionId() {
+        return subscriptionId;
+    }
+
+    @Override
+    public UUID getBundleId() {
+        return bundleId;
+    }
+
+
+    @Override
+    public SubscriptionState getPreviousState() {
+        return previousState;
+    }
+
+    @Override
+    public String getPreviousPlan() {
+        return previousPlan;
+    }
+
+    @Override
+    public String getPreviousPhase() {
+        return previousPhase;
+    }
+
+    @Override
+    public String getNextPlan() {
+        return nextPlan;
+    }
+
+    @Override
+    public String getNextPhase() {
+        return nextPhase;
+    }
+
+    @Override
+    public SubscriptionState getNextState() {
+        return nextState;
+    }
+
+
+    @Override
+    public String getPreviousPriceList() {
+        return previousPriceList;
+    }
+
+    @Override
+    public String getNextPriceList() {
+        return nextPriceList;
+    }
+    
+    @Override
+    public UUID getUserToken() {
+        return userToken;
+    }
+    
+    @Override
+    public Integer getRemainingEventsForUserOperation() {
+        return remainingEventsForUserOperation;
+    }
+
+
+    @Override
+    public DateTime getRequestedTransitionTime() {
+        return requestedTransitionTime;
+    }
+
+    @Override
+    public DateTime getEffectiveTransitionTime() {
+        return effectiveTransitionTime;
+    }
+
+    @Override
+    public Long getTotalOrdering() {
+        return totalOrdering;
+    }
+
+    @Override
+    public SubscriptionTransitionType getTransitionType() {
+        return transitionType;
+    }
+    
+    @JsonProperty("startDate")
+    @Override
+    public DateTime getSubscriptionStartDate() {
+        return startDate;
+    }
+
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result
+                + ((bundleId == null) ? 0 : bundleId.hashCode());
+        result = prime
+                * result
+                + ((effectiveTransitionTime == null) ? 0
+                        : effectiveTransitionTime.hashCode());
+        result = prime * result + ((eventId == null) ? 0 : eventId.hashCode());
+        result = prime * result
+                + ((nextPhase == null) ? 0 : nextPhase.hashCode());
+        result = prime * result
+                + ((nextPlan == null) ? 0 : nextPlan.hashCode());
+        result = prime * result
+                + ((nextPriceList == null) ? 0 : nextPriceList.hashCode());
+        result = prime * result
+                + ((nextState == null) ? 0 : nextState.hashCode());
+        result = prime * result
+                + ((previousPhase == null) ? 0 : previousPhase.hashCode());
+        result = prime * result
+                + ((previousPlan == null) ? 0 : previousPlan.hashCode());
+        result = prime
+                * result
+                + ((previousPriceList == null) ? 0 : previousPriceList
+                        .hashCode());
+        result = prime * result
+                + ((previousState == null) ? 0 : previousState.hashCode());
+        result = prime
+                * result
+                + ((remainingEventsForUserOperation == null) ? 0
+                        : remainingEventsForUserOperation.hashCode());
+        result = prime
+                * result
+                + ((requestedTransitionTime == null) ? 0
+                        : requestedTransitionTime.hashCode());
+        result = prime * result
+                + ((subscriptionId == null) ? 0 : subscriptionId.hashCode());
+        result = prime * result
+                + ((totalOrdering == null) ? 0 : totalOrdering.hashCode());
+        result = prime * result
+                + ((transitionType == null) ? 0 : transitionType.hashCode());
+        result = prime * result
+                + ((userToken == null) ? 0 : userToken.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        MockSubscriptionEvent other = (MockSubscriptionEvent) obj;
+        if (bundleId == null) {
+            if (other.bundleId != null)
+                return false;
+        } else if (!bundleId.equals(other.bundleId))
+            return false;
+        if (effectiveTransitionTime == null) {
+            if (other.effectiveTransitionTime != null)
+                return false;
+        } else if (effectiveTransitionTime
+                .compareTo(other.effectiveTransitionTime) != 0)
+            return false;
+        if (eventId == null) {
+            if (other.eventId != null)
+                return false;
+        } else if (!eventId.equals(other.eventId))
+            return false;
+        if (nextPhase == null) {
+            if (other.nextPhase != null)
+                return false;
+        } else if (!nextPhase.equals(other.nextPhase))
+            return false;
+        if (nextPlan == null) {
+            if (other.nextPlan != null)
+                return false;
+        } else if (!nextPlan.equals(other.nextPlan))
+            return false;
+        if (nextPriceList == null) {
+            if (other.nextPriceList != null)
+                return false;
+        } else if (!nextPriceList.equals(other.nextPriceList))
+            return false;
+        if (nextState != other.nextState)
+            return false;
+        if (previousPhase == null) {
+            if (other.previousPhase != null)
+                return false;
+        } else if (!previousPhase.equals(other.previousPhase))
+            return false;
+        if (previousPlan == null) {
+            if (other.previousPlan != null)
+                return false;
+        } else if (!previousPlan.equals(other.previousPlan))
+            return false;
+        if (previousPriceList == null) {
+            if (other.previousPriceList != null)
+                return false;
+        } else if (!previousPriceList.equals(other.previousPriceList))
+            return false;
+        if (previousState != other.previousState)
+            return false;
+        if (remainingEventsForUserOperation == null) {
+            if (other.remainingEventsForUserOperation != null)
+                return false;
+        } else if (!remainingEventsForUserOperation
+                .equals(other.remainingEventsForUserOperation))
+            return false;
+        if (requestedTransitionTime == null) {
+            if (other.requestedTransitionTime != null)
+                return false;
+        } else if (requestedTransitionTime
+                .compareTo(other.requestedTransitionTime) != 0)
+            return false;
+        if (subscriptionId == null) {
+            if (other.subscriptionId != null)
+                return false;
+        } else if (!subscriptionId.equals(other.subscriptionId))
+            return false;
+        if (totalOrdering == null) {
+            if (other.totalOrdering != null)
+                return false;
+        } else if (!totalOrdering.equals(other.totalOrdering))
+            return false;
+        if (transitionType != other.transitionType)
+            return false;
+        if (userToken == null) {
+            if (other.userToken != null)
+                return false;
+        } else if (!userToken.equals(other.userToken))
+            return false;
+        return true;
+    }
+    
+}
diff --git a/junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestBlockingCalculator.java b/junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestBlockingCalculator.java
new file mode 100644
index 0000000..435ee50
--- /dev/null
+++ b/junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestBlockingCalculator.java
@@ -0,0 +1,734 @@
+/*
+ * Copyright 2010-2011 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.junction.plumbing.billing;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.ning.billing.account.api.Account;
+import com.ning.billing.catalog.MockPlan;
+import com.ning.billing.catalog.MockPlanPhase;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.catalog.api.PlanPhase;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
+import com.ning.billing.entitlement.api.billing.BillingEvent;
+import com.ning.billing.entitlement.api.billing.BillingModeType;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.junction.api.Blockable;
+import com.ning.billing.junction.api.Blockable.Type;
+import com.ning.billing.junction.api.BlockingApi;
+import com.ning.billing.junction.api.BlockingState;
+import com.ning.billing.junction.api.DefaultBlockingState;
+import com.ning.billing.junction.dao.BlockingStateDao;
+import com.ning.billing.junction.plumbing.billing.BlockingCalculator.DisabledDuration;
+import com.ning.billing.mock.BrainDeadProxyFactory;
+import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
+import com.ning.billing.util.clock.Clock;
+import com.ning.billing.util.clock.ClockMock;
+
+
+public class TestBlockingCalculator {
+
+    private static final String DISABLED_BUNDLE = "disabled-bundle";
+    private static final String CLEAR_BUNDLE = "clear-bundle";
+
+    private BlockingApi blockingApi;
+    private Account account;
+    private Subscription subscription1;
+    private Subscription subscription2;
+    private Subscription subscription3;
+    private Subscription subscription4;
+    private UUID bundleId1 = UUID.randomUUID();
+    private UUID bundleId2 = UUID.randomUUID();
+    private Clock clock;
+    private BlockingCalculator odc;
+
+    @BeforeClass
+    public void setUpBeforeClass() throws Exception {
+
+        clock = new ClockMock();
+        
+        Injector i = Guice.createInjector(new AbstractModule() {
+
+            @Override
+            protected void configure() {
+                blockingApi = BrainDeadProxyFactory.createBrainDeadProxyFor(BlockingApi.class);
+                account = BrainDeadProxyFactory.createBrainDeadProxyFor(Account.class);
+                subscription1 = BrainDeadProxyFactory.createBrainDeadProxyFor(Subscription.class, Comparable.class);
+                subscription2 = BrainDeadProxyFactory.createBrainDeadProxyFor(Subscription.class, Comparable.class);
+                subscription3 = BrainDeadProxyFactory.createBrainDeadProxyFor(Subscription.class, Comparable.class);
+                subscription4 = BrainDeadProxyFactory.createBrainDeadProxyFor(Subscription.class, Comparable.class);
+                ((ZombieControl) account).addResult("getId", UUID.randomUUID());
+                ((ZombieControl) subscription1).addResult("getBundleId", bundleId1);
+                ((ZombieControl) subscription2).addResult("getBundleId", bundleId1);
+                ((ZombieControl) subscription3).addResult("getBundleId", bundleId1);
+                ((ZombieControl) subscription4).addResult("getBundleId", bundleId2);
+                ((ZombieControl) subscription1).addResult("compareTo", 1);
+                ((ZombieControl) subscription2).addResult("compareTo", 1);
+                ((ZombieControl) subscription3).addResult("compareTo", 1);
+                ((ZombieControl) subscription4).addResult("compareTo", 1);
+                ((ZombieControl) subscription1).addResult("getId", UUID.randomUUID());
+                ((ZombieControl) subscription2).addResult("getId", UUID.randomUUID());
+                ((ZombieControl) subscription3).addResult("getId", UUID.randomUUID());
+                ((ZombieControl) subscription4).addResult("getId", UUID.randomUUID());
+         
+         
+                bind(BlockingStateDao.class).toInstance(BrainDeadProxyFactory.createBrainDeadProxyFor(BlockingStateDao.class));
+                bind(BlockingApi.class).toInstance(blockingApi);              
+                              
+            }
+            
+        });
+        odc = i.getInstance(BlockingCalculator.class);
+
+    }
+
+    @Test
+    // S1-S2-S3 subscriptions in B1
+    // B1 -----[--------]
+    // S1 --A-------------------------------------
+    // S2 --B------C------------------------------
+    // S3 ------------------D---------------------
+    
+
+    //Result
+    // S1 --A--[-------]--------------------------
+    // S2 --B--[-------]--------------------------
+    // S3 ------------------D---------------------
+    
+    public void testInsertBlockingEvents() {
+        DateTime now = clock.getUTCNow();
+        List<DisabledDuration> disabledDuration = new ArrayList<BlockingCalculator.DisabledDuration>();
+        SortedSet<BillingEvent> billingEvents = new TreeSet<BillingEvent>();
+        
+        disabledDuration.add(new DisabledDuration(now, null));
+        BillingEvent A = createRealEvent(now.minusDays(1).minusHours(1), subscription1);
+        BillingEvent B = createRealEvent(now.minusDays(1), subscription2);
+        BillingEvent C = createRealEvent(now.plusDays(1), subscription2);
+        BillingEvent D = createRealEvent(now.plusDays(3), subscription3);
+        billingEvents.add(A);
+        billingEvents.add(B);
+        billingEvents.add(C);
+        billingEvents.add(D);
+
+        SortedSet<BlockingState> blockingStates = new TreeSet<BlockingState>();
+        blockingStates.add(new DefaultBlockingState(bundleId1,DISABLED_BUNDLE, Blockable.Type.SUBSCRIPTION_BUNDLE, "test", true, true, true, now));
+        blockingStates.add(new DefaultBlockingState(bundleId1,CLEAR_BUNDLE, Blockable.Type.SUBSCRIPTION_BUNDLE, "test", false, false, false, now.plusDays(2)));
+        
+        ((ZombieControl)blockingApi).addResult("getBlockingHistory", blockingStates);
+        
+        
+        odc.insertBlockingEvents(billingEvents);
+        
+        assertEquals(billingEvents.size(), 7);
+        
+        SortedSet<BillingEvent> s1Events = odc.filter(billingEvents, subscription1);
+        Iterator<BillingEvent> it1 = s1Events.iterator();
+        assertEquals(it1.next(), A);
+        assertEquals(it1.next().getTransitionType(), SubscriptionTransitionType.CANCEL);
+        assertEquals(it1.next().getTransitionType(), SubscriptionTransitionType.RE_CREATE);
+        
+        SortedSet<BillingEvent> s2Events = odc.filter(billingEvents, subscription2);
+        Iterator<BillingEvent> it2 = s2Events.iterator();       
+        assertEquals(it2.next(), B);
+        assertEquals(it2.next().getTransitionType(), SubscriptionTransitionType.CANCEL);
+        assertEquals(it2.next().getTransitionType(), SubscriptionTransitionType.RE_CREATE);
+                
+        SortedSet<BillingEvent> s3Events = odc.filter(billingEvents, subscription3);
+        Iterator<BillingEvent> it3 = s3Events.iterator();       
+        assertEquals(it3.next(),D);
+    }
+
+    // Open ended duration with a previous event
+    // --X--[----------------------------------
+   @Test
+    public void testEventsToRemoveOpenPrev() {
+       DateTime now = clock.getUTCNow();
+       List<DisabledDuration> disabledDuration = new ArrayList<BlockingCalculator.DisabledDuration>();
+       SortedSet<BillingEvent> billingEvents = new TreeSet<BillingEvent>();
+       
+       disabledDuration.add(new DisabledDuration(now, null));
+       billingEvents.add(createRealEvent(now.minusDays(1), subscription1));
+       
+       SortedSet<BillingEvent> results = odc.eventsToRemove( disabledDuration, billingEvents, subscription1);
+       
+       assertEquals(results.size(), 0);
+   }
+
+   // Open with previous and following events
+   // --X--[----Y-----------------------------
+    @Test
+    public void testEventsToRemoveOpenPrevFollow() {
+        DateTime now = clock.getUTCNow();
+        List<DisabledDuration> disabledDuration = new ArrayList<BlockingCalculator.DisabledDuration>();
+        SortedSet<BillingEvent> billingEvents = new TreeSet<BillingEvent>();
+        
+        disabledDuration.add(new DisabledDuration(now, null));
+        BillingEvent e1 = createRealEvent(now.minusDays(1), subscription1);
+        BillingEvent e2  = createRealEvent(now.plusDays(1), subscription1);
+        billingEvents.add(e1);
+        billingEvents.add(e2);
+        
+        SortedSet<BillingEvent> results = odc.eventsToRemove( disabledDuration, billingEvents,  subscription1);
+        
+        assertEquals(results.size(), 1);
+        assertEquals(results.first(), e2);
+    }
+
+    // Open with no previous event (only following)
+    // -----[----X-----------------------------
+    @Test
+    public void testEventsToRemoveOpenFollow() {
+        DateTime now = clock.getUTCNow();
+        List<DisabledDuration> disabledDuration = new ArrayList<BlockingCalculator.DisabledDuration>();
+        SortedSet<BillingEvent> billingEvents = new TreeSet<BillingEvent>();
+        
+        disabledDuration.add(new DisabledDuration(now, null));
+        BillingEvent e1  = createRealEvent(now.plusDays(1), subscription1);
+        billingEvents.add(e1);
+        
+        SortedSet<BillingEvent> results = odc.eventsToRemove( disabledDuration, billingEvents,  subscription1);
+        
+        assertEquals(results.size(), 1);
+        assertEquals(results.first(), e1);
+   }
+
+    // Closed duration with a single previous event
+    // --X--[------------]---------------------
+        @Test
+    public void testEventsToRemoveClosedPrev() {
+            DateTime now = clock.getUTCNow();
+            List<DisabledDuration> disabledDuration = new ArrayList<BlockingCalculator.DisabledDuration>();
+            SortedSet<BillingEvent> billingEvents = new TreeSet<BillingEvent>();
+            
+            disabledDuration.add(new DisabledDuration(now, now.plusDays(2)));
+            BillingEvent e1  = createRealEvent(now.minusDays(1), subscription1);
+            billingEvents.add(e1);
+            
+            SortedSet<BillingEvent> results = odc.eventsToRemove( disabledDuration, billingEvents,  subscription1);
+            
+            assertEquals(results.size(), 0);
+    }
+
+    // Closed duration with a previous event and in-between event
+    // --X--[------Y-----]---------------------
+    @Test
+    public void testEventsToRemoveClosedPrevBetw() {
+        DateTime now = clock.getUTCNow();
+        List<DisabledDuration> disabledDuration = new ArrayList<BlockingCalculator.DisabledDuration>();
+        SortedSet<BillingEvent> billingEvents = new TreeSet<BillingEvent>();
+        
+        disabledDuration.add(new DisabledDuration(now, now.plusDays(2)));
+        BillingEvent e1 = createRealEvent(now.minusDays(1), subscription1);
+        BillingEvent e2  = createRealEvent(now.plusDays(1), subscription1);
+        billingEvents.add(e1);
+        billingEvents.add(e2);        
+       
+        
+        SortedSet<BillingEvent> results = odc.eventsToRemove( disabledDuration, billingEvents, subscription1);
+        
+        assertEquals(results.size(), 1);
+        assertEquals(results.first(), e2);
+    }
+
+    // Closed duration with a previous event and in-between event and following
+    // --X--[------Y-----]-------Z-------------
+    @Test
+    public void testEventsToRemoveClosedPrevBetwNext() {
+        DateTime now = clock.getUTCNow();
+        List<DisabledDuration> disabledDuration = new ArrayList<BlockingCalculator.DisabledDuration>();
+        SortedSet<BillingEvent> billingEvents = new TreeSet<BillingEvent>();
+        
+        disabledDuration.add(new DisabledDuration(now, now.plusDays(2)));
+        BillingEvent e1 = createRealEvent(now.minusDays(1), subscription1);
+        BillingEvent e2  = createRealEvent(now.plusDays(1), subscription1);
+        BillingEvent e3  = createRealEvent(now.plusDays(3), subscription1);
+        billingEvents.add(e1);
+        billingEvents.add(e2);        
+        billingEvents.add(e3);        
+        
+        SortedSet<BillingEvent> results = odc.eventsToRemove( disabledDuration, billingEvents, subscription1);
+        
+        assertEquals(results.size(), 1);
+        assertEquals(results.first(), e2);
+   }
+
+    // Closed with no previous event but in-between events
+    // -----[------Y-----]---------------------
+    @Test
+    public void testEventsToRemoveClosedBetwn() {
+        DateTime now = clock.getUTCNow();
+        List<DisabledDuration> disabledDuration = new ArrayList<BlockingCalculator.DisabledDuration>();
+        SortedSet<BillingEvent> billingEvents = new TreeSet<BillingEvent>();
+        
+        disabledDuration.add(new DisabledDuration(now, now.plusDays(2)));
+        BillingEvent e2  = createRealEvent(now.plusDays(1), subscription1);
+        billingEvents.add(e2);        
+      
+        
+        SortedSet<BillingEvent> results = odc.eventsToRemove( disabledDuration, billingEvents, subscription1);
+        
+        assertEquals(results.size(), 1);
+        assertEquals(results.first(), e2);
+    }
+
+    // Closed with no previous event but in-between events and following
+    // -----[------Y-----]-------Z-------------
+    @Test
+    public void testEventsToRemoveClosedBetweenFollow() {
+        DateTime now = clock.getUTCNow();
+        List<DisabledDuration> disabledDuration = new ArrayList<BlockingCalculator.DisabledDuration>();
+        SortedSet<BillingEvent> billingEvents = new TreeSet<BillingEvent>();
+        
+        disabledDuration.add(new DisabledDuration(now, now.plusDays(2)));
+
+        BillingEvent e2  = createRealEvent(now.plusDays(1), subscription1);
+        BillingEvent e3  = createRealEvent(now.plusDays(3), subscription1);
+        billingEvents.add(e2);        
+        billingEvents.add(e3);        
+        
+        SortedSet<BillingEvent> results = odc.eventsToRemove( disabledDuration, billingEvents, subscription1);
+        
+        assertEquals(results.size(), 1);
+        assertEquals(results.first(), e2);
+    }
+
+    // Closed duration with only following
+    // -----[------------]-------Z-------------
+    @Test
+    public void testEventsToRemoveClosedFollow() {
+        DateTime now = clock.getUTCNow();
+        List<DisabledDuration> disabledDuration = new ArrayList<BlockingCalculator.DisabledDuration>();
+        SortedSet<BillingEvent> billingEvents = new TreeSet<BillingEvent>();
+        
+        disabledDuration.add(new DisabledDuration(now, now.plusDays(2)));
+
+        BillingEvent e3  = createRealEvent(now.plusDays(3), subscription1);
+       
+        billingEvents.add(e3);        
+        
+        SortedSet<BillingEvent> results = odc.eventsToRemove( disabledDuration, billingEvents, subscription1);
+        
+        assertEquals(results.size(), 0);
+     }
+    
+    // Open ended duration with a previous event
+    // --X--[----------------------------------
+   @Test
+    public void testCreateNewEventsOpenPrev() {
+       DateTime now = clock.getUTCNow();
+       List<DisabledDuration> disabledDuration = new ArrayList<BlockingCalculator.DisabledDuration>();
+       SortedSet<BillingEvent> billingEvents = new TreeSet<BillingEvent>();
+       
+       disabledDuration.add(new DisabledDuration(now, null));
+       billingEvents.add(createRealEvent(now.minusDays(1), subscription1));
+       
+       SortedSet<BillingEvent> results = odc.createNewEvents( disabledDuration, billingEvents,  account, subscription1);
+       
+       assertEquals(results.size(), 1);
+       assertEquals(results.first().getEffectiveDate(), now);
+       assertEquals(results.first().getRecurringPrice(), BigDecimal.ZERO);
+       assertEquals(results.first().getTransitionType(), SubscriptionTransitionType.CANCEL);
+   }
+
+   // Open with previous and following events
+   // --X--[----Y-----------------------------
+    @Test
+    public void testCreateNewEventsOpenPrevFollow() {
+        DateTime now = clock.getUTCNow();
+        List<DisabledDuration> disabledDuration = new ArrayList<BlockingCalculator.DisabledDuration>();
+        SortedSet<BillingEvent> billingEvents = new TreeSet<BillingEvent>();
+        
+        disabledDuration.add(new DisabledDuration(now, null));
+        billingEvents.add(createRealEvent(now.minusDays(1), subscription1));
+        billingEvents.add(createRealEvent(now.plusDays(1), subscription1));
+        
+        SortedSet<BillingEvent> results = odc.createNewEvents( disabledDuration, billingEvents,  account, subscription1);
+        
+        assertEquals(results.size(), 1);
+        assertEquals(results.first().getEffectiveDate(), now);
+        assertEquals(results.first().getRecurringPrice(), BigDecimal.ZERO);
+        assertEquals(results.first().getTransitionType(), SubscriptionTransitionType.CANCEL);
+    }
+
+    // Open with no previous event (only following)
+    // -----[----X-----------------------------
+    @Test
+    public void testCreateNewEventsOpenFollow() {
+        DateTime now = clock.getUTCNow();
+        List<DisabledDuration> disabledDuration = new ArrayList<BlockingCalculator.DisabledDuration>();
+        SortedSet<BillingEvent> billingEvents = new TreeSet<BillingEvent>();
+        
+        disabledDuration.add(new DisabledDuration(now, null));
+        billingEvents.add(createRealEvent(now.plusDays(1), subscription1));
+        
+        SortedSet<BillingEvent> results = odc.createNewEvents( disabledDuration, billingEvents,  account, subscription1);
+        
+        assertEquals(results.size(), 0);
+   }
+
+    // Closed duration with a single previous event
+    // --X--[------------]---------------------
+        @Test
+    public void testCreateNewEventsClosedPrev() {
+            DateTime now = clock.getUTCNow();
+            List<DisabledDuration> disabledDuration = new ArrayList<BlockingCalculator.DisabledDuration>();
+            SortedSet<BillingEvent> billingEvents = new TreeSet<BillingEvent>();
+            
+            disabledDuration.add(new DisabledDuration(now, now.plusDays(2)));
+            billingEvents.add(createRealEvent(now.minusDays(1), subscription1));
+            
+            SortedSet<BillingEvent> results = odc.createNewEvents( disabledDuration, billingEvents,  account, subscription1);
+            
+            assertEquals(results.size(), 2);
+            assertEquals(results.first().getEffectiveDate(), now);
+            assertEquals(results.first().getRecurringPrice(), BigDecimal.ZERO);
+            assertEquals(results.first().getTransitionType(), SubscriptionTransitionType.CANCEL);
+            assertEquals(results.last().getEffectiveDate(), now.plusDays(2));
+            assertEquals(results.last().getRecurringPrice(), billingEvents.first().getRecurringPrice());
+            assertEquals(results.last().getTransitionType(), SubscriptionTransitionType.RE_CREATE);
+    }
+
+    // Closed duration with a previous event and in-between event
+    // --X--[------Y-----]---------------------
+    @Test
+    public void testCreateNewEventsClosedPrevBetw() {
+        DateTime now = clock.getUTCNow();
+        List<DisabledDuration> disabledDuration = new ArrayList<BlockingCalculator.DisabledDuration>();
+        SortedSet<BillingEvent> billingEvents = new TreeSet<BillingEvent>();
+        
+        disabledDuration.add(new DisabledDuration(now, now.plusDays(2)));
+        billingEvents.add(createRealEvent(now.minusDays(1), subscription1));
+        billingEvents.add(createRealEvent(now.plusDays(1), subscription1));
+        
+        SortedSet<BillingEvent> results = odc.createNewEvents( disabledDuration, billingEvents,  account, subscription1);
+        
+        assertEquals(results.size(), 2);
+        assertEquals(results.first().getEffectiveDate(), now);
+        assertEquals(results.first().getRecurringPrice(), BigDecimal.ZERO);
+        assertEquals(results.first().getTransitionType(), SubscriptionTransitionType.CANCEL);
+        assertEquals(results.last().getEffectiveDate(), now.plusDays(2));
+        assertEquals(results.last().getRecurringPrice(), billingEvents.first().getRecurringPrice());
+        assertEquals(results.last().getTransitionType(), SubscriptionTransitionType.RE_CREATE);
+    }
+
+    // Closed duration with a previous event and in-between event and following
+    // --X--[------Y-----]-------Z-------------
+    @Test
+    public void testCreateNewEventsClosedPrevBetwNext() {
+        DateTime now = clock.getUTCNow();
+        List<DisabledDuration> disabledDuration = new ArrayList<BlockingCalculator.DisabledDuration>();
+        SortedSet<BillingEvent> billingEvents = new TreeSet<BillingEvent>();
+        
+        disabledDuration.add(new DisabledDuration(now, now.plusDays(2)));
+        billingEvents.add(createRealEvent(now.minusDays(1), subscription1));
+        billingEvents.add(createRealEvent(now.plusDays(1), subscription1));
+        billingEvents.add(createRealEvent(now.plusDays(3), subscription1));
+        
+        SortedSet<BillingEvent> results = odc.createNewEvents( disabledDuration, billingEvents,  account, subscription1);
+        
+        assertEquals(results.size(), 2);
+        assertEquals(results.first().getEffectiveDate(), now);
+        assertEquals(results.first().getRecurringPrice(), BigDecimal.ZERO);
+        assertEquals(results.first().getTransitionType(), SubscriptionTransitionType.CANCEL);
+        assertEquals(results.last().getEffectiveDate(), now.plusDays(2));
+        assertEquals(results.last().getRecurringPrice(), billingEvents.first().getRecurringPrice());
+        assertEquals(results.last().getTransitionType(), SubscriptionTransitionType.RE_CREATE);
+   }
+
+    // Closed with no previous event but in-between events
+    // -----[------Y-----]---------------------
+    @Test
+    public void testCreateNewEventsClosedBetwn() {
+        DateTime now = clock.getUTCNow();
+        List<DisabledDuration> disabledDuration = new ArrayList<BlockingCalculator.DisabledDuration>();
+        SortedSet<BillingEvent> billingEvents = new TreeSet<BillingEvent>();
+        
+        disabledDuration.add(new DisabledDuration(now, now.plusDays(2)));
+        billingEvents.add(createRealEvent(now.plusDays(1), subscription1));
+        
+        SortedSet<BillingEvent> results = odc.createNewEvents( disabledDuration, billingEvents,  account, subscription1);
+        
+        assertEquals(results.size(), 1);
+        assertEquals(results.last().getEffectiveDate(), now.plusDays(2));
+        assertEquals(results.last().getRecurringPrice(), billingEvents.first().getRecurringPrice());
+        assertEquals(results.last().getTransitionType(), SubscriptionTransitionType.RE_CREATE);
+    }
+
+    // Closed with no previous event but in-between events and following
+    // -----[------Y-----]-------Z-------------
+    @Test
+    public void testCreateNewEventsClosedBetweenFollow() {
+        DateTime now = clock.getUTCNow();
+        List<DisabledDuration> disabledDuration = new ArrayList<BlockingCalculator.DisabledDuration>();
+        SortedSet<BillingEvent> billingEvents = new TreeSet<BillingEvent>();
+        
+        disabledDuration.add(new DisabledDuration(now, now.plusDays(2)));
+        billingEvents.add(createRealEvent(now.plusDays(1), subscription1));
+        
+        SortedSet<BillingEvent> results = odc.createNewEvents( disabledDuration, billingEvents,  account, subscription1);
+        
+        assertEquals(results.size(), 1);
+        assertEquals(results.last().getEffectiveDate(), now.plusDays(2));
+        assertEquals(results.last().getRecurringPrice(), billingEvents.first().getRecurringPrice());
+        assertEquals(results.last().getTransitionType(), SubscriptionTransitionType.RE_CREATE);
+    }
+
+    // Closed duration with only following
+    // -----[------------]-------Z-------------
+    @Test
+    public void testCreateNewEventsClosedFollow() {
+        DateTime now = clock.getUTCNow();
+        List<DisabledDuration> disabledDuration = new ArrayList<BlockingCalculator.DisabledDuration>();
+        SortedSet<BillingEvent> billingEvents = new TreeSet<BillingEvent>();
+        
+        disabledDuration.add(new DisabledDuration(now, now.plusDays(2)));
+        billingEvents.add(createRealEvent(now.plusDays(3), subscription1));
+        
+        SortedSet<BillingEvent> results = odc.createNewEvents( disabledDuration, billingEvents,  account, subscription1);
+        
+        assertEquals(results.size(), 0);
+     }
+
+    @Test
+    public void testPrecedingBillingEventForSubscription() {
+        DateTime now = new DateTime();
+        
+        SortedSet<BillingEvent> events = new TreeSet<BillingEvent>();
+
+        events.add(createRealEvent(now.minusDays(10), subscription1));
+        events.add(createRealEvent(now.minusDays(6), subscription1));
+        events.add(createRealEvent(now.minusDays(5), subscription1));
+        events.add(createRealEvent(now.minusDays(1), subscription1));
+        
+        BillingEvent minus11 = odc.precedingBillingEventForSubscription(now.minusDays(11), events, subscription1);
+        assertNull(minus11);
+        
+        BillingEvent minus5andAHalf= odc.precedingBillingEventForSubscription(now.minusDays(5).minusHours(12), events, subscription1);
+        assertNotNull(minus5andAHalf);
+        assertEquals(minus5andAHalf.getEffectiveDate(), now.minusDays(6));
+      
+        
+    }
+    
+    protected BillingEvent createRealEvent(DateTime effectiveDate, Subscription subscription) {
+        final Account account = this.account;
+        final int billCycleDay = 1;
+        final PlanPhase planPhase = new MockPlanPhase();
+        final Plan plan = new MockPlan();
+        final BigDecimal fixedPrice = BigDecimal.TEN;
+        final BigDecimal recurringPrice = BigDecimal.TEN;
+        final Currency currency = Currency.USD;
+        final String description = "";
+        final BillingModeType billingModeType = BillingModeType.IN_ADVANCE;
+        final BillingPeriod billingPeriod = BillingPeriod.MONTHLY;
+        final SubscriptionTransitionType type = SubscriptionTransitionType.CHANGE;
+        final Long totalOrdering = 0L; //TODO
+
+        return new DefaultBillingEvent(account, subscription, effectiveDate, plan, planPhase,
+                fixedPrice, recurringPrice, currency,
+                billingPeriod, billCycleDay, billingModeType,
+                description, totalOrdering, type);
+    }
+
+
+    @Test
+    public void testFilter() {
+        SortedSet<BillingEvent> events = new TreeSet<BillingEvent>();
+        
+        events.add(createBillingEvent(subscription1));
+        events.add(createBillingEvent(subscription1));
+        events.add(createBillingEvent(subscription1));
+        events.add(createBillingEvent(subscription2));
+        
+        SortedSet<BillingEvent> result1 = odc.filter(events, subscription1);
+        SortedSet<BillingEvent> result2 = odc.filter(events, subscription2);
+        SortedSet<BillingEvent> result3 = odc.filter(events, subscription3);
+        
+        assertEquals(result1.size(), 3);
+        assertEquals(result1.first().getSubscription(), subscription1);
+        assertEquals(result1.last().getSubscription(), subscription1);
+        assertEquals(result2.size(), 1);
+        assertEquals(result2.first().getSubscription(), subscription2);
+        assertEquals(result3.size(), 0);
+    }
+
+    @Test
+    public void testCreateNewDisableEvent() {
+        DateTime now = clock.getUTCNow();
+        BillingEvent event = new MockBillingEvent();
+        
+        BillingEvent result = odc.createNewDisableEvent(now, event);
+        assertEquals(result.getBillCycleDay(),event.getBillCycleDay());
+        assertEquals(result.getEffectiveDate(), now);
+        assertEquals(result.getPlanPhase(), event.getPlanPhase());
+        assertEquals(result.getPlan(), event.getPlan());
+        assertEquals(result.getFixedPrice(),BigDecimal.ZERO);
+        assertEquals(result.getRecurringPrice(), BigDecimal.ZERO);
+        assertEquals(result.getCurrency(), event.getCurrency());
+        assertEquals(result.getDescription(), "");
+        assertEquals(result.getBillingMode(), event.getBillingMode());
+        assertEquals(result.getBillingPeriod(), event.getBillingPeriod());
+        assertEquals(result.getTransitionType(),  SubscriptionTransitionType.CANCEL);
+        assertEquals(result.getTotalOrdering(), new Long(0));
+    }
+
+    @Test
+    public void testCreateNewReenableEvent() {
+        DateTime now = clock.getUTCNow();
+        BillingEvent event = new MockBillingEvent();
+        
+        BillingEvent result = odc.createNewReenableEvent(now, event);
+        assertEquals(result.getBillCycleDay(),event.getBillCycleDay());
+        assertEquals(result.getEffectiveDate(), now);
+        assertEquals(result.getPlanPhase(), event.getPlanPhase());
+        assertEquals(result.getPlan(), event.getPlan());
+        assertEquals(result.getFixedPrice(),event.getFixedPrice());
+        assertEquals(result.getRecurringPrice(), event.getRecurringPrice());
+        assertEquals(result.getCurrency(), event.getCurrency());
+        assertEquals(result.getDescription(), "");
+        assertEquals(result.getBillingMode(), event.getBillingMode());
+        assertEquals(result.getBillingPeriod(), event.getBillingPeriod());
+        assertEquals(result.getTransitionType(),  SubscriptionTransitionType.RE_CREATE);
+        assertEquals(result.getTotalOrdering(), new Long(0));
+    }
+    
+    private class MockBillingEvent extends DefaultBillingEvent {
+        public MockBillingEvent() {
+            super(account, subscription1, clock.getUTCNow(), null, null, BigDecimal.ZERO, BigDecimal.TEN, Currency.USD, BillingPeriod.ANNUAL,
+                    4, BillingModeType.IN_ADVANCE, "", 3L, SubscriptionTransitionType.CREATE);
+        }        
+    }
+
+    @Test
+    public void testCreateBundleSubscriptionMap() {
+        SortedSet<BillingEvent> events = new TreeSet<BillingEvent>();
+        events.add(createBillingEvent(subscription1));
+        events.add(createBillingEvent(subscription2));
+        events.add(createBillingEvent(subscription3));
+        events.add(createBillingEvent(subscription4));
+        
+        Hashtable<UUID,List<Subscription>> map = odc.createBundleSubscriptionMap(events);
+        
+        assertNotNull(map);
+        assertEquals(map.keySet().size(),2);
+        assertEquals(map.get(bundleId1).size(), 3);
+        assertEquals(map.get(bundleId2).size(), 1);
+        
+    }
+
+    private BillingEvent createBillingEvent(Subscription subscription) {
+        BillingEvent result =  BrainDeadProxyFactory.createBrainDeadProxyFor(BillingEvent.class, Comparable.class);
+        ((ZombieControl)result).addResult("getSubscription", subscription);
+        ((ZombieControl)result).addResult("compareTo", 1);
+        return result;
+    }
+
+    @Test
+    public void testCreateDisablePairs() {
+        SortedSet<BlockingState> blockingEvents;
+        UUID ovdId = UUID.randomUUID();
+        DateTime now = clock.getUTCNow();
+        
+        //simple events open clear -> disabled
+        blockingEvents = new TreeSet<BlockingState>();
+        blockingEvents.add(new DefaultBlockingState(ovdId,CLEAR_BUNDLE, Type.SUBSCRIPTION_BUNDLE,"test", false, false, false, now));
+        blockingEvents.add(new DefaultBlockingState(ovdId,DISABLED_BUNDLE, Type.SUBSCRIPTION_BUNDLE, "test", true, true, true,now.plusDays(1)));
+        
+        List<DisabledDuration> pairs = odc.createBlockingDurations(blockingEvents);
+        assertEquals(pairs.size(), 1);
+        assertNotNull(pairs.get(0).getStart());
+        assertEquals(pairs.get(0).getStart(),now.plusDays(1));
+        assertNull(pairs.get(0).getEnd());
+        
+        //simple events closed clear -> disabled
+        blockingEvents = new TreeSet<BlockingState>();
+        blockingEvents.add(new DefaultBlockingState(ovdId,CLEAR_BUNDLE, Type.SUBSCRIPTION_BUNDLE,"test", false, false, false,now));
+        blockingEvents.add(new DefaultBlockingState(ovdId,DISABLED_BUNDLE, Type.SUBSCRIPTION_BUNDLE, "test", true, true, true,now.plusDays(1)));
+        blockingEvents.add(new DefaultBlockingState(ovdId,CLEAR_BUNDLE, Type.SUBSCRIPTION_BUNDLE,"test", false, false, false,now.plusDays(2)));
+        
+        pairs = odc.createBlockingDurations(blockingEvents);
+        assertEquals(pairs.size(), 1);
+        assertNotNull(pairs.get(0).getStart());
+        assertEquals(pairs.get(0).getStart(),now.plusDays(1));
+        assertNotNull(pairs.get(0).getEnd());
+        assertEquals(pairs.get(0).getEnd(),now.plusDays(2));
+
+        //simple BUNDLE events closed clear -> disabled
+        blockingEvents = new TreeSet<BlockingState>();
+        blockingEvents.add(new DefaultBlockingState(ovdId,CLEAR_BUNDLE, Type.SUBSCRIPTION_BUNDLE,"test", false, false, false,now));
+        blockingEvents.add(new DefaultBlockingState(ovdId,DISABLED_BUNDLE, Type.SUBSCRIPTION_BUNDLE, "test", true, true, true,now.plusDays(1)));
+        blockingEvents.add(new DefaultBlockingState(ovdId,CLEAR_BUNDLE, Type.SUBSCRIPTION_BUNDLE,"test", false, false, false,now.plusDays(2)));
+        
+        pairs = odc.createBlockingDurations(blockingEvents);
+        assertEquals(pairs.size(), 1);
+        assertNotNull(pairs.get(0).getStart());
+        assertEquals(pairs.get(0).getStart(),now.plusDays(1));
+        assertNotNull(pairs.get(0).getEnd());
+        assertEquals(pairs.get(0).getEnd(),now.plusDays(2));
+        
+        
+        //two or more disableds in a row
+        blockingEvents = new TreeSet<BlockingState>();
+        blockingEvents.add(new DefaultBlockingState(ovdId,CLEAR_BUNDLE, Type.SUBSCRIPTION_BUNDLE,"test", false, false, false,now));
+        blockingEvents.add(new DefaultBlockingState(ovdId,DISABLED_BUNDLE, Type.SUBSCRIPTION_BUNDLE, "test", true, true, true,now.plusDays(1)));
+        blockingEvents.add(new DefaultBlockingState(ovdId,DISABLED_BUNDLE, Type.SUBSCRIPTION_BUNDLE, "test", true, true, true,now.plusDays(2)));
+        blockingEvents.add(new DefaultBlockingState(ovdId,CLEAR_BUNDLE, Type.SUBSCRIPTION_BUNDLE,"test", false, false, false,now.plusDays(3)));
+        
+        pairs = odc.createBlockingDurations(blockingEvents);
+        assertEquals(pairs.size(), 1);
+        assertNotNull(pairs.get(0).getStart());
+        assertEquals(pairs.get(0).getStart(),now.plusDays(1));
+        assertNotNull(pairs.get(0).getEnd());
+        assertEquals(pairs.get(0).getEnd(),now.plusDays(3));
+
+       
+        blockingEvents = new TreeSet<BlockingState>();
+        blockingEvents.add(new DefaultBlockingState(ovdId,CLEAR_BUNDLE, Type.SUBSCRIPTION_BUNDLE,"test", false, false, false,now));
+        blockingEvents.add(new DefaultBlockingState(ovdId,DISABLED_BUNDLE, Type.SUBSCRIPTION_BUNDLE, "test", true, true, true,now.plusDays(1)));
+        blockingEvents.add(new DefaultBlockingState(ovdId,DISABLED_BUNDLE, Type.SUBSCRIPTION_BUNDLE, "test", true, true, true,now.plusDays(2)));
+        blockingEvents.add(new DefaultBlockingState(ovdId,DISABLED_BUNDLE, Type.SUBSCRIPTION_BUNDLE, "test", true, true, true,now.plusDays(3)));
+        blockingEvents.add(new DefaultBlockingState(ovdId,CLEAR_BUNDLE, Type.SUBSCRIPTION_BUNDLE,"test", false, false, false,now.plusDays(4)));
+        
+        pairs = odc.createBlockingDurations(blockingEvents);
+        assertEquals(pairs.size(), 1);
+        assertNotNull(pairs.get(0).getStart());
+        assertEquals(pairs.get(0).getStart(),now.plusDays(1));
+        assertNotNull(pairs.get(0).getEnd());
+        assertEquals(pairs.get(0).getEnd(),now.plusDays(4));
+  
+    }
+}
diff --git a/junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestDefaultEntitlementBillingApi.java b/junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestDefaultEntitlementBillingApi.java
new file mode 100644
index 0000000..85e239f
--- /dev/null
+++ b/junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestDefaultEntitlementBillingApi.java
@@ -0,0 +1,459 @@
+/*
+ * Copyright 2010-2011 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.junction.plumbing.billing;
+
+
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.BeforeSuite;
+import org.testng.annotations.Test;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.AccountUserApi;
+import com.ning.billing.catalog.MockCatalog;
+import com.ning.billing.catalog.MockCatalogService;
+import com.ning.billing.catalog.api.BillingAlignment;
+import com.ning.billing.catalog.api.CatalogApiException;
+import com.ning.billing.catalog.api.CatalogService;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.catalog.api.CurrencyValueNull;
+import com.ning.billing.catalog.api.InternationalPrice;
+import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.catalog.api.PlanPhase;
+import com.ning.billing.catalog.api.Price;
+import com.ning.billing.catalog.api.PriceList;
+import com.ning.billing.catalog.api.PriceListSet;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
+import com.ning.billing.entitlement.api.billing.BillingEvent;
+import com.ning.billing.entitlement.api.billing.BillingModeType;
+import com.ning.billing.entitlement.api.user.EntitlementUserApi;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.api.user.Subscription.SubscriptionState;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.entitlement.api.user.SubscriptionEvent;
+import com.ning.billing.junction.api.BillingApi;
+import com.ning.billing.junction.api.Blockable;
+import com.ning.billing.junction.api.Blockable.Type;
+import com.ning.billing.junction.api.BlockingApi;
+import com.ning.billing.junction.api.BlockingState;
+import com.ning.billing.junction.api.DefaultBlockingState;
+import com.ning.billing.lifecycle.KillbillService.ServiceException;
+import com.ning.billing.mock.BrainDeadProxyFactory;
+import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
+import com.ning.billing.util.callcontext.CallContextFactory;
+import com.ning.billing.util.callcontext.DefaultCallContextFactory;
+import com.ning.billing.util.clock.Clock;
+import com.ning.billing.util.clock.ClockMock;
+
+public class TestDefaultEntitlementBillingApi {
+    
+    class MockPrice implements InternationalPrice {
+        private final BigDecimal price;
+        
+        public MockPrice(String val) {
+            price = new BigDecimal(val);
+        }
+        
+        @Override
+        public boolean isZero() {
+            return price.compareTo(BigDecimal.ZERO) == 0;
+        }
+        
+        @Override
+        public Price[] getPrices() {
+            return new Price[]{ 
+                    new Price() {
+
+                        @Override
+                        public Currency getCurrency() {
+                            return Currency.USD;
+                        }
+
+                        @Override
+                        public BigDecimal getValue() throws CurrencyValueNull {
+                            return price;
+                        }
+
+                    }
+            };
+        }
+        
+        @Override
+        public BigDecimal getPrice(Currency currency) throws CatalogApiException {
+             return price;
+        }
+    };
+    
+    private static final String DISABLED_BUNDLE = "disabled-bundle";
+    private static final String CLEAR_BUNDLE = "clear-bundle";
+
+	private static final UUID eventId = new UUID(0L,0L);
+	private static final UUID subId = new UUID(1L,0L);
+	private static final UUID bunId = new UUID(2L,0L);
+
+	private CatalogService catalogService;
+	private List<SubscriptionBundle> bundles;
+	private List<Subscription> subscriptions;
+
+	private List<SubscriptionEvent> subscriptionTransitions;
+	private EntitlementUserApi entitlementApi;
+
+    private BlockingCalculator blockCalculator = new BlockingCalculator(null) {
+        @Override
+        public void insertBlockingEvents(SortedSet<BillingEvent> billingEvents) {
+           
+        }
+        
+    };
+
+
+
+	private Clock clock;
+	private Subscription subscription;
+	private DateTime subscriptionStartDate;
+	private Plan subscriptionPlan;
+
+	@BeforeSuite(groups={"fast", "slow"})
+	public void setup() throws ServiceException {
+        catalogService = new MockCatalogService(new MockCatalog());
+        clock = new ClockMock();
+	}
+
+	@BeforeMethod(groups={"fast", "slow"})
+	public void setupEveryTime() {
+		bundles = new ArrayList<SubscriptionBundle>();
+		final SubscriptionBundle bundle = BrainDeadProxyFactory.createBrainDeadProxyFor(SubscriptionBundle.class);
+		((ZombieControl)bundle).addResult("getId", eventId);
+		        
+		        //new SubscriptionBundleData( eventId,"TestKey", subId,  clock.getUTCNow().minusDays(4), null);
+		bundles.add(bundle);
+
+
+		subscriptionTransitions = new LinkedList<SubscriptionEvent>();
+		subscriptions = new LinkedList<Subscription>();
+
+		subscriptionStartDate = clock.getUTCNow().minusDays(3);
+		subscription = new MockSubscription() {
+		    @Override
+            public List<SubscriptionEvent> getBillingTransitions() {
+		    	return subscriptionTransitions;
+		    }
+
+            @Override
+            public Plan getCurrentPlan() {
+                return subscriptionPlan;
+            }
+
+            @Override
+            public UUID getId() {
+                return subId;
+            }
+
+            @Override
+            public UUID getBundleId() {
+                return bunId;
+            }
+
+            @Override
+            public DateTime getStartDate() {
+                return subscriptionStartDate;
+            }
+            
+		    
+		};
+
+		subscriptions.add(subscription);
+
+        entitlementApi = BrainDeadProxyFactory.createBrainDeadProxyFor(EntitlementUserApi.class);
+        ((ZombieControl) entitlementApi).addResult("getBundlesForAccount", bundles);
+        ((ZombieControl) entitlementApi).addResult("getSubscriptionsForBundle", subscriptions);
+        ((ZombieControl) entitlementApi).addResult("getSubscriptionFromId", subscription);
+        ((ZombieControl) entitlementApi).addResult("getBundleFromId", bundle);
+        ((ZombieControl) entitlementApi).addResult("getBaseSubscription", subscription);
+
+        assertTrue(true);
+	}
+
+    @Test(enabled=true, groups="fast")
+	public void testBillingEventsEmpty() {
+        UUID accountId = UUID.randomUUID();
+        Account account = BrainDeadProxyFactory.createBrainDeadProxyFor(Account.class);
+        ((ZombieControl) account).addResult("getId", accountId).addResult("getCurrency", Currency.USD);
+
+        AccountUserApi accountApi = BrainDeadProxyFactory.createBrainDeadProxyFor(AccountUserApi.class);
+        ((ZombieControl) accountApi).addResult("getAccountById", account);
+
+        BillCycleDayCalculator bcdCalculator = new BillCycleDayCalculator(catalogService, entitlementApi);
+        CallContextFactory factory = new DefaultCallContextFactory(clock);
+
+		BillingApi api = new DefaultBillingApi(null, factory, accountApi, bcdCalculator, entitlementApi, blockCalculator, catalogService);
+
+		SortedSet<BillingEvent> events = api.getBillingEventsForAccountAndUpdateAccountBCD(new UUID(0L,0L));
+		Assert.assertEquals(events.size(), 0);
+	}
+
+    @Test(enabled=true, groups="fast")
+	public void testBillingEventsNoBillingPeriod() throws CatalogApiException {
+		DateTime now = clock.getUTCNow();
+		DateTime then = now.minusDays(1);
+		Plan nextPlan = catalogService.getFullCatalog().findPlan("PickupTrialEvergreen10USD", now);
+		PlanPhase nextPhase = nextPlan.getAllPhases()[0]; // The trial has no billing period
+        PriceList nextPriceList = catalogService.getFullCatalog().findPriceList(PriceListSet.DEFAULT_PRICELIST_NAME, now);
+
+        SubscriptionEvent t = new MockSubscriptionEvent(
+                eventId, subId, bunId, then, now, null, null, null, null, SubscriptionState.ACTIVE, 
+                nextPlan.getName(), nextPhase.getName(), 
+                nextPriceList.getName(), 1L,null, 
+                SubscriptionTransitionType.CREATE, 0, null); 
+
+		subscriptionTransitions.add(t);
+
+        AccountUserApi accountApi = BrainDeadProxyFactory.createBrainDeadProxyFor(AccountUserApi.class);
+        Account account = BrainDeadProxyFactory.createBrainDeadProxyFor(Account.class);
+        ((ZombieControl)account).addResult("getBillCycleDay", 32);
+        ((ZombieControl)account).addResult("getCurrency", Currency.USD);
+        ((ZombieControl)accountApi).addResult("getAccountById", account);
+		       
+        BillCycleDayCalculator bcdCalculator = new BillCycleDayCalculator(catalogService, entitlementApi);
+        CallContextFactory factory = new DefaultCallContextFactory(clock);
+        BillingApi api = new DefaultBillingApi(null, factory, accountApi, bcdCalculator, entitlementApi, blockCalculator, catalogService);
+        SortedSet<BillingEvent> events = api.getBillingEventsForAccountAndUpdateAccountBCD(new UUID(0L,0L));
+
+		checkFirstEvent(events, nextPlan, 32, subId, now, nextPhase, SubscriptionTransitionType.CREATE.toString());
+	}
+
+    @Test(enabled=false, groups="fast")
+	public void testBillingEventsAnnual() throws CatalogApiException {
+		DateTime now = clock.getUTCNow();
+		DateTime then = now.minusDays(1);
+		Plan nextPlan = catalogService.getFullCatalog().findPlan("PickupTrialEvergreen10USD", now);
+		PlanPhase nextPhase = nextPlan.getAllPhases()[1];
+		PriceList nextPriceList = catalogService.getFullCatalog().findPriceList(PriceListSet.DEFAULT_PRICELIST_NAME, now);
+        SubscriptionEvent t = new MockSubscriptionEvent(
+                eventId, subId, bunId, then, now, null, null, null, null, SubscriptionState.ACTIVE, 
+                nextPlan.getName(), nextPhase.getName(), 
+                nextPriceList.getName(), 1L,null, 
+                SubscriptionTransitionType.CREATE, 0, null); 
+
+		subscriptionTransitions.add(t);
+
+		Account account = BrainDeadProxyFactory.createBrainDeadProxyFor(Account.class);
+		((ZombieControl)account).addResult("getBillCycleDay", 1).addResult("getTimeZone", DateTimeZone.UTC)
+                                .addResult("getCurrency", Currency.USD);
+
+        ((MockCatalog)catalogService.getFullCatalog()).setBillingAlignment(BillingAlignment.SUBSCRIPTION);
+        
+		AccountUserApi accountApi = BrainDeadProxyFactory.createBrainDeadProxyFor(AccountUserApi.class);
+		((ZombieControl)accountApi).addResult("getAccountById", account);
+
+        BillCycleDayCalculator bcdCalculator = new BillCycleDayCalculator(catalogService, entitlementApi);
+        CallContextFactory factory = new DefaultCallContextFactory(clock);
+
+        BillingApi api = new DefaultBillingApi(null, factory, accountApi, bcdCalculator, entitlementApi, blockCalculator, catalogService);
+        SortedSet<BillingEvent> events = api.getBillingEventsForAccountAndUpdateAccountBCD(new UUID(0L,0L));
+
+		checkFirstEvent(events, nextPlan, subscription.getStartDate().plusDays(30).getDayOfMonth(), subId, now, nextPhase, SubscriptionTransitionType.CREATE.toString());
+	}
+
+    @Test(enabled=true, groups="fast")
+	public void testBillingEventsMonthly() throws CatalogApiException {
+		DateTime now = clock.getUTCNow();
+		DateTime then = now.minusDays(1);
+		Plan nextPlan = catalogService.getFullCatalog().findPlan("PickupTrialEvergreen10USD", now);
+		PlanPhase nextPhase = nextPlan.getAllPhases()[1];
+        PriceList nextPriceList = catalogService.getFullCatalog().findPriceList(PriceListSet.DEFAULT_PRICELIST_NAME, now);
+
+        SubscriptionEvent t = new MockSubscriptionEvent(
+                eventId, subId, bunId, then, now, null, null, null, null, SubscriptionState.ACTIVE, 
+                nextPlan.getName(), nextPhase.getName(), 
+                nextPriceList.getName(), 1L,null, 
+                SubscriptionTransitionType.CREATE, 0, null); 
+
+
+		subscriptionTransitions.add(t);
+
+        AccountUserApi accountApi = BrainDeadProxyFactory.createBrainDeadProxyFor(AccountUserApi.class);
+        Account account = BrainDeadProxyFactory.createBrainDeadProxyFor(Account.class);
+        ((ZombieControl)account).addResult("getBillCycleDay", 32);
+        ((ZombieControl)account).addResult("getCurrency", Currency.USD);
+        ((ZombieControl)accountApi).addResult("getAccountById", account);
+
+        ((MockCatalog)catalogService.getFullCatalog()).setBillingAlignment(BillingAlignment.ACCOUNT);
+        
+        BillCycleDayCalculator bcdCalculator = new BillCycleDayCalculator(catalogService, entitlementApi);
+        CallContextFactory factory = new DefaultCallContextFactory(clock);
+        BillingApi api = new DefaultBillingApi(null, factory, accountApi, bcdCalculator, entitlementApi, blockCalculator, catalogService);
+
+        SortedSet<BillingEvent> events = api.getBillingEventsForAccountAndUpdateAccountBCD(new UUID(0L,0L));
+
+		checkFirstEvent(events, nextPlan, 32, subId, now, nextPhase, SubscriptionTransitionType.CREATE.toString());
+	}
+
+    @Test(enabled=false, groups="fast")
+	public void testBillingEventsAddOn() throws CatalogApiException {
+		DateTime now = clock.getUTCNow();
+		DateTime then = now.minusDays(1);
+		Plan nextPlan = catalogService.getFullCatalog().findPlan("Horn1USD", now);
+		PlanPhase nextPhase = nextPlan.getAllPhases()[0];
+        PriceList nextPriceList = catalogService.getFullCatalog().findPriceList(PriceListSet.DEFAULT_PRICELIST_NAME, now);
+
+        SubscriptionEvent t = new MockSubscriptionEvent(
+                eventId, subId, bunId, then, now, null, null, null, null, SubscriptionState.ACTIVE, 
+                nextPlan.getName(), nextPhase.getName(), 
+                nextPriceList.getName(), 1L,null, 
+                SubscriptionTransitionType.CREATE, 0, null); 
+
+		subscriptionTransitions.add(t);
+
+		Account account = BrainDeadProxyFactory.createBrainDeadProxyFor(Account.class);
+		((ZombieControl)account).addResult("getBillCycleDay", 1).addResult("getTimeZone", DateTimeZone.UTC);
+        ((ZombieControl)account).addResult("getCurrency", Currency.USD);
+
+        AccountUserApi accountApi = BrainDeadProxyFactory.createBrainDeadProxyFor(AccountUserApi.class);
+        ((ZombieControl)accountApi).addResult("getAccountById", account);
+             
+        ((MockCatalog)catalogService.getFullCatalog()).setBillingAlignment(BillingAlignment.BUNDLE);
+        
+        BillCycleDayCalculator bcdCalculator = new BillCycleDayCalculator(catalogService, entitlementApi);
+        CallContextFactory factory = new DefaultCallContextFactory(clock);
+        
+        BillingApi api = new DefaultBillingApi(null, factory, accountApi, bcdCalculator, entitlementApi, blockCalculator, catalogService);
+        subscriptionPlan = catalogService.getFullCatalog().findPlan("PickupTrialEvergreen10USD", now);
+
+        SortedSet<BillingEvent> events = api.getBillingEventsForAccountAndUpdateAccountBCD(new UUID(0L,0L));
+
+		checkFirstEvent(events, nextPlan, subscription.getStartDate().plusDays(30).getDayOfMonth(), subId, now, nextPhase, SubscriptionTransitionType.CREATE.toString());
+	}
+
+    @Test(enabled=true, groups="fast")
+    public void testBillingEventsWithBlock() throws CatalogApiException {
+        DateTime now = clock.getUTCNow();
+        DateTime then = now.minusDays(1);
+        Plan nextPlan = catalogService.getFullCatalog().findPlan("PickupTrialEvergreen10USD", now);
+        PlanPhase nextPhase = nextPlan.getAllPhases()[1];
+        PriceList nextPriceList = catalogService.getFullCatalog().findPriceList(PriceListSet.DEFAULT_PRICELIST_NAME, now);
+
+        
+        SubscriptionEvent t = new MockSubscriptionEvent(
+                eventId, subId, bunId, then, now, null, null, null, null, SubscriptionState.ACTIVE, 
+                nextPlan.getName(), nextPhase.getName(), 
+                nextPriceList.getName(), 1L,null, 
+                SubscriptionTransitionType.CREATE, 0, null); 
+
+        subscriptionTransitions.add(t);
+
+        AccountUserApi accountApi = BrainDeadProxyFactory.createBrainDeadProxyFor(AccountUserApi.class);
+        Account account = BrainDeadProxyFactory.createBrainDeadProxyFor(Account.class);
+        ((ZombieControl)account).addResult("getBillCycleDay", 32);
+        ((ZombieControl)account).addResult("getCurrency", Currency.USD);
+        ((ZombieControl)accountApi).addResult("getAccountById", account);
+        ((ZombieControl)account).addResult("getId", UUID.randomUUID());
+
+        ((MockCatalog)catalogService.getFullCatalog()).setBillingAlignment(BillingAlignment.ACCOUNT);
+        
+        final SortedSet<BlockingState> blockingStates = new TreeSet<BlockingState>();
+        blockingStates.add(new DefaultBlockingState(bunId,DISABLED_BUNDLE, Blockable.Type.SUBSCRIPTION_BUNDLE, "test", true, true, true, now.plusDays(1)));
+        blockingStates.add(new DefaultBlockingState(bunId,CLEAR_BUNDLE, Blockable.Type.SUBSCRIPTION_BUNDLE, "test", false, false, false, now.plusDays(2)));
+        
+        BlockingCalculator blockingCal = new BlockingCalculator(new BlockingApi() {
+            
+            @Override
+            public <T extends Blockable> void setBlockingState(BlockingState state) {}
+            
+            @Override
+            public BlockingState getBlockingStateFor(UUID overdueableId) {
+                return null;
+            }
+            
+            @Override
+            public BlockingState getBlockingStateFor(Blockable overdueable) {
+                return null;
+            }
+            
+            @Override
+            public SortedSet<BlockingState> getBlockingHistory(UUID overdueableId) {
+                if(overdueableId == bunId) {
+                    return blockingStates;
+                }
+                return new TreeSet<BlockingState>();
+            }
+            
+            @Override
+            public SortedSet<BlockingState> getBlockingHistory(Blockable overdueable) {
+                return new TreeSet<BlockingState>();
+            }
+        });
+        
+        BillCycleDayCalculator bcdCalculator = new BillCycleDayCalculator(catalogService, entitlementApi);
+        CallContextFactory factory = new DefaultCallContextFactory(clock);
+        BillingApi api = new DefaultBillingApi(null, factory, accountApi, bcdCalculator, entitlementApi, blockingCal, catalogService);
+        SortedSet<BillingEvent> events = api.getBillingEventsForAccountAndUpdateAccountBCD(new UUID(0L,0L));
+
+        Assert.assertEquals(events.size(), 3);
+        Iterator<BillingEvent> it = events.iterator();
+       
+        checkEvent(it.next(), nextPlan, 32, subId, now, nextPhase, SubscriptionTransitionType.CREATE.toString(), nextPhase.getFixedPrice(), nextPhase.getRecurringPrice());
+        checkEvent(it.next(), nextPlan, 32, subId, now.plusDays(1), nextPhase, SubscriptionTransitionType.CANCEL.toString(), new MockPrice("0"), new MockPrice("0"));
+        checkEvent(it.next(), nextPlan, 32, subId, now.plusDays(2), nextPhase, SubscriptionTransitionType.RE_CREATE.toString(), nextPhase.getFixedPrice(), nextPhase.getRecurringPrice());
+        
+    }
+
+	private void checkFirstEvent(SortedSet<BillingEvent> events, Plan nextPlan,
+			int BCD, UUID id, DateTime time, PlanPhase nextPhase, String desc) throws CatalogApiException {
+		Assert.assertEquals(events.size(), 1);
+		checkEvent(events.first(), nextPlan,
+	            BCD, id, time, nextPhase, desc, nextPhase.getFixedPrice(), nextPhase.getRecurringPrice());
+	}
+
+	private void checkEvent(BillingEvent event, Plan nextPlan,
+	            int BCD, UUID id, DateTime time, PlanPhase nextPhase, String desc, InternationalPrice fixedPrice, InternationalPrice recurringPrice) throws CatalogApiException {
+        if(fixedPrice != null) {
+			Assert.assertEquals(fixedPrice.getPrice(Currency.USD), event.getFixedPrice());
+        } else {
+            assertNull(event.getFixedPrice());
+		}
+
+		if(recurringPrice != null) {
+			Assert.assertEquals(recurringPrice.getPrice(Currency.USD), event.getRecurringPrice());
+        } else {
+            assertNull(event.getRecurringPrice());
+		}
+
+		Assert.assertEquals(BCD, event.getBillCycleDay());
+		Assert.assertEquals(id, event.getSubscription().getId());
+		Assert.assertEquals(time, event.getEffectiveDate());
+		Assert.assertEquals(nextPhase, event.getPlanPhase());
+		Assert.assertEquals(nextPlan, event.getPlan());
+		Assert.assertEquals(nextPhase.getBillingPeriod(), event.getBillingPeriod());
+		Assert.assertEquals(BillingModeType.IN_ADVANCE, event.getBillingMode());
+		Assert.assertEquals(desc, event.getTransitionType().toString());
+	}
+}
diff --git a/junction/src/test/resources/log4j.xml b/junction/src/test/resources/log4j.xml
new file mode 100644
index 0000000..ac530a1
--- /dev/null
+++ b/junction/src/test/resources/log4j.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright 2010-2011 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.
+  -->
+
+<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
+<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
+    <appender name="stdout" class="org.apache.log4j.ConsoleAppender">
+        <param name="Target" value="System.out"/>
+        <layout class="org.apache.log4j.PatternLayout">
+            <param name="ConversionPattern" value="%p	%d{ISO8601}	%X{trace}	%t	%c	%m%n"/>
+        </layout>
+    </appender>
+
+
+    <logger name="com.ning.billing.entitlement">
+        <level value="info"/>
+    </logger>
+
+    <logger name="com.ning.billing.util.notificationq">
+        <level value="info"/>
+    </logger>
+
+    <root>
+        <priority value="info"/>
+        <appender-ref ref="stdout"/>
+    </root>
+</log4j:configuration>
diff --git a/junction/src/test/resources/resource.properties b/junction/src/test/resources/resource.properties
new file mode 100644
index 0000000..d63334b
--- /dev/null
+++ b/junction/src/test/resources/resource.properties
@@ -0,0 +1,7 @@
+killbill.catalog.uri=file:src/test/resources/catalogSample.xml
+killbill.entitlement.dao.claim.time=60000
+killbill.entitlement.dao.ready.max=1
+killbill.entitlement.engine.notifications.sleep=500
+user.timezone=UTC
+
+

overdue/pom.xml 118(+118 -0)

diff --git a/overdue/pom.xml b/overdue/pom.xml
new file mode 100644
index 0000000..3662f2b
--- /dev/null
+++ b/overdue/pom.xml
@@ -0,0 +1,118 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- ~ Copyright 2010-2011 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. -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>com.ning.billing</groupId>
+        <artifactId>killbill</artifactId>
+        <version>0.1.11-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+    <artifactId>killbill-overdue</artifactId>
+    <name>killbill-overdue</name>
+    <packaging>jar</packaging>
+    <dependencies>
+        <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-util</artifactId>
+        </dependency>
+          <dependency>
+            <groupId>com.google.inject</groupId>
+            <artifactId>guice</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.skife.config</groupId>
+            <artifactId>config-magic</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>joda-time</groupId>
+            <artifactId>joda-time</artifactId>
+        </dependency>
+         <dependency>
+            <groupId>org.jdbi</groupId>
+            <artifactId>jdbi</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+         </dependency>
+        
+        <!-- TEST SCOPE -->
+        <dependency>
+            <groupId>org.testng</groupId>
+            <artifactId>testng</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>commons-io</groupId>
+            <artifactId>commons-io</artifactId>
+            <scope>test</scope>
+        </dependency>
+                
+        <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-util</artifactId>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-catalog</artifactId>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-catalog</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+            <scope>runtime</scope>
+        </dependency>
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-mxj</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-mxj-db-files</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.jayway.awaitility</groupId>
+            <artifactId>awaitility</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-jar-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>test-jar</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/overdue/src/main/java/com/ning/billing/ovedue/notification/DefaultOverdueCheckNotifier.java b/overdue/src/main/java/com/ning/billing/ovedue/notification/DefaultOverdueCheckNotifier.java
new file mode 100644
index 0000000..9f45075
--- /dev/null
+++ b/overdue/src/main/java/com/ning/billing/ovedue/notification/DefaultOverdueCheckNotifier.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2010-2011 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.ovedue.notification;
+
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.inject.Inject;
+import com.ning.billing.config.NotificationConfig;
+import com.ning.billing.overdue.OverdueProperties;
+import com.ning.billing.overdue.listener.OverdueListener;
+import com.ning.billing.overdue.service.DefaultOverdueService;
+import com.ning.billing.util.notificationq.NotificationQueue;
+import com.ning.billing.util.notificationq.NotificationQueueService;
+import com.ning.billing.util.notificationq.NotificationQueueService.NotificationQueueAlreadyExists;
+import com.ning.billing.util.notificationq.NotificationQueueService.NotificationQueueHandler;
+
+public class DefaultOverdueCheckNotifier implements  OverdueCheckNotifier { 
+
+    private final static Logger log = LoggerFactory.getLogger(DefaultOverdueCheckNotifier.class);
+
+    public static final String OVERDUE_CHECK_NOTIFIER_QUEUE = "overdue-check-queue";
+
+    private final NotificationQueueService notificationQueueService;
+	private final OverdueProperties config;
+
+    private NotificationQueue overdueQueue;
+	private final OverdueListener listener;
+
+    @Inject
+	public DefaultOverdueCheckNotifier(NotificationQueueService notificationQueueService,
+	        OverdueProperties config, OverdueListener listener){
+		this.notificationQueueService = notificationQueueService;
+		this.config = config;
+        this.listener = listener;
+	}
+
+    @Override
+    public void initialize() {
+		try {
+            overdueQueue = notificationQueueService.createNotificationQueue(DefaultOverdueService.OVERDUE_SERVICE_NAME,
+            		OVERDUE_CHECK_NOTIFIER_QUEUE,
+                    new NotificationQueueHandler() {
+                @Override
+                public void handleReadyNotification(String notificationKey, DateTime eventDate) {
+                	try {
+                 		UUID key = UUID.fromString(notificationKey);
+                        processEvent(key , eventDate);
+                   	} catch (IllegalArgumentException e) {
+                		log.error("The key returned from the NextBillingNotificationQueue is not a valid UUID", e);
+                		return;
+                	}
+
+                }
+            },
+            new NotificationConfig() {
+                @Override
+                public boolean isNotificationProcessingOff() {
+                    return config.isNotificationProcessingOff();
+                }
+                @Override
+                public long getSleepTimeMs() {
+                    return config.getSleepTimeMs();
+                }
+            });
+        } catch (NotificationQueueAlreadyExists e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public void start() {
+    	overdueQueue.startQueue();
+    }
+
+    @Override
+    public void stop() {
+        if (overdueQueue != null) {
+        	overdueQueue.stopQueue();
+        }
+    }
+
+    private void processEvent(UUID overdueableId, DateTime eventDateTime) {
+        listener.handleNextOverdueCheck(overdueableId); 
+    }
+
+
+}
diff --git a/overdue/src/main/java/com/ning/billing/ovedue/notification/DefaultOverdueCheckPoster.java b/overdue/src/main/java/com/ning/billing/ovedue/notification/DefaultOverdueCheckPoster.java
new file mode 100644
index 0000000..81ba766
--- /dev/null
+++ b/overdue/src/main/java/com/ning/billing/ovedue/notification/DefaultOverdueCheckPoster.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2010-2011 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.ovedue.notification;
+
+import org.joda.time.DateTime;
+import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.inject.Inject;
+import com.ning.billing.junction.api.Blockable;
+import com.ning.billing.overdue.service.DefaultOverdueService;
+import com.ning.billing.util.notificationq.NotificationKey;
+import com.ning.billing.util.notificationq.NotificationQueue;
+import com.ning.billing.util.notificationq.NotificationQueueService;
+import com.ning.billing.util.notificationq.NotificationQueueService.NoSuchNotificationQueue;
+
+public class DefaultOverdueCheckPoster implements OverdueCheckPoster {
+    private final static Logger log = LoggerFactory.getLogger(DefaultOverdueCheckNotifier.class);
+
+	private final NotificationQueueService notificationQueueService;
+
+	@Inject
+    public DefaultOverdueCheckPoster(
+			NotificationQueueService notificationQueueService) {
+		super();
+		this.notificationQueueService = notificationQueueService;
+	}
+
+	@Override
+	public void insertOverdueCheckNotification(final Blockable overdueable, final DateTime futureNotificationTime) {
+    	NotificationQueue checkOverdueQueue;
+		try {
+			checkOverdueQueue = notificationQueueService.getNotificationQueue(DefaultOverdueService.OVERDUE_SERVICE_NAME,
+					DefaultOverdueCheckNotifier.OVERDUE_CHECK_NOTIFIER_QUEUE);
+			 log.info("Queuing overdue check notification. id: {}, timestamp: {}", overdueable.getId().toString(), futureNotificationTime.toString());
+
+	            checkOverdueQueue.recordFutureNotification(futureNotificationTime, new NotificationKey(){
+	                @Override
+	                public String toString() {
+	                    return overdueable.getId().toString();
+	                }
+	    	    });
+		} catch (NoSuchNotificationQueue e) {
+			log.error("Attempting to put items on a non-existent queue (DefaultOverdueCheck).", e);
+		}
+		
+    }
+	
+	
+	@Override
+	public void clearNotificationsFor(final Blockable overdueable) {
+	    NotificationQueue checkOverdueQueue;
+        try {
+            checkOverdueQueue = notificationQueueService.getNotificationQueue(DefaultOverdueService.OVERDUE_SERVICE_NAME,
+                DefaultOverdueCheckNotifier.OVERDUE_CHECK_NOTIFIER_QUEUE);
+            checkOverdueQueue.removeNotificationsByKey(overdueable.getId());
+        } catch (NoSuchNotificationQueue e) {
+            log.error("Attempting to clear items from a non-existent queue (DefaultOverdueCheck).", e);
+        }
+	}
+
+}
diff --git a/overdue/src/main/java/com/ning/billing/ovedue/notification/OverdueCheckNotifier.java b/overdue/src/main/java/com/ning/billing/ovedue/notification/OverdueCheckNotifier.java
new file mode 100644
index 0000000..7ef6ab8
--- /dev/null
+++ b/overdue/src/main/java/com/ning/billing/ovedue/notification/OverdueCheckNotifier.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2010-2011 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.ovedue.notification;
+
+
+public interface OverdueCheckNotifier {
+
+    public void initialize();
+
+    public void start();
+
+    public void stop();
+
+}
diff --git a/overdue/src/main/java/com/ning/billing/ovedue/notification/OverdueCheckPoster.java b/overdue/src/main/java/com/ning/billing/ovedue/notification/OverdueCheckPoster.java
new file mode 100644
index 0000000..3a2aa48
--- /dev/null
+++ b/overdue/src/main/java/com/ning/billing/ovedue/notification/OverdueCheckPoster.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2010-2011 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.ovedue.notification;
+
+import org.joda.time.DateTime;
+import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
+
+import com.ning.billing.junction.api.Blockable;
+
+
+public interface OverdueCheckPoster {
+
+	void insertOverdueCheckNotification(Blockable blockable, DateTime futureNotificationTime);
+	
+    void clearNotificationsFor(Blockable blockable);
+
+}
\ No newline at end of file
diff --git a/overdue/src/main/java/com/ning/billing/overdue/api/DefaultOverdueUserApi.java b/overdue/src/main/java/com/ning/billing/overdue/api/DefaultOverdueUserApi.java
new file mode 100644
index 0000000..83487ea
--- /dev/null
+++ b/overdue/src/main/java/com/ning/billing/overdue/api/DefaultOverdueUserApi.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2010-2011 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.overdue.api;
+
+import org.apache.commons.lang.NotImplementedException;
+
+import com.google.inject.Inject;
+import com.ning.billing.ErrorCode;
+import com.ning.billing.catalog.api.CatalogService;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.junction.api.Blockable;
+import com.ning.billing.junction.api.BlockingApi;
+import com.ning.billing.overdue.OverdueApiException;
+import com.ning.billing.overdue.OverdueState;
+import com.ning.billing.overdue.OverdueUserApi;
+import com.ning.billing.overdue.config.OverdueConfig;
+import com.ning.billing.overdue.config.api.BillingState;
+import com.ning.billing.overdue.config.api.OverdueError;
+import com.ning.billing.overdue.config.api.OverdueStateSet;
+import com.ning.billing.overdue.service.ExtendedOverdueService;
+import com.ning.billing.overdue.wrapper.OverdueWrapper;
+import com.ning.billing.overdue.wrapper.OverdueWrapperFactory;
+
+public class DefaultOverdueUserApi implements OverdueUserApi { 
+
+    
+    private final OverdueWrapperFactory factory;
+    private final BlockingApi accessApi; 
+    private final OverdueConfig overdueConfig;
+   
+    @Inject
+    public DefaultOverdueUserApi(OverdueWrapperFactory factory,BlockingApi accessApi, ExtendedOverdueService service,  CatalogService catalogService) {
+        this.factory = factory;
+        this.accessApi = accessApi;
+        this.overdueConfig = service.getOverdueConfig();
+    }
+    
+    @SuppressWarnings("unchecked")
+    @Override
+    public <T extends Blockable> OverdueState<T> getOverdueStateFor(T overdueable) throws OverdueError {
+        try {
+            String stateName = accessApi.getBlockingStateFor(overdueable).getStateName();
+            OverdueStateSet<SubscriptionBundle> states = overdueConfig.getBundleStateSet();
+            return (OverdueState<T>) states.findState(stateName);
+        } catch (OverdueApiException e) {
+            throw new OverdueError(e, ErrorCode.OVERDUE_CAT_ERROR_ENCOUNTERED,overdueable.getId(), overdueable.getClass().getSimpleName());
+        }
+    }
+    
+    @Override
+    public <T extends Blockable> OverdueState<T> refreshOverdueStateFor(T overdueable) throws OverdueError, OverdueApiException {
+        OverdueWrapper<T> wrapper = factory.createOverdueWrapperFor(overdueable);
+        return wrapper.refresh();
+    } 
+ 
+
+    @Override
+    public <T extends Blockable> void setOverrideBillingStateForAccount(
+            T overdueable, BillingState<T> state) {
+        throw new NotImplementedException();
+    }
+    
+}
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
new file mode 100644
index 0000000..abd4e0a
--- /dev/null
+++ b/overdue/src/main/java/com/ning/billing/overdue/applicator/OverdueStateApplicator.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2010-2011 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.overdue.applicator;
+
+import org.apache.commons.lang.NotImplementedException;
+import org.joda.time.DateTime;
+import org.joda.time.Period;
+
+import com.google.inject.Inject;
+import com.ning.billing.ErrorCode;
+import com.ning.billing.junction.api.Blockable;
+import com.ning.billing.junction.api.BlockingApi;
+import com.ning.billing.junction.api.DefaultBlockingState;
+import com.ning.billing.ovedue.notification.OverdueCheckPoster;
+import com.ning.billing.overdue.OverdueApiException;
+import com.ning.billing.overdue.OverdueService;
+import com.ning.billing.overdue.OverdueState;
+import com.ning.billing.overdue.config.api.OverdueError;
+import com.ning.billing.util.clock.Clock;
+
+public class OverdueStateApplicator<T extends Blockable>{
+
+    private final BlockingApi blockingApi;
+    private final Clock clock;
+    private final OverdueCheckPoster poster;
+
+
+    @Inject
+    public OverdueStateApplicator(BlockingApi accessApi, Clock clock, OverdueCheckPoster poster) {
+        this.blockingApi = accessApi;
+        this.clock = clock;
+        this.poster = poster;
+    }
+
+    public void apply(T overdueable, OverdueState<T> previousOverdueState, OverdueState<T> nextOverdueState) throws OverdueError {
+        if(previousOverdueState.getName().equals(nextOverdueState.getName())) {
+            return; // nothing to do
+        }
+
+        storeNewState(overdueable, nextOverdueState);
+        try {
+            Period reevaluationInterval     = nextOverdueState.getReevaluationInterval();
+            if(!nextOverdueState.isClearState()) {
+                createFutureNotification(overdueable, clock.getUTCNow().plus(reevaluationInterval));
+            }
+        } catch(OverdueApiException e) {
+            if(e.getCode() != ErrorCode.OVERDUE_NO_REEVALUATION_INTERVAL.getCode()) {
+                new OverdueError(e);
+            }
+        }
+
+        if(nextOverdueState.isClearState()) {
+            clear(overdueable);
+        }
+
+
+
+    }
+
+
+    protected void storeNewState(T blockable, OverdueState<T> nextOverdueState) throws OverdueError {
+        try {
+            blockingApi.setBlockingState(new DefaultBlockingState(blockable.getId(), nextOverdueState.getName(), Blockable.Type.get(blockable), 
+                    OverdueService.OVERDUE_SERVICE_NAME, blockChanges(nextOverdueState), blockEntitlement(nextOverdueState), blockBilling(nextOverdueState)));
+        } catch (Exception e) {
+            throw new OverdueError(e, ErrorCode.OVERDUE_CAT_ERROR_ENCOUNTERED, blockable.getId(), blockable.getClass().getName());
+        }
+    }
+
+    private boolean blockChanges(OverdueState<T> nextOverdueState) {
+        return nextOverdueState.blockChanges();
+    }
+
+    private boolean blockBilling(OverdueState<T> nextOverdueState) {
+        return nextOverdueState.disableEntitlementAndChangesBlocked();
+    }
+
+    private boolean blockEntitlement(OverdueState<T> nextOverdueState) {
+        return nextOverdueState.disableEntitlementAndChangesBlocked();
+    }
+
+    protected void createFutureNotification(T overdueable,
+            DateTime timeOfNextCheck) {
+        poster.insertOverdueCheckNotification(overdueable, timeOfNextCheck);
+
+    }
+
+    protected void clear(T blockable) {
+        //Need to clear the overrride table here too (when we add it)
+        poster.clearNotificationsFor(blockable);
+    }
+
+}
diff --git a/overdue/src/main/java/com/ning/billing/overdue/calculator/BillingStateCalculator.java b/overdue/src/main/java/com/ning/billing/overdue/calculator/BillingStateCalculator.java
new file mode 100644
index 0000000..d47f3f3
--- /dev/null
+++ b/overdue/src/main/java/com/ning/billing/overdue/calculator/BillingStateCalculator.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2010-2011 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.overdue.calculator;
+
+import java.math.BigDecimal;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+
+import com.google.inject.Inject;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceUserApi;
+import com.ning.billing.junction.api.Blockable;
+import com.ning.billing.overdue.config.api.BillingState;
+import com.ning.billing.util.clock.Clock;
+
+public abstract class BillingStateCalculator<T extends Blockable> {
+
+    private final InvoiceUserApi invoiceApi;
+    private final Clock clock;
+    
+    protected class InvoiceDateComparator implements Comparator<Invoice> {
+        @Override
+        public int compare(Invoice i1, Invoice i2) {
+            DateTime d1 = i1.getInvoiceDate();
+            DateTime d2 = i2.getInvoiceDate();
+            if(d1.compareTo(d2) == 0) {
+                return i1.hashCode() - i2.hashCode(); // consistent (arbitrary) resolution for tied dates
+            }
+            return d1.compareTo(d2);
+        }
+    }
+
+    @Inject 
+    public BillingStateCalculator(InvoiceUserApi invoiceApi, Clock clock) {
+        this.invoiceApi = invoiceApi;
+        this.clock = clock;
+    }
+    
+    public abstract BillingState<T> calculateBillingState(T overdueable) throws EntitlementUserApiException;
+    
+    protected DateTime earliest(SortedSet<Invoice> unpaidInvoices) {
+        return unpaidInvoices.first().getInvoiceDate();
+    }
+
+    protected BigDecimal sumBalance(SortedSet<Invoice> unpaidInvoices) {
+        BigDecimal sum = BigDecimal.ZERO;
+        Iterator<Invoice> it = unpaidInvoices.iterator();
+        while(it.hasNext()) {
+            sum = sum.add(it.next().getBalance());
+        }
+        return sum;
+    }
+
+    protected SortedSet<Invoice> unpaidInvoicesForAccount(UUID accountId) {
+        Collection<Invoice> invoices = invoiceApi.getUnpaidInvoicesByAccountId(accountId, clock.getUTCNow());
+        SortedSet<Invoice> sortedInvoices = new TreeSet<Invoice>(new InvoiceDateComparator());
+        sortedInvoices.addAll(invoices);
+        return sortedInvoices;
+    }
+}
diff --git a/overdue/src/main/java/com/ning/billing/overdue/calculator/BillingStateCalculatorBundle.java b/overdue/src/main/java/com/ning/billing/overdue/calculator/BillingStateCalculatorBundle.java
new file mode 100644
index 0000000..89a7cc6
--- /dev/null
+++ b/overdue/src/main/java/com/ning/billing/overdue/calculator/BillingStateCalculatorBundle.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2010-2011 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.overdue.calculator;
+
+import java.math.BigDecimal;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+
+import com.google.inject.Inject;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.PhaseType;
+import com.ning.billing.catalog.api.PriceList;
+import com.ning.billing.catalog.api.Product;
+import com.ning.billing.entitlement.api.user.EntitlementUserApi;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.api.InvoiceUserApi;
+import com.ning.billing.overdue.config.api.BillingStateBundle;
+import com.ning.billing.overdue.config.api.PaymentResponse;
+import com.ning.billing.util.clock.Clock;
+import com.ning.billing.util.tag.Tag;
+
+public class BillingStateCalculatorBundle  extends BillingStateCalculator<SubscriptionBundle>{
+
+    private EntitlementUserApi entitlementApi;
+
+    @Inject 
+    public BillingStateCalculatorBundle(EntitlementUserApi entitlementApi, InvoiceUserApi invoiceApi, Clock clock) {
+        super(invoiceApi, clock);
+        this.entitlementApi = entitlementApi;
+    }
+    
+    @Override
+    public BillingStateBundle calculateBillingState(SubscriptionBundle bundle) throws EntitlementUserApiException {
+        
+        SortedSet<Invoice> unpaidInvoices = unpaidInvoicesForBundle(bundle.getId(), bundle.getAccountId());
+ 
+        Subscription basePlan = entitlementApi.getBaseSubscription(bundle.getId());
+        
+        UUID id = bundle.getId();
+        int numberOfUnpaidInvoices = unpaidInvoices.size(); 
+        BigDecimal unpaidInvoiceBalance = sumBalance(unpaidInvoices);
+        DateTime dateOfEarliestUnpaidInvoice = earliest(unpaidInvoices);
+        PaymentResponse responseForLastFailedPayment = PaymentResponse.INSUFFICIENT_FUNDS; //TODO MDW
+        Tag[] tags = new Tag[]{}; //TODO MDW
+        Product basePlanProduct = basePlan.getCurrentPlan().getProduct();
+        BillingPeriod basePlanBillingPeriod = basePlan.getCurrentPlan().getBillingPeriod();
+        PriceList basePlanPriceList = basePlan.getCurrentPriceList();
+        PhaseType basePlanPhaseType = basePlan.getCurrentPhase().getPhaseType();
+        
+
+        return new BillingStateBundle( 
+            id, 
+            numberOfUnpaidInvoices, 
+            unpaidInvoiceBalance,
+            dateOfEarliestUnpaidInvoice,
+            responseForLastFailedPayment,
+            tags, 
+            basePlanProduct,
+            basePlanBillingPeriod, 
+            basePlanPriceList, 
+            basePlanPhaseType);
+        
+    }
+
+    public SortedSet<Invoice> unpaidInvoicesForBundle(UUID bundleId, UUID accountId) {
+        SortedSet<Invoice> unpaidInvoices = unpaidInvoicesForAccount(accountId);
+        SortedSet<Invoice> result = new TreeSet<Invoice>(new InvoiceDateComparator());
+        result.addAll(unpaidInvoices);
+        for(Invoice invoice : unpaidInvoices) {
+            if(!invoiceHasAnItemFromBundle(invoice, bundleId)) {
+                result.remove(invoice);
+            }
+        }
+        return result;
+    }
+
+    private boolean invoiceHasAnItemFromBundle(Invoice invoice, UUID bundleId) {
+        for(InvoiceItem item : invoice.getInvoiceItems()) {
+            if(item.getBundleId().equals(bundleId)) {
+                return true;
+            }
+        }
+        return false;
+    }
+    
+    
+}
diff --git a/overdue/src/main/java/com/ning/billing/overdue/config/Condition.java b/overdue/src/main/java/com/ning/billing/overdue/config/Condition.java
new file mode 100644
index 0000000..6fc8564
--- /dev/null
+++ b/overdue/src/main/java/com/ning/billing/overdue/config/Condition.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2010-2011 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.overdue.config;
+
+import org.joda.time.DateTime;
+
+import com.ning.billing.junction.api.Blockable;
+import com.ning.billing.overdue.config.api.BillingState;
+
+
+
+public interface Condition<T extends Blockable> {
+
+    public boolean evaluate(BillingState state, DateTime now);
+
+}
\ No newline at end of file
diff --git a/overdue/src/main/java/com/ning/billing/overdue/config/DefaultCondition.java b/overdue/src/main/java/com/ning/billing/overdue/config/DefaultCondition.java
new file mode 100644
index 0000000..44f6c99
--- /dev/null
+++ b/overdue/src/main/java/com/ning/billing/overdue/config/DefaultCondition.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2010-2011 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.overdue.config;
+
+import java.math.BigDecimal;
+import java.net.URI;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+
+import org.joda.time.DateTime;
+
+import com.ning.billing.catalog.api.Duration;
+import com.ning.billing.catalog.api.TimeUnit;
+import com.ning.billing.junction.api.Blockable;
+import com.ning.billing.overdue.config.api.BillingState;
+import com.ning.billing.overdue.config.api.PaymentResponse;
+import com.ning.billing.util.config.ValidatingConfig;
+import com.ning.billing.util.config.ValidationErrors;
+import com.ning.billing.util.tag.ControlTagType;
+import com.ning.billing.util.tag.Tag;
+
+@XmlAccessorType(XmlAccessType.NONE)
+
+public class DefaultCondition<T extends Blockable> extends ValidatingConfig<OverdueConfig> implements Condition<T> {
+	@XmlElement(required=false, name="numberOfUnpaidInvoicesEqualsOrExceeds")
+	private Integer numberOfUnpaidInvoicesEqualsOrExceeds;
+
+	@XmlElement(required=false, name="totalUnpaidInvoiceBalanceEqualsOrExceeds")
+	private BigDecimal totalUnpaidInvoiceBalanceEqualsOrExceeds;
+
+	@XmlElement(required=false, name="timeSinceEarliestUnpaidInvoiceEqualsOrExceeds")
+	private DefaultDuration timeSinceEarliestUnpaidInvoiceEqualsOrExceeds;
+
+	@XmlElementWrapper(required=false, name="responseForLastFailedPaymentIn")
+	@XmlElement(required=false, name="response")
+	private PaymentResponse[] responseForLastFailedPayment;
+
+	@XmlElement(required=false, name="controlTag")
+	private ControlTagType controlTag;
+	
+	/* (non-Javadoc)
+     * @see com.ning.billing.catalog.overdue.Condition#evaluate(com.ning.billing.catalog.api.overdue.BillingState, org.joda.time.DateTime)
+     */
+	@Override
+    public boolean evaluate(BillingState state, DateTime now) {
+		return 
+				(numberOfUnpaidInvoicesEqualsOrExceeds == null || state.getNumberOfUnpaidInvoices() >= numberOfUnpaidInvoicesEqualsOrExceeds.intValue() ) &&
+				(totalUnpaidInvoiceBalanceEqualsOrExceeds == null || totalUnpaidInvoiceBalanceEqualsOrExceeds.compareTo(state.getBalanceOfUnpaidInvoices()) <= 0) &&
+				(timeSinceEarliestUnpaidInvoiceEqualsOrExceeds == null || !timeSinceEarliestUnpaidInvoiceEqualsOrExceeds.addToDateTime(state.getDateOfEarliestUnpaidInvoice()).isAfter(now)) &&
+				(responseForLastFailedPayment == null || responseIsIn(state.getResponseForLastFailedPayment(), responseForLastFailedPayment)) &&
+				(controlTag == null || isTagIn(controlTag, state.getTags()));
+	}
+	
+	private boolean responseIsIn(PaymentResponse actualResponse,
+			PaymentResponse[] responseForLastFailedPayment) {
+		for(PaymentResponse response: responseForLastFailedPayment) {
+			if(response.equals(actualResponse)) return true;
+		}
+		return false;
+	}
+
+	private boolean isTagIn(ControlTagType tag, Tag[] tags) {
+		for(Tag t : tags) {
+			if (t.getTagDefinitionName().equals(tag.toString())) return true;
+		}
+		return false;
+	}
+
+	@Override
+	public ValidationErrors validate(OverdueConfig root,
+			ValidationErrors errors) {
+		return errors;
+	}
+
+	@Override
+	public void initialize(OverdueConfig root, URI uri) {
+	}
+
+    public Duration getTimeOffset() {
+        if (timeSinceEarliestUnpaidInvoiceEqualsOrExceeds != null) {
+            return timeSinceEarliestUnpaidInvoiceEqualsOrExceeds;
+        } else { 
+            return new DefaultDuration().setUnit(TimeUnit.DAYS).setNumber(0); // zero time
+        }
+        
+    }
+}
diff --git a/overdue/src/main/java/com/ning/billing/overdue/config/DefaultDuration.java b/overdue/src/main/java/com/ning/billing/overdue/config/DefaultDuration.java
new file mode 100644
index 0000000..7301e46
--- /dev/null
+++ b/overdue/src/main/java/com/ning/billing/overdue/config/DefaultDuration.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2010-2011 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.overdue.config;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+
+import org.joda.time.DateTime;
+import org.joda.time.Period;
+
+import com.ning.billing.catalog.api.Duration;
+import com.ning.billing.catalog.api.TimeUnit;
+import com.ning.billing.util.config.ValidatingConfig;
+import com.ning.billing.util.config.ValidationErrors;
+
+@XmlAccessorType(XmlAccessType.NONE)
+public class DefaultDuration extends ValidatingConfig<OverdueConfig> implements Duration {
+	@XmlElement(required=true)
+    private TimeUnit unit;
+
+	@XmlElement(required=false)
+    private Integer number = -1;
+	
+    /* (non-Javadoc)
+	 * @see com.ning.billing.catalog.IDuration#getUnit()
+	 */
+    @Override
+	public TimeUnit getUnit() {
+        return unit;
+    }
+
+    /* (non-Javadoc)
+	 * @see com.ning.billing.catalog.IDuration#getLength()
+	 */
+    @Override
+	public int getNumber() {
+        return number;
+    }
+
+    @Override
+    public DateTime addToDateTime(DateTime dateTime) {
+        if ((number == null) && (unit != TimeUnit.UNLIMITED)) {return dateTime;}
+
+        switch (unit) {
+            case DAYS:
+                return dateTime.plusDays(number);
+            case MONTHS:
+                return dateTime.plusMonths(number);
+            case YEARS:
+                return dateTime.plusYears(number);
+            case UNLIMITED:
+                return dateTime.plusYears(100);
+            default:
+                return dateTime;
+        }
+    }
+
+    @Override
+    public Period toJodaPeriod() {
+        if ((number == null) && (unit != TimeUnit.UNLIMITED)) {return new Period();}
+
+        switch (unit) {
+            case DAYS:
+                return new Period().withDays(number);
+            case MONTHS:
+                return new Period().withMonths(number);
+            case YEARS:
+                return new Period().withYears(number);
+            case UNLIMITED:
+                return new Period().withYears(100);
+            default:
+                return new Period();
+        }
+    }
+
+    @Override
+	public ValidationErrors validate(OverdueConfig catalog, ValidationErrors errors) {
+		//TODO MDW - Validation TimeUnit UNLIMITED iff number == -1
+		return errors;
+	}
+
+	protected DefaultDuration setUnit(TimeUnit unit) {
+		this.unit = unit;
+		return this;
+	}
+
+	protected DefaultDuration setNumber(Integer number) {
+		this.number = number;
+		return this;
+	}
+	
+	
+}
diff --git a/overdue/src/main/java/com/ning/billing/overdue/config/DefaultOverdueState.java b/overdue/src/main/java/com/ning/billing/overdue/config/DefaultOverdueState.java
new file mode 100644
index 0000000..690d717
--- /dev/null
+++ b/overdue/src/main/java/com/ning/billing/overdue/config/DefaultOverdueState.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright 2010-2011 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.overdue.config;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlID;
+
+import org.joda.time.DateTime;
+import org.joda.time.Period;
+
+import com.ning.billing.ErrorCode;
+import com.ning.billing.catalog.api.TimeUnit;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.junction.api.Blockable;
+import com.ning.billing.overdue.OverdueApiException;
+import com.ning.billing.overdue.OverdueState;
+import com.ning.billing.util.config.ValidatingConfig;
+import com.ning.billing.util.config.ValidationError;
+import com.ning.billing.util.config.ValidationErrors;
+
+@XmlAccessorType(XmlAccessType.NONE)
+public class DefaultOverdueState<T extends Blockable> extends ValidatingConfig<OverdueConfig>  implements OverdueState<T> {
+
+    private static final int MAX_NAME_LENGTH = 50;
+    
+    @XmlElement(required=false, name="condition")
+	private DefaultCondition<T> condition;
+
+	@XmlAttribute(required=true, name="name")
+    @XmlID
+    private String name; 
+
+	@XmlElement(required=false, name="externalMessage")
+	private String externalMessage = "";
+    
+    @XmlElement(required=false, name="blockChanges")
+    private Boolean blockChanges = false;
+
+    @XmlElement(required=false, name="disableEntitlementAndChangesBlocked")
+    private Boolean disableEntitlement = false;
+    
+    @XmlElement(required=false, name="daysBetweenPaymentRetries")
+    private Integer daysBetweenPaymentRetries = 8;
+    
+    @XmlElement(required=false, name="isClearState")
+    private Boolean isClearState = false;
+    
+    @XmlElement(required=false, name="autoReevaluationInterval")
+    private DefaultDuration autoReevaluationInterval;
+
+
+    
+	//Other actions could include
+	// - send email
+	// - trigger payment retry?
+	// - add tagStore to bundle/account
+	// - set payment failure email template
+	// - set payment retry interval
+	// - backup payment mechanism?
+
+	/* (non-Javadoc)
+     * @see com.ning.billing.catalog.overdue.OverdueState#getStageName()
+     */
+	@Override
+    public String getName() {
+		return name;
+	}
+
+	/* (non-Javadoc)
+     * @see com.ning.billing.catalog.overdue.OverdueState#getExternalMessage()
+     */
+	@Override
+    public String getExternalMessage() {
+		return externalMessage;
+	}
+	
+    @Override
+    public boolean blockChanges() {
+        return blockChanges || disableEntitlement;
+    }
+
+	/* (non-Javadoc)
+     * @see com.ning.billing.catalog.overdue.OverdueState#applyCancel()
+     */
+	@Override
+    public boolean disableEntitlementAndChangesBlocked() {
+		return disableEntitlement;
+	}
+
+    @Override
+    public Period getReevaluationInterval() throws OverdueApiException {
+        if(autoReevaluationInterval == null || autoReevaluationInterval.getUnit() == TimeUnit.UNLIMITED || autoReevaluationInterval.getNumber() == 0) {
+            throw new OverdueApiException(ErrorCode.OVERDUE_NO_REEVALUATION_INTERVAL, name);
+        }
+        return autoReevaluationInterval.toJodaPeriod();       
+    }
+	
+    protected DefaultCondition<T> getCondition() {
+		return condition;
+	}
+
+	protected DefaultOverdueState<T> setName(String name) {
+		this.name = name;
+		return this;
+	}
+
+	protected DefaultOverdueState<T> setExternalMessage(String externalMessage) {
+		this.externalMessage = externalMessage;
+		return this;
+	}
+
+    protected DefaultOverdueState<T> setDisableEntitlement(boolean cancel) {
+        this.disableEntitlement = cancel;
+        return this;
+    }
+
+    protected DefaultOverdueState<T> setBlockChanges(boolean cancel) {
+        this.blockChanges = cancel;
+        return this;
+    }
+
+	protected DefaultOverdueState<T> setCondition(DefaultCondition<T> condition) {
+		this.condition = condition;
+		return this;
+	}
+
+    @Override
+    public boolean isClearState() {
+        return isClearState;
+    }
+
+    @Override
+    public ValidationErrors validate(OverdueConfig root,
+            ValidationErrors errors) {
+        if(name.length() > MAX_NAME_LENGTH) {
+            errors.add(new ValidationError(String.format("Name of state '%s' exceeds the maximum length of %d",name,MAX_NAME_LENGTH),root.getURI(), DefaultOverdueState.class, name));
+        }
+        return errors;
+    }
+
+    @Override
+    public int getDaysBetweenPaymentRetries() {
+         return daysBetweenPaymentRetries;
+    }
+
+
+}
diff --git a/overdue/src/main/java/com/ning/billing/overdue/config/DefaultOverdueStateSet.java b/overdue/src/main/java/com/ning/billing/overdue/config/DefaultOverdueStateSet.java
new file mode 100644
index 0000000..277fb1b
--- /dev/null
+++ b/overdue/src/main/java/com/ning/billing/overdue/config/DefaultOverdueStateSet.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2010-2011 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.overdue.config;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+
+import org.joda.time.DateTime;
+import org.joda.time.MutablePeriod;
+import org.joda.time.Period;
+
+import com.ning.billing.ErrorCode;
+import com.ning.billing.catalog.api.Duration;
+import com.ning.billing.junction.api.Blockable;
+import com.ning.billing.overdue.OverdueApiException;
+import com.ning.billing.overdue.OverdueState;
+import com.ning.billing.overdue.config.api.BillingState;
+import com.ning.billing.overdue.config.api.OverdueStateSet;
+import com.ning.billing.util.config.ValidatingConfig;
+import com.ning.billing.util.config.ValidationErrors;
+
+@XmlAccessorType(XmlAccessType.NONE)
+public abstract class DefaultOverdueStateSet<T extends Blockable> extends ValidatingConfig<OverdueConfig> implements OverdueStateSet<T> {
+    private static final Period ZERO_PERIOD = new Period();
+    private DefaultOverdueState<T> clearState;
+
+    protected abstract DefaultOverdueState<T>[] getStates();
+
+    private DefaultOverdueState<T> getClearState() throws OverdueApiException {
+        for(DefaultOverdueState<T> overdueState : getStates()) {
+            if(overdueState.isClearState()) {   
+                return overdueState;
+            }
+        }
+        throw new OverdueApiException(ErrorCode.CAT_MISSING_CLEAR_STATE);
+    }
+
+    @Override
+    public OverdueState<T> findState(String stateName) throws OverdueApiException {
+        for(DefaultOverdueState<T> state: getStates()) {
+            if(state.getName().equals(stateName) ) { return state; }
+        }
+        throw new OverdueApiException(ErrorCode.CAT_NO_SUCH_OVEDUE_STATE, stateName);
+    }
+
+
+    /* (non-Javadoc)
+     * @see com.ning.billing.catalog.overdue.OverdueBillingState#findClearState()
+     */
+    @Override
+    public DefaultOverdueState<T> findClearState() throws OverdueApiException {
+        if (clearState != null) {
+            clearState = getClearState();
+        }
+        return clearState;
+    }
+
+    /* (non-Javadoc)
+     * @see com.ning.billing.catalog.overdue.OverdueBillingState#calculateOverdueState(com.ning.billing.catalog.api.overdue.BillingState, org.joda.time.DateTime)
+     */
+    @Override
+    public DefaultOverdueState<T> calculateOverdueState(BillingState<T> billingState, DateTime now) throws OverdueApiException {         
+        for(DefaultOverdueState<T> overdueState : getStates()) {
+            if(overdueState.getCondition().evaluate(billingState, now)) {   
+                return overdueState;
+            }
+        }
+        return  findClearState();
+    }
+
+    @Override
+    public ValidationErrors validate(OverdueConfig root,
+            ValidationErrors errors) {
+        for(DefaultOverdueState<T> state: getStates()) {
+            state.validate(root, errors);
+        }
+        try {
+            getClearState();
+        } catch (OverdueApiException e) {
+            if(e.getCode() == ErrorCode.CAT_MISSING_CLEAR_STATE.getCode()) {
+                errors.add("Overdue state set is missing a clear state.", 
+                        root.getURI(), this.getClass(), "");
+            }
+        }
+
+        return errors;
+    }
+}
diff --git a/overdue/src/main/java/com/ning/billing/overdue/config/OverdueConfig.java b/overdue/src/main/java/com/ning/billing/overdue/config/OverdueConfig.java
new file mode 100644
index 0000000..16d5a51
--- /dev/null
+++ b/overdue/src/main/java/com/ning/billing/overdue/config/OverdueConfig.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2010-2011 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.overdue.config;
+
+import java.net.URI;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.util.config.ValidatingConfig;
+import com.ning.billing.util.config.ValidationErrors;
+
+@XmlRootElement(name="overdueConfig")
+@XmlAccessorType(XmlAccessType.NONE)
+public class OverdueConfig  extends ValidatingConfig<OverdueConfig> {
+
+    @XmlElement(required=true, name="bundleOverdueStates")
+    private OverdueStatesBundle bundleOverdueStates;
+
+    public DefaultOverdueStateSet<SubscriptionBundle> getBundleStateSet() {
+        return bundleOverdueStates;
+    }
+
+    @Override
+    public ValidationErrors validate(OverdueConfig root,
+            ValidationErrors errors) {
+        return bundleOverdueStates.validate(root, errors);
+    }
+    
+    public OverdueConfig setOverdueStatesBundle(OverdueStatesBundle bundleODS) {
+        this.bundleOverdueStates = bundleODS;
+        return this;
+    }
+
+
+    public URI getURI() {
+        return null;
+    }
+
+}
diff --git a/overdue/src/main/java/com/ning/billing/overdue/config/OverdueStatesBundle.java b/overdue/src/main/java/com/ning/billing/overdue/config/OverdueStatesBundle.java
new file mode 100644
index 0000000..3f07db5
--- /dev/null
+++ b/overdue/src/main/java/com/ning/billing/overdue/config/OverdueStatesBundle.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2010-2011 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.overdue.config;
+
+import javax.xml.bind.annotation.XmlElement;
+
+import org.joda.time.DateTime;
+
+import com.ning.billing.ErrorCode;
+import com.ning.billing.catalog.api.Duration;
+import com.ning.billing.catalog.api.TimeUnit;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.overdue.OverdueApiException;
+import com.ning.billing.overdue.OverdueState;
+
+public class OverdueStatesBundle extends DefaultOverdueStateSet<SubscriptionBundle>{
+
+    @XmlElement(required=true, name="state")
+    private DefaultOverdueState<SubscriptionBundle>[] bundleOverdueStates;
+
+    @Override
+    protected DefaultOverdueState<SubscriptionBundle>[] getStates() {
+        return bundleOverdueStates;
+    }
+
+    protected OverdueStatesBundle setBundleOverdueStates(DefaultOverdueState<SubscriptionBundle>[] bundleOverdueStates) {
+        this.bundleOverdueStates = bundleOverdueStates;
+        return this;
+    }
+}
diff --git a/overdue/src/main/java/com/ning/billing/overdue/exceptions/OverdueError.java b/overdue/src/main/java/com/ning/billing/overdue/exceptions/OverdueError.java
new file mode 100644
index 0000000..3959408
--- /dev/null
+++ b/overdue/src/main/java/com/ning/billing/overdue/exceptions/OverdueError.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2010-2011 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.overdue.exceptions;
+
+public class OverdueError extends Error {
+
+    private static final long serialVersionUID = 131398536;
+
+    public OverdueError() {
+        super();
+    }
+
+    public OverdueError(String msg, Throwable arg1) {
+        super(msg, arg1);
+    }
+
+    public OverdueError(String msg) {
+        super(msg);
+    }
+
+    public OverdueError(Throwable msg) {
+        super(msg);
+    }
+}
diff --git a/overdue/src/main/java/com/ning/billing/overdue/glue/OverdueModule.java b/overdue/src/main/java/com/ning/billing/overdue/glue/OverdueModule.java
new file mode 100644
index 0000000..da54c7b
--- /dev/null
+++ b/overdue/src/main/java/com/ning/billing/overdue/glue/OverdueModule.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2010-2011 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.overdue.glue;
+
+import org.skife.config.ConfigurationObjectFactory;
+
+import com.google.inject.AbstractModule;
+import com.ning.billing.overdue.OverdueProperties;
+
+
+public class OverdueModule extends AbstractModule {
+
+    @Override
+    protected void configure() {
+        final OverdueProperties config = new ConfigurationObjectFactory(System.getProperties()).build(OverdueProperties.class);
+        bind(OverdueProperties.class).toInstance(config);
+    }
+    
+
+}
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
new file mode 100644
index 0000000..8f2d03f
--- /dev/null
+++ b/overdue/src/main/java/com/ning/billing/overdue/listener/OverdueDispatcher.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2010-2011 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.overdue.listener;
+
+import java.util.UUID;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.inject.Inject;
+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.entitlement.api.user.EntitlementUserApi;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.junction.api.Blockable;
+import com.ning.billing.overdue.OverdueApiException;
+import com.ning.billing.overdue.config.api.OverdueError;
+import com.ning.billing.overdue.wrapper.OverdueWrapper;
+import com.ning.billing.overdue.wrapper.OverdueWrapperFactory;
+
+public class OverdueDispatcher {
+    Logger log = LoggerFactory.getLogger(OverdueDispatcher.class);
+    
+    private final EntitlementUserApi entitlementUserApi;
+    private final AccountUserApi accountUserApi;
+    private final OverdueWrapperFactory factory;
+    
+    @Inject
+    public OverdueDispatcher(AccountUserApi accountUserApi, 
+            EntitlementUserApi entitlementUserApi, 
+            OverdueWrapperFactory factory) {
+        this.accountUserApi = accountUserApi;
+        this.entitlementUserApi = entitlementUserApi;
+        this.factory = factory;
+    }
+    
+    public void processOverdueForAccount(UUID accountId) {
+        try {
+            Account account = accountUserApi.getAccountById(accountId);
+            processOverdue(account);
+        } catch (AccountApiException e) {
+            log.error("Error processing Overdue for Account with id: " + accountId.toString(), e);
+        }
+    }
+    
+    public void processOverdueForBundle(UUID bundleId) {
+        try {
+            SubscriptionBundle bundle        = entitlementUserApi.getBundleFromId(bundleId);
+            processOverdue(bundle);
+        } catch (EntitlementUserApiException e) {
+            log.error("Error processing Overdue for Bundle with id: " + bundleId.toString(), e);
+        }
+    }
+
+    public void processOverdue(Blockable bloackable) {
+        try {
+            OverdueWrapper<?> wrapper = factory.createOverdueWrapperFor(bloackable);
+            wrapper.refresh();
+        } catch (OverdueError e) {
+            log.error("Error processing Overdue for Blockable with id: " + bloackable.getId().toString(), e);
+        } catch (OverdueApiException e) {
+            log.error("Error processing Overdue for Blockable with id: " + bloackable.getId().toString(), e);
+        }
+    }
+
+    public void processOverdue(UUID blockableId) {
+        
+        
+    }
+
+}
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
new file mode 100644
index 0000000..2c193b4
--- /dev/null
+++ b/overdue/src/main/java/com/ning/billing/overdue/listener/OverdueListener.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2010-2011 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.overdue.listener;
+
+import java.util.UUID;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.eventbus.Subscribe;
+import com.google.inject.Inject;
+import com.ning.billing.payment.api.PaymentApi;
+import com.ning.billing.payment.api.PaymentAttempt;
+import com.ning.billing.payment.api.PaymentErrorEvent;
+import com.ning.billing.payment.api.PaymentInfoEvent;
+
+public class OverdueListener {
+    OverdueDispatcher dispatcher;
+    
+    //
+    //TODO disabled overdue for prod deployment - comments should be removed
+    //
+    
+    private final static Logger log = LoggerFactory.getLogger(OverdueListener.class);
+    private final PaymentApi paymentApi;
+
+    @Inject
+    public OverdueListener(OverdueDispatcher dispatcher, PaymentApi paymentApi) {
+        this.dispatcher = dispatcher;
+        this.paymentApi = paymentApi;
+    }
+
+    @Subscribe
+    public void handlePaymentInfoEvent(final PaymentInfoEvent event) {
+//       String paymentId = event.getPaymentId();
+//       PaymentAttempt attempt = paymentApi.getPaymentAttemptForPaymentId(paymentId);
+//       UUID accountId = attempt.getAccountId();
+//       dispatcher.processOverdueForAccount(accountId);
+    }
+    
+    @Subscribe
+    public void handlePaymentErrorEvent(final PaymentErrorEvent event) {
+//       UUID accountId = event.getAccountId();
+//       dispatcher.processOverdueForAccount(accountId);
+    }
+
+    public void handleNextOverdueCheck(UUID overdueableId) { 
+//       dispatcher.processOverdue(overdueableId);
+    }
+    
+ 
+}
diff --git a/overdue/src/main/java/com/ning/billing/overdue/OverdueProperties.java b/overdue/src/main/java/com/ning/billing/overdue/OverdueProperties.java
new file mode 100644
index 0000000..b4f4d0e
--- /dev/null
+++ b/overdue/src/main/java/com/ning/billing/overdue/OverdueProperties.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2010-2011 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.overdue;
+
+import org.skife.config.Config;
+import org.skife.config.Default;
+
+import com.ning.billing.config.KillbillConfig;
+import com.ning.billing.config.NotificationConfig;
+
+
+public interface OverdueProperties extends NotificationConfig, KillbillConfig  {
+
+    @Override
+    @Config("killbill.overdue.engine.notifications.sleep")
+    @Default("500")
+    public long getSleepTimeMs();
+
+    @Override
+    @Config("killbill.notifications.off")
+    @Default("false")
+    public boolean isNotificationProcessingOff();
+
+    @Config("killbill.overdue.maxNumberOfMonthsInFuture")
+    @Default("36")
+    public int getNumberOfMonthsInFuture();
+
+    @Config("killbill.overdue.configUri")
+    @Default("jar:///com/ning/billing/irs/catalog/Catalog.xml")
+    public String getConfigURI();
+}
\ No newline at end of file
diff --git a/overdue/src/main/java/com/ning/billing/overdue/service/DefaultOverdueService.java b/overdue/src/main/java/com/ning/billing/overdue/service/DefaultOverdueService.java
new file mode 100644
index 0000000..d810a84
--- /dev/null
+++ b/overdue/src/main/java/com/ning/billing/overdue/service/DefaultOverdueService.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2010-2011 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.overdue.service;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import com.google.inject.Inject;
+import com.ning.billing.lifecycle.LifecycleHandlerType;
+import com.ning.billing.lifecycle.LifecycleHandlerType.LifecycleLevel;
+import com.ning.billing.overdue.OverdueProperties;
+import com.ning.billing.overdue.OverdueUserApi;
+import com.ning.billing.overdue.config.OverdueConfig;
+import com.ning.billing.util.config.XMLLoader;
+
+public class DefaultOverdueService implements ExtendedOverdueService {
+    public static final String OVERDUE_SERVICE_NAME = "overdue-service";
+    private OverdueUserApi userApi;
+    private OverdueConfig overdueConfig;
+    private OverdueProperties properties;
+
+    private boolean isInitialized;
+
+    @Inject
+    public DefaultOverdueService(OverdueUserApi userApi, OverdueProperties properties){
+        this.userApi = userApi;
+        this.properties = properties;
+    }
+    
+    @Override
+    public String getName() {
+        return OVERDUE_SERVICE_NAME;
+    }
+
+    @Override
+    public OverdueUserApi getUserApi() {
+        return userApi;
+    }
+
+    @Override
+   public OverdueConfig getOverdueConfig() {
+        return overdueConfig;
+    }
+
+    @LifecycleHandlerType(LifecycleLevel.INIT_SERVICE)
+    public synchronized void loadConfig() throws ServiceException {
+        if (!isInitialized) {
+            try {
+                System.out.println("Overdue config URI" + properties.getConfigURI());
+                URI u = new URI(properties.getConfigURI());
+                overdueConfig = XMLLoader.getObjectFromUri(u, OverdueConfig.class);
+
+                isInitialized = true;
+            } catch (URISyntaxException e) {
+		//                overdueConfig = new OverdueConfig();
+            } catch (Exception e) {
+                throw new ServiceException(e);
+            }
+        }
+    }
+
+}
diff --git a/overdue/src/main/java/com/ning/billing/overdue/service/ExtendedOverdueService.java b/overdue/src/main/java/com/ning/billing/overdue/service/ExtendedOverdueService.java
new file mode 100644
index 0000000..abcb38a
--- /dev/null
+++ b/overdue/src/main/java/com/ning/billing/overdue/service/ExtendedOverdueService.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2010-2011 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.overdue.service;
+
+import com.ning.billing.overdue.OverdueService;
+import com.ning.billing.overdue.config.OverdueConfig;
+
+public interface ExtendedOverdueService extends OverdueService {
+
+    public OverdueConfig getOverdueConfig();
+
+}
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
new file mode 100644
index 0000000..5b811ee
--- /dev/null
+++ b/overdue/src/main/java/com/ning/billing/overdue/wrapper/OverdueWrapper.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2010-2011 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.overdue.wrapper;
+
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
+import com.ning.billing.junction.api.Blockable;
+import com.ning.billing.junction.api.BlockingApi;
+import com.ning.billing.overdue.OverdueApiException;
+import com.ning.billing.overdue.OverdueState;
+import com.ning.billing.overdue.applicator.OverdueStateApplicator;
+import com.ning.billing.overdue.calculator.BillingStateCalculator;
+import com.ning.billing.overdue.config.api.BillingState;
+import com.ning.billing.overdue.config.api.OverdueError;
+import com.ning.billing.overdue.config.api.OverdueStateSet;
+import com.ning.billing.util.clock.Clock;
+
+public class OverdueWrapper<T extends Blockable> {
+    private final T overdueable;
+    private final BlockingApi api;
+    private final Clock clock;
+    private final OverdueStateSet<T> overdueStateSet;
+    private final BillingStateCalculator<T> billingStateCalcuator;
+    private final OverdueStateApplicator<T> overdueStateApplicator;
+
+    public OverdueWrapper(T overdueable, BlockingApi api,
+            OverdueStateSet<T> overdueStateSet,
+            Clock clock,
+            BillingStateCalculator<T> billingStateCalcuator,
+            OverdueStateApplicator<T> overdueStateApplicator) {
+        this.overdueable = overdueable;
+        this.overdueStateSet = overdueStateSet;
+        this.api = api;
+        this.clock = clock;
+        this.billingStateCalcuator = billingStateCalcuator;
+        this.overdueStateApplicator = overdueStateApplicator;
+    }
+
+    public OverdueState<T> refresh() throws OverdueError, OverdueApiException {
+        try {
+	    if(overdueStateSet == null) { // No configuration available
+		return null;
+	    } 
+
+            OverdueState<T> nextOverdueState;
+            BillingState<T> billingState    = billingStateCalcuator.calculateBillingState(overdueable);
+            String previousOverdueStateName = api.getBlockingStateFor(overdueable).getStateName();
+            nextOverdueState                = overdueStateSet.calculateOverdueState(billingState, clock.getUTCNow());
+
+            if(!previousOverdueStateName.equals(nextOverdueState.getName())) {
+                overdueStateApplicator.apply(overdueable, nextOverdueState, nextOverdueState); 
+            }
+
+            return nextOverdueState;
+        } catch (EntitlementUserApiException e) {
+            throw new OverdueError(e);
+        }
+    }
+}
\ No newline at end of file
diff --git a/overdue/src/main/java/com/ning/billing/overdue/wrapper/OverdueWrapperFactory.java b/overdue/src/main/java/com/ning/billing/overdue/wrapper/OverdueWrapperFactory.java
new file mode 100644
index 0000000..0fd6854
--- /dev/null
+++ b/overdue/src/main/java/com/ning/billing/overdue/wrapper/OverdueWrapperFactory.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2010-2011 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.overdue.wrapper;
+
+import java.util.UUID;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.inject.Inject;
+import com.ning.billing.ErrorCode;
+import com.ning.billing.entitlement.api.user.EntitlementUserApi;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.junction.api.Blockable;
+import com.ning.billing.junction.api.BlockingApi;
+import com.ning.billing.junction.api.BlockingState;
+import com.ning.billing.overdue.applicator.OverdueStateApplicator;
+import com.ning.billing.overdue.calculator.BillingStateCalculatorBundle;
+import com.ning.billing.overdue.config.OverdueConfig;
+import com.ning.billing.overdue.config.api.OverdueError;
+import com.ning.billing.overdue.service.ExtendedOverdueService;
+import com.ning.billing.util.clock.Clock;
+
+public class OverdueWrapperFactory {
+    private static final Logger log =  LoggerFactory.getLogger(OverdueWrapperFactory.class);
+
+    private final OverdueConfig overdueConfig;
+    private final EntitlementUserApi entitlementApi;
+    private final BillingStateCalculatorBundle billingStateCalcuatorBundle;
+    private final OverdueStateApplicator<SubscriptionBundle> overdueStateApplicatorBundle;
+    private final BlockingApi api;
+    private final Clock clock;
+
+    @Inject
+    public OverdueWrapperFactory(BlockingApi api, ExtendedOverdueService service, Clock clock, 
+            BillingStateCalculatorBundle billingStateCalcuatorBundle, 
+            OverdueStateApplicator<SubscriptionBundle> overdueStateApplicatorBundle,
+            EntitlementUserApi entitlementApi) {
+        this.billingStateCalcuatorBundle = billingStateCalcuatorBundle;
+        this.overdueStateApplicatorBundle = overdueStateApplicatorBundle;
+        this.entitlementApi = entitlementApi;
+        this.overdueConfig = service.getOverdueConfig();
+        this.api = api;
+        this.clock = clock;
+    }
+
+    @SuppressWarnings("unchecked")
+    public <T extends Blockable> OverdueWrapper<T> createOverdueWrapperFor(T bloackable) throws OverdueError {
+        if(bloackable instanceof SubscriptionBundle) {
+            return (OverdueWrapper<T>)new OverdueWrapper<SubscriptionBundle>((SubscriptionBundle)bloackable, api, overdueConfig.getBundleStateSet(), 
+                    clock, billingStateCalcuatorBundle, overdueStateApplicatorBundle );
+        } else {
+            throw new OverdueError(ErrorCode.OVERDUE_TYPE_NOT_SUPPORTED, bloackable.getId(), bloackable.getClass());
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    public <T extends Blockable> OverdueWrapper<T> createOverdueWrapperFor(UUID id) throws OverdueError {
+        BlockingState state = api.getBlockingStateFor(id);
+
+        try {
+            switch (state.getType()) {
+            case SUBSCRIPTION_BUNDLE : {
+                SubscriptionBundle bundle = entitlementApi.getBundleFromId(id);
+                return (OverdueWrapper<T>)new OverdueWrapper<SubscriptionBundle>(bundle, api, overdueConfig.getBundleStateSet(), 
+                        clock, billingStateCalcuatorBundle, overdueStateApplicatorBundle );
+            }
+            default : {
+                throw new OverdueError(ErrorCode.OVERDUE_TYPE_NOT_SUPPORTED, id, state.getType());
+            }
+                
+            }  
+        } catch (EntitlementUserApiException e) {
+            throw new OverdueError(e);
+        }
+    }
+
+
+}
diff --git a/overdue/src/test/java/com/ning/billing/overdue/calculator/TestBillingStateCalculator.java b/overdue/src/test/java/com/ning/billing/overdue/calculator/TestBillingStateCalculator.java
new file mode 100644
index 0000000..db9ab74
--- /dev/null
+++ b/overdue/src/test/java/com/ning/billing/overdue/calculator/TestBillingStateCalculator.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2010-2011 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.overdue.calculator;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.SortedSet;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.api.InvoiceUserApi;
+import com.ning.billing.mock.BrainDeadProxyFactory;
+import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
+import com.ning.billing.overdue.config.api.BillingState;
+import com.ning.billing.util.clock.Clock;
+import com.ning.billing.util.clock.ClockMock;
+
+public class TestBillingStateCalculator {
+    Clock clock = new ClockMock();
+    InvoiceUserApi invoiceApi = BrainDeadProxyFactory.createBrainDeadProxyFor(InvoiceUserApi.class);
+    private int hash = 0;
+    DateTime now;
+    
+    public BillingStateCalculator<SubscriptionBundle> createBSCalc() {
+        now = new DateTime();
+        Collection<Invoice> invoices = new ArrayList<Invoice>();
+        invoices.add(createInvoice(now, BigDecimal.ZERO, null));
+        invoices.add(createInvoice(now.plusDays(1), BigDecimal.TEN, null));
+        invoices.add(createInvoice(now.plusDays(2), new BigDecimal("100.0"), null));
+     
+        ((ZombieControl)invoiceApi).addResult("getUnpaidInvoicesByAccountId", invoices);
+            
+        return new BillingStateCalculator<SubscriptionBundle>(invoiceApi, clock) {
+            @Override
+            public BillingState<SubscriptionBundle> calculateBillingState(
+                    SubscriptionBundle overdueable) {
+               return null;
+            }};
+    }
+    
+    public Invoice createInvoice(DateTime date, BigDecimal balance, List<InvoiceItem> invoiceItems) {
+        Invoice invoice = BrainDeadProxyFactory.createBrainDeadProxyFor(Invoice.class);
+        ((ZombieControl)invoice).addResult("getBalance", balance);
+        ((ZombieControl)invoice).addResult("getInvoiceDate", date);
+        ((ZombieControl)invoice).addResult("hashCode", hash++);
+        ((ZombieControl)invoice).addResult("getInvoiceItems", invoiceItems);
+        
+        return invoice;
+    }
+    
+    @Test(groups={"fast"}, enabled=true)
+    public void testUnpaidInvoices() {
+        BillingStateCalculator<SubscriptionBundle> calc = createBSCalc();
+        SortedSet<Invoice> invoices = calc.unpaidInvoicesForAccount(new UUID(0L,0L));
+        
+        Assert.assertEquals(invoices.size(), 3);
+        Assert.assertEquals(BigDecimal.ZERO.compareTo(invoices.first().getBalance()), 0);
+        Assert.assertEquals(new BigDecimal("100.0").compareTo(invoices.last().getBalance()), 0);
+    }
+    
+    @Test(groups={"fast"}, enabled=true)
+    public void testSum() {
+        
+        BillingStateCalculator<SubscriptionBundle> calc = createBSCalc();
+        SortedSet<Invoice> invoices = calc.unpaidInvoicesForAccount(new UUID(0L,0L));
+        Assert.assertEquals(new BigDecimal("110.0").compareTo(calc.sumBalance(invoices)), 0);
+    }
+
+    @Test(groups={"fast"}, enabled=true)
+    public void testEarliest() {
+        
+        BillingStateCalculator<SubscriptionBundle> calc = createBSCalc();
+        SortedSet<Invoice> invoices = calc.unpaidInvoicesForAccount(new UUID(0L,0L));
+        Assert.assertEquals(calc.earliest(invoices), now);
+    }
+
+    
+    
+    
+}
diff --git a/overdue/src/test/java/com/ning/billing/overdue/calculator/TestBillingStateCalculatorBundle.java b/overdue/src/test/java/com/ning/billing/overdue/calculator/TestBillingStateCalculatorBundle.java
new file mode 100644
index 0000000..bb7ece3
--- /dev/null
+++ b/overdue/src/test/java/com/ning/billing/overdue/calculator/TestBillingStateCalculatorBundle.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2010-2011 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.overdue.calculator;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.SortedSet;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.ning.billing.catalog.MockPlan;
+import com.ning.billing.catalog.MockPriceList;
+import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.catalog.api.PriceList;
+import com.ning.billing.entitlement.api.user.EntitlementUserApi;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.api.InvoiceUserApi;
+import com.ning.billing.mock.BrainDeadProxyFactory;
+import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
+import com.ning.billing.overdue.config.api.BillingStateBundle;
+import com.ning.billing.overdue.config.api.PaymentResponse;
+import com.ning.billing.util.clock.Clock;
+import com.ning.billing.util.clock.ClockMock;
+
+public class TestBillingStateCalculatorBundle extends TestBillingStateCalculator {
+    
+    
+    private List<InvoiceItem> createInvoiceItems(UUID[] bundleIds) {
+        List<InvoiceItem> result = new ArrayList<InvoiceItem> ();
+        for (UUID id : bundleIds) {
+           InvoiceItem ii = BrainDeadProxyFactory.createBrainDeadProxyFor(InvoiceItem.class);
+           ((ZombieControl)ii).addResult("getBundleId", id);
+           result.add(ii);
+        }
+        return result;
+    }
+    
+    @Test(groups = {"fast"}, enabled=true)
+    public void testUnpaidInvoiceForBundle() {
+       UUID thisBundleId = new UUID(0L,0L);
+       UUID thatBundleId = new UUID(0L,1L);
+       
+       now = new DateTime();
+       List<Invoice> invoices = new ArrayList<Invoice>(5);
+       invoices.add(createInvoice(now, BigDecimal.ZERO, createInvoiceItems(new UUID[]{thisBundleId,thatBundleId})));
+       invoices.add(createInvoice(now, BigDecimal.TEN, createInvoiceItems(new UUID[]{thatBundleId})));
+       invoices.add(createInvoice(now, new BigDecimal("100.00"), createInvoiceItems(new UUID[]{thatBundleId,thisBundleId,thatBundleId})));
+       invoices.add(createInvoice(now, new BigDecimal("1000.00"), createInvoiceItems(new UUID[]{thisBundleId})));
+       invoices.add(createInvoice(now, new BigDecimal("10000.00"), createInvoiceItems(new UUID[]{thatBundleId, thisBundleId})));
+       
+       
+       Clock clock = new ClockMock();
+       InvoiceUserApi invoiceApi = BrainDeadProxyFactory.createBrainDeadProxyFor(InvoiceUserApi.class);
+       EntitlementUserApi entitlementApi = BrainDeadProxyFactory.createBrainDeadProxyFor(EntitlementUserApi.class);
+       ((ZombieControl)invoiceApi).addResult("getUnpaidInvoicesByAccountId", invoices);
+       
+       
+       BillingStateCalculatorBundle calc = new BillingStateCalculatorBundle(entitlementApi, invoiceApi, clock);
+       SortedSet<Invoice> resultinvoices = calc.unpaidInvoicesForBundle(thisBundleId, new UUID(0L,0L));
+       
+       Assert.assertEquals(resultinvoices.size(), 4);
+       Assert.assertEquals(BigDecimal.ZERO.compareTo(resultinvoices.first().getBalance()), 0);
+       Assert.assertEquals(new BigDecimal("10000.0").compareTo(resultinvoices.last().getBalance()), 0);
+       
+    }
+    
+    @Test(groups = {"fast"}, enabled=true)
+    public void testcalculateBillingStateForBundle() throws Exception {
+        
+       UUID thisBundleId = new UUID(0L,0L);
+       UUID thatBundleId = new UUID(0L,1L);
+       
+       now = new DateTime();
+       List<Invoice> invoices = new ArrayList<Invoice>(5);
+       invoices.add(createInvoice(now.minusDays(5), BigDecimal.ZERO, createInvoiceItems(new UUID[]{thisBundleId,thatBundleId})));
+       invoices.add(createInvoice(now.minusDays(4), BigDecimal.TEN, createInvoiceItems(new UUID[]{thatBundleId})));
+       invoices.add(createInvoice(now.minusDays(3), new BigDecimal("100.00"), createInvoiceItems(new UUID[]{thatBundleId,thisBundleId,thatBundleId})));
+       invoices.add(createInvoice(now.minusDays(2), new BigDecimal("1000.00"), createInvoiceItems(new UUID[]{thisBundleId})));
+       invoices.add(createInvoice(now.minusDays(1), new BigDecimal("10000.00"), createInvoiceItems(new UUID[]{thatBundleId, thisBundleId})));
+       
+       
+       Clock clock = new ClockMock();
+       InvoiceUserApi invoiceApi = BrainDeadProxyFactory.createBrainDeadProxyFor(InvoiceUserApi.class);
+       ((ZombieControl)invoiceApi).addResult("getUnpaidInvoicesByAccountId", invoices);
+       
+       SubscriptionBundle bundle = BrainDeadProxyFactory.createBrainDeadProxyFor(SubscriptionBundle.class);
+       ((ZombieControl)bundle).addResult("getId", thisBundleId);
+       ((ZombieControl)bundle).addResult("getAccountId", UUID.randomUUID());
+       
+       EntitlementUserApi entitlementApi = BrainDeadProxyFactory.createBrainDeadProxyFor(EntitlementUserApi.class);
+       Subscription subscription = BrainDeadProxyFactory.createBrainDeadProxyFor(Subscription.class);
+       ((ZombieControl)entitlementApi).addResult("getBaseSubscription",subscription);
+       
+       Plan plan = MockPlan.createBicycleNoTrialEvergreen1USD();
+       PriceList pricelist = new MockPriceList();
+       ((ZombieControl)subscription).addResult("getCurrentPlan", plan);
+       ((ZombieControl)subscription).addResult("getCurrentPriceList", pricelist);
+       ((ZombieControl)subscription).addResult("getCurrentPhase", plan.getFinalPhase());
+      
+       BillingStateCalculatorBundle calc = new BillingStateCalculatorBundle(entitlementApi, invoiceApi, clock);
+            
+       BillingStateBundle state = calc.calculateBillingState(bundle); 
+       
+       Assert.assertEquals(state.getNumberOfUnpaidInvoices(),4);
+       Assert.assertEquals(state.getBalanceOfUnpaidInvoices().intValue(), 11100);
+       Assert.assertEquals(state.getDateOfEarliestUnpaidInvoice().compareTo(now.minusDays(5)), 0);
+       Assert.assertEquals(state.getResponseForLastFailedPayment(),PaymentResponse.INSUFFICIENT_FUNDS); //TODO needs more when implemented
+       Assert.assertEquals(state.getTags().length,0);//TODO needs more when implemented
+       Assert.assertEquals(state.getBasePlanBillingPeriod(), plan.getBillingPeriod());
+       Assert.assertEquals(state.getBasePlanPhaseType(), plan.getFinalPhase().getPhaseType());
+       Assert.assertEquals(state.getBasePlanPriceList(), pricelist);
+       Assert.assertEquals(state.getBasePlanProduct(), plan.getProduct());
+       
+    }
+    
+    
+    
+    
+    
+    
+    
+    
+}
diff --git a/overdue/src/test/java/com/ning/billing/overdue/config/CreateOverdueConfigSchema.java b/overdue/src/test/java/com/ning/billing/overdue/config/CreateOverdueConfigSchema.java
new file mode 100644
index 0000000..bbd4b18
--- /dev/null
+++ b/overdue/src/test/java/com/ning/billing/overdue/config/CreateOverdueConfigSchema.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2010-2011 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.overdue.config;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.Writer;
+
+import com.ning.billing.util.config.XMLSchemaGenerator;
+
+public class CreateOverdueConfigSchema {
+
+	/**
+	 * @param args
+	 */
+	public static void main(String[] args) throws Exception {
+		if(args.length != 1) {
+			System.err.println("Usage: <filepath>");
+			System.exit(0);
+		}
+		
+		File f = new File(args[0]);
+		Writer w = new FileWriter(f);
+		w.write(XMLSchemaGenerator.xmlSchemaAsString(OverdueConfig.class));
+		w.close();
+
+	}
+
+}
diff --git a/overdue/src/test/java/com/ning/billing/overdue/config/io/TestReadConfig.java b/overdue/src/test/java/com/ning/billing/overdue/config/io/TestReadConfig.java
new file mode 100644
index 0000000..8208a73
--- /dev/null
+++ b/overdue/src/test/java/com/ning/billing/overdue/config/io/TestReadConfig.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2010-2011 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.overdue.config.io;
+
+import org.testng.annotations.Test;
+
+import com.google.common.io.Resources;
+import com.ning.billing.overdue.config.OverdueConfig;
+import com.ning.billing.util.config.XMLLoader;
+
+public class TestReadConfig {
+    @Test(enabled=true) 
+    public void testConfigLoad() throws Exception {
+        XMLLoader.getObjectFromString(Resources.getResource("OverdueConfig.xml").toExternalForm(), OverdueConfig.class);
+    }
+
+}
diff --git a/overdue/src/test/java/com/ning/billing/overdue/config/MockOverdueRules.java b/overdue/src/test/java/com/ning/billing/overdue/config/MockOverdueRules.java
new file mode 100644
index 0000000..514c85f
--- /dev/null
+++ b/overdue/src/test/java/com/ning/billing/overdue/config/MockOverdueRules.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2010-2011 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.overdue.config;
+
+
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+
+
+public class MockOverdueRules extends OverdueConfig {
+    public static final String CLEAR_STATE="Clear";
+
+    @SuppressWarnings("unchecked")
+    public MockOverdueRules() {
+        OverdueStatesBundle bundleODS =  new OverdueStatesBundle();
+        bundleODS.setBundleOverdueStates(new DefaultOverdueState[] { new DefaultOverdueState<SubscriptionBundle>().setName(CLEAR_STATE) });
+        setOverdueStatesBundle(bundleODS);
+
+    }
+}
diff --git a/overdue/src/test/java/com/ning/billing/overdue/config/MockOverdueState.java b/overdue/src/test/java/com/ning/billing/overdue/config/MockOverdueState.java
new file mode 100644
index 0000000..d1b9ab1
--- /dev/null
+++ b/overdue/src/test/java/com/ning/billing/overdue/config/MockOverdueState.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2010-2011 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.overdue.config;
+
+import com.ning.billing.junction.api.Blockable;
+
+public class MockOverdueState<T extends Blockable> extends DefaultOverdueState<T> {
+    
+    public MockOverdueState() {
+        setName(MockOverdueRules.CLEAR_STATE);
+    }
+
+    public MockOverdueState(String name, boolean blockChanges, boolean disableEntitlementAndBlockChanges) {
+        setName(name);
+        setBlockChanges(blockChanges);
+        setDisableEntitlement(disableEntitlementAndBlockChanges);
+    }
+}
diff --git a/overdue/src/test/java/com/ning/billing/overdue/config/MockOverdueStatesBundle.java b/overdue/src/test/java/com/ning/billing/overdue/config/MockOverdueStatesBundle.java
new file mode 100644
index 0000000..f1020b2
--- /dev/null
+++ b/overdue/src/test/java/com/ning/billing/overdue/config/MockOverdueStatesBundle.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2010-2011 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.overdue.config;
+
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+
+public class MockOverdueStatesBundle extends OverdueStatesBundle {
+
+    public MockOverdueStatesBundle() {
+        
+    }
+    
+   public MockOverdueStatesBundle(DefaultOverdueState<SubscriptionBundle>[] states) {
+       setBundleOverdueStates(states);
+    }
+
+}
diff --git a/overdue/src/test/java/com/ning/billing/overdue/config/TestCondition.java b/overdue/src/test/java/com/ning/billing/overdue/config/TestCondition.java
new file mode 100644
index 0000000..1e5f1e0
--- /dev/null
+++ b/overdue/src/test/java/com/ning/billing/overdue/config/TestCondition.java
@@ -0,0 +1,147 @@
+/*
+00 * Copyright 2010-2011 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.overdue.config;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import javax.xml.bind.annotation.XmlRootElement;
+
+import org.joda.time.DateTime;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.ning.billing.junction.api.Blockable;
+import com.ning.billing.overdue.config.api.BillingState;
+import com.ning.billing.overdue.config.api.PaymentResponse;
+import com.ning.billing.util.config.XMLLoader;
+import com.ning.billing.util.tag.ControlTagType;
+import com.ning.billing.util.tag.DefaultControlTag;
+import com.ning.billing.util.tag.DescriptiveTag;
+import com.ning.billing.util.tag.Tag;
+
+public class TestCondition {
+	
+	@XmlRootElement(name="condition")
+	private static class MockCondition extends DefaultCondition<Blockable> {}
+
+	@Test(groups={"fast"}, enabled=true)
+	public void testNumberOfUnpaidInvoicesEqualsOrExceeds() throws Exception {
+		String xml = 
+				"<condition>" +
+				"	<numberOfUnpaidInvoicesEqualsOrExceeds>1</numberOfUnpaidInvoicesEqualsOrExceeds>" +
+				"</condition>";
+		InputStream is = new ByteArrayInputStream(xml.getBytes());
+		MockCondition c = XMLLoader.getObjectFromStreamNoValidation(is,  MockCondition.class);
+		
+		BillingState<Blockable> state0 = new BillingState<Blockable>(new UUID(0L,1L), 0, BigDecimal.ZERO, new DateTime(), PaymentResponse.INSUFFICIENT_FUNDS, new Tag[]{});
+		BillingState<Blockable> state1 = new BillingState<Blockable>(new UUID(0L,1L), 1, BigDecimal.ZERO, new DateTime(), PaymentResponse.INSUFFICIENT_FUNDS, new Tag[]{});
+		BillingState<Blockable> state2 = new BillingState<Blockable>(new UUID(0L,1L), 2, BigDecimal.ZERO, new DateTime(), PaymentResponse.INSUFFICIENT_FUNDS, new Tag[]{});
+		
+		Assert.assertTrue(!c.evaluate(state0, new DateTime()));
+		Assert.assertTrue(c.evaluate(state1, new DateTime()));
+		Assert.assertTrue(c.evaluate(state2, new DateTime()));
+	}
+	
+	@Test(groups={"fast"}, enabled=true)
+	public void testTotalUnpaidInvoiceBalanceEqualsOrExceeds() throws Exception {
+		String xml = 
+				"<condition>" +
+				"	<totalUnpaidInvoiceBalanceEqualsOrExceeds>100.00</totalUnpaidInvoiceBalanceEqualsOrExceeds>" +
+				"</condition>";
+		InputStream is = new ByteArrayInputStream(xml.getBytes());
+		MockCondition c = XMLLoader.getObjectFromStreamNoValidation(is,  MockCondition.class);
+		
+		BillingState<Blockable> state0 = new BillingState<Blockable>(new UUID(0L,1L), 0, BigDecimal.ZERO, new DateTime(), PaymentResponse.INSUFFICIENT_FUNDS, new Tag[]{});
+		BillingState<Blockable> state1 = new BillingState<Blockable>(new UUID(0L,1L), 1, new BigDecimal("100.00"), new DateTime(), PaymentResponse.INSUFFICIENT_FUNDS, new Tag[]{});
+		BillingState<Blockable> state2 = new BillingState<Blockable>(new UUID(0L,1L), 1, new BigDecimal("200.00"), new DateTime(), PaymentResponse.INSUFFICIENT_FUNDS, new Tag[]{});
+		
+		Assert.assertTrue(!c.evaluate(state0, new DateTime()));
+		Assert.assertTrue(c.evaluate(state1, new DateTime()));
+		Assert.assertTrue(c.evaluate(state2, new DateTime()));
+	}
+
+	
+	@Test(groups={"fast"}, enabled=true)
+	public void testTimeSinceEarliestUnpaidInvoiceEqualsOrExceeds() throws Exception {
+		String xml = 
+				"<condition>" +
+				"	<timeSinceEarliestUnpaidInvoiceEqualsOrExceeds><unit>DAYS</unit><number>10</number></timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>" +
+				"</condition>";
+		InputStream is = new ByteArrayInputStream(xml.getBytes());
+		MockCondition c = XMLLoader.getObjectFromStreamNoValidation(is,  MockCondition.class);
+		
+		DateTime now = new DateTime();
+		
+		BillingState<Blockable> state0 = new BillingState<Blockable>(new UUID(0L,1L), 0, BigDecimal.ZERO, now, PaymentResponse.INSUFFICIENT_FUNDS, new Tag[]{});
+		BillingState<Blockable> state1 = new BillingState<Blockable>(new UUID(0L,1L), 1, new BigDecimal("100.00"), now.minusDays(10), PaymentResponse.INSUFFICIENT_FUNDS, new Tag[]{});
+		BillingState<Blockable> state2 = new BillingState<Blockable>(new UUID(0L,1L), 1, new BigDecimal("200.00"), now.minusDays(20), PaymentResponse.INSUFFICIENT_FUNDS, new Tag[]{});
+		
+		Assert.assertTrue(!c.evaluate(state0, now));
+		Assert.assertTrue(c.evaluate(state1, now));
+		Assert.assertTrue(c.evaluate(state2, now));
+	}
+
+	@Test(groups={"fast"}, enabled=true)
+	public void testResponseForLastFailedPaymentIn() throws Exception {
+		String xml = 
+				"<condition>" +
+				"	<responseForLastFailedPaymentIn><response>INSUFFICIENT_FUNDS</response><response>DO_NOT_HONOR</response></responseForLastFailedPaymentIn>" +
+				"</condition>";
+		InputStream is = new ByteArrayInputStream(xml.getBytes());
+		MockCondition c = XMLLoader.getObjectFromStreamNoValidation(is,  MockCondition.class);
+		
+		DateTime now = new DateTime();
+		
+		BillingState<Blockable> state0 = new BillingState<Blockable>(new UUID(0L,1L), 0, BigDecimal.ZERO, now, PaymentResponse.LOST_OR_STOLEN_CARD, new Tag[]{});
+		BillingState<Blockable> state1 = new BillingState<Blockable>(new UUID(0L,1L), 1, new BigDecimal("100.00"), now.minusDays(10), PaymentResponse.INSUFFICIENT_FUNDS, new Tag[]{});
+		BillingState<Blockable> state2 = new BillingState<Blockable>(new UUID(0L,1L), 1, new BigDecimal("200.00"), now.minusDays(20), PaymentResponse.DO_NOT_HONOR , new Tag[]{});
+		
+		Assert.assertTrue(!c.evaluate(state0, now));
+		Assert.assertTrue(c.evaluate(state1, now));
+		Assert.assertTrue(c.evaluate(state2, now));
+	}
+
+	@Test(groups={"fast"}, enabled=true)
+	public void testHasControlTag() throws Exception {
+		String xml = 
+				"<condition>" +
+				"	<controlTag>OVERDUE_ENFORCEMENT_OFF</controlTag>" +
+				"</condition>";
+		InputStream is = new ByteArrayInputStream(xml.getBytes());
+		MockCondition c = XMLLoader.getObjectFromStreamNoValidation(is,  MockCondition.class);
+		
+		DateTime now = new DateTime();
+		
+		BillingState<Blockable> state0 = new BillingState<Blockable>(new UUID(0L,1L), 0, BigDecimal.ZERO, now, PaymentResponse.LOST_OR_STOLEN_CARD, new Tag[]{new DefaultControlTag(ControlTagType.AUTO_INVOICING_OFF),new DescriptiveTag("Tag")});
+		BillingState<Blockable> state1 = new BillingState<Blockable>(new UUID(0L,1L), 1, new BigDecimal("100.00"), now.minusDays(10), PaymentResponse.INSUFFICIENT_FUNDS, new Tag[]{new DefaultControlTag(ControlTagType.OVERDUE_ENFORCEMENT_OFF)});
+		BillingState<Blockable> state2 = new BillingState<Blockable>(new UUID(0L,1L), 1, new BigDecimal("200.00"), now.minusDays(20), 
+				PaymentResponse.DO_NOT_HONOR, 
+				new Tag[]{new DefaultControlTag(ControlTagType.OVERDUE_ENFORCEMENT_OFF), 
+						  new DefaultControlTag(ControlTagType.AUTO_INVOICING_OFF),
+						  new DescriptiveTag("Tag")});
+		
+		Assert.assertTrue(!c.evaluate(state0, now));
+		Assert.assertTrue(c.evaluate(state1, now));
+		Assert.assertTrue(c.evaluate(state2, now));
+	}
+
+
+
+}
diff --git a/overdue/src/test/java/com/ning/billing/overdue/config/TestOverdueConfig.java b/overdue/src/test/java/com/ning/billing/overdue/config/TestOverdueConfig.java
new file mode 100644
index 0000000..df4f4c3
--- /dev/null
+++ b/overdue/src/test/java/com/ning/billing/overdue/config/TestOverdueConfig.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2010-2011 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.overdue.config;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+
+import org.testng.annotations.Test;
+
+import com.ning.billing.util.config.XMLLoader;
+
+public class TestOverdueConfig {
+    private String xml = 
+            "<overdueConfig>" +
+                    "   <bundleOverdueStates>" +
+                    "       <state name=\"OD1\">" +
+                    "           <condition>" +
+                    "               <timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>" +
+                    "                   <unit>MONTHS</unit><number>1</number>" +
+                    "               </timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>" +
+                    "           </condition>" +
+                    "           <externalMessage>Reached OD1</externalMessage>" +
+                    "           <blockChanges>true</blockChanges>" +
+                    "           <disableEntitlementAndChangesBlocked>false</disableEntitlementAndChangesBlocked>" +
+                    "       </state>" +
+                    "       <state name=\"OD2\">" +
+                    "           <condition>" +
+                    "               <timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>" +
+                    "                   <unit>MONTHS</unit><number>2</number>" +
+                    "               </timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>" +
+                    "           </condition>" +
+                    "           <externalMessage>Reached OD1</externalMessage>" +
+                    "           <blockChanges>true</blockChanges>" +
+                    "           <disableEntitlementAndChangesBlocked>true</disableEntitlementAndChangesBlocked>" +
+                    "       </state>" +
+                    "   </bundleOverdueStates>" +
+                    "</overdueConfig>";
+
+    @Test
+    public void testParseConfig() throws Exception {
+        InputStream is = new ByteArrayInputStream(xml.getBytes());
+        OverdueConfig c = XMLLoader.getObjectFromStreamNoValidation(is,  OverdueConfig.class);
+
+    }
+
+}
diff --git a/overdue/src/test/java/com/ning/billing/overdue/notification/MockOverdueCheckNotifier.java b/overdue/src/test/java/com/ning/billing/overdue/notification/MockOverdueCheckNotifier.java
new file mode 100644
index 0000000..a427ddb
--- /dev/null
+++ b/overdue/src/test/java/com/ning/billing/overdue/notification/MockOverdueCheckNotifier.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2010-2011 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.overdue.notification;
+
+import com.ning.billing.ovedue.notification.OverdueCheckNotifier;
+
+public class MockOverdueCheckNotifier implements OverdueCheckNotifier {
+    @Override
+    public void initialize() {
+        // do nothing
+    }
+
+    @Override
+    public void start() {
+        // do nothing
+    }
+
+    @Override
+    public void stop() {
+        // do nothing
+    }
+}
diff --git a/overdue/src/test/java/com/ning/billing/overdue/notification/MockOverdueCheckPoster.java b/overdue/src/test/java/com/ning/billing/overdue/notification/MockOverdueCheckPoster.java
new file mode 100644
index 0000000..c1cfb65
--- /dev/null
+++ b/overdue/src/test/java/com/ning/billing/overdue/notification/MockOverdueCheckPoster.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2010-2011 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.overdue.notification;
+
+import org.joda.time.DateTime;
+
+import com.ning.billing.junction.api.Blockable;
+import com.ning.billing.ovedue.notification.OverdueCheckPoster;
+
+public class MockOverdueCheckPoster implements OverdueCheckPoster {
+    
+    @Override
+    public void insertOverdueCheckNotification(Blockable overdueable,
+            DateTime futureNotificationTime) {
+        // TODO Auto-generated method stub
+        
+    }
+
+    @Override
+    public void clearNotificationsFor(Blockable blockable) {
+        // TODO Auto-generated method stub
+        
+    }
+}
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
new file mode 100644
index 0000000..7f48dda
--- /dev/null
+++ b/overdue/src/test/java/com/ning/billing/overdue/notification/TestOverdueCheckNotifier.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright 2010-2011 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.overdue.notification;
+
+import static com.jayway.awaitility.Awaitility.await;
+import static java.util.concurrent.TimeUnit.MINUTES;
+
+import java.io.IOException;
+import java.sql.SQLException;
+import java.util.UUID;
+import java.util.concurrent.Callable;
+
+import org.apache.commons.io.IOUtils;
+import org.joda.time.DateTime;
+import org.skife.config.ConfigurationObjectFactory;
+import org.skife.jdbi.v2.IDBI;
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Stage;
+import com.ning.billing.catalog.DefaultCatalogService;
+import com.ning.billing.catalog.api.CatalogService;
+import com.ning.billing.config.CatalogConfig;
+import com.ning.billing.config.InvoiceConfig;
+import com.ning.billing.dbi.MysqlTestingHelper;
+import com.ning.billing.entitlement.api.billing.ChargeThruApi;
+import com.ning.billing.entitlement.api.user.EntitlementUserApi;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.junction.api.Blockable;
+import com.ning.billing.lifecycle.KillbillService.ServiceException;
+import com.ning.billing.mock.BrainDeadProxyFactory;
+import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
+import com.ning.billing.mock.glue.MockJunctionModule;
+import com.ning.billing.ovedue.notification.DefaultOverdueCheckNotifier;
+import com.ning.billing.ovedue.notification.DefaultOverdueCheckPoster;
+import com.ning.billing.ovedue.notification.OverdueCheckPoster;
+import com.ning.billing.overdue.OverdueProperties;
+import com.ning.billing.overdue.glue.OverdueModule;
+import com.ning.billing.overdue.listener.OverdueListener;
+import com.ning.billing.util.bus.Bus;
+import com.ning.billing.util.bus.InMemoryBus;
+import com.ning.billing.util.callcontext.CallContextFactory;
+import com.ning.billing.util.callcontext.DefaultCallContextFactory;
+import com.ning.billing.util.clock.Clock;
+import com.ning.billing.util.clock.ClockMock;
+import com.ning.billing.util.customfield.dao.AuditedCustomFieldDao;
+import com.ning.billing.util.customfield.dao.CustomFieldDao;
+import com.ning.billing.util.globallocker.GlobalLocker;
+import com.ning.billing.util.globallocker.MySqlGlobalLocker;
+import com.ning.billing.util.notificationq.DefaultNotificationQueueService;
+import com.ning.billing.util.notificationq.NotificationQueueService;
+import com.ning.billing.util.notificationq.dao.NotificationSqlDao;
+import com.ning.billing.util.tag.dao.AuditedTagDao;
+import com.ning.billing.util.tag.dao.TagDao;
+
+public class TestOverdueCheckNotifier {
+	private Clock clock;
+	private DefaultOverdueCheckNotifier notifier;
+
+	private Bus eventBus;
+	private MysqlTestingHelper helper;
+	private OverdueListenerMock listener;
+	private NotificationQueueService notificationQueueService;
+
+	private static final class OverdueListenerMock extends OverdueListener {
+		int eventCount = 0;
+		UUID latestSubscriptionId = null;
+
+		public OverdueListenerMock() {
+			super(null,null);
+		}
+
+		@Override
+		public void handleNextOverdueCheck(UUID subscriptionId) {
+			eventCount++;
+			latestSubscriptionId=subscriptionId;
+		}
+
+		public int getEventCount() {
+			return eventCount;
+		}
+
+		public UUID getLatestSubscriptionId(){
+			return latestSubscriptionId;
+		}
+
+	}
+
+
+	@BeforeClass(groups={"slow"})
+	public void setup() throws ServiceException, IOException, ClassNotFoundException, SQLException {
+		//TestApiBase.loadSystemPropertiesFromClasspath("/entitlement.properties");
+        final Injector g = Guice.createInjector(Stage.PRODUCTION,  new OverdueModule() {
+			
+            protected void configure() {
+                super.configure();
+                bind(Clock.class).to(ClockMock.class).asEagerSingleton();
+                bind(CallContextFactory.class).to(DefaultCallContextFactory.class).asEagerSingleton();
+                bind(Bus.class).to(InMemoryBus.class).asEagerSingleton();
+                bind(NotificationQueueService.class).to(DefaultNotificationQueueService.class).asEagerSingleton();
+                final InvoiceConfig invoiceConfig = new ConfigurationObjectFactory(System.getProperties()).build(InvoiceConfig.class);
+                bind(InvoiceConfig.class).toInstance(invoiceConfig);
+                final CatalogConfig catalogConfig = new ConfigurationObjectFactory(System.getProperties()).build(CatalogConfig.class);
+                bind(CatalogConfig.class).toInstance(catalogConfig);
+                bind(CatalogService.class).to(DefaultCatalogService.class).asEagerSingleton();
+                final MysqlTestingHelper helper = new MysqlTestingHelper();
+                bind(MysqlTestingHelper.class).toInstance(helper);
+                IDBI dbi = helper.getDBI();
+                bind(IDBI.class).toInstance(dbi);
+                bind(TagDao.class).to(AuditedTagDao.class).asEagerSingleton();
+                bind(CustomFieldDao.class).to(AuditedCustomFieldDao.class).asEagerSingleton();
+                bind(GlobalLocker.class).to(MySqlGlobalLocker.class).asEagerSingleton();
+                bind(ChargeThruApi.class).toInstance(BrainDeadProxyFactory.createBrainDeadProxyFor(ChargeThruApi.class));
+                install(new MockJunctionModule());
+            }
+        });
+
+        clock = g.getInstance(Clock.class);
+        IDBI dbi = g.getInstance(IDBI.class);
+
+        eventBus = g.getInstance(Bus.class);
+        helper = g.getInstance(MysqlTestingHelper.class);
+        notificationQueueService = g.getInstance(NotificationQueueService.class);
+        
+        OverdueProperties properties = g.getInstance(OverdueProperties.class);
+
+        Subscription subscription = BrainDeadProxyFactory.createBrainDeadProxyFor(Subscription.class);
+        EntitlementUserApi entitlementUserApi = BrainDeadProxyFactory.createBrainDeadProxyFor(EntitlementUserApi.class);
+        ((ZombieControl) entitlementUserApi).addResult("getSubscriptionFromId", subscription);
+
+//        CallContextFactory factory = new DefaultCallContextFactory(clock);
+        listener = new OverdueListenerMock();
+        notifier = new DefaultOverdueCheckNotifier(notificationQueueService,
+                 properties, listener);
+        
+        startMysql();
+        eventBus.start();
+        notifier.initialize();
+        notifier.start();
+	}
+
+	private void startMysql() throws IOException, ClassNotFoundException, SQLException {
+		final String ddl = IOUtils.toString(NotificationSqlDao.class.getResourceAsStream("/com/ning/billing/util/ddl.sql"));
+		final String testDdl = IOUtils.toString(NotificationSqlDao.class.getResourceAsStream("/com/ning/billing/util/ddl_test.sql"));
+
+		helper.startMysql();
+		helper.initDb(ddl);
+		helper.initDb(testDdl);
+	}
+
+	@Test(enabled=true, groups="slow")
+	public void test() throws Exception {
+		final UUID subscriptionId = new UUID(0L,1L);
+		final Blockable blockable = BrainDeadProxyFactory.createBrainDeadProxyFor(Subscription.class);
+		((ZombieControl)blockable).addResult("getId", subscriptionId);
+		final DateTime now = new DateTime();
+		final DateTime readyTime = now.plusMillis(2000);
+		final OverdueCheckPoster poster = new DefaultOverdueCheckPoster(notificationQueueService);
+
+
+		poster.insertOverdueCheckNotification(blockable, readyTime);
+
+
+		// Move time in the future after the notification effectiveDate
+		((ClockMock) clock).setDeltaFromReality(3000);
+
+
+	    await().atMost(1, MINUTES).until(new Callable<Boolean>() {
+	            @Override
+	            public Boolean call() throws Exception {
+	                return listener.getEventCount() == 1;
+	            }
+	        });
+
+		Assert.assertEquals(listener.getEventCount(), 1);
+		Assert.assertEquals(listener.getLatestSubscriptionId(), subscriptionId);
+        
+	}
+
+	@AfterClass(groups="slow")
+    public void tearDown() {
+	    eventBus.stop();
+        notifier.stop();
+        helper.stopMysql();
+    }
+
+}
diff --git a/overdue/src/test/resources/log4j.xml b/overdue/src/test/resources/log4j.xml
new file mode 100644
index 0000000..ac530a1
--- /dev/null
+++ b/overdue/src/test/resources/log4j.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright 2010-2011 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.
+  -->
+
+<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
+<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
+    <appender name="stdout" class="org.apache.log4j.ConsoleAppender">
+        <param name="Target" value="System.out"/>
+        <layout class="org.apache.log4j.PatternLayout">
+            <param name="ConversionPattern" value="%p	%d{ISO8601}	%X{trace}	%t	%c	%m%n"/>
+        </layout>
+    </appender>
+
+
+    <logger name="com.ning.billing.entitlement">
+        <level value="info"/>
+    </logger>
+
+    <logger name="com.ning.billing.util.notificationq">
+        <level value="info"/>
+    </logger>
+
+    <root>
+        <priority value="info"/>
+        <appender-ref ref="stdout"/>
+    </root>
+</log4j:configuration>
diff --git a/overdue/src/test/resources/OverdueConfig.xml b/overdue/src/test/resources/OverdueConfig.xml
new file mode 100644
index 0000000..e58ccc1
--- /dev/null
+++ b/overdue/src/test/resources/OverdueConfig.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- ~ Copyright 2010-2011 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. -->
+
+<overdueConfig xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:noNamespaceSchemaLocation="CatalogSchema.xsd ">
+	<bundleOverdueStates>
+		<state name="Clear">
+			<isClearState>true</isClearState>
+		</state>
+	</bundleOverdueStates>
+</overdueConfig>
+
+    
\ No newline at end of file
diff --git a/overdue/src/test/resources/OverdueConfigSchema.xsd b/overdue/src/test/resources/OverdueConfigSchema.xsd
new file mode 100644
index 0000000..256da08
--- /dev/null
+++ b/overdue/src/test/resources/OverdueConfigSchema.xsd
@@ -0,0 +1,106 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" version="1.0">
+<xs:element name="overdueConfig" type="overdueConfig"/>
+<xs:complexType name="overdueConfig">
+<xs:complexContent>
+<xs:extension base="validatingConfig">
+<xs:sequence>
+<xs:element name="bundleOverdueStates" type="overdueStatesBundle"/>
+</xs:sequence>
+</xs:extension>
+</xs:complexContent>
+</xs:complexType>
+<xs:complexType abstract="true" name="validatingConfig">
+<xs:sequence/>
+</xs:complexType>
+<xs:complexType name="overdueStatesBundle">
+<xs:complexContent>
+<xs:extension base="defaultOverdueStateSet">
+<xs:sequence>
+<xs:element maxOccurs="unbounded" name="state" type="defaultOverdueState"/>
+</xs:sequence>
+</xs:extension>
+</xs:complexContent>
+</xs:complexType>
+<xs:complexType abstract="true" name="defaultOverdueStateSet">
+<xs:complexContent>
+<xs:extension base="validatingConfig">
+<xs:sequence/>
+</xs:extension>
+</xs:complexContent>
+</xs:complexType>
+<xs:complexType name="defaultOverdueState">
+<xs:complexContent>
+<xs:extension base="validatingConfig">
+<xs:sequence>
+<xs:element minOccurs="0" name="condition" type="defaultCondition"/>
+<xs:element minOccurs="0" name="externalMessage" type="xs:string"/>
+<xs:element minOccurs="0" name="disableEntitlementAndChangesBlocked" type="xs:boolean"/>
+<xs:element minOccurs="0" name="blockChanges" type="xs:boolean"/>
+<xs:element minOccurs="0" name="daysBetweenPaymentRetries" type="xs:int"/>
+<xs:element minOccurs="0" name="isClearState" type="xs:boolean"/>
+</xs:sequence>
+<xs:attribute name="name" type="xs:ID" use="required"/>
+</xs:extension>
+</xs:complexContent>
+</xs:complexType>
+<xs:complexType name="defaultCondition">
+<xs:complexContent>
+<xs:extension base="validatingConfig">
+<xs:sequence>
+<xs:element minOccurs="0" name="numberOfUnpaidInvoicesEqualsOrExceeds" type="xs:int"/>
+<xs:element minOccurs="0" name="totalUnpaidInvoiceBalanceEqualsOrExceeds" type="xs:decimal"/>
+<xs:element minOccurs="0" name="timeSinceEarliestUnpaidInvoiceEqualsOrExceeds" type="defaultDuration"/>
+<xs:element minOccurs="0" name="responseForLastFailedPaymentIn">
+<xs:complexType>
+<xs:sequence>
+<xs:element maxOccurs="unbounded" minOccurs="0" name="response" type="paymentResponse"/>
+</xs:sequence>
+</xs:complexType>
+</xs:element>
+<xs:element minOccurs="0" name="controlTag" type="controlTagType"/>
+</xs:sequence>
+</xs:extension>
+</xs:complexContent>
+</xs:complexType>
+<xs:complexType name="defaultDuration">
+<xs:complexContent>
+<xs:extension base="validatingConfig">
+<xs:sequence>
+<xs:element name="unit" type="timeUnit"/>
+<xs:element minOccurs="0" name="number" type="xs:int"/>
+</xs:sequence>
+</xs:extension>
+</xs:complexContent>
+</xs:complexType>
+<xs:simpleType name="timeUnit">
+<xs:restriction base="xs:string">
+<xs:enumeration value="DAYS"/>
+<xs:enumeration value="MONTHS"/>
+<xs:enumeration value="YEARS"/>
+<xs:enumeration value="UNLIMITED"/>
+</xs:restriction>
+</xs:simpleType>
+<xs:simpleType name="paymentResponse">
+<xs:restriction base="xs:string">
+<xs:enumeration value="INVALID_CARD"/>
+<xs:enumeration value="EXPIRED_CARD"/>
+<xs:enumeration value="LOST_OR_STOLEN_CARD"/>
+<xs:enumeration value="DO_NOT_HONOR"/>
+<xs:enumeration value="INSUFFICIENT_FUNDS"/>
+<xs:enumeration value="DECLINE"/>
+<xs:enumeration value="PROCESSING_ERROR"/>
+<xs:enumeration value="INVALID_AMOUNT"/>
+<xs:enumeration value="DUPLICATE_TRANSACTION"/>
+<xs:enumeration value="OTHER"/>
+</xs:restriction>
+</xs:simpleType>
+<xs:simpleType name="controlTagType">
+<xs:restriction base="xs:string">
+<xs:enumeration value="AUTO_PAY_OFF"/>
+<xs:enumeration value="AUTO_INVOICING_OFF"/>
+<xs:enumeration value="OVERDUE_ENFORCEMENT_OFF"/>
+<xs:enumeration value="WRITTEN_OFF"/>
+</xs:restriction>
+</xs:simpleType>
+</xs:schema>
diff --git a/overdue/src/test/resources/resource.properties b/overdue/src/test/resources/resource.properties
new file mode 100644
index 0000000..d63334b
--- /dev/null
+++ b/overdue/src/test/resources/resource.properties
@@ -0,0 +1,7 @@
+killbill.catalog.uri=file:src/test/resources/catalogSample.xml
+killbill.entitlement.dao.claim.time=60000
+killbill.entitlement.dao.ready.max=1
+killbill.entitlement.engine.notifications.sleep=500
+user.timezone=UTC
+
+

payment/pom.xml 29(+14 -15)

diff --git a/payment/pom.xml b/payment/pom.xml
index 8615efc..b9e1793 100644
--- a/payment/pom.xml
+++ b/payment/pom.xml
@@ -26,15 +26,24 @@
         </dependency>
         <dependency>
             <groupId>com.ning.billing</groupId>
-            <artifactId>killbill-invoice</artifactId>
+            <artifactId>killbill-util</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-junction</artifactId>
+            <scope>test</scope>
         </dependency>
         <dependency>
             <groupId>com.ning.billing</groupId>
             <artifactId>killbill-account</artifactId>
+            <type>test-jar</type>
+            <scope>test</scope>
         </dependency>
         <dependency>
             <groupId>com.ning.billing</groupId>
-            <artifactId>killbill-util</artifactId>
+            <artifactId>killbill-invoice</artifactId>
+            <type>test-jar</type>
+            <scope>test</scope>
         </dependency>
         <dependency>
             <groupId>com.google.guava</groupId>
@@ -73,12 +82,13 @@
             <artifactId>slf4j-api</artifactId>
         </dependency>
 
+        <!--  TEST SCOPE -->
         <dependency>
             <groupId>org.testng</groupId>
             <artifactId>testng</artifactId>
             <scope>test</scope>
         </dependency>
-        <dependency>
+        <dependency> 
             <groupId>com.jayway.awaitility</groupId>
             <artifactId>awaitility</artifactId>
             <scope>test</scope>
@@ -89,18 +99,7 @@
             <type>test-jar</type>
             <scope>test</scope>
         </dependency>
-        <dependency>
-            <groupId>com.ning.billing</groupId>
-            <artifactId>killbill-account</artifactId>
-            <type>test-jar</type>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>com.ning.billing</groupId>
-            <artifactId>killbill-invoice</artifactId>
-            <type>test-jar</type>
-            <scope>test</scope>
-        </dependency>
+
         <dependency>
             <groupId>mysql</groupId>
             <artifactId>mysql-connector-mxj</artifactId>
diff --git a/payment/src/main/java/com/ning/billing/payment/api/DefaultPaymentApi.java b/payment/src/main/java/com/ning/billing/payment/api/DefaultPaymentApi.java
index c014b45..46b43e0 100644
--- a/payment/src/main/java/com/ning/billing/payment/api/DefaultPaymentApi.java
+++ b/payment/src/main/java/com/ning/billing/payment/api/DefaultPaymentApi.java
@@ -18,28 +18,28 @@ package com.ning.billing.payment.api;
 
 import java.math.BigDecimal;
 import java.util.ArrayList;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.UUID;
 
-import javax.annotation.Nullable;
-
-import com.ning.billing.util.callcontext.CallContext;
 import org.apache.commons.lang.StringUtils;
 import org.joda.time.DateTime;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import com.google.inject.Inject;
+import com.ning.billing.ErrorCode;
 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.config.PaymentConfig;
 import com.ning.billing.invoice.api.Invoice;
 import com.ning.billing.invoice.api.InvoicePaymentApi;
-import com.ning.billing.invoice.model.DefaultInvoicePayment;
 import com.ning.billing.payment.RetryService;
 import com.ning.billing.payment.dao.PaymentDao;
 import com.ning.billing.payment.provider.PaymentProviderPlugin;
 import com.ning.billing.payment.provider.PaymentProviderPluginRegistry;
-import com.ning.billing.payment.setup.PaymentConfig;
+import com.ning.billing.util.callcontext.CallContext;
 
 public class DefaultPaymentApi implements PaymentApi {
     private final PaymentProviderPluginRegistry pluginRegistry;
@@ -54,11 +54,11 @@ public class DefaultPaymentApi implements PaymentApi {
 
     @Inject
     public DefaultPaymentApi(PaymentProviderPluginRegistry pluginRegistry,
-                             AccountUserApi accountUserApi,
-                             InvoicePaymentApi invoicePaymentApi,
-                             RetryService retryService,
-                             PaymentDao paymentDao,
-                             PaymentConfig config) {
+            AccountUserApi accountUserApi,
+            InvoicePaymentApi invoicePaymentApi,
+            RetryService retryService,
+            PaymentDao paymentDao,
+            PaymentConfig config) {
         this.pluginRegistry = pluginRegistry;
         this.accountUserApi = accountUserApi;
         this.invoicePaymentApi = invoicePaymentApi;
@@ -68,18 +68,26 @@ public class DefaultPaymentApi implements PaymentApi {
     }
 
     @Override
-    public Either<PaymentError, PaymentMethodInfo> getPaymentMethod(@Nullable String accountKey, String paymentMethodId) {
+    public PaymentMethodInfo getPaymentMethod(final String accountKey, final String paymentMethodId) 
+    throws PaymentApiException {
         final PaymentProviderPlugin plugin = getPaymentProviderPlugin(accountKey);
-        return plugin.getPaymentMethodInfo(paymentMethodId);
+        Either<PaymentErrorEvent, PaymentMethodInfo> result = plugin.getPaymentMethodInfo(paymentMethodId);
+        if (result.isLeft()) {
+            throw new PaymentApiException(ErrorCode.PAYMENT_NO_SUCH_PAYMENT_METHOD, accountKey, paymentMethodId);
+        }
+        return result.getRight();
     }
 
     private PaymentProviderPlugin getPaymentProviderPlugin(String accountKey) {
         String paymentProviderName = null;
 
         if (accountKey != null) {
-            final Account account = accountUserApi.getAccountByKey(accountKey);
-            if (account != null) {
+            Account account;
+            try {
+                account = accountUserApi.getAccountByKey(accountKey);
                 return getPaymentProviderPlugin(account);
+            } catch (AccountApiException e) {
+                log.error("Error getting payment provider plugin.", e);
             }
         }
 
@@ -97,106 +105,144 @@ public class DefaultPaymentApi implements PaymentApi {
     }
 
     @Override
-    public Either<PaymentError, List<PaymentMethodInfo>> getPaymentMethods(String accountKey) {
+    public List<PaymentMethodInfo> getPaymentMethods(String accountKey)
+    throws PaymentApiException {
         final PaymentProviderPlugin plugin = getPaymentProviderPlugin(accountKey);
-        return plugin.getPaymentMethods(accountKey);
+        Either<PaymentErrorEvent, List<PaymentMethodInfo>> result =  plugin.getPaymentMethods(accountKey);
+        if (result.isLeft()) {
+            throw new PaymentApiException(ErrorCode.PAYMENT_NO_PAYMENT_METHODS, accountKey);
+        }
+        return result.getRight();
     }
 
     @Override
-    public Either<PaymentError, Void> updatePaymentGateway(String accountKey, CallContext context) {
+    public void updatePaymentGateway(String accountKey, CallContext context) 
+    throws PaymentApiException {
         final PaymentProviderPlugin plugin = getPaymentProviderPlugin(accountKey);
-        return plugin.updatePaymentGateway(accountKey);
+        Either<PaymentErrorEvent, Void> result =  plugin.updatePaymentGateway(accountKey);
+        if (result.isLeft()) {
+            throw new PaymentApiException(ErrorCode.PAYMENT_UPD_GATEWAY_FAILED, accountKey, result.getLeft().getMessage());
+        }
+        return;
     }
 
     @Override
-    public Either<PaymentError, PaymentProviderAccount> getPaymentProviderAccount(String accountKey) {
+    public PaymentProviderAccount getPaymentProviderAccount(String accountKey)
+    throws PaymentApiException {
         final PaymentProviderPlugin plugin = getPaymentProviderPlugin(accountKey);
-        return plugin.getPaymentProviderAccount(accountKey);
+        Either<PaymentErrorEvent, PaymentProviderAccount> result = plugin.getPaymentProviderAccount(accountKey);
+        if (result.isLeft()) {
+            throw new PaymentApiException(ErrorCode.PAYMENT_GET_PAYMENT_PROVIDER, accountKey, result.getLeft().getMessage());
+        }
+        return result.getRight();
     }
 
     @Override
-    public Either<PaymentError, String> addPaymentMethod(@Nullable String accountKey, PaymentMethodInfo paymentMethod, CallContext context) {
+    public String addPaymentMethod(String accountKey, PaymentMethodInfo paymentMethod, CallContext context) 
+    throws PaymentApiException {
         final PaymentProviderPlugin plugin = getPaymentProviderPlugin(accountKey);
-        return plugin.addPaymentMethod(accountKey, paymentMethod);
+        Either<PaymentErrorEvent, String> result =  plugin.addPaymentMethod(accountKey, paymentMethod);
+        if (result.isLeft()) {
+            throw new PaymentApiException(ErrorCode.PAYMENT_ADD_PAYMENT_METHOD, accountKey, result.getLeft().getMessage());
+        }
+        return result.getRight();
     }
 
+
     @Override
-    public Either<PaymentError, Void> deletePaymentMethod(String accountKey, String paymentMethodId, CallContext context) {
+    public void deletePaymentMethod(String accountKey, String paymentMethodId, CallContext context) 
+    throws PaymentApiException {
         final PaymentProviderPlugin plugin = getPaymentProviderPlugin(accountKey);
-        return plugin.deletePaymentMethod(accountKey, paymentMethodId);
+        Either<PaymentErrorEvent, Void> result =  plugin.deletePaymentMethod(accountKey, paymentMethodId);
+        if (result.isLeft()) {
+            throw new PaymentApiException(ErrorCode.PAYMENT_DEL_PAYMENT_METHOD, accountKey, result.getLeft().getMessage());
+        }
+        return;
     }
 
     @Override
-    public Either<PaymentError, PaymentMethodInfo> updatePaymentMethod(String accountKey, PaymentMethodInfo paymentMethodInfo, CallContext context) {
+    public PaymentMethodInfo updatePaymentMethod(String accountKey, PaymentMethodInfo paymentMethodInfo, CallContext context) 
+    throws PaymentApiException {
         final PaymentProviderPlugin plugin = getPaymentProviderPlugin(accountKey);
-        return plugin.updatePaymentMethod(accountKey, paymentMethodInfo);
+        Either<PaymentErrorEvent, PaymentMethodInfo> result = plugin.updatePaymentMethod(accountKey, paymentMethodInfo);
+        if (result.isLeft()) {
+            throw new PaymentApiException(ErrorCode.PAYMENT_UPD_PAYMENT_METHOD, accountKey, result.getLeft().getMessage());
+        }
+        return result.getRight();
     }
 
     @Override
-    public List<Either<PaymentError, PaymentInfo>> createPayment(String accountKey, List<String> invoiceIds, CallContext context) {
-        final Account account = accountUserApi.getAccountByKey(accountKey);
-        return createPayment(account, invoiceIds, context);
+    public List<PaymentInfoEvent> createPayment(String accountKey, List<String> invoiceIds, CallContext context) 
+    throws PaymentApiException {
+        try {
+            final Account account = accountUserApi.getAccountByKey(accountKey);
+            return createPayment(account, invoiceIds, context);
+        } catch (AccountApiException e) {
+            throw new PaymentApiException(e);
+        }
     }
 
     @Override
-    public Either<PaymentError, PaymentInfo> createPaymentForPaymentAttempt(UUID paymentAttemptId, CallContext context) {
-        PaymentAttempt paymentAttempt = paymentDao.getPaymentAttemptById(paymentAttemptId);
+    public PaymentInfoEvent createPaymentForPaymentAttempt(UUID paymentAttemptId, CallContext context) 
+    throws PaymentApiException {
 
+        PaymentAttempt paymentAttempt = paymentDao.getPaymentAttemptById(paymentAttemptId);
         if (paymentAttempt != null) {
-            Invoice invoice = invoicePaymentApi.getInvoice(paymentAttempt.getInvoiceId());
-            Account account = accountUserApi.getAccountById(paymentAttempt.getAccountId());
-
-            if (invoice != null && account != null) {
-                if (invoice.getBalance().compareTo(BigDecimal.ZERO) <= 0 ) {
-                    // TODO: send a notification that invoice was ignored?
-                    log.info("Received invoice for payment with outstanding amount of 0 {} ", invoice);
-                    return Either.left(new PaymentError("invoice_balance_0",
-                                                        "Invoice balance was 0 or less",
-                                                        paymentAttempt.getAccountId(),
-                                                        paymentAttempt.getInvoiceId()));
-                }
-                else {
-                    PaymentAttempt newPaymentAttempt = new PaymentAttempt.Builder(paymentAttempt)
-                                                                         .setRetryCount(paymentAttempt.getRetryCount() + 1)
-                                                                         .setPaymentAttemptId(UUID.randomUUID())
-                                                                         .build();
-
-                    paymentDao.createPaymentAttempt(newPaymentAttempt, context);
-                    return processPayment(getPaymentProviderPlugin(account), account, invoice, newPaymentAttempt, context);
+            try {
+                Invoice invoice = invoicePaymentApi.getInvoice(paymentAttempt.getInvoiceId());
+                Account account = accountUserApi.getAccountById(paymentAttempt.getAccountId());
+
+                if (invoice != null && account != null) {
+                    if (invoice.getBalance().compareTo(BigDecimal.ZERO) <= 0 ) {
+                        // TODO: send a notification that invoice was ignored?
+                        log.info("Received invoice for payment with outstanding amount of 0 {} ", invoice);
+                        throw new PaymentApiException(ErrorCode.PAYMENT_CREATE_PAYMENT_FOR_ATTEMPT_WITH_NON_POSITIVE_INV, account.getId());
+
+                    } else {
+                        
+                        PaymentAttempt newPaymentAttempt = new DefaultPaymentAttempt.Builder(paymentAttempt)
+                                                                                    .setRetryCount(paymentAttempt.getRetryCount() + 1)
+                                                                                    .setPaymentAttemptId(UUID.randomUUID())
+                                                                                    .build();
+
+                        paymentDao.createPaymentAttempt(newPaymentAttempt, context);
+                        Either<PaymentErrorEvent, PaymentInfoEvent> result =  processPayment(getPaymentProviderPlugin(account), account, invoice, newPaymentAttempt, context);
+                        if (result.isLeft()) {
+                            throw new PaymentApiException(ErrorCode.PAYMENT_CREATE_PAYMENT_FOR_ATTEMPT, account.getId(),  paymentAttemptId, result.getLeft().getMessage());                            
+                        }
+                        return result.getRight();
+
+                    }
                 }
+            } catch (AccountApiException e) {
+                throw new PaymentApiException(e);
             }
         }
-        return Either.left(new PaymentError("retry_payment_error",
-                                            "Could not load payment attempt, invoice or account for id " + paymentAttemptId,
-                                            paymentAttempt.getAccountId(),
-                                            paymentAttempt.getInvoiceId()));
+        throw new PaymentApiException(ErrorCode.PAYMENT_CREATE_PAYMENT_FOR_ATTEMPT_BAD, paymentAttemptId);
     }
 
     @Override
-    public List<Either<PaymentError, PaymentInfo>> createPayment(Account account, List<String> invoiceIds, CallContext context) {
+    public List<PaymentInfoEvent> createPayment(Account account, List<String> invoiceIds, CallContext context) 
+        throws PaymentApiException {
+        
         final PaymentProviderPlugin plugin = getPaymentProviderPlugin(account);
 
-        List<Either<PaymentError, PaymentInfo>> processedPaymentsOrErrors = new ArrayList<Either<PaymentError, PaymentInfo>>(invoiceIds.size());
+        List<Either<PaymentErrorEvent, PaymentInfoEvent>> processedPaymentsOrErrors = new ArrayList<Either<PaymentErrorEvent, PaymentInfoEvent>>(invoiceIds.size());
 
         for (String invoiceId : invoiceIds) {
             Invoice invoice = invoicePaymentApi.getInvoice(UUID.fromString(invoiceId));
 
             if (invoice.getBalance().compareTo(BigDecimal.ZERO) <= 0 ) {
-                // TODO: send a notification that invoice was ignored?
-                log.info("Received invoice for payment with balance of 0 {} ", invoice);
-                Either<PaymentError, PaymentInfo> result = Either.left(new PaymentError("invoice_balance_0",
-                                                                                        "Invoice balance was 0 or less",
-                                                                                        account.getId(),
-                                                                                        UUID.fromString(invoiceId)));
-                processedPaymentsOrErrors.add(result);
+                log.debug("Received invoice for payment with balance of 0 {} ", invoice);
             }
             else if (invoice.isMigrationInvoice()) {
-            	log.info("Received invoice for payment that is a migration invoice - don't know how to handle those yet: {}", invoice);
-            	Either<PaymentError, PaymentInfo> result = Either.left(new PaymentError("migration invoice",
+                log.info("Received invoice for payment that is a migration invoice - don't know how to handle those yet: {}", invoice);
+                Either<PaymentErrorEvent, PaymentInfoEvent> result = Either.left((PaymentErrorEvent) new DefaultPaymentErrorEvent("migration invoice",
                         "Invoice balance was a migration invoice",
                         account.getId(),
-                        UUID.fromString(invoiceId)));
-            			processedPaymentsOrErrors.add(result);
+                        UUID.fromString(invoiceId),
+                        context.getUserToken()));
+                processedPaymentsOrErrors.add(result);
             }
             else {
                 PaymentAttempt paymentAttempt = paymentDao.createPaymentAttempt(invoice, context);
@@ -205,16 +251,24 @@ public class DefaultPaymentApi implements PaymentApi {
             }
         }
 
-        return processedPaymentsOrErrors;
+        List<Either<PaymentErrorEvent, PaymentInfoEvent>> result =  processedPaymentsOrErrors;
+        List<PaymentInfoEvent> info = new LinkedList<PaymentInfoEvent>();
+        for (Either<PaymentErrorEvent, PaymentInfoEvent> cur : result) {
+            if (cur.isLeft()) {
+                throw new PaymentApiException(ErrorCode.PAYMENT_CREATE_PAYMENT, account.getId(), cur.getLeft().getMessage());
+            }
+            info.add(cur.getRight());
+        }
+        return info;
     }
 
-    private Either<PaymentError, PaymentInfo> processPayment(PaymentProviderPlugin plugin, Account account, Invoice invoice,
-                                                             PaymentAttempt paymentAttempt, CallContext context) {
-        Either<PaymentError, PaymentInfo> paymentOrError = plugin.processInvoice(account, invoice);
-        PaymentInfo paymentInfo = null;
+    private Either<PaymentErrorEvent, PaymentInfoEvent> processPayment(PaymentProviderPlugin plugin, Account account, Invoice invoice,
+            PaymentAttempt paymentAttempt, CallContext context) {
+        Either<PaymentErrorEvent, PaymentInfoEvent> paymentOrError = plugin.processInvoice(account, invoice);
+        PaymentInfoEvent paymentInfo = null;
 
         if (paymentOrError.isLeft()) {
-            String error = StringUtils.substring(paymentOrError.getLeft().getMessage() + paymentOrError.getLeft().getType(), 0, 100);
+            String error = StringUtils.substring(paymentOrError.getLeft().getMessage() + paymentOrError.getLeft().getBusEventType(), 0, 100);
             log.info("Could not process a payment for " + paymentAttempt + " error was " + error);
 
             scheduleRetry(paymentAttempt, error);
@@ -225,35 +279,36 @@ public class DefaultPaymentApi implements PaymentApi {
 
             final String paymentMethodId = paymentInfo.getPaymentMethodId();
             log.debug("Fetching payment method info for payment method id " + ((paymentMethodId == null) ? "null" : paymentMethodId));
-            Either<PaymentError, PaymentMethodInfo> paymentMethodInfoOrError = plugin.getPaymentMethodInfo(paymentMethodId);
+            Either<PaymentErrorEvent, PaymentMethodInfo> paymentMethodInfoOrError = plugin.getPaymentMethodInfo(paymentMethodId);
 
             if (paymentMethodInfoOrError.isRight()) {
                 PaymentMethodInfo paymentMethodInfo = paymentMethodInfoOrError.getRight();
 
                 if (paymentMethodInfo instanceof CreditCardPaymentMethodInfo) {
                     CreditCardPaymentMethodInfo ccPaymentMethod = (CreditCardPaymentMethodInfo)paymentMethodInfo;
-                    paymentDao.updatePaymentInfo(ccPaymentMethod.getType(), paymentInfo.getPaymentId(), ccPaymentMethod.getCardType(), ccPaymentMethod.getCardCountry(), context);
+                    paymentDao.updatePaymentInfo(ccPaymentMethod.getType(), paymentInfo.getId(), ccPaymentMethod.getCardType(), ccPaymentMethod.getCardCountry(), context);
                 }
                 else if (paymentMethodInfo instanceof PaypalPaymentMethodInfo) {
                     PaypalPaymentMethodInfo paypalPaymentMethodInfo = (PaypalPaymentMethodInfo)paymentMethodInfo;
-                    paymentDao.updatePaymentInfo(paypalPaymentMethodInfo.getType(), paymentInfo.getPaymentId(), null, null, context);
+                    paymentDao.updatePaymentInfo(paypalPaymentMethodInfo.getType(), paymentInfo.getId(), null, null, context);
                 }
             } else {
                 log.info(paymentMethodInfoOrError.getLeft().getMessage());
             }
 
-            if (paymentInfo.getPaymentId() != null) {
-                paymentDao.updatePaymentAttemptWithPaymentId(paymentAttempt.getPaymentAttemptId(), paymentInfo.getPaymentId(), context);
+            if (paymentInfo.getId() != null) {
+                paymentDao.updatePaymentAttemptWithPaymentId(paymentAttempt.getId(), paymentInfo.getId(), context);
             }
         }
 
-        invoicePaymentApi.notifyOfPaymentAttempt(new DefaultInvoicePayment(paymentAttempt.getPaymentAttemptId(),
-                                                                           invoice.getId(),
-                                                                           paymentAttempt.getPaymentAttemptDate(),
-                                                                           paymentInfo == null || paymentInfo.getStatus().equalsIgnoreCase("Error") ? null : paymentInfo.getAmount(),
-//                                                                         paymentInfo.getRefundAmount(), TODO
-                                                                           paymentInfo == null || paymentInfo.getStatus().equalsIgnoreCase("Error") ? null : invoice.getCurrency()),
-                                                                           context);
+        invoicePaymentApi.notifyOfPaymentAttempt(
+                invoice.getId(),
+                paymentInfo == null || paymentInfo.getStatus().equalsIgnoreCase("Error") ? null : paymentInfo.getAmount(),
+                        //                                                                         paymentInfo.getRefundAmount(), TODO
+                        paymentInfo == null || paymentInfo.getStatus().equalsIgnoreCase("Error") ? null : invoice.getCurrency(),
+                                paymentAttempt.getId(),
+                                paymentAttempt.getPaymentAttemptDate(),
+                                context);
 
         return paymentOrError;
     }
@@ -290,32 +345,61 @@ public class DefaultPaymentApi implements PaymentApi {
     }
 
     @Override
-    public Either<PaymentError, String> createPaymentProviderAccount(Account account, CallContext context) {
+    public String createPaymentProviderAccount(Account account, CallContext context) 
+        throws PaymentApiException {
         final PaymentProviderPlugin plugin = getPaymentProviderPlugin((Account)null);
-        return plugin.createPaymentProviderAccount(account);
+        Either<PaymentErrorEvent, String> result =  plugin.createPaymentProviderAccount(account);
+        if (result.isLeft()) {
+            throw new PaymentApiException(ErrorCode.PAYMENT_CREATE_PAYMENT_PROVIDER_ACCOUNT, account.getId(), result.getLeft().getMessage());
+        }
+        return result.getRight();
     }
 
     @Override
-    public Either<PaymentError, Void> updatePaymentProviderAccountContact(String externalKey, CallContext context) {
-    	Account account = accountUserApi.getAccountByKey(externalKey);
-        final PaymentProviderPlugin plugin = getPaymentProviderPlugin(account);
-        return plugin.updatePaymentProviderAccountExistingContact(account);
+    public void updatePaymentProviderAccountContact(String externalKey, CallContext context) 
+        throws PaymentApiException {
+        try {
+            Account account = accountUserApi.getAccountByKey(externalKey);
+            final PaymentProviderPlugin plugin = getPaymentProviderPlugin(account);
+            Either<PaymentErrorEvent, Void> result = plugin.updatePaymentProviderAccountExistingContact(account);
+            if (result.isLeft()) {
+                throw new PaymentApiException(ErrorCode.PAYMENT_UPD_PAYMENT_PROVIDER_ACCOUNT, account.getId(), result.getLeft().getMessage());
+            }
+            return;
+        } catch (AccountApiException e) {
+            throw new PaymentApiException(e);
+        }
     }
 
     @Override
-    public PaymentAttempt getPaymentAttemptForPaymentId(String id) {
+    public PaymentAttempt getPaymentAttemptForPaymentId(UUID id) {
         return paymentDao.getPaymentAttemptForPaymentId(id);
     }
 
     @Override
-    public List<Either<PaymentError, PaymentInfo>> createRefund(Account account, List<String> invoiceIds, CallContext context) {
-        //TODO
-        throw new UnsupportedOperationException();
+    public List<PaymentInfoEvent> createRefund(Account account, List<String> invoiceIds, CallContext context)
+        throws PaymentApiException {
+            
+        final PaymentProviderPlugin plugin = getPaymentProviderPlugin(account);
+        List<Either<PaymentErrorEvent, PaymentInfoEvent>> result = plugin.processRefund(account);
+        List<PaymentInfoEvent> info =  new LinkedList<PaymentInfoEvent>();
+        for (Either<PaymentErrorEvent, PaymentInfoEvent> cur : result) {
+            if (cur.isLeft()) {
+                throw new PaymentApiException(ErrorCode.PAYMENT_CREATE_REFUND, account.getId(), cur.getLeft().getMessage());
+            }
+            info.add(cur.getRight());
+        }
+        return info;
+    }
+
+    @Override
+    public List<PaymentInfoEvent> getPaymentInfoList(List<String> invoiceIds) {
+        return paymentDao.getPaymentInfoList(invoiceIds);
     }
 
     @Override
-    public List<PaymentInfo> getPaymentInfo(List<String> invoiceIds) {
-        return paymentDao.getPaymentInfo(invoiceIds);
+    public PaymentInfoEvent getLastPaymentInfo(List<String> invoiceIds) {
+        return paymentDao.getLastPaymentInfo(invoiceIds);
     }
 
     @Override
@@ -324,7 +408,7 @@ public class DefaultPaymentApi implements PaymentApi {
     }
 
     @Override
-    public PaymentInfo getPaymentInfoForPaymentAttemptId(String paymentAttemptId) {
+    public PaymentInfoEvent getPaymentInfoForPaymentAttemptId(String paymentAttemptId) {
         return paymentDao.getPaymentInfoForPaymentAttemptId(paymentAttemptId);
     }
 
diff --git a/payment/src/main/java/com/ning/billing/payment/api/DefaultPaymentAttempt.java b/payment/src/main/java/com/ning/billing/payment/api/DefaultPaymentAttempt.java
new file mode 100644
index 0000000..681f0ca
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/api/DefaultPaymentAttempt.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright 2010-2011 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.payment.api;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import com.ning.billing.util.entity.EntityBase;
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+
+import com.google.common.base.Objects;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.Invoice;
+
+public class DefaultPaymentAttempt extends EntityBase implements PaymentAttempt  {
+    private final UUID invoiceId;
+    private final UUID accountId;
+    private final BigDecimal amount;
+    private final Currency currency;
+    private final UUID paymentId;
+    private final DateTime invoiceDate;
+    private final DateTime paymentAttemptDate;
+    private final Integer retryCount;
+    private final DateTime createdDate;
+    private final DateTime updatedDate;
+
+    public DefaultPaymentAttempt(UUID id,
+                          UUID invoiceId,
+                          UUID accountId,
+                          BigDecimal amount,
+                          Currency currency,
+                          DateTime invoiceDate,
+                          DateTime paymentAttemptDate,
+                          UUID paymentId,
+                          Integer retryCount,
+                          DateTime createdDate, DateTime updatedDate) {
+        super(id);
+        this.invoiceId = invoiceId;
+        this.accountId = accountId;
+        this.amount = amount;
+        this.currency = currency;
+        this.invoiceDate = invoiceDate;
+        this.paymentAttemptDate = paymentAttemptDate == null ? new DateTime(DateTimeZone.UTC) : paymentAttemptDate;
+        this.paymentId = paymentId;
+        this.retryCount = retryCount == null ? 0 : retryCount;
+        this.createdDate = createdDate;
+        this.updatedDate = updatedDate;
+    }
+
+    public DefaultPaymentAttempt(UUID paymentAttemptId, UUID invoiceId, UUID accountId, BigDecimal amount, Currency currency, DateTime invoiceDate, DateTime paymentAttemptDate) {
+        this(paymentAttemptId, invoiceId, accountId, amount, currency, invoiceDate, paymentAttemptDate, null, null, null, null);
+    }
+
+    public DefaultPaymentAttempt(UUID paymentAttemptId, UUID invoiceId, UUID accountId, DateTime invoiceDate, DateTime paymentAttemptDate) {
+        this(paymentAttemptId, invoiceId, accountId, null, null, invoiceDate, paymentAttemptDate, null, null, null, null);
+    }
+
+    public DefaultPaymentAttempt(UUID paymentAttemptId, Invoice invoice) {
+        this(paymentAttemptId, invoice.getId(), invoice.getAccountId(), invoice.getBalance(), invoice.getCurrency(), invoice.getInvoiceDate(), null, null, null, null, null);
+    }
+
+    @Override public DateTime getInvoiceDate() {
+        return invoiceDate;
+    }
+
+    @Override public UUID getId() {
+        return id;
+    }
+
+    @Override public UUID getPaymentId() {
+        return paymentId;
+    }
+
+    @Override public DateTime getPaymentAttemptDate() {
+        return paymentAttemptDate;
+    }
+
+    @Override public UUID getInvoiceId() {
+        return invoiceId;
+    }
+
+    @Override public UUID getAccountId() {
+        return accountId;
+    }
+
+    @Override public BigDecimal getAmount() {
+        return amount;
+    }
+
+    @Override public Currency getCurrency() {
+        return currency;
+    }
+
+    @Override public Integer getRetryCount() {
+        return retryCount;
+    }
+
+    @Override public DateTime getCreatedDate() {
+        return createdDate;
+    }
+
+    @Override public DateTime getUpdatedDate() {
+        return updatedDate;
+    }
+
+    @Override
+    public String toString() {
+        return "PaymentAttempt [paymentAttemptId=" + id + ", invoiceId=" + invoiceId + ", accountId=" + accountId + ", amount=" + amount + ", currency=" + currency + ", paymentId=" + paymentId + ", invoiceDate=" + invoiceDate + ", paymentAttemptDate=" + paymentAttemptDate + ", retryCount=" + retryCount + "]";
+    }
+
+    public Builder cloner() {
+        return new Builder(this);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(id,
+                                invoiceId,
+                                accountId,
+                                amount,
+                                currency,
+                                invoiceDate,
+                                paymentAttemptDate,
+                                paymentId,
+                                retryCount,
+                                createdDate, updatedDate);
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        final PaymentAttempt that = (PaymentAttempt) o;
+
+        if (accountId != null ? !accountId.equals(that.getAccountId()) : that.getAccountId() != null) return false;
+        if (amount != null ? !(amount.compareTo(that.getAmount()) == 0) : that.getAmount() != null) return false;
+        if (currency != that.getCurrency()) return false;
+        if (invoiceDate == null ? that.getInvoiceDate() != null : invoiceDate.compareTo(that.getInvoiceDate()) != 0) return false;
+        if (invoiceId != null ? !invoiceId.equals(that.getInvoiceId()) : that.getInvoiceId() != null) return false;
+        if (paymentAttemptDate == null ? that.getPaymentAttemptDate() != null : paymentAttemptDate.compareTo(that.getPaymentAttemptDate()) != 0) return false;
+        if (id != null ? !id.equals(that.getId()) : that.getId() != null)
+            return false;
+        if (paymentId != null ? !paymentId.equals(that.getPaymentId()) : that.getPaymentId() != null) return false;
+        if (retryCount != null ? !retryCount.equals(that.getRetryCount()) : that.getRetryCount() != null) return false;
+        if (createdDate.compareTo(that.getCreatedDate()) != 0) return false;
+        if (updatedDate.compareTo(that.getUpdatedDate()) != 0) return false;
+
+        return true;
+    }
+
+    public static class Builder {
+        private UUID id;
+        private UUID invoiceId;
+        private UUID accountId;
+        private BigDecimal amount;
+        private Currency currency;
+        private DateTime invoiceDate;
+        private DateTime paymentAttemptDate;
+        private UUID paymentId;
+        private Integer retryCount;
+        private DateTime createdDate;
+        private DateTime updatedDate;
+
+        public Builder() {
+        }
+
+        public Builder(PaymentAttempt src) {
+            this.id = src.getId();
+            this.invoiceId = src.getInvoiceId();
+            this.accountId = src.getAccountId();
+            this.amount = src.getAmount();
+            this.currency = src.getCurrency();
+            this.invoiceDate = src.getInvoiceDate();
+            this.paymentAttemptDate = src.getPaymentAttemptDate();
+            this.paymentId = src.getPaymentId();
+            this.retryCount = src.getRetryCount();
+            this.createdDate = src.getCreatedDate();
+            this.updatedDate = src.getUpdatedDate();
+        }
+
+        public Builder setPaymentAttemptId(UUID paymentAttemptId) {
+            this.id = paymentAttemptId;
+            return this;
+        }
+
+        public Builder setInvoiceId(UUID invoiceId) {
+            this.invoiceId = invoiceId;
+            return this;
+        }
+
+        public Builder setAccountId(UUID accountId) {
+            this.accountId = accountId;
+            return this;
+        }
+
+        public Builder setAmount(BigDecimal amount) {
+            this.amount = amount;
+            return this;
+        }
+
+        public Builder setCurrency(Currency currency) {
+            this.currency = currency;
+            return this;
+        }
+
+        public Builder setInvoiceDate(DateTime invoiceDate) {
+            this.invoiceDate = invoiceDate;
+            return this;
+        }
+
+        public Builder setPaymentAttemptDate(DateTime paymentAttemptDate) {
+            this.paymentAttemptDate = paymentAttemptDate;
+            return this;
+        }
+
+        public Builder setPaymentId(UUID paymentId) {
+            this.paymentId = paymentId;
+            return this;
+        }
+
+        public Builder setRetryCount(Integer retryCount) {
+            this.retryCount = retryCount;
+            return this;
+        }
+
+        public Builder setCreatedDate(DateTime createdDate) {
+            this.createdDate = createdDate;
+            return this;
+        }
+
+        public Builder setUpdateDate(DateTime updateDate) {
+            this.updatedDate = updateDate;
+            return this;
+        }
+
+        public PaymentAttempt build() {
+            return new DefaultPaymentAttempt(id,
+                                      invoiceId,
+                                      accountId,
+                                      amount,
+                                      currency,
+                                      invoiceDate,
+                                      paymentAttemptDate,
+                                      paymentId,
+                                      retryCount,
+                                      createdDate, updatedDate);
+        }
+    }
+}
diff --git a/payment/src/main/java/com/ning/billing/payment/dao/AuditedPaymentDao.java b/payment/src/main/java/com/ning/billing/payment/dao/AuditedPaymentDao.java
index bcdda9a..39201ac 100644
--- a/payment/src/main/java/com/ning/billing/payment/dao/AuditedPaymentDao.java
+++ b/payment/src/main/java/com/ning/billing/payment/dao/AuditedPaymentDao.java
@@ -19,50 +19,57 @@ package com.ning.billing.payment.dao;
 import java.util.List;
 import java.util.UUID;
 
+import com.ning.billing.payment.api.DefaultPaymentAttempt;
 import com.ning.billing.util.ChangeType;
-import com.ning.billing.util.audit.dao.AuditSqlDao;
 import com.ning.billing.util.callcontext.CallContext;
-import org.apache.commons.lang.Validate;
+import com.ning.billing.util.dao.EntityAudit;
+import com.ning.billing.util.dao.EntityHistory;
+import com.ning.billing.util.dao.TableName;
 import org.skife.jdbi.v2.IDBI;
 
 import com.google.common.collect.ImmutableList;
 import com.google.inject.Inject;
 import com.ning.billing.invoice.api.Invoice;
 import com.ning.billing.payment.api.PaymentAttempt;
-import com.ning.billing.payment.api.PaymentInfo;
+import com.ning.billing.payment.api.PaymentInfoEvent;
 import org.skife.jdbi.v2.Transaction;
 import org.skife.jdbi.v2.TransactionStatus;
 
 public class AuditedPaymentDao implements PaymentDao {
-    private final PaymentSqlDao sqlDao;
+    private final PaymentSqlDao paymentSqlDao;
+    private final PaymentAttemptSqlDao paymentAttemptSqlDao;
 
     @Inject
     public AuditedPaymentDao(IDBI dbi) {
-        this.sqlDao = dbi.onDemand(PaymentSqlDao.class);
+        this.paymentSqlDao = dbi.onDemand(PaymentSqlDao.class);
+        this.paymentAttemptSqlDao = dbi.onDemand(PaymentAttemptSqlDao.class);
     }
 
     @Override
-    public PaymentAttempt getPaymentAttemptForPaymentId(String paymentId) {
-        return sqlDao.getPaymentAttemptForPaymentId(paymentId);
+    public PaymentAttempt getPaymentAttemptForPaymentId(UUID paymentId) {
+        return paymentAttemptSqlDao.getPaymentAttemptForPaymentId(paymentId.toString());
     }
 
     @Override
     public List<PaymentAttempt> getPaymentAttemptsForInvoiceId(String invoiceId) {
-        return sqlDao.getPaymentAttemptsForInvoiceId(invoiceId);
+        return paymentAttemptSqlDao.getPaymentAttemptsForInvoiceId(invoiceId);
     }
 
     @Override
     public PaymentAttempt createPaymentAttempt(final PaymentAttempt paymentAttempt, final CallContext context) {
-        return sqlDao.inTransaction(new Transaction<PaymentAttempt, PaymentSqlDao>() {
+        return paymentAttemptSqlDao.inTransaction(new Transaction<PaymentAttempt, PaymentAttemptSqlDao>() {
             @Override
-            public PaymentAttempt inTransaction(PaymentSqlDao transactional, TransactionStatus status) throws Exception {
+            public PaymentAttempt inTransaction(PaymentAttemptSqlDao transactional, TransactionStatus status) throws Exception {
                 transactional.insertPaymentAttempt(paymentAttempt, context);
-                PaymentAttempt savedPaymentAttempt = transactional.getPaymentAttemptById(paymentAttempt.getPaymentAttemptId().toString());
-                UUID historyRecordId = UUID.randomUUID();
-                transactional.insertPaymentAttemptHistory(historyRecordId.toString(), paymentAttempt, context);
-                AuditSqlDao auditSqlDao = transactional.become(AuditSqlDao.class);
-                auditSqlDao.insertAuditFromTransaction("payment_attempt", historyRecordId.toString(),
-                                                       ChangeType.INSERT, context);
+                PaymentAttempt savedPaymentAttempt = transactional.getPaymentAttemptById(paymentAttempt.getId().toString());
+
+                Long recordId = transactional.getRecordId(paymentAttempt.getId().toString());
+                EntityHistory<PaymentAttempt> history = new EntityHistory<PaymentAttempt>(paymentAttempt.getId(), recordId, paymentAttempt, ChangeType.INSERT);
+                transactional.insertHistoryFromTransaction(history, context);
+
+                Long historyRecordId = transactional.getHistoryRecordId(recordId);
+                EntityAudit audit = new EntityAudit(TableName.PAYMENT_ATTEMPTS, historyRecordId, ChangeType.INSERT);
+                transactional.insertAuditFromTransaction(audit, context);
                 return savedPaymentAttempt;
             }
         });
@@ -70,16 +77,19 @@ public class AuditedPaymentDao implements PaymentDao {
 
     @Override
     public PaymentAttempt createPaymentAttempt(final Invoice invoice, final CallContext context) {
-        return sqlDao.inTransaction(new Transaction<PaymentAttempt, PaymentSqlDao>() {
+        return paymentAttemptSqlDao.inTransaction(new Transaction<PaymentAttempt, PaymentAttemptSqlDao>() {
             @Override
-            public PaymentAttempt inTransaction(PaymentSqlDao transactional, TransactionStatus status) throws Exception {
-                final PaymentAttempt paymentAttempt = new PaymentAttempt(UUID.randomUUID(), invoice);
+            public PaymentAttempt inTransaction(PaymentAttemptSqlDao transactional, TransactionStatus status) throws Exception {
+                final PaymentAttempt paymentAttempt = new DefaultPaymentAttempt(UUID.randomUUID(), invoice);
                 transactional.insertPaymentAttempt(paymentAttempt, context);
-                UUID historyRecordId = UUID.randomUUID();
-                transactional.insertPaymentAttemptHistory(historyRecordId.toString(), paymentAttempt, context);
-                AuditSqlDao auditSqlDao = transactional.become(AuditSqlDao.class);
-                auditSqlDao.insertAuditFromTransaction("payment_attempt", historyRecordId.toString(),
-                                                       ChangeType.INSERT, context);
+
+                Long recordId = transactional.getRecordId(paymentAttempt.getId().toString());
+                EntityHistory<PaymentAttempt> history = new EntityHistory<PaymentAttempt>(paymentAttempt.getId(), recordId, paymentAttempt, ChangeType.INSERT);
+                transactional.insertHistoryFromTransaction(history, context);
+
+                Long historyRecordId = transactional.getHistoryRecordId(recordId);
+                EntityAudit audit = new EntityAudit(TableName.PAYMENT_ATTEMPTS, historyRecordId, ChangeType.INSERT);
+                transactional.insertAuditFromTransaction(audit, context);
 
                 return paymentAttempt;
             }
@@ -87,16 +97,18 @@ public class AuditedPaymentDao implements PaymentDao {
     }
 
     @Override
-    public void savePaymentInfo(final PaymentInfo info, final CallContext context) {
-        sqlDao.inTransaction(new Transaction<Void, PaymentSqlDao>() {
+    public void savePaymentInfo(final PaymentInfoEvent info, final CallContext context) {
+        paymentSqlDao.inTransaction(new Transaction<Void, PaymentSqlDao>() {
             @Override
             public Void inTransaction(PaymentSqlDao transactional, TransactionStatus status) throws Exception {
                 transactional.insertPaymentInfo(info, context);
-                UUID historyRecordId = UUID.randomUUID();
-                transactional.insertPaymentInfoHistory(historyRecordId.toString(), info, context);
-                AuditSqlDao auditSqlDao = transactional.become(AuditSqlDao.class);
-                auditSqlDao.insertAuditFromTransaction("payment", historyRecordId.toString(),
-                                                       ChangeType.INSERT, context);
+                Long recordId = transactional.getRecordId(info.getId().toString());
+                EntityHistory<PaymentInfoEvent> history = new EntityHistory<PaymentInfoEvent>(info.getId(), recordId, info, ChangeType.INSERT);
+                transactional.insertHistoryFromTransaction(history, context);
+
+                Long historyRecordId = transactional.getHistoryRecordId(recordId);
+                EntityAudit audit = new EntityAudit(TableName.PAYMENTS, historyRecordId, ChangeType.INSERT);
+                transactional.insertAuditFromTransaction(audit, context);
 
                 return null;
             }
@@ -104,17 +116,19 @@ public class AuditedPaymentDao implements PaymentDao {
     }
 
     @Override
-    public void updatePaymentAttemptWithPaymentId(final UUID paymentAttemptId, final String paymentId, final CallContext context) {
-        sqlDao.inTransaction(new Transaction<Void, PaymentSqlDao>() {
+    public void updatePaymentAttemptWithPaymentId(final UUID paymentAttemptId, final UUID id, final CallContext context) {
+        paymentAttemptSqlDao.inTransaction(new Transaction<Void, PaymentAttemptSqlDao>() {
             @Override
-            public Void inTransaction(PaymentSqlDao transactional, TransactionStatus status) throws Exception {
-                transactional.updatePaymentAttemptWithPaymentId(paymentAttemptId.toString(), paymentId, context);
+            public Void inTransaction(PaymentAttemptSqlDao transactional, TransactionStatus status) throws Exception {
+                transactional.updatePaymentAttemptWithPaymentId(paymentAttemptId.toString(), id.toString(), context);
                 PaymentAttempt paymentAttempt = transactional.getPaymentAttemptById(paymentAttemptId.toString());
-                UUID historyRecordId = UUID.randomUUID();
-                transactional.insertPaymentAttemptHistory(historyRecordId.toString(), paymentAttempt, context);
-                AuditSqlDao auditSqlDao = transactional.become(AuditSqlDao.class);
-                auditSqlDao.insertAuditFromTransaction("payment_attempt", historyRecordId.toString(),
-                                                       ChangeType.UPDATE, context);
+                Long recordId = transactional.getRecordId(paymentAttemptId.toString());
+                EntityHistory<PaymentAttempt> history = new EntityHistory<PaymentAttempt>(paymentAttemptId, recordId, paymentAttempt, ChangeType.UPDATE);
+                transactional.insertHistoryFromTransaction(history, context);
+
+                Long historyRecordId = transactional.getHistoryRecordId(recordId);
+                EntityAudit audit = new EntityAudit(TableName.PAYMENT_ATTEMPTS, historyRecordId, ChangeType.UPDATE);
+                transactional.insertAuditFromTransaction(audit, context);
 
                 return null;
             }
@@ -122,18 +136,21 @@ public class AuditedPaymentDao implements PaymentDao {
     }
 
     @Override
-    public void updatePaymentInfo(final String type, final String paymentId, final String cardType,
+    public void updatePaymentInfo(final String type, final UUID paymentId, final String cardType,
                                   final String cardCountry, final CallContext context) {
-        sqlDao.inTransaction(new Transaction<Void, PaymentSqlDao>() {
+        paymentSqlDao.inTransaction(new Transaction<Void, PaymentSqlDao>() {
             @Override
             public Void inTransaction(PaymentSqlDao transactional, TransactionStatus status) throws Exception {
-                transactional.updatePaymentInfo(type, paymentId, cardType, cardCountry, context);
-                PaymentInfo paymentInfo = transactional.getPaymentInfo(paymentId);
-                UUID historyRecordId = UUID.randomUUID();
-                transactional.insertPaymentInfoHistory(historyRecordId.toString(), paymentInfo, context);
-                AuditSqlDao auditSqlDao = transactional.become(AuditSqlDao.class);
-                auditSqlDao.insertAuditFromTransaction("payments", historyRecordId.toString(),
-                                                       ChangeType.UPDATE, context);
+                transactional.updatePaymentInfo(type, paymentId.toString(), cardType, cardCountry, context);
+                PaymentInfoEvent paymentInfo = transactional.getPaymentInfo(paymentId.toString());
+
+                Long recordId = transactional.getRecordId(paymentId.toString());
+                EntityHistory<PaymentInfoEvent> history = new EntityHistory<PaymentInfoEvent>(paymentInfo.getId(), recordId, paymentInfo, ChangeType.UPDATE);
+                transactional.insertHistoryFromTransaction(history, context);
+
+                Long historyRecordId = transactional.getHistoryRecordId(recordId);
+                EntityAudit audit = new EntityAudit(TableName.PAYMENT_HISTORY, historyRecordId, ChangeType.UPDATE);
+                transactional.insertAuditFromTransaction(audit, context);
 
                 return null;
             }
@@ -141,11 +158,20 @@ public class AuditedPaymentDao implements PaymentDao {
     }
 
     @Override
-    public List<PaymentInfo> getPaymentInfo(List<String> invoiceIds) {
+    public List<PaymentInfoEvent> getPaymentInfoList(List<String> invoiceIds) {
+        if (invoiceIds == null || invoiceIds.size() == 0) {
+            return ImmutableList.<PaymentInfoEvent>of();
+        } else {
+            return paymentSqlDao.getPaymentInfoList(invoiceIds);
+        }
+    }
+
+    @Override
+    public PaymentInfoEvent getLastPaymentInfo(List<String> invoiceIds) {
         if (invoiceIds == null || invoiceIds.size() == 0) {
-            return ImmutableList.<PaymentInfo>of();
+            return null;
         } else {
-            return sqlDao.getPaymentInfos(invoiceIds);
+            return paymentSqlDao.getLastPaymentInfo(invoiceIds);
         }
     }
 
@@ -154,18 +180,18 @@ public class AuditedPaymentDao implements PaymentDao {
         if (invoiceIds == null || invoiceIds.size() == 0) {
             return ImmutableList.<PaymentAttempt>of();
         } else {
-            return sqlDao.getPaymentAttemptsForInvoiceIds(invoiceIds);
+            return paymentAttemptSqlDao.getPaymentAttemptsForInvoiceIds(invoiceIds);
         }
     }
 
     @Override
     public PaymentAttempt getPaymentAttemptById(UUID paymentAttemptId) {
-        return sqlDao.getPaymentAttemptById(paymentAttemptId.toString());
+        return paymentAttemptSqlDao.getPaymentAttemptById(paymentAttemptId.toString());
     }
 
     @Override
-    public PaymentInfo getPaymentInfoForPaymentAttemptId(String paymentAttemptIdStr) {
-        return sqlDao.getPaymentInfoForPaymentAttemptId(paymentAttemptIdStr);
+    public PaymentInfoEvent getPaymentInfoForPaymentAttemptId(String paymentAttemptIdStr) {
+        return paymentSqlDao.getPaymentInfoForPaymentAttemptId(paymentAttemptIdStr);
     }
 
 }
diff --git a/payment/src/main/java/com/ning/billing/payment/dao/PaymentAttemptHistoryBinder.java b/payment/src/main/java/com/ning/billing/payment/dao/PaymentAttemptHistoryBinder.java
new file mode 100644
index 0000000..569ef40
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/dao/PaymentAttemptHistoryBinder.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2010-2011 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.payment.dao;
+
+import com.ning.billing.payment.api.PaymentAttempt;
+import com.ning.billing.util.dao.BinderBase;
+import com.ning.billing.util.dao.EntityHistory;
+import org.skife.jdbi.v2.SQLStatement;
+import org.skife.jdbi.v2.sqlobject.Binder;
+import org.skife.jdbi.v2.sqlobject.BinderFactory;
+import org.skife.jdbi.v2.sqlobject.BindingAnnotation;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@BindingAnnotation(PaymentAttemptHistoryBinder.PaymentAttemptHistoryBinderFactory.class)
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.PARAMETER})
+public @interface PaymentAttemptHistoryBinder {
+    public static class PaymentAttemptHistoryBinderFactory extends BinderBase implements BinderFactory {
+        @Override
+        public Binder<PaymentAttemptHistoryBinder, EntityHistory<PaymentAttempt>> build(Annotation annotation) {
+            return new Binder<PaymentAttemptHistoryBinder, EntityHistory<PaymentAttempt>>() {
+                @Override
+                public void bind(SQLStatement q, PaymentAttemptHistoryBinder bind, EntityHistory<PaymentAttempt> history) {
+                    q.bind("recordId", history.getValue());
+                    q.bind("changeType", history.getChangeType().toString());
+
+                    PaymentAttempt paymentAttempt = history.getEntity();
+                    q.bind("id", paymentAttempt.getId().toString());
+                    q.bind("invoiceId", paymentAttempt.getInvoiceId().toString());
+                    q.bind("accountId", paymentAttempt.getAccountId().toString());
+                    q.bind("amount", paymentAttempt.getAmount());
+                    q.bind("currency", paymentAttempt.getCurrency().toString());
+                    q.bind("invoiceDate", getDate(paymentAttempt.getInvoiceDate()));
+                    q.bind("paymentAttemptDate", getDate(paymentAttempt.getPaymentAttemptDate()));
+                    q.bind("paymentId", paymentAttempt.getPaymentId() == null ? null : paymentAttempt.getPaymentId().toString());
+                    q.bind("retryCount", paymentAttempt.getRetryCount());
+                }
+            };
+        }
+    }
+}
\ No newline at end of file
diff --git a/payment/src/main/java/com/ning/billing/payment/dao/PaymentAttemptSqlDao.java b/payment/src/main/java/com/ning/billing/payment/dao/PaymentAttemptSqlDao.java
new file mode 100644
index 0000000..a1fed90
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/dao/PaymentAttemptSqlDao.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2010-2011 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.payment.dao;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.payment.api.DefaultPaymentAttempt;
+import com.ning.billing.payment.api.PaymentAttempt;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.callcontext.CallContextBinder;
+import com.ning.billing.util.dao.BinderBase;
+import com.ning.billing.util.dao.EntityHistory;
+import com.ning.billing.util.dao.MapperBase;
+import com.ning.billing.util.entity.dao.UpdatableEntitySqlDao;
+import org.joda.time.DateTime;
+import org.skife.jdbi.v2.SQLStatement;
+import org.skife.jdbi.v2.StatementContext;
+import org.skife.jdbi.v2.sqlobject.Bind;
+import org.skife.jdbi.v2.sqlobject.Binder;
+import org.skife.jdbi.v2.sqlobject.SqlQuery;
+import org.skife.jdbi.v2.sqlobject.SqlUpdate;
+import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
+import org.skife.jdbi.v2.sqlobject.mixins.CloseMe;
+import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
+import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
+import org.skife.jdbi.v2.tweak.ResultSetMapper;
+import org.skife.jdbi.v2.unstable.BindIn;
+
+import java.math.BigDecimal;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.List;
+import java.util.UUID;
+
+@ExternalizedSqlViaStringTemplate3()
+@RegisterMapper(PaymentAttemptSqlDao.PaymentAttemptMapper.class)
+public interface PaymentAttemptSqlDao extends Transactional<PaymentAttemptSqlDao>, UpdatableEntitySqlDao<PaymentAttempt>, CloseMe {
+    @SqlUpdate
+    void insertPaymentAttempt(@Bind(binder = PaymentAttemptBinder.class) PaymentAttempt paymentAttempt,
+                              @CallContextBinder CallContext context);
+
+    @SqlQuery
+    PaymentAttempt getPaymentAttemptForPaymentId(@Bind("paymentId") String paymentId);
+
+    @SqlQuery
+    PaymentAttempt getPaymentAttemptById(@Bind("id") String paymentAttemptId);
+
+    @SqlQuery
+    List<PaymentAttempt> getPaymentAttemptsForInvoiceId(@Bind("invoiceId") String invoiceId);
+
+    @SqlQuery
+    List<PaymentAttempt> getPaymentAttemptsForInvoiceIds(@BindIn("invoiceIds") List<String> invoiceIds);
+
+
+    @SqlUpdate
+    void updatePaymentAttemptWithPaymentId(@Bind("id") String paymentAttemptId,
+                                           @Bind("payment_id") String paymentId,
+                                           @CallContextBinder CallContext context);
+
+    @SqlUpdate
+    void updatePaymentAttemptWithRetryInfo(@Bind("id") String paymentAttemptId,
+                                           @Bind("retry_count") int retryCount,
+                                           @CallContextBinder CallContext context);
+    
+    @Override
+    @SqlUpdate
+    public void insertHistoryFromTransaction(@PaymentAttemptHistoryBinder final EntityHistory<PaymentAttempt> account,
+                                            @CallContextBinder final CallContext context);
+
+    public static class PaymentAttemptMapper extends MapperBase implements ResultSetMapper<PaymentAttempt> {
+        @Override
+        public PaymentAttempt map(int index, ResultSet rs, StatementContext ctx) throws SQLException {
+
+            UUID paymentAttemptId = getUUID(rs, "id");
+            UUID invoiceId = getUUID(rs, "invoice_id");
+            UUID accountId = getUUID(rs, "account_id");
+            BigDecimal amount = rs.getBigDecimal("amount");
+            Currency currency = Currency.valueOf(rs.getString("currency"));
+            DateTime invoiceDate = getDate(rs, "invoice_date");
+            DateTime paymentAttemptDate = getDate(rs, "payment_attempt_date");
+            UUID paymentId = getUUID(rs, "payment_id");
+            Integer retryCount = rs.getInt("retry_count");
+            DateTime createdDate = getDate(rs, "created_date");
+            DateTime updatedDate = getDate(rs, "updated_date");
+
+            return new DefaultPaymentAttempt(paymentAttemptId,
+                                      invoiceId,
+                                      accountId,
+                                      amount,
+                                      currency,
+                                      invoiceDate,
+                                      paymentAttemptDate,
+                                      paymentId,
+                                      retryCount,
+                                      createdDate, updatedDate);
+        }
+    }
+
+    public static final class PaymentAttemptBinder extends BinderBase implements Binder<Bind, PaymentAttempt> {
+        @Override
+        public void bind(@SuppressWarnings("rawtypes") SQLStatement stmt, Bind bind, PaymentAttempt paymentAttempt) {
+            stmt.bind("id", paymentAttempt.getId().toString());
+            stmt.bind("invoiceId", paymentAttempt.getInvoiceId().toString());
+            stmt.bind("accountId", paymentAttempt.getAccountId().toString());
+            stmt.bind("amount", paymentAttempt.getAmount());
+            stmt.bind("currency", paymentAttempt.getCurrency().toString());
+            stmt.bind("invoiceDate", getDate(paymentAttempt.getInvoiceDate()));
+            stmt.bind("paymentAttemptDate", getDate(paymentAttempt.getPaymentAttemptDate()));
+            stmt.bind("paymentId", paymentAttempt.getPaymentId() == null ? null : paymentAttempt.getPaymentId().toString());
+            stmt.bind("retryCount", paymentAttempt.getRetryCount());
+        }
+    }
+}
diff --git a/payment/src/main/java/com/ning/billing/payment/dao/PaymentDao.java b/payment/src/main/java/com/ning/billing/payment/dao/PaymentDao.java
index ce6adf6..581ed71 100644
--- a/payment/src/main/java/com/ning/billing/payment/dao/PaymentDao.java
+++ b/payment/src/main/java/com/ning/billing/payment/dao/PaymentDao.java
@@ -21,7 +21,7 @@ import java.util.UUID;
 
 import com.ning.billing.invoice.api.Invoice;
 import com.ning.billing.payment.api.PaymentAttempt;
-import com.ning.billing.payment.api.PaymentInfo;
+import com.ning.billing.payment.api.PaymentInfoEvent;
 import com.ning.billing.util.callcontext.CallContext;
 
 public interface PaymentDao {
@@ -29,19 +29,21 @@ public interface PaymentDao {
     PaymentAttempt createPaymentAttempt(Invoice invoice, CallContext context);
     PaymentAttempt createPaymentAttempt(PaymentAttempt paymentAttempt, CallContext context);
 
-    void savePaymentInfo(PaymentInfo right, CallContext context);
+    void savePaymentInfo(PaymentInfoEvent right, CallContext context);
 
-    PaymentAttempt getPaymentAttemptForPaymentId(String paymentId);
+    PaymentAttempt getPaymentAttemptForPaymentId(UUID paymentId);
     List<PaymentAttempt> getPaymentAttemptsForInvoiceIds(List<String> invoiceIds);
 
-    void updatePaymentAttemptWithPaymentId(UUID paymentAttemptId, String paymentId, CallContext context);
+    void updatePaymentAttemptWithPaymentId(UUID paymentAttemptId, UUID paymentId, CallContext context);
 
     List<PaymentAttempt> getPaymentAttemptsForInvoiceId(String invoiceId);
 
-    void updatePaymentInfo(String paymentMethodType, String paymentId, String cardType, String cardCountry, CallContext context);
+    void updatePaymentInfo(String paymentMethodType, UUID paymentId, String cardType, String cardCountry, CallContext context);
 
-    List<PaymentInfo> getPaymentInfo(List<String> invoiceIds);
+    List<PaymentInfoEvent> getPaymentInfoList(List<String> invoiceIds);
+
+    PaymentInfoEvent getLastPaymentInfo(List<String> invoiceIds);
 
     PaymentAttempt getPaymentAttemptById(UUID paymentAttemptId);
-    PaymentInfo getPaymentInfoForPaymentAttemptId(String paymentAttemptId);
+    PaymentInfoEvent getPaymentInfoForPaymentAttemptId(String paymentAttemptId);
 }
diff --git a/payment/src/main/java/com/ning/billing/payment/dao/PaymentHistoryBinder.java b/payment/src/main/java/com/ning/billing/payment/dao/PaymentHistoryBinder.java
new file mode 100644
index 0000000..2ec9fc7
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/dao/PaymentHistoryBinder.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2010-2011 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.payment.dao;
+
+import com.ning.billing.payment.api.PaymentInfoEvent;
+import com.ning.billing.util.dao.BinderBase;
+import com.ning.billing.util.dao.EntityHistory;
+import org.skife.jdbi.v2.SQLStatement;
+import org.skife.jdbi.v2.sqlobject.Binder;
+import org.skife.jdbi.v2.sqlobject.BinderFactory;
+import org.skife.jdbi.v2.sqlobject.BindingAnnotation;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@BindingAnnotation(PaymentHistoryBinder.PaymentHistoryBinderFactory.class)
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.PARAMETER})
+public @interface PaymentHistoryBinder {
+    public static class PaymentHistoryBinderFactory extends BinderBase implements BinderFactory {
+        @Override
+        public Binder<PaymentHistoryBinder, EntityHistory<PaymentInfoEvent>> build(Annotation annotation) {
+            return new Binder<PaymentHistoryBinder, EntityHistory<PaymentInfoEvent>>() {
+                @Override
+                public void bind(SQLStatement q, PaymentHistoryBinder bind, EntityHistory<PaymentInfoEvent> history) {
+                    q.bind("recordId", history.getValue());
+                    q.bind("changeType", history.getChangeType().toString());
+
+                    PaymentInfoEvent paymentInfo = history.getEntity();
+                    q.bind("id", paymentInfo.getId().toString());
+                    q.bind("amount", paymentInfo.getAmount());
+                    q.bind("refund_amount", paymentInfo.getRefundAmount());
+                    q.bind("payment_number", paymentInfo.getPaymentNumber());
+                    q.bind("bank_identification_number", paymentInfo.getBankIdentificationNumber());
+                    q.bind("status", paymentInfo.getStatus());
+                    q.bind("payment_type", paymentInfo.getType());
+                    q.bind("reference_id", paymentInfo.getReferenceId());
+                    q.bind("payment_method_id", paymentInfo.getPaymentMethodId());
+                    q.bind("payment_method", paymentInfo.getPaymentMethod());
+                    q.bind("card_type", paymentInfo.getCardType());
+                    q.bind("card_country", paymentInfo.getCardCountry());
+                    q.bind("effective_date", getDate(paymentInfo.getEffectiveDate()));
+                }
+            };
+        }
+    }
+}
diff --git a/payment/src/main/java/com/ning/billing/payment/dao/PaymentSqlDao.java b/payment/src/main/java/com/ning/billing/payment/dao/PaymentSqlDao.java
index 96d7e0b..bdd9829 100644
--- a/payment/src/main/java/com/ning/billing/payment/dao/PaymentSqlDao.java
+++ b/payment/src/main/java/com/ning/billing/payment/dao/PaymentSqlDao.java
@@ -25,7 +25,9 @@ import java.util.UUID;
 import com.ning.billing.util.callcontext.CallContext;
 import com.ning.billing.util.callcontext.CallContextBinder;
 import com.ning.billing.util.dao.BinderBase;
+import com.ning.billing.util.dao.EntityHistory;
 import com.ning.billing.util.dao.MapperBase;
+import com.ning.billing.util.entity.dao.UpdatableEntitySqlDao;
 import org.joda.time.DateTime;
 import org.skife.jdbi.v2.SQLStatement;
 import org.skife.jdbi.v2.StatementContext;
@@ -36,125 +38,48 @@ import org.skife.jdbi.v2.sqlobject.SqlUpdate;
 import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
 import org.skife.jdbi.v2.sqlobject.mixins.CloseMe;
 import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
-import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
 import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
 import org.skife.jdbi.v2.tweak.ResultSetMapper;
 import org.skife.jdbi.v2.unstable.BindIn;
 
-import com.ning.billing.catalog.api.Currency;
-import com.ning.billing.payment.api.PaymentAttempt;
-import com.ning.billing.payment.api.PaymentInfo;
+import com.ning.billing.payment.api.DefaultPaymentInfoEvent;
+import com.ning.billing.payment.api.PaymentInfoEvent;
 
 @ExternalizedSqlViaStringTemplate3()
-@RegisterMapper({PaymentSqlDao.PaymentAttemptMapper.class, PaymentSqlDao.PaymentInfoMapper.class})
-public interface PaymentSqlDao extends Transactional<PaymentSqlDao>, CloseMe, Transmogrifier {
-    @SqlUpdate
-    void insertPaymentAttempt(@Bind(binder = PaymentAttemptBinder.class) PaymentAttempt paymentAttempt,
-                              @CallContextBinder CallContext context);
-
-    @SqlUpdate
-    void insertPaymentAttemptHistory(@Bind("historyRecordId") final String historyRecordId,
-                                     @Bind(binder = PaymentAttemptBinder.class) final PaymentAttempt paymentAttempt,
-                                     @CallContextBinder final CallContext context);
-
-    @SqlQuery
-    PaymentAttempt getPaymentAttemptForPaymentId(@Bind("payment_id") String paymentId);
-
-    @SqlQuery
-    PaymentAttempt getPaymentAttemptById(@Bind("payment_attempt_id") String paymentAttemptId);
-
+@RegisterMapper(PaymentSqlDao.PaymentInfoMapper.class)
+public interface PaymentSqlDao extends Transactional<PaymentSqlDao>, UpdatableEntitySqlDao<PaymentInfoEvent>, CloseMe {
     @SqlQuery
-    List<PaymentAttempt> getPaymentAttemptsForInvoiceId(@Bind("invoice_id") String invoiceId);
-
-    @SqlQuery
-    List<PaymentAttempt> getPaymentAttemptsForInvoiceIds(@BindIn("invoiceIds") List<String> invoiceIds);
-
-    @SqlQuery
-    PaymentInfo getPaymentInfoForPaymentAttemptId(@Bind("payment_attempt_id") String paymentAttemptId);
-
-    @SqlUpdate
-    void updatePaymentAttemptWithPaymentId(@Bind("payment_attempt_id") String paymentAttemptId,
-                                           @Bind("payment_id") String paymentId,
-                                           @CallContextBinder CallContext context);
-
-    @SqlUpdate
-    void updatePaymentAttemptWithRetryInfo(@Bind("payment_attempt_id") String paymentAttemptId,
-                                           @Bind("retry_count") int retryCount,
-                                           @CallContextBinder CallContext context);
+    PaymentInfoEvent getPaymentInfoForPaymentAttemptId(@Bind("payment_attempt_id") String paymentAttemptId);
 
     @SqlUpdate
     void updatePaymentInfo(@Bind("payment_method") String paymentMethod,
-                           @Bind("payment_id") String paymentId,
+                           @Bind("id") String paymentId,
                            @Bind("card_type") String cardType,
                            @Bind("card_country") String cardCountry,
                            @CallContextBinder CallContext context);
 
     @SqlQuery
-    List<PaymentInfo> getPaymentInfos(@BindIn("invoiceIds") final List<String> invoiceIds);
+    List<PaymentInfoEvent> getPaymentInfoList(@BindIn("invoiceIds") final List<String> invoiceIds);
 
-    @SqlUpdate
-    void insertPaymentInfo(@Bind(binder = PaymentInfoBinder.class) final PaymentInfo paymentInfo,
-                           @CallContextBinder final CallContext context);
+    @SqlQuery
+    PaymentInfoEvent getLastPaymentInfo(@BindIn("invoiceIds") final List<String> invoiceIds);
 
     @SqlUpdate
-    void insertPaymentInfoHistory(@Bind("historyRecordId") final String historyRecordId,
-                                  @Bind(binder = PaymentInfoBinder.class) final PaymentInfo paymentInfo,
-                                  @CallContextBinder final CallContext context);
+    void insertPaymentInfo(@Bind(binder = PaymentInfoBinder.class) final PaymentInfoEvent paymentInfo,
+                           @CallContextBinder final CallContext context);
 
     @SqlQuery
-    PaymentInfo getPaymentInfo(@Bind("paymentId") final String paymentId);
-
-    public static final class PaymentAttemptBinder extends BinderBase implements Binder<Bind, PaymentAttempt> {
-        @Override
-        public void bind(@SuppressWarnings("rawtypes") SQLStatement stmt, Bind bind, PaymentAttempt paymentAttempt) {
-            stmt.bind("payment_attempt_id", paymentAttempt.getPaymentAttemptId().toString());
-            stmt.bind("invoice_id", paymentAttempt.getInvoiceId().toString());
-            stmt.bind("account_id", paymentAttempt.getAccountId().toString());
-            stmt.bind("amount", paymentAttempt.getAmount());
-            stmt.bind("currency", paymentAttempt.getCurrency().toString());
-            stmt.bind("invoice_dt", getDate(paymentAttempt.getInvoiceDate()));
-            stmt.bind("payment_attempt_dt", getDate(paymentAttempt.getPaymentAttemptDate()));
-            stmt.bind("payment_id", paymentAttempt.getPaymentId());
-            stmt.bind("retry_count", paymentAttempt.getRetryCount());
-            stmt.bind("created_dt", getDate(paymentAttempt.getCreatedDate()));
-            stmt.bind("updated_dt", getDate(paymentAttempt.getUpdatedDate()));
-        }
-    }
+    PaymentInfoEvent getPaymentInfo(@Bind("id") final String paymentId);
 
-    public static class PaymentAttemptMapper extends MapperBase implements ResultSetMapper<PaymentAttempt> {
-        @Override
-        public PaymentAttempt map(int index, ResultSet rs, StatementContext ctx) throws SQLException {
-
-            UUID paymentAttemptId = UUID.fromString(rs.getString("payment_attempt_id"));
-            UUID invoiceId = UUID.fromString(rs.getString("invoice_id"));
-            UUID accountId = UUID.fromString(rs.getString("account_id"));
-            BigDecimal amount = rs.getBigDecimal("amount");
-            Currency currency = Currency.valueOf(rs.getString("currency"));
-            DateTime invoiceDate = getDate(rs, "invoice_dt");
-            DateTime paymentAttemptDate = getDate(rs, "payment_attempt_dt");
-            String paymentId = rs.getString("payment_id");
-            Integer retryCount = rs.getInt("retry_count");
-            DateTime createdDate = getDate(rs, "created_dt");
-            DateTime updatedDate = getDate(rs, "updated_dt");
-
-            return new PaymentAttempt(paymentAttemptId,
-                                      invoiceId,
-                                      accountId,
-                                      amount,
-                                      currency,
-                                      invoiceDate,
-                                      paymentAttemptDate,
-                                      paymentId,
-                                      retryCount,
-                                      createdDate,
-                                      updatedDate);
-        }
-    }
+    @Override
+    @SqlUpdate
+    public void insertHistoryFromTransaction(@PaymentHistoryBinder final EntityHistory<PaymentInfoEvent> account,
+                                            @CallContextBinder final CallContext context);
 
-    public static final class PaymentInfoBinder extends BinderBase implements Binder<Bind, PaymentInfo> {
+    public static final class PaymentInfoBinder extends BinderBase implements Binder<Bind, PaymentInfoEvent> {
         @Override
-        public void bind(@SuppressWarnings("rawtypes") SQLStatement stmt, Bind bind, PaymentInfo paymentInfo) {
-            stmt.bind("payment_id", paymentInfo.getPaymentId().toString());
+        public void bind(@SuppressWarnings("rawtypes") SQLStatement stmt, Bind bind, PaymentInfoEvent paymentInfo) {
+            stmt.bind("id", paymentInfo.getId().toString());
             stmt.bind("amount", paymentInfo.getAmount());
             stmt.bind("refund_amount", paymentInfo.getRefundAmount());
             stmt.bind("payment_number", paymentInfo.getPaymentNumber());
@@ -166,17 +91,14 @@ public interface PaymentSqlDao extends Transactional<PaymentSqlDao>, CloseMe, Tr
             stmt.bind("payment_method", paymentInfo.getPaymentMethod());
             stmt.bind("card_type", paymentInfo.getCardType());
             stmt.bind("card_country", paymentInfo.getCardCountry());
-            stmt.bind("effective_dt", getDate(paymentInfo.getEffectiveDate()));
-            stmt.bind("created_dt", getDate(paymentInfo.getCreatedDate()));
-            stmt.bind("updated_dt", getDate(paymentInfo.getUpdatedDate()));
+            stmt.bind("effective_date", getDate(paymentInfo.getEffectiveDate()));
         }
     }
 
-    public static class PaymentInfoMapper extends MapperBase implements ResultSetMapper<PaymentInfo> {
+    public static class PaymentInfoMapper extends MapperBase implements ResultSetMapper<PaymentInfoEvent> {
         @Override
-        public PaymentInfo map(int index, ResultSet rs, StatementContext ctx) throws SQLException {
-
-            String paymentId = rs.getString("payment_id");
+        public PaymentInfoEvent map(int index, ResultSet rs, StatementContext ctx) throws SQLException {
+            UUID id = getUUID(rs, "id");
             BigDecimal amount = rs.getBigDecimal("amount");
             BigDecimal refundAmount = rs.getBigDecimal("refund_amount");
             String paymentNumber = rs.getString("payment_number");
@@ -187,12 +109,12 @@ public interface PaymentSqlDao extends Transactional<PaymentSqlDao>, CloseMe, Tr
             String paymentMethodId = rs.getString("payment_method_id");
             String paymentMethod = rs.getString("payment_method");
             String cardType = rs.getString("card_type");
-            String cardCountry = rs.getString("card_country");
-            DateTime effectiveDate = getDate(rs, "effective_dt");
-            DateTime createdDate = getDate(rs, "created_dt");
-            DateTime updatedDate = getDate(rs, "updated_dt");
+            String cardCountry = rs.getString("card_country");            
+            DateTime effectiveDate = getDate(rs, "effective_date");
 
-            return new PaymentInfo(paymentId,
+            UUID userToken = null; //rs.getString("user_token") != null ? UUID.fromString(rs.getString("user_token")) : null;
+            
+            return new DefaultPaymentInfoEvent(id,
                                    amount,
                                    refundAmount,
                                    bankIdentificationNumber,
@@ -204,9 +126,8 @@ public interface PaymentSqlDao extends Transactional<PaymentSqlDao>, CloseMe, Tr
                                    paymentMethod,
                                    cardType,
                                    cardCountry,
-                                   effectiveDate,
-                                   createdDate,
-                                   updatedDate);
+                                   userToken,
+                                   effectiveDate);
         }
     }
 
diff --git a/payment/src/main/java/com/ning/billing/payment/provider/NoOpPaymentProviderPlugin.java b/payment/src/main/java/com/ning/billing/payment/provider/NoOpPaymentProviderPlugin.java
index 280d1e0..94a97db 100644
--- a/payment/src/main/java/com/ning/billing/payment/provider/NoOpPaymentProviderPlugin.java
+++ b/payment/src/main/java/com/ning/billing/payment/provider/NoOpPaymentProviderPlugin.java
@@ -25,21 +25,22 @@ import org.joda.time.DateTimeZone;
 
 import com.ning.billing.account.api.Account;
 import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.payment.api.DefaultPaymentErrorEvent;
+import com.ning.billing.payment.api.DefaultPaymentInfoEvent;
 import com.ning.billing.payment.api.Either;
-import com.ning.billing.payment.api.PaymentError;
-import com.ning.billing.payment.api.PaymentInfo;
+import com.ning.billing.payment.api.PaymentErrorEvent;
+import com.ning.billing.payment.api.PaymentInfoEvent;
 import com.ning.billing.payment.api.PaymentMethodInfo;
 import com.ning.billing.payment.api.PaymentProviderAccount;
 
 public class NoOpPaymentProviderPlugin implements PaymentProviderPlugin {
 
     @Override
-    public Either<PaymentError, PaymentInfo> processInvoice(Account account, Invoice invoice) {
-        PaymentInfo payment = new PaymentInfo.Builder()
-                                             .setPaymentId(UUID.randomUUID().toString())
+    public Either<PaymentErrorEvent, PaymentInfoEvent> processInvoice(Account account, Invoice invoice) {
+        PaymentInfoEvent payment = new DefaultPaymentInfoEvent.Builder()
+                                             .setId(UUID.randomUUID())
                                              .setAmount(invoice.getBalance())
                                              .setStatus("Processed")
-                                             .setCreatedDate(new DateTime(DateTimeZone.UTC))
                                              .setEffectiveDate(new DateTime(DateTimeZone.UTC))
                                              .setType("Electronic")
                                              .build();
@@ -47,25 +48,25 @@ public class NoOpPaymentProviderPlugin implements PaymentProviderPlugin {
     }
 
     @Override
-    public Either<PaymentError, PaymentInfo> getPaymentInfo(String paymentId) {
+    public Either<PaymentErrorEvent, PaymentInfoEvent> getPaymentInfo(String paymentId) {
         return Either.right(null);
     }
 
     @Override
-    public Either<PaymentError, String> createPaymentProviderAccount(Account account) {
-        return Either.left(new PaymentError("unsupported",
-                                            "Account creation not supported in this plugin",
+    public Either<PaymentErrorEvent, String> createPaymentProviderAccount(Account account) {
+        return Either.left((PaymentErrorEvent) new DefaultPaymentErrorEvent("unsupported",
+                                            "ACCOUNT creation not supported in this plugin",
                                             account.getId(),
-                                            null));
+                                            null, null));
     }
 
     @Override
-    public Either<PaymentError, PaymentProviderAccount> getPaymentProviderAccount(String accountKey) {
+    public Either<PaymentErrorEvent, PaymentProviderAccount> getPaymentProviderAccount(String accountKey) {
         return Either.right(null);
     }
 
     @Override
-    public Either<PaymentError, String> addPaymentMethod(String accountKey, PaymentMethodInfo paymentMethod) {
+    public Either<PaymentErrorEvent, String> addPaymentMethod(String accountKey, PaymentMethodInfo paymentMethod) {
         return Either.right(null);
     }
 
@@ -74,38 +75,44 @@ public class NoOpPaymentProviderPlugin implements PaymentProviderPlugin {
     }
 
     @Override
-    public Either<PaymentError, PaymentMethodInfo> updatePaymentMethod(String accountKey, PaymentMethodInfo paymentMethod) {
+    public Either<PaymentErrorEvent, PaymentMethodInfo> updatePaymentMethod(String accountKey, PaymentMethodInfo paymentMethod) {
         return Either.right(paymentMethod);
     }
 
     @Override
-    public Either<PaymentError, Void> deletePaymentMethod(String accountKey, String paymentMethodId) {
+    public Either<PaymentErrorEvent, Void> deletePaymentMethod(String accountKey, String paymentMethodId) {
         return Either.right(null);
     }
 
     @Override
-    public Either<PaymentError, PaymentMethodInfo> getPaymentMethodInfo(String paymentMethodId) {
+    public Either<PaymentErrorEvent, PaymentMethodInfo> getPaymentMethodInfo(String paymentMethodId) {
         return Either.right(null);
     }
 
     @Override
-    public Either<PaymentError, List<PaymentMethodInfo>> getPaymentMethods(final String accountKey) {
+    public Either<PaymentErrorEvent, List<PaymentMethodInfo>> getPaymentMethods(final String accountKey) {
         return Either.right(Arrays.<PaymentMethodInfo>asList());
     }
 
     @Override
-    public Either<PaymentError, Void> updatePaymentGateway(String accountKey) {
+    public Either<PaymentErrorEvent, Void> updatePaymentGateway(String accountKey) {
         return Either.right(null);
     }
 
     @Override
-    public Either<PaymentError, Void> updatePaymentProviderAccountExistingContact(Account account) {
+    public Either<PaymentErrorEvent, Void> updatePaymentProviderAccountExistingContact(Account account) {
         return Either.right(null);
     }
 
     @Override
-    public Either<PaymentError, Void> updatePaymentProviderAccountWithNewContact(Account account) {
+    public Either<PaymentErrorEvent, Void> updatePaymentProviderAccountWithNewContact(Account account) {
         return Either.right(null);
     }
 
+    @Override
+    public List<Either<PaymentErrorEvent, PaymentInfoEvent>> processRefund(Account account) {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
 }
diff --git a/payment/src/main/java/com/ning/billing/payment/provider/PaymentProviderPlugin.java b/payment/src/main/java/com/ning/billing/payment/provider/PaymentProviderPlugin.java
index 7a9ae71..2daa160 100644
--- a/payment/src/main/java/com/ning/billing/payment/provider/PaymentProviderPlugin.java
+++ b/payment/src/main/java/com/ning/billing/payment/provider/PaymentProviderPlugin.java
@@ -21,26 +21,27 @@ import java.util.List;
 import com.ning.billing.account.api.Account;
 import com.ning.billing.invoice.api.Invoice;
 import com.ning.billing.payment.api.Either;
-import com.ning.billing.payment.api.PaymentError;
-import com.ning.billing.payment.api.PaymentInfo;
+import com.ning.billing.payment.api.PaymentErrorEvent;
+import com.ning.billing.payment.api.PaymentInfoEvent;
 import com.ning.billing.payment.api.PaymentMethodInfo;
 import com.ning.billing.payment.api.PaymentProviderAccount;
 
 public interface PaymentProviderPlugin {
-    Either<PaymentError, PaymentInfo> processInvoice(Account account, Invoice invoice);
-    Either<PaymentError, String> createPaymentProviderAccount(Account account);
+    Either<PaymentErrorEvent, PaymentInfoEvent> processInvoice(Account account, Invoice invoice);
+    Either<PaymentErrorEvent, String> createPaymentProviderAccount(Account account);
 
-    Either<PaymentError, PaymentInfo> getPaymentInfo(String paymentId);
-    Either<PaymentError, PaymentProviderAccount> getPaymentProviderAccount(String accountKey);
-    Either<PaymentError, Void> updatePaymentGateway(String accountKey);
+    Either<PaymentErrorEvent, PaymentInfoEvent> getPaymentInfo(String paymentId);
+    Either<PaymentErrorEvent, PaymentProviderAccount> getPaymentProviderAccount(String accountKey);
+    Either<PaymentErrorEvent, Void> updatePaymentGateway(String accountKey);
 
-    Either<PaymentError, PaymentMethodInfo> getPaymentMethodInfo(String paymentMethodId);
-    Either<PaymentError, List<PaymentMethodInfo>> getPaymentMethods(String accountKey);
-    Either<PaymentError, String> addPaymentMethod(String accountKey, PaymentMethodInfo paymentMethod);
-    Either<PaymentError, PaymentMethodInfo> updatePaymentMethod(String accountKey, PaymentMethodInfo paymentMethodInfo);
-    Either<PaymentError, Void> deletePaymentMethod(String accountKey, String paymentMethodId);
+    Either<PaymentErrorEvent, PaymentMethodInfo> getPaymentMethodInfo(String paymentMethodId);
+    Either<PaymentErrorEvent, List<PaymentMethodInfo>> getPaymentMethods(String accountKey);
+    Either<PaymentErrorEvent, String> addPaymentMethod(String accountKey, PaymentMethodInfo paymentMethod);
+    Either<PaymentErrorEvent, PaymentMethodInfo> updatePaymentMethod(String accountKey, PaymentMethodInfo paymentMethodInfo);
+    Either<PaymentErrorEvent, Void> deletePaymentMethod(String accountKey, String paymentMethodId);
 
-    Either<PaymentError, Void> updatePaymentProviderAccountExistingContact(Account account);
-    Either<PaymentError, Void> updatePaymentProviderAccountWithNewContact(Account account);
 
+    Either<PaymentErrorEvent, Void> updatePaymentProviderAccountExistingContact(Account account);
+    Either<PaymentErrorEvent, Void> updatePaymentProviderAccountWithNewContact(Account account);
+    List<Either<PaymentErrorEvent, PaymentInfoEvent>> processRefund(Account account);
 }
diff --git a/payment/src/main/java/com/ning/billing/payment/provider/PaymentProviderPluginRegistry.java b/payment/src/main/java/com/ning/billing/payment/provider/PaymentProviderPluginRegistry.java
index fc9c149..bdad7c6 100644
--- a/payment/src/main/java/com/ning/billing/payment/provider/PaymentProviderPluginRegistry.java
+++ b/payment/src/main/java/com/ning/billing/payment/provider/PaymentProviderPluginRegistry.java
@@ -22,7 +22,8 @@ import java.util.concurrent.ConcurrentHashMap;
 import org.apache.commons.lang.StringUtils;
 
 import com.google.inject.Inject;
-import com.ning.billing.payment.setup.PaymentConfig;
+import com.ning.billing.config.PaymentConfig;
+
 
 public class PaymentProviderPluginRegistry {
     private final String defaultPlugin;
diff --git a/payment/src/main/java/com/ning/billing/payment/RequestProcessor.java b/payment/src/main/java/com/ning/billing/payment/RequestProcessor.java
index e8034a1..c7c359d 100644
--- a/payment/src/main/java/com/ning/billing/payment/RequestProcessor.java
+++ b/payment/src/main/java/com/ning/billing/payment/RequestProcessor.java
@@ -18,27 +18,34 @@ package com.ning.billing.payment;
 
 import java.util.Arrays;
 import java.util.List;
+import java.util.UUID;
 
-import com.ning.billing.util.callcontext.CallContext;
-import com.ning.billing.util.callcontext.CallOrigin;
-import com.ning.billing.util.callcontext.DefaultCallContext;
-import com.ning.billing.util.callcontext.UserType;
-import com.ning.billing.util.clock.Clock;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.eventbus.EventBus;
 import com.google.common.eventbus.Subscribe;
 import com.google.inject.Inject;
 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.invoice.api.InvoiceCreationNotification;
+import com.ning.billing.invoice.api.InvoiceCreationEvent;
+import com.ning.billing.payment.api.DefaultPaymentErrorEvent;
 import com.ning.billing.payment.api.Either;
 import com.ning.billing.payment.api.PaymentApi;
-import com.ning.billing.payment.api.PaymentError;
-import com.ning.billing.payment.api.PaymentInfo;
+import com.ning.billing.payment.api.PaymentApiException;
+import com.ning.billing.payment.api.PaymentErrorEvent;
+import com.ning.billing.payment.api.PaymentInfoEvent;
 import com.ning.billing.payment.provider.PaymentProviderPluginRegistry;
 import com.ning.billing.util.bus.Bus;
 import com.ning.billing.util.bus.Bus.EventBusException;
+import com.ning.billing.util.bus.BusEvent.BusEventType;
+import com.ning.billing.util.bus.BusEvent;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.callcontext.CallOrigin;
+import com.ning.billing.util.callcontext.DefaultCallContext;
+import com.ning.billing.util.callcontext.UserType;
+import com.ning.billing.util.clock.Clock;
 
 public class RequestProcessor {
     public static final String PAYMENT_PROVIDER_KEY = "paymentProvider";
@@ -51,36 +58,49 @@ public class RequestProcessor {
 
     @Inject
     public RequestProcessor(Clock clock,
-                            AccountUserApi accountUserApi,
-                            PaymentApi paymentApi,
-                            PaymentProviderPluginRegistry pluginRegistry,
-                            Bus eventBus) {
+            AccountUserApi accountUserApi,
+            PaymentApi paymentApi,
+            PaymentProviderPluginRegistry pluginRegistry,
+            Bus eventBus) {
         this.clock = clock;
         this.accountUserApi = accountUserApi;
         this.paymentApi = paymentApi;
         this.eventBus = eventBus;
     }
+    
+    private void postPaymentEvent(BusEvent ev, UUID accountId) {
+        if (ev == null) {
+            return;
+        }
+        try {
+            eventBus.post(ev);
+        } catch (EventBusException e) {
+            log.error("Failed to post Payment event event for account {} ", accountId, e);
+        }
+    }
 
     @Subscribe
-    public void receiveInvoice(InvoiceCreationNotification event) {
+    public void receiveInvoice(InvoiceCreationEvent event) {
         log.info("Received invoice creation notification for account {} and invoice {}", event.getAccountId(), event.getInvoiceId());
+        PaymentErrorEvent errorEvent = null;
         try {
             final Account account = accountUserApi.getAccountById(event.getAccountId());
-
-            if (account == null) {
-                log.info("could not process invoice payment: could not find a valid account for event {}", event);
-            }
-            else {
+            if (account != null) {
                 CallContext context = new DefaultCallContext("PaymentRequestProcessor", CallOrigin.INTERNAL, UserType.SYSTEM, clock);
-                List<Either<PaymentError, PaymentInfo>> results = paymentApi.createPayment(account, Arrays.asList(event.getInvoiceId().toString()), context);
-                if (!results.isEmpty()) {
-                    Either<PaymentError, PaymentInfo> result = results.get(0);
-                    eventBus.post(result.isLeft() ? result.getLeft() : result.getRight());
-                }
+                List<PaymentInfoEvent> results = paymentApi.createPayment(account, Arrays.asList(event.getInvoiceId().toString()), context);
+                PaymentInfoEvent infoEvent = (!results.isEmpty()) ?  results.get(0) : null;
+                postPaymentEvent(infoEvent, account.getId());
+                return;
+            } else {
+                errorEvent = new DefaultPaymentErrorEvent(null, "Failed to retrieve account", event.getAccountId(), null, null);
             }
+        } catch(AccountApiException e) {
+            log.error("Failed to process invoice payment", e);
+            errorEvent = new DefaultPaymentErrorEvent(null, e.getMessage(), event.getAccountId(), null, null);            
+        } catch (PaymentApiException e) {
+            log.error("Failed to process invoice payment", e);
+            errorEvent = new DefaultPaymentErrorEvent(null, e.getMessage(), event.getAccountId(), null, null);                        
         }
-        catch (EventBusException ex) {
-            throw new RuntimeException(ex);
-        }
+        postPaymentEvent(errorEvent, event.getAccountId());
     }
 }
diff --git a/payment/src/main/java/com/ning/billing/payment/RetryService.java b/payment/src/main/java/com/ning/billing/payment/RetryService.java
index d03b241..2563638 100644
--- a/payment/src/main/java/com/ning/billing/payment/RetryService.java
+++ b/payment/src/main/java/com/ning/billing/payment/RetryService.java
@@ -24,16 +24,20 @@ import com.ning.billing.util.callcontext.DefaultCallContext;
 import com.ning.billing.util.callcontext.UserType;
 import com.ning.billing.util.clock.Clock;
 import org.joda.time.DateTime;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import com.google.inject.Inject;
+import com.ning.billing.config.PaymentConfig;
 import com.ning.billing.lifecycle.KillbillService;
 import com.ning.billing.lifecycle.LifecycleHandlerType;
 import com.ning.billing.lifecycle.LifecycleHandlerType.LifecycleLevel;
 import com.ning.billing.payment.api.PaymentApi;
+import com.ning.billing.payment.api.PaymentApiException;
 import com.ning.billing.payment.api.PaymentAttempt;
-import com.ning.billing.payment.api.PaymentInfo;
+import com.ning.billing.payment.api.PaymentInfoEvent;
 import com.ning.billing.payment.api.PaymentStatus;
-import com.ning.billing.payment.setup.PaymentConfig;
+
 import com.ning.billing.util.notificationq.NotificationKey;
 import com.ning.billing.util.notificationq.NotificationQueue;
 import com.ning.billing.util.notificationq.NotificationQueueService;
@@ -41,6 +45,9 @@ import com.ning.billing.util.notificationq.NotificationQueueService.Notification
 import com.ning.billing.util.notificationq.NotificationQueueService.NotificationQueueHandler;
 
 public class RetryService implements KillbillService {
+    
+    private static final Logger log = LoggerFactory.getLogger(RetryService.class);
+    
     public static final String SERVICE_NAME = "retry-service";
     public static final String QUEUE_NAME = "retry-events";
 
@@ -91,7 +98,7 @@ public class RetryService implements KillbillService {
     }
 
     public void scheduleRetry(PaymentAttempt paymentAttempt, DateTime timeOfRetry) {
-        final String id = paymentAttempt.getPaymentAttemptId().toString();
+        final String id = paymentAttempt.getId().toString();
 
         NotificationKey key = new NotificationKey() {
             @Override
@@ -99,19 +106,21 @@ public class RetryService implements KillbillService {
                 return id;
             }
         };
-        retryQueue.recordFutureNotification(timeOfRetry, key);
+
+        if (retryQueue != null) {
+            retryQueue.recordFutureNotification(timeOfRetry, key);
+        }
     }
 
     private void retry(String paymentAttemptId, CallContext context) {
-        PaymentInfo paymentInfo = paymentApi.getPaymentInfoForPaymentAttemptId(paymentAttemptId);
-
-        if (paymentInfo != null && PaymentStatus.Processed.equals(PaymentStatus.valueOf(paymentInfo.getStatus()))) {
-            // update payment attempt with success and notify invoice api of payment
-            System.out.println("Found processed payment");
-        }
-        else {
-            System.out.println("Creating payment for payment attempt " + paymentAttemptId);
+        try {
+            PaymentInfoEvent paymentInfo = paymentApi.getPaymentInfoForPaymentAttemptId(paymentAttemptId);
+            if (paymentInfo != null && PaymentStatus.Processed.equals(PaymentStatus.valueOf(paymentInfo.getStatus()))) {
+                return;
+            }
             paymentApi.createPaymentForPaymentAttempt(UUID.fromString(paymentAttemptId), context);
+        } catch (PaymentApiException e) {
+            log.error(String.format("Failed to retry payment for %s"), e);
         }
     }
 }
diff --git a/payment/src/main/java/com/ning/billing/payment/setup/PaymentModule.java b/payment/src/main/java/com/ning/billing/payment/setup/PaymentModule.java
index 0c769fd..de4bf3c 100644
--- a/payment/src/main/java/com/ning/billing/payment/setup/PaymentModule.java
+++ b/payment/src/main/java/com/ning/billing/payment/setup/PaymentModule.java
@@ -21,6 +21,7 @@ import java.util.Properties;
 import org.skife.config.ConfigurationObjectFactory;
 
 import com.google.inject.AbstractModule;
+import com.ning.billing.config.PaymentConfig;
 import com.ning.billing.payment.RequestProcessor;
 import com.ning.billing.payment.RetryService;
 import com.ning.billing.payment.api.DefaultPaymentApi;
diff --git a/payment/src/main/resources/com/ning/billing/payment/dao/PaymentAttemptSqlDao.sql.stg b/payment/src/main/resources/com/ning/billing/payment/dao/PaymentAttemptSqlDao.sql.stg
new file mode 100644
index 0000000..15302fe
--- /dev/null
+++ b/payment/src/main/resources/com/ning/billing/payment/dao/PaymentAttemptSqlDao.sql.stg
@@ -0,0 +1,106 @@
+group paymentAttemptSqlDao;
+
+paymentAttemptFields(prefix) ::= <<
+    <prefix>id,
+    <prefix>invoice_id,
+    <prefix>account_id,
+    <prefix>amount,
+    <prefix>currency,
+    <prefix>payment_id,
+    <prefix>payment_attempt_date,
+    <prefix>invoice_date,
+    <prefix>retry_count,
+    <prefix>created_by,
+    <prefix>created_date,
+    <prefix>updated_by,
+    <prefix>updated_date
+>>
+
+insertPaymentAttempt() ::= <<
+    INSERT INTO payment_attempts (<paymentAttemptFields()>)
+    VALUES (:id, :invoiceId, :accountId, :amount, :currency, :paymentId,
+            :paymentAttemptDate, :invoiceDate, :retryCount, :userName, :createdDate, :userName, :createdDate);
+>>
+
+getPaymentAttemptForPaymentId() ::= <<
+    SELECT <paymentAttemptFields()>
+      FROM payment_attempts
+     WHERE payment_id = :paymentId;
+>>
+
+getPaymentAttemptById() ::= <<
+    SELECT <paymentAttemptFields()>
+      FROM payment_attempts
+     WHERE id = :id;
+>>
+
+getPaymentAttemptsForInvoiceIds(invoiceIds) ::= <<
+    SELECT <paymentAttemptFields()>
+      FROM payment_attempts
+     WHERE invoice_id in (<invoiceIds>);
+>>
+
+getPaymentAttemptsForInvoiceId() ::= <<
+    SELECT <paymentAttemptFields()>
+      FROM payment_attempts
+     WHERE invoice_id = :invoiceId;
+>>
+
+updatePaymentAttemptWithPaymentId() ::= <<
+    UPDATE payment_attempts
+       SET payment_id = :payment_id,
+           updated_by = :userName,
+           updated_date = :updatedDate
+     WHERE id = :id;
+>>
+
+historyFields(prefix) ::= <<
+    record_id,
+    id,
+    account_id,
+    invoice_id,
+    amount,
+    currency,
+    payment_attempt_date,
+    payment_id,
+    retry_count,
+    invoice_date,
+    created_by,
+    created_date,
+    updated_by,
+    updated_date
+>>
+
+insertHistoryFromTransaction() ::= <<
+    INSERT INTO payment_attempt_history (<historyFields()>)
+    VALUES (:recordId, :id, :accountId, :invoiceId, :amount, :currency, :paymentAttemptDate, :paymentId,
+            :retryCount, :invoiceDate, :userName, :createdDate, :userName, :updatedDate);
+>>
+
+getRecordId() ::= <<
+    SELECT record_id
+    FROM payment_attempts
+    WHERE id = :id;
+>>
+
+getHistoryRecordId() ::= <<
+    SELECT MAX(history_record_id)
+    FROM payment_attempt_history
+    WHERE record_id = :recordId;
+>>
+
+auditFields(prefix) ::= <<
+    <prefix>table_name,
+    <prefix>record_id,
+    <prefix>change_type,
+    <prefix>change_date,
+    <prefix>changed_by,
+    <prefix>reason_code,
+    <prefix>comments,
+    <prefix>user_token
+>>
+
+insertAuditFromTransaction() ::= <<
+    INSERT INTO audit_log(<auditFields()>)
+    VALUES(:tableName, :recordId, :changeType, :createdDate, :userName, NULL, NULL, NULL);
+>>
diff --git a/payment/src/main/resources/com/ning/billing/payment/dao/PaymentSqlDao.sql.stg b/payment/src/main/resources/com/ning/billing/payment/dao/PaymentSqlDao.sql.stg
index 87c8d8f..3f125ab 100644
--- a/payment/src/main/resources/com/ning/billing/payment/dao/PaymentSqlDao.sql.stg
+++ b/payment/src/main/resources/com/ning/billing/payment/dao/PaymentSqlDao.sql.stg
@@ -1,23 +1,7 @@
 group PaymentSqlDao;
 
-paymentAttemptFields(prefix) ::= <<
-    <prefix>payment_attempt_id,
-    <prefix>invoice_id,
-    <prefix>account_id,
-    <prefix>amount,
-    <prefix>currency,
-    <prefix>payment_id,
-    <prefix>payment_attempt_dt,
-    <prefix>invoice_dt,
-    <prefix>retry_count,
-    <prefix>created_by,
-    <prefix>created_dt,
-    <prefix>updated_by,
-    <prefix>updated_dt
->>
-
 paymentInfoFields(prefix) ::= <<
-    <prefix>payment_id,
+    <prefix>id,
     <prefix>amount,
     <prefix>refund_amount,
     <prefix>bank_identification_number,
@@ -29,69 +13,18 @@ paymentInfoFields(prefix) ::= <<
     <prefix>payment_method,
     <prefix>card_type,
     <prefix>card_country,
-    <prefix>effective_dt,
+    <prefix>effective_date,
     <prefix>created_by,
-    <prefix>created_dt,
+    <prefix>created_date,
     <prefix>updated_by,
-    <prefix>updated_dt
->>
-
-insertPaymentAttempt() ::= <<
-    INSERT INTO payment_attempts (<paymentAttemptFields()>)
-    VALUES (:payment_attempt_id, :invoice_id, :account_id, :amount, :currency, :payment_id,
-            :payment_attempt_dt, :invoice_dt, :retry_count, :userName, :createdDate, :userName, :createdDate);
->>
-
-insertPaymentAttemptHistory() ::= <<
-    INSERT INTO payment_attempt_history (history_record_id, <paymentAttemptFields()>)
-    VALUES (:historyRecordId, :payment_attempt_id, :invoice_id, :account_id, :amount, :currency, :payment_id,
-            :payment_attempt_dt, :invoice_dt, :retry_count, :userName, :createdDate, :userName, :createdDate);
->>
-
-getPaymentAttemptForPaymentId() ::= <<
-    SELECT <paymentAttemptFields()>
-      FROM payment_attempts
-     WHERE payment_id = :payment_id
->>
-
-getPaymentAttemptById() ::= <<
-    SELECT <paymentAttemptFields()>
-      FROM payment_attempts
-     WHERE payment_attempt_id = :payment_attempt_id
->>
-
-getPaymentAttemptsForInvoiceIds(invoiceIds) ::= <<
-    SELECT <paymentAttemptFields()>
-      FROM payment_attempts
-     WHERE invoice_id in (<invoiceIds>)
->>
-
-getPaymentAttemptsForInvoiceId() ::= <<
-    SELECT <paymentAttemptFields()>
-      FROM payment_attempts
-     WHERE invoice_id = :invoice_id
->>
-
-updatePaymentAttemptWithPaymentId() ::= <<
-    UPDATE payment_attempts
-       SET payment_id = :payment_id,
-           updated_by = :userName,
-           updated_dt = :updatedDate
-     WHERE payment_attempt_id = :payment_attempt_id
+    <prefix>updated_date
 >>
 
 insertPaymentInfo() ::= <<
     INSERT INTO payments (<paymentInfoFields()>)
-    VALUES (:payment_id, :amount, :refund_amount, :bank_identification_number, :payment_number,
+    VALUES (:id, :amount, :refund_amount, :bank_identification_number, :payment_number,
     :payment_type, :status, :reference_id, :payment_method_id, :payment_method, :card_type,
-    :card_country, :effective_dt, :userName, :createdDate, :userName, :createdDate);
->>
-
-insertPaymentInfoHistory() ::= <<
-    INSERT INTO payment_history (history_record_id, <paymentInfoFields()>)
-    VALUES (:historyRecordId, :payment_id, :amount, :refund_amount, :bank_identification_number, :payment_number,
-    :payment_type, :status, :reference_id, :payment_method_id, :payment_method, :card_type,
-    :card_country, :effective_dt, :userName, :createdDate, :userName, :createdDate);
+    :card_country, :effective_date, :userName, :createdDate, :userName, :createdDate);
 >>
 
 updatePaymentInfo() ::= <<
@@ -100,26 +33,92 @@ updatePaymentInfo() ::= <<
            card_type = :card_type,
            card_country = :card_country,
            updated_by = :userName,
-           updated_dt = :updatedDate
-     WHERE payment_id = :payment_id
+           updated_date = :updatedDate
+     WHERE id = :id
 >>
 
-getPaymentInfos(invoiceIds) ::= <<
+getPaymentInfoList(invoiceIds) ::= <<
     SELECT <paymentInfoFields("p.")>
       FROM payments p, payment_attempts pa
-     WHERE pa.invoice_id in (<invoiceIds>)
-       AND pa.payment_id = p.payment_id
+    WHERE pa.invoice_id in (<invoiceIds>)
+       AND pa.payment_id = p.id
+>>
+
+getLastPaymentInfo(invoiceIds) ::= <<
+    SELECT <paymentInfoFields("p.")>
+    FROM payments p, payment_attempts pa
+    WHERE pa.invoice_id in (<invoiceIds>)
+    AND pa.payment_id = p.id
+    ORDER BY p.created_date DESC
+    LIMIT 1;
 >>
 
 getPaymentInfoForPaymentAttemptId() ::= <<
     SELECT <paymentInfoFields("p.")>
       FROM payments p, payment_attempts pa
-     WHERE pa.payment_attempt_id = :payment_attempt_id
-       AND pa.payment_id = p.payment_id
+    WHERE pa.payment_attempt_id = :payment_attempt_id
+       AND pa.payment_id = p.id
 >>
 
 getPaymentInfo() ::= <<
     SELECT <paymentInfoFields()>
     FROM payments
-    WHERE payment_id = :paymentId
+    WHERE id = :id
+>>
+
+historyFields(prefix) ::= <<
+    record_id,
+    id,
+    amount,
+    refund_amount,
+    payment_number,
+    bank_identification_number,
+    status,
+    reference_id,
+    payment_type,
+    payment_method_id,
+    payment_method,
+    card_type,
+    card_country,
+    effective_date,
+    created_by,
+    created_date,
+    updated_by,
+    updated_date
+>>
+
+insertHistoryFromTransaction() ::= <<
+    INSERT INTO payment_history (<historyFields()>)
+    VALUES (:recordId, :id, :amount, :refund_amount, :bank_identification_number, :payment_number,
+    :payment_type, :status, :reference_id, :payment_method_id, :payment_method, :card_type,
+    :card_country, :effective_date, :userName, :createdDate, :userName, :updatedDate);
+>>
+
+getRecordId() ::= <<
+    SELECT record_id
+    FROM payments
+    WHERE id = :id;
+>>
+
+getHistoryRecordId() ::= <<
+    SELECT MAX(history_record_id)
+    FROM payment_history
+    WHERE record_id = :recordId;
 >>
+
+auditFields(prefix) ::= <<
+    <prefix>table_name,
+    <prefix>record_id,
+    <prefix>change_type,
+    <prefix>change_date,
+    <prefix>changed_by,
+    <prefix>reason_code,
+    <prefix>comments,
+    <prefix>user_token
+>>
+
+insertAuditFromTransaction() ::= <<
+    INSERT INTO audit_log(<auditFields()>)
+    VALUES(:tableName, :recordId, :changeType, :createdDate, :userName, NULL, NULL, NULL);
+>>
+
diff --git a/payment/src/main/resources/com/ning/billing/payment/ddl.sql b/payment/src/main/resources/com/ning/billing/payment/ddl.sql
index 3a344f9..c77945f 100644
--- a/payment/src/main/resources/com/ning/billing/payment/ddl.sql
+++ b/payment/src/main/resources/com/ning/billing/payment/ddl.sql
@@ -1,81 +1,90 @@
 DROP TABLE IF EXISTS payment_attempts;
 CREATE TABLE payment_attempts (
-      payment_attempt_id char(36) COLLATE utf8_bin NOT NULL,
-      account_id char(36) COLLATE utf8_bin NOT NULL,
-      invoice_id char(36) COLLATE utf8_bin NOT NULL,
-      amount decimal(8,2),
-      currency char(3),
-      payment_attempt_dt datetime NOT NULL,
-      payment_id varchar(36) COLLATE utf8_bin,
-      retry_count tinyint,
-      invoice_dt datetime NOT NULL,
-      created_by varchar(50) NOT NULL,
-      created_dt datetime NOT NULL,
-      updated_by varchar(50) NOT NULL,
-      updated_dt datetime NOT NULL,
-      PRIMARY KEY (payment_attempt_id)
+    record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
+    id char(36) NOT NULL,
+    account_id char(36) COLLATE utf8_bin NOT NULL,
+    invoice_id char(36) COLLATE utf8_bin NOT NULL,
+    amount decimal(8,2),
+    currency char(3),
+    payment_attempt_date datetime NOT NULL,
+    payment_id varchar(36) COLLATE utf8_bin,
+    retry_count tinyint,
+    invoice_date datetime NOT NULL,
+    created_by varchar(50) NOT NULL,
+    created_date datetime NOT NULL,
+    updated_by varchar(50) NOT NULL,
+    updated_date datetime NOT NULL,
+    PRIMARY KEY (record_id)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
+CREATE UNIQUE INDEX payment_attempts_id ON payment_attempts(id);
+CREATE INDEX payment_attempts_account_id_invoice_id ON payment_attempts(account_id, invoice_id);
 
 DROP TABLE IF EXISTS payment_attempt_history;
 CREATE TABLE payment_attempt_history (
-      history_record_id char(36) NOT NULL,
-      payment_attempt_id char(36) COLLATE utf8_bin NOT NULL,
-      account_id char(36) COLLATE utf8_bin NOT NULL,
-      invoice_id char(36) COLLATE utf8_bin NOT NULL,
-      amount decimal(8,2),
-      currency char(3),
-      payment_attempt_dt datetime NOT NULL,
-      payment_id varchar(36) COLLATE utf8_bin,
-      retry_count tinyint,
-      invoice_dt datetime NOT NULL,
-      created_by varchar(50) NOT NULL,
-      created_dt datetime NOT NULL,
-      updated_by varchar(50) NOT NULL,
-      updated_dt datetime NOT NULL,
-      PRIMARY KEY (history_record_id)
+    history_record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
+    record_id int(11) unsigned NOT NULL,
+    id char(36) NOT NULL,
+    account_id char(36) COLLATE utf8_bin NOT NULL,
+    invoice_id char(36) COLLATE utf8_bin NOT NULL,
+    amount decimal(8,2),
+    currency char(3),
+    payment_attempt_date datetime NOT NULL,
+    payment_id varchar(36) COLLATE utf8_bin,
+    retry_count tinyint,
+    invoice_date datetime NOT NULL,
+    created_by varchar(50) NOT NULL,
+    created_date datetime NOT NULL,
+    updated_by varchar(50) NOT NULL,
+    updated_date datetime NOT NULL,
+    PRIMARY KEY (history_record_id)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
+CREATE INDEX payment_attempt_history_record_id ON payment_attempt_history(record_id);
 
 DROP TABLE IF EXISTS payments; 
 CREATE TABLE payments (
-      payment_id varchar(36) COLLATE utf8_bin NOT NULL,
-      amount decimal(8,2),
-      refund_amount decimal(8,2),
-      payment_number varchar(36) COLLATE utf8_bin,
-      bank_identification_number varchar(36) COLLATE utf8_bin,
-      status varchar(20) COLLATE utf8_bin,
-      reference_id varchar(36) COLLATE utf8_bin,
-      payment_type varchar(20) COLLATE utf8_bin,
-      payment_method_id varchar(36) COLLATE utf8_bin,
-      payment_method varchar(20) COLLATE utf8_bin,
-      card_type varchar(20) COLLATE utf8_bin,
-      card_country varchar(50) COLLATE utf8_bin,
-      effective_dt datetime,
-      created_by varchar(50) NOT NULL,
-      created_dt datetime NOT NULL,
-      updated_by varchar(50) NOT NULL,
-      updated_dt datetime NOT NULL,
-      PRIMARY KEY (payment_id)
+    record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
+    id char(36) NOT NULL,
+    amount decimal(8,2),
+    refund_amount decimal(8,2),
+    payment_number varchar(36) COLLATE utf8_bin,
+    bank_identification_number varchar(36) COLLATE utf8_bin,
+    status varchar(20) COLLATE utf8_bin,
+    reference_id varchar(36) COLLATE utf8_bin,
+    payment_type varchar(20) COLLATE utf8_bin,
+    payment_method_id varchar(36) COLLATE utf8_bin,
+    payment_method varchar(20) COLLATE utf8_bin,
+    card_type varchar(20) COLLATE utf8_bin,
+    card_country varchar(50) COLLATE utf8_bin,
+    effective_date datetime,
+    created_by varchar(50) NOT NULL,
+    created_date datetime NOT NULL,
+    updated_by varchar(50) NOT NULL,
+    updated_date datetime NOT NULL,
+    PRIMARY KEY (record_id)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
+CREATE UNIQUE INDEX payments_id ON payments(id);
 
 DROP TABLE IF EXISTS payment_history;
 CREATE TABLE payment_history (
-      history_record_id char(36) NOT NULL,
-      payment_id varchar(36) COLLATE utf8_bin NOT NULL,
-      amount decimal(8,2),
-      refund_amount decimal(8,2),
-      payment_number varchar(36) COLLATE utf8_bin,
-      bank_identification_number varchar(36) COLLATE utf8_bin,
-      status varchar(20) COLLATE utf8_bin,
-      reference_id varchar(36) COLLATE utf8_bin,
-      payment_type varchar(20) COLLATE utf8_bin,
-      payment_method_id varchar(36) COLLATE utf8_bin,
-      payment_method varchar(20) COLLATE utf8_bin,
-      card_type varchar(20) COLLATE utf8_bin,
-      card_country varchar(50) COLLATE utf8_bin,
-      effective_dt datetime,
-      created_by varchar(50) NOT NULL,
-      created_dt datetime NOT NULL,
-      updated_by varchar(50) NOT NULL,
-      updated_dt datetime NOT NULL,
-      PRIMARY KEY (history_record_id)
+    history_record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
+    record_id int(11) unsigned NOT NULL,
+    id char(36) NOT NULL,
+    amount decimal(8,2),
+    refund_amount decimal(8,2),
+    payment_number varchar(36) COLLATE utf8_bin,
+    bank_identification_number varchar(36) COLLATE utf8_bin,
+    status varchar(20) COLLATE utf8_bin,
+    reference_id varchar(36) COLLATE utf8_bin,
+    payment_type varchar(20) COLLATE utf8_bin,
+    payment_method_id varchar(36) COLLATE utf8_bin,
+    payment_method varchar(20) COLLATE utf8_bin,
+    card_type varchar(20) COLLATE utf8_bin,
+    card_country varchar(50) COLLATE utf8_bin,
+    effective_date datetime,
+    created_by varchar(50) NOT NULL,
+    created_date datetime NOT NULL,
+    updated_by varchar(50) NOT NULL,
+    updated_date datetime NOT NULL,
+    PRIMARY KEY (history_record_id)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
+CREATE INDEX payment_history_record_id ON payment_history(record_id);
diff --git a/payment/src/test/java/com/ning/billing/payment/api/TestEventJson.java b/payment/src/test/java/com/ning/billing/payment/api/TestEventJson.java
new file mode 100644
index 0000000..198e986
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/api/TestEventJson.java
@@ -0,0 +1,61 @@
+/* 
+ * Copyright 2010-2011 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.payment.api;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import org.codehaus.jackson.map.ObjectMapper;
+import org.codehaus.jackson.map.SerializationConfig;
+import org.joda.time.DateTime;
+import org.testng.Assert;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Test;
+
+
+public class TestEventJson {
+
+
+    private ObjectMapper mapper = new ObjectMapper();
+
+    @BeforeTest(groups= {"fast"})
+    public void setup() {
+        mapper = new ObjectMapper();
+        mapper.disable(SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS);
+    }
+
+    @Test(groups= {"fast"})
+    public void testPaymentErrorEvent() throws Exception {
+        PaymentErrorEvent e = new DefaultPaymentErrorEvent("credit card", "Failed payment", UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID());
+        String json = mapper.writeValueAsString(e);
+
+        Class<?> claz = Class.forName(DefaultPaymentErrorEvent.class.getName());
+        Object obj =  mapper.readValue(json, claz);
+        Assert.assertTrue(obj.equals(e));
+    }
+    
+    @Test(groups= {"fast"})
+    public void testPaymentInfoEvent() throws Exception {
+        PaymentInfoEvent e = new DefaultPaymentInfoEvent(UUID.randomUUID(), new BigDecimal(12), new BigDecimal(12.9), "BNP", "eeert", "success",
+                "credit", "ref", "paypal", "paypal", "", "", UUID.randomUUID(), new DateTime());
+        
+        String json = mapper.writeValueAsString(e);
+
+        Class<?> clazz = Class.forName(DefaultPaymentInfoEvent.class.getName());
+        Object obj =  mapper.readValue(json, clazz);
+        Assert.assertTrue(obj.equals(e));
+    }
+}
diff --git a/payment/src/test/java/com/ning/billing/payment/api/TestMockPaymentApi.java b/payment/src/test/java/com/ning/billing/payment/api/TestMockPaymentApi.java
index 7c005ee..0ff39d2 100644
--- a/payment/src/test/java/com/ning/billing/payment/api/TestMockPaymentApi.java
+++ b/payment/src/test/java/com/ning/billing/payment/api/TestMockPaymentApi.java
@@ -16,16 +16,17 @@
 
 package com.ning.billing.payment.api;
 
-import com.google.inject.Inject;
-import com.ning.billing.util.clock.Clock;
 import org.testng.annotations.Guice;
 import org.testng.annotations.Test;
 
-import com.ning.billing.account.glue.AccountModuleWithMocks;
-import com.ning.billing.invoice.glue.InvoiceModuleWithMocks;
+import com.google.inject.Inject;
+import com.ning.billing.mock.glue.MockJunctionModule;
 import com.ning.billing.payment.setup.PaymentTestModuleWithMocks;
+import com.ning.billing.util.clock.Clock;
+import com.ning.billing.util.clock.MockClockModule;
+import com.ning.billing.util.glue.CallContextModule;
 
-@Guice(modules = { PaymentTestModuleWithMocks.class, AccountModuleWithMocks.class, InvoiceModuleWithMocks.class })
+@Guice(modules = { PaymentTestModuleWithMocks.class, MockClockModule.class, MockJunctionModule.class, CallContextModule.class })
 @Test(groups = "fast")
 public class TestMockPaymentApi extends TestPaymentApi {
     @Inject
diff --git a/payment/src/test/java/com/ning/billing/payment/api/TestPaymentApi.java b/payment/src/test/java/com/ning/billing/payment/api/TestPaymentApi.java
index ad0bd03..c7f925b 100644
--- a/payment/src/test/java/com/ning/billing/payment/api/TestPaymentApi.java
+++ b/payment/src/test/java/com/ning/billing/payment/api/TestPaymentApi.java
@@ -22,15 +22,11 @@ import static org.testng.Assert.assertNotNull;
 import static org.testng.Assert.assertTrue;
 
 import java.math.BigDecimal;
+import java.math.RoundingMode;
 import java.util.Arrays;
 import java.util.List;
 import java.util.UUID;
 
-import com.ning.billing.util.callcontext.CallContext;
-import com.ning.billing.util.callcontext.CallOrigin;
-import com.ning.billing.util.callcontext.DefaultCallContext;
-import com.ning.billing.util.callcontext.UserType;
-import com.ning.billing.util.clock.Clock;
 import org.apache.commons.lang.RandomStringUtils;
 import org.joda.time.DateTime;
 import org.joda.time.DateTimeZone;
@@ -40,15 +36,20 @@ import org.testng.annotations.Test;
 
 import com.google.inject.Inject;
 import com.ning.billing.account.api.Account;
-import com.ning.billing.account.api.AccountApiException;
-import com.ning.billing.account.api.user.AccountBuilder;
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.invoice.api.Invoice;
-import com.ning.billing.invoice.model.RecurringInvoiceItem;
+import com.ning.billing.invoice.api.InvoicePaymentApi;
+import com.ning.billing.mock.BrainDeadProxyFactory;
+import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
+import com.ning.billing.payment.MockRecurringInvoiceItem;
 import com.ning.billing.payment.TestHelper;
 import com.ning.billing.util.bus.Bus;
 import com.ning.billing.util.bus.Bus.EventBusException;
-import com.ning.billing.util.entity.EntityPersistenceException;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.callcontext.CallOrigin;
+import com.ning.billing.util.callcontext.DefaultCallContext;
+import com.ning.billing.util.callcontext.UserType;
+import com.ning.billing.util.clock.Clock;
 
 public abstract class TestPaymentApi {
     @Inject
@@ -57,6 +58,8 @@ public abstract class TestPaymentApi {
     protected PaymentApi paymentApi;
     @Inject
     protected TestHelper testHelper;
+    @Inject
+    protected InvoicePaymentApi invoicePaymentApi;
 
     protected CallContext context;
 
@@ -76,15 +79,19 @@ public abstract class TestPaymentApi {
     }
 
     @Test(enabled=true)
-    public void testCreateCreditCardPayment() throws AccountApiException, EntityPersistenceException {
+    public void testCreateCreditCardPayment() throws Exception {
+        ((ZombieControl)invoicePaymentApi).addResult("notifyOfPaymentAttempt", BrainDeadProxyFactory.ZOMBIE_VOID);
+
         final DateTime now = new DateTime(DateTimeZone.UTC);
         final Account account = testHelper.createTestCreditCardAccount();
         final Invoice invoice = testHelper.createTestInvoice(account, now, Currency.USD);
-        final BigDecimal amount = new BigDecimal("10.00");
+        final BigDecimal amount = new BigDecimal("10.0011");
         final UUID subscriptionId = UUID.randomUUID();
+        final UUID bundleId = UUID.randomUUID();
 
-        invoice.addInvoiceItem(new RecurringInvoiceItem(invoice.getId(), account.getId(),
+        invoice.addInvoiceItem(new MockRecurringInvoiceItem(invoice.getId(), account.getId(),
                                                        subscriptionId,
+                                                       bundleId,
                                                        "test plan", "test phase",
                                                        now,
                                                        now.plusMonths(1),
@@ -92,37 +99,36 @@ public abstract class TestPaymentApi {
                                                        new BigDecimal("1.0"),
                                                        Currency.USD));
 
-        List<Either<PaymentError, PaymentInfo>> results = paymentApi.createPayment(account.getExternalKey(), Arrays.asList(invoice.getId().toString()), context);
+        List<PaymentInfoEvent> results = paymentApi.createPayment(account.getExternalKey(), Arrays.asList(invoice.getId().toString()), context);
 
         assertEquals(results.size(), 1);
-        assertTrue(results.get(0).isRight());
 
-        PaymentInfo paymentInfo = results.get(0).getRight();
+        PaymentInfoEvent paymentInfo = results.get(0);
 
-        assertNotNull(paymentInfo.getPaymentId());
-        assertTrue(paymentInfo.getAmount().compareTo(amount) == 0);
+        assertNotNull(paymentInfo.getId());
+        assertTrue(paymentInfo.getAmount().compareTo(amount.setScale(2, RoundingMode.HALF_EVEN)) == 0);
         assertNotNull(paymentInfo.getPaymentNumber());
         assertFalse(paymentInfo.getStatus().equals("Error"));
 
-        PaymentAttempt paymentAttempt = paymentApi.getPaymentAttemptForPaymentId(paymentInfo.getPaymentId());
+        PaymentAttempt paymentAttempt = paymentApi.getPaymentAttemptForPaymentId(paymentInfo.getId());
         assertNotNull(paymentAttempt);
-        assertNotNull(paymentAttempt.getPaymentAttemptId());
+        assertNotNull(paymentAttempt.getId());
         assertEquals(paymentAttempt.getInvoiceId(), invoice.getId());
-        assertTrue(paymentAttempt.getAmount().compareTo(amount) == 0);
+        assertTrue(paymentAttempt.getAmount().compareTo(amount.setScale(2, RoundingMode.HALF_EVEN)) == 0);
         assertEquals(paymentAttempt.getCurrency(), Currency.USD);
-        assertEquals(paymentAttempt.getPaymentId(), paymentInfo.getPaymentId());
+        assertEquals(paymentAttempt.getPaymentId(), paymentInfo.getId());
         DateTime nowTruncated = now.withMillisOfSecond(0).withSecondOfMinute(0);
         DateTime paymentAttemptDateTruncated = paymentAttempt.getPaymentAttemptDate().withMillisOfSecond(0).withSecondOfMinute(0);
         assertEquals(paymentAttemptDateTruncated.compareTo(nowTruncated), 0);
 
-        List<PaymentInfo> paymentInfos = paymentApi.getPaymentInfo(Arrays.asList(invoice.getId().toString()));
+        List<PaymentInfoEvent> paymentInfos = paymentApi.getPaymentInfoList(Arrays.asList(invoice.getId().toString()));
         assertNotNull(paymentInfos);
         assertTrue(paymentInfos.size() > 0);
 
-        PaymentInfo paymentInfoFromGet = paymentInfos.get(0);
+        PaymentInfoEvent paymentInfoFromGet = paymentInfos.get(0);
         assertEquals(paymentInfo.getAmount(), paymentInfoFromGet.getAmount());
         assertEquals(paymentInfo.getRefundAmount(), paymentInfoFromGet.getRefundAmount());
-        assertEquals(paymentInfo.getPaymentId(), paymentInfoFromGet.getPaymentId());
+        assertEquals(paymentInfo.getId(), paymentInfoFromGet.getId());
         assertEquals(paymentInfo.getPaymentNumber(), paymentInfoFromGet.getPaymentNumber());
         assertEquals(paymentInfo.getStatus(), paymentInfoFromGet.getStatus());
         assertEquals(paymentInfo.getBankIdentificationNumber(), paymentInfoFromGet.getBankIdentificationNumber());
@@ -135,7 +141,7 @@ public abstract class TestPaymentApi {
 
     }
 
-    private PaymentProviderAccount setupAccountWithPaypalPaymentMethod() throws AccountApiException, EntityPersistenceException {
+    private PaymentProviderAccount setupAccountWithPaypalPaymentMethod() throws Exception  {
         final Account account = testHelper.createTestPayPalAccount();
         paymentApi.createPaymentProviderAccount(account, context);
 
@@ -146,90 +152,42 @@ public abstract class TestPaymentApi {
                                                                            .setEmail(account.getEmail())
                                                                            .setDefaultMethod(true)
                                                                            .build();
-        Either<PaymentError, String> paymentMethodIdOrError = paymentApi.addPaymentMethod(accountKey, paymentMethod, context);
-
-        assertTrue(paymentMethodIdOrError.isRight());
-        assertNotNull(paymentMethodIdOrError.getRight());
-
-        Either<PaymentError, PaymentMethodInfo> paymentMethodInfoOrError = paymentApi.getPaymentMethod(accountKey, paymentMethodIdOrError.getRight());
+        String paymentMethodId = paymentApi.addPaymentMethod(accountKey, paymentMethod, context);
 
-        assertTrue(paymentMethodInfoOrError.isRight());
-        assertNotNull(paymentMethodInfoOrError.getRight());
+        PaymentMethodInfo paymentMethodInfo = paymentApi.getPaymentMethod(accountKey, paymentMethodId);
 
-        Either<PaymentError, PaymentProviderAccount> accountOrError = paymentApi.getPaymentProviderAccount(accountKey);
-
-        assertTrue(accountOrError.isRight());
-
-        return accountOrError.getRight();
+        return paymentApi.getPaymentProviderAccount(accountKey);
     }
 
     @Test(enabled=true)
-    public void testCreatePaypalPaymentMethod() throws AccountApiException, EntityPersistenceException {
+    public void testCreatePaypalPaymentMethod() throws Exception  {
         PaymentProviderAccount account = setupAccountWithPaypalPaymentMethod();
         assertNotNull(account);
-        Either<PaymentError, List<PaymentMethodInfo>> paymentMethodsOrError = paymentApi.getPaymentMethods(account.getAccountKey());
+        paymentApi.getPaymentMethods(account.getAccountKey());
     }
 
     @Test(enabled=true)
-    public void testUpdatePaymentProviderAccountContact() throws AccountApiException, EntityPersistenceException {
+    public void testUpdatePaymentProviderAccountContact() throws Exception {
         final Account account = testHelper.createTestPayPalAccount();
         paymentApi.createPaymentProviderAccount(account, context);
 
-        String newName = "Tester " + RandomStringUtils.randomAlphanumeric(10);
-        String newNumber = "888-888-" + RandomStringUtils.randomNumeric(4);
-
-        final Account accountToUpdate = new AccountBuilder(account.getId())
-                                                                  .name(newName)
-                                                                  .firstNameLength(newName.length())
-                                                                  .externalKey(account.getExternalKey())
-                                                                  .phone(newNumber)
-                                                                  .email(account.getEmail())
-                                                                  .currency(account.getCurrency())
-                                                                  .billingCycleDay(account.getBillCycleDay())
-                                                                  .build();
-
-        Either<PaymentError, Void> voidOrError = paymentApi.updatePaymentProviderAccountContact(accountToUpdate.getExternalKey(), context);
-        assertTrue(voidOrError.isRight());
+        Account updatedAccount = BrainDeadProxyFactory.createBrainDeadProxyFor(Account.class);
+        ZombieControl zombieAccount = (ZombieControl) updatedAccount;
+        zombieAccount.addResult("getId", account.getId());
+        zombieAccount.addResult("getName", "Tester " + RandomStringUtils.randomAlphanumeric(10));
+        zombieAccount.addResult("getFirstNameLength", 6);
+        zombieAccount.addResult("getExternalKey", account.getExternalKey());
+        zombieAccount.addResult("getPhone", "888-888-" + RandomStringUtils.randomNumeric(4));
+        zombieAccount.addResult("getEmail", account.getEmail());
+        zombieAccount.addResult("getCurrency", account.getCurrency());
+        zombieAccount.addResult("getBillCycleDay", account.getBillCycleDay());
+
+        paymentApi.updatePaymentProviderAccountContact(updatedAccount.getExternalKey(), context);
     }
 
     @Test(enabled=true)
-    public void testCannotDeleteDefaultPaymentMethod() throws AccountApiException, EntityPersistenceException {
+    public void testCannotDeleteDefaultPaymentMethod() throws Exception  {
         PaymentProviderAccount account = setupAccountWithPaypalPaymentMethod();
-
-        Either<PaymentError, Void> errorOrVoid = paymentApi.deletePaymentMethod(account.getAccountKey(), account.getDefaultPaymentMethodId(), context);
-
-        assertTrue(errorOrVoid.isLeft());
+        paymentApi.deletePaymentMethod(account.getAccountKey(), account.getDefaultPaymentMethodId(), context);
     }
-
-    @Test(enabled=true)
-    public void testDeleteNonDefaultPaymentMethod() throws AccountApiException, EntityPersistenceException {
-        final Account account = testHelper.createTestPayPalAccount();
-        paymentApi.createPaymentProviderAccount(account, context);
-
-        String accountKey = account.getExternalKey();
-
-        PaypalPaymentMethodInfo paymentMethod1 = new PaypalPaymentMethodInfo.Builder().setDefaultMethod(false).setBaid("12345").setEmail(account.getEmail()).build();
-        Either<PaymentError, String> paymentMethodIdOrError1 = paymentApi.addPaymentMethod(accountKey, paymentMethod1, context);
-
-        assertTrue(paymentMethodIdOrError1.isRight());
-        assertNotNull(paymentMethodIdOrError1.getRight());
-
-        PaypalPaymentMethodInfo paymentMethod2 = new PaypalPaymentMethodInfo.Builder().setDefaultMethod(true).setBaid("12345").setEmail(account.getEmail()).build();
-
-        Either<PaymentError, String> paymentMethodIdOrError2 = paymentApi.addPaymentMethod(accountKey, paymentMethod2, context);
-
-        assertTrue(paymentMethodIdOrError2.isRight());
-        assertNotNull(paymentMethodIdOrError2.getRight());
-
-        Either<PaymentError, List<PaymentMethodInfo>> paymentMethodsOrError = paymentApi.getPaymentMethods(accountKey);
-
-        assertTrue(paymentMethodsOrError.isRight());
-
-        Either<PaymentError, Void> errorOrVoid1 = paymentApi.deletePaymentMethod(accountKey, paymentMethodIdOrError1.getRight(), context);
-        Either<PaymentError, Void> errorOrVoid2 = paymentApi.deletePaymentMethod(accountKey, paymentMethodIdOrError2.getRight(), context);
-
-        assertTrue(errorOrVoid1.isRight());
-        assertTrue(errorOrVoid2.isLeft());
-    }
-
 }
diff --git a/payment/src/test/java/com/ning/billing/payment/dao/MockPaymentDao.java b/payment/src/test/java/com/ning/billing/payment/dao/MockPaymentDao.java
index 7c8239f..3f90466 100644
--- a/payment/src/test/java/com/ning/billing/payment/dao/MockPaymentDao.java
+++ b/payment/src/test/java/com/ning/billing/payment/dao/MockPaymentDao.java
@@ -23,21 +23,23 @@ import java.util.Map;
 import java.util.UUID;
 import java.util.concurrent.ConcurrentHashMap;
 
+import com.ning.billing.payment.api.DefaultPaymentAttempt;
 import com.ning.billing.util.callcontext.CallContext;
 import org.apache.commons.collections.CollectionUtils;
 
 import com.google.common.base.Predicate;
 import com.google.common.collect.Collections2;
 import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.payment.api.DefaultPaymentInfoEvent;
 import com.ning.billing.payment.api.PaymentAttempt;
-import com.ning.billing.payment.api.PaymentInfo;
+import com.ning.billing.payment.api.PaymentInfoEvent;
 
 public class MockPaymentDao implements PaymentDao {
-    private final Map<String, PaymentInfo> payments = new ConcurrentHashMap<String, PaymentInfo>();
+    private final Map<UUID, PaymentInfoEvent> payments = new ConcurrentHashMap<UUID, PaymentInfoEvent>();
     private final Map<UUID, PaymentAttempt> paymentAttempts = new ConcurrentHashMap<UUID, PaymentAttempt>();
 
     @Override
-    public PaymentAttempt getPaymentAttemptForPaymentId(String paymentId) {
+    public PaymentAttempt getPaymentAttemptForPaymentId(UUID paymentId) {
         for (PaymentAttempt paymentAttempt : paymentAttempts.values()) {
             if (paymentId.equals(paymentAttempt.getPaymentId())) {
                 return paymentAttempt;
@@ -48,39 +50,39 @@ public class MockPaymentDao implements PaymentDao {
 
     @Override
     public PaymentAttempt createPaymentAttempt(Invoice invoice, CallContext context) {
-        PaymentAttempt updatedPaymentAttempt = new PaymentAttempt(UUID.randomUUID(), invoice.getId(), invoice.getAccountId(),
+        PaymentAttempt updatedPaymentAttempt = new DefaultPaymentAttempt(UUID.randomUUID(), invoice.getId(), invoice.getAccountId(),
                 invoice.getBalance(), invoice.getCurrency(), invoice.getInvoiceDate(),
-                null, null, null, context.getCreatedDate(), context.getUpdatedDate());
+                null, null, null, null, null);
 
-        paymentAttempts.put(updatedPaymentAttempt.getPaymentAttemptId(), updatedPaymentAttempt);
+        paymentAttempts.put(updatedPaymentAttempt.getId(), updatedPaymentAttempt);
         return updatedPaymentAttempt;
     }
 
     @Override
     public PaymentAttempt createPaymentAttempt(PaymentAttempt paymentAttempt, CallContext context) {
-        PaymentAttempt updatedPaymentAttempt = new PaymentAttempt(paymentAttempt.getPaymentAttemptId(),
+        PaymentAttempt updatedPaymentAttempt = new DefaultPaymentAttempt(paymentAttempt.getId(),
                 paymentAttempt.getInvoiceId(),
                 paymentAttempt.getAccountId(), paymentAttempt.getAmount(), paymentAttempt.getCurrency(),
                 paymentAttempt.getInvoiceDate(), paymentAttempt.getPaymentAttemptDate(),
                 paymentAttempt.getPaymentId(), paymentAttempt.getRetryCount(),
-                context.getCreatedDate(), context.getUpdatedDate());
+                paymentAttempt.getCreatedDate(), paymentAttempt.getUpdatedDate());
 
-        paymentAttempts.put(updatedPaymentAttempt.getPaymentAttemptId(), updatedPaymentAttempt);
+        paymentAttempts.put(updatedPaymentAttempt.getId(), updatedPaymentAttempt);
         return updatedPaymentAttempt;
     }
 
     @Override
-    public void savePaymentInfo(PaymentInfo paymentInfo, CallContext context) {
-        payments.put(paymentInfo.getPaymentId(), paymentInfo);
+    public void savePaymentInfo(PaymentInfoEvent paymentInfo, CallContext context) {
+        payments.put(paymentInfo.getId(), paymentInfo);
     }
 
     @Override
-    public void updatePaymentAttemptWithPaymentId(UUID paymentAttemptId, String paymentId, CallContext context) {
+    public void updatePaymentAttemptWithPaymentId(UUID paymentAttemptId, UUID paymentId, CallContext context) {
         PaymentAttempt existingPaymentAttempt = paymentAttempts.get(paymentAttemptId);
 
         if (existingPaymentAttempt != null) {
-            paymentAttempts.put(existingPaymentAttempt.getPaymentAttemptId(),
-                                existingPaymentAttempt.cloner().setPaymentId(paymentId).build());
+            paymentAttempts.put(existingPaymentAttempt.getId(),
+                                ((DefaultPaymentAttempt) existingPaymentAttempt).cloner().setPaymentId(paymentId).build());
         }
     }
 
@@ -96,29 +98,28 @@ public class MockPaymentDao implements PaymentDao {
     }
 
     @Override
-    public void updatePaymentInfo(String paymentMethodType, String paymentId, String cardType, String cardCountry, CallContext context) {
-        PaymentInfo existingPayment = payments.get(paymentId);
+    public void updatePaymentInfo(String paymentMethodType, UUID paymentId, String cardType, String cardCountry, CallContext context) {
+        DefaultPaymentInfoEvent existingPayment = (DefaultPaymentInfoEvent) payments.get(paymentId);
         if (existingPayment != null) {
-            PaymentInfo payment = existingPayment.cloner()
+            PaymentInfoEvent payment = existingPayment.cloner()
                     .setPaymentMethod(paymentMethodType)
                     .setCardType(cardType)
                     .setCardCountry(cardCountry)
-                    .setUpdatedDate(context.getUpdatedDate())
                     .build();
             payments.put(paymentId, payment);
         }
     }
 
     @Override
-    public List<PaymentInfo> getPaymentInfo(List<String> invoiceIds) {
+    public List<PaymentInfoEvent> getPaymentInfoList(List<String> invoiceIds) {
         List<PaymentAttempt> attempts = getPaymentAttemptsForInvoiceIds(invoiceIds);
-        List<PaymentInfo> paymentsToReturn = new ArrayList<PaymentInfo>(invoiceIds.size());
+        List<PaymentInfoEvent> paymentsToReturn = new ArrayList<PaymentInfoEvent>(invoiceIds.size());
 
         for (final PaymentAttempt attempt : attempts) {
-            paymentsToReturn.addAll(Collections2.filter(payments.values(), new Predicate<PaymentInfo>() {
+            paymentsToReturn.addAll(Collections2.filter(payments.values(), new Predicate<PaymentInfoEvent>() {
                 @Override
-                public boolean apply(PaymentInfo input) {
-                    return input.getPaymentId().equals(attempt.getPaymentId());
+                public boolean apply(PaymentInfoEvent input) {
+                    return input.getId().equals(attempt.getPaymentId());
                 }
             }));
         }
@@ -126,6 +127,20 @@ public class MockPaymentDao implements PaymentDao {
     }
 
     @Override
+    public PaymentInfoEvent getLastPaymentInfo(List<String> invoiceIds) {
+        List<PaymentInfoEvent> payments = getPaymentInfoList(invoiceIds);
+        PaymentInfoEvent lastPayment = null;
+
+        for (PaymentInfoEvent payment : payments) {
+            if ((lastPayment == null) || (payment.getEffectiveDate().isAfter(lastPayment.getEffectiveDate()))) {
+                lastPayment = payment;
+            }
+        }
+
+        return lastPayment;
+    }
+
+    @Override
     public List<PaymentAttempt> getPaymentAttemptsForInvoiceIds(List<String> invoiceIds) {
         List<PaymentAttempt> paymentAttempts = new ArrayList<PaymentAttempt>(invoiceIds.size());
         for (String invoiceId : invoiceIds) {
@@ -143,7 +158,7 @@ public class MockPaymentDao implements PaymentDao {
     }
 
     @Override
-    public PaymentInfo getPaymentInfoForPaymentAttemptId(String paymentAttemptId) {
+    public PaymentInfoEvent getPaymentInfoForPaymentAttemptId(String paymentAttemptId) {
         // TODO Auto-generated method stub
         return null;
     }
diff --git a/payment/src/test/java/com/ning/billing/payment/dao/TestPaymentDao.java b/payment/src/test/java/com/ning/billing/payment/dao/TestPaymentDao.java
index 472fc2f..40b92ef 100644
--- a/payment/src/test/java/com/ning/billing/payment/dao/TestPaymentDao.java
+++ b/payment/src/test/java/com/ning/billing/payment/dao/TestPaymentDao.java
@@ -21,12 +21,12 @@ import java.util.Arrays;
 import java.util.List;
 import java.util.UUID;
 
+import com.ning.billing.payment.api.DefaultPaymentAttempt;
 import com.ning.billing.util.callcontext.CallContext;
 import com.ning.billing.util.callcontext.CallOrigin;
 import com.ning.billing.util.callcontext.DefaultCallContext;
 import com.ning.billing.util.callcontext.TestCallContext;
 import com.ning.billing.util.callcontext.UserType;
-import com.ning.billing.util.clock.Clock;
 import com.ning.billing.util.clock.ClockMock;
 import com.ning.billing.util.clock.DefaultClock;
 import org.testng.Assert;
@@ -34,8 +34,9 @@ import org.testng.annotations.Test;
 
 import com.ning.billing.account.api.AccountApiException;
 import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.payment.api.DefaultPaymentInfoEvent;
 import com.ning.billing.payment.api.PaymentAttempt;
-import com.ning.billing.payment.api.PaymentInfo;
+import com.ning.billing.payment.api.PaymentInfoEvent;
 
 public abstract class TestPaymentDao {
     protected PaymentDao paymentDao;
@@ -43,7 +44,7 @@ public abstract class TestPaymentDao {
 
     @Test
     public void testCreatePayment() {
-        PaymentInfo paymentInfo = new PaymentInfo.Builder().setPaymentId(UUID.randomUUID().toString())
+        PaymentInfoEvent paymentInfo = new DefaultPaymentInfoEvent.Builder().setId(UUID.randomUUID())
                 .setAmount(BigDecimal.TEN)
                 .setStatus("Processed")
                 .setBankIdentificationNumber("1234")
@@ -59,7 +60,7 @@ public abstract class TestPaymentDao {
 
     @Test
     public void testUpdatePaymentInfo() {
-        PaymentInfo paymentInfo = new PaymentInfo.Builder().setPaymentId(UUID.randomUUID().toString())
+        PaymentInfoEvent paymentInfo = new DefaultPaymentInfoEvent.Builder().setId(UUID.randomUUID())
                 .setAmount(BigDecimal.TEN)
                 .setStatus("Processed")
                 .setBankIdentificationNumber("1234")
@@ -67,20 +68,18 @@ public abstract class TestPaymentDao {
                 .setPaymentMethodId("12345")
                 .setReferenceId("12345")
                 .setType("Electronic")
-                .setCreatedDate(new DefaultClock().getUTCNow())
-                .setUpdatedDate(new DefaultClock().getUTCNow())
                 .setEffectiveDate(new DefaultClock().getUTCNow())
                 .build();
 
         CallContext context = new TestCallContext("PaymentTests");
         paymentDao.savePaymentInfo(paymentInfo, context);
-        paymentDao.updatePaymentInfo("CreditCard", paymentInfo.getPaymentId(), "Visa", "US", context);
+        paymentDao.updatePaymentInfo("CreditCard", paymentInfo.getId(), "Visa", "US", context);
     }
 
     @Test
     public void testUpdatePaymentAttempt() {
-        PaymentAttempt paymentAttempt = new PaymentAttempt.Builder().setPaymentAttemptId(UUID.randomUUID())
-                .setPaymentId(UUID.randomUUID().toString())
+        PaymentAttempt paymentAttempt = new DefaultPaymentAttempt.Builder().setPaymentAttemptId(UUID.randomUUID())
+                .setPaymentId(UUID.randomUUID())
                 .setInvoiceId(UUID.randomUUID())
                 .setAccountId(UUID.randomUUID())
                 .setAmount(BigDecimal.TEN)
@@ -96,14 +95,14 @@ public abstract class TestPaymentDao {
         final UUID invoiceId = UUID.randomUUID();
         final UUID paymentAttemptId = UUID.randomUUID();
         final UUID accountId = UUID.randomUUID();
-        final String paymentId = UUID.randomUUID().toString();
+        final UUID paymentId = UUID.randomUUID();
         final BigDecimal invoiceAmount = BigDecimal.TEN;
 
         // Move the clock backwards to test the updated_date field (see below)
         ClockMock clock = new ClockMock();
         CallContext thisContext = new DefaultCallContext("Payment Tests", CallOrigin.TEST, UserType.TEST, clock);
 
-        PaymentAttempt originalPaymentAttempt = new PaymentAttempt(paymentAttemptId, invoiceId, accountId, invoiceAmount, Currency.USD, clock.getUTCNow(), clock.getUTCNow(), paymentId, 0);
+        PaymentAttempt originalPaymentAttempt = new DefaultPaymentAttempt(paymentAttemptId, invoiceId, accountId, invoiceAmount, Currency.USD, clock.getUTCNow(), clock.getUTCNow(), paymentId, 0, null, null);
         PaymentAttempt attempt = paymentDao.createPaymentAttempt(originalPaymentAttempt, thisContext);
 
         List<PaymentAttempt> attemptsFromGet = paymentDao.getPaymentAttemptsForInvoiceId(invoiceId.toString());
@@ -114,11 +113,11 @@ public abstract class TestPaymentDao {
 
         Assert.assertEquals(attempt, attempt3);
 
-        PaymentAttempt attempt4 = paymentDao.getPaymentAttemptById(attempt3.getPaymentAttemptId());
+        PaymentAttempt attempt4 = paymentDao.getPaymentAttemptById(attempt3.getId());
 
         Assert.assertEquals(attempt3, attempt4);
 
-        PaymentInfo originalPaymentInfo = new PaymentInfo.Builder().setPaymentId(paymentId)
+        PaymentInfoEvent originalPaymentInfo = new DefaultPaymentInfoEvent.Builder().setId(paymentId)
                 .setAmount(invoiceAmount)
                 .setStatus("Processed")
                 .setBankIdentificationNumber("1234")
@@ -126,19 +125,18 @@ public abstract class TestPaymentDao {
                 .setPaymentMethodId("12345")
                 .setReferenceId("12345")
                 .setType("Electronic")
-                .setCreatedDate(clock.getUTCNow())
-                .setUpdatedDate(clock.getUTCNow())
                 .setEffectiveDate(clock.getUTCNow())
                 .build();
 
         paymentDao.savePaymentInfo(originalPaymentInfo, thisContext);
-        PaymentInfo paymentInfo = paymentDao.getPaymentInfo(Arrays.asList(invoiceId.toString())).get(0);
+        PaymentInfoEvent paymentInfo = paymentDao.getPaymentInfoList(Arrays.asList(invoiceId.toString())).get(0);
         Assert.assertEquals(paymentInfo, originalPaymentInfo);
 
         clock.setDeltaFromReality(60 * 60 * 1000); // move clock forward one hour
-        paymentDao.updatePaymentInfo(originalPaymentInfo.getPaymentMethod(), originalPaymentInfo.getPaymentId(), originalPaymentInfo.getCardType(), originalPaymentInfo.getCardCountry(), thisContext);
-        paymentInfo = paymentDao.getPaymentInfo(Arrays.asList(invoiceId.toString())).get(0);
-        Assert.assertEquals(paymentInfo.getCreatedDate().compareTo(attempt.getCreatedDate()), 0);
-        Assert.assertTrue(paymentInfo.getUpdatedDate().isAfter(originalPaymentInfo.getUpdatedDate()));
+        paymentDao.updatePaymentInfo(originalPaymentInfo.getPaymentMethod(), originalPaymentInfo.getId(), originalPaymentInfo.getCardType(), originalPaymentInfo.getCardCountry(), thisContext);
+        paymentInfo = paymentDao.getPaymentInfoList(Arrays.asList(invoiceId.toString())).get(0);
+        // TODO: replace these asserts
+//        Assert.assertEquals(paymentInfo.getCreatedDate().compareTo(attempt.getCreatedDate()), 0);
+//        Assert.assertTrue(paymentInfo.getUpdatedDate().isAfter(originalPaymentInfo.getUpdatedDate()));
     }
 }
diff --git a/payment/src/test/java/com/ning/billing/payment/dao/TestPaymentDaoWithEmbeddedDb.java b/payment/src/test/java/com/ning/billing/payment/dao/TestPaymentDaoWithEmbeddedDb.java
index 84e3e5a..7c2fe65 100644
--- a/payment/src/test/java/com/ning/billing/payment/dao/TestPaymentDaoWithEmbeddedDb.java
+++ b/payment/src/test/java/com/ning/billing/payment/dao/TestPaymentDaoWithEmbeddedDb.java
@@ -33,9 +33,11 @@ public class TestPaymentDaoWithEmbeddedDb extends TestPaymentDao {
     @BeforeClass(groups = { "slow", "database" })
     public void startMysql() throws IOException {
         final String paymentddl = IOUtils.toString(MysqlTestingHelper.class.getResourceAsStream("/com/ning/billing/payment/ddl.sql"));
+        final String utilddl = IOUtils.toString(MysqlTestingHelper.class.getResourceAsStream("/com/ning/billing/util/ddl.sql"));
 
         helper.startMysql();
         helper.initDb(paymentddl);
+        helper.initDb(utilddl);
     }
 
     @AfterClass(groups = { "slow", "database" })
diff --git a/payment/src/test/java/com/ning/billing/payment/MockInvoice.java b/payment/src/test/java/com/ning/billing/payment/MockInvoice.java
new file mode 100644
index 0000000..20c0446
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/MockInvoice.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright 2010-2011 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.payment;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import javax.annotation.Nullable;
+
+import com.ning.billing.util.dao.ObjectType;
+import org.joda.time.DateTime;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.api.InvoicePayment;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.customfield.CustomField;
+import com.ning.billing.util.entity.ExtendedEntityBase;
+
+public class MockInvoice extends ExtendedEntityBase implements Invoice {
+    private final List<InvoiceItem> invoiceItems = new ArrayList<InvoiceItem>();
+    private final List<InvoicePayment> payments = new ArrayList<InvoicePayment>();
+    private final UUID accountId;
+    private final Integer invoiceNumber;
+    private final DateTime invoiceDate;
+    private final DateTime targetDate;
+    private final Currency currency;
+    private final boolean migrationInvoice;
+
+    // used to create a new invoice
+    public MockInvoice(UUID accountId, DateTime invoiceDate, DateTime targetDate, Currency currency) {
+        this(UUID.randomUUID(), accountId, null, invoiceDate, targetDate, currency, false);
+    }
+
+    // used to hydrate invoice from persistence layer
+    public MockInvoice(UUID invoiceId, UUID accountId, @Nullable Integer invoiceNumber, DateTime invoiceDate,
+                          DateTime targetDate, Currency currency, boolean isMigrationInvoice) {
+        super(invoiceId);
+        this.accountId = accountId;
+        this.invoiceNumber = invoiceNumber;
+        this.invoiceDate = invoiceDate;
+        this.targetDate = targetDate;
+        this.currency = currency;
+        this.migrationInvoice = isMigrationInvoice;
+    }
+
+    @Override
+    public boolean addInvoiceItem(final InvoiceItem item) {
+        return invoiceItems.add(item);
+    }
+
+    @Override
+    public boolean addInvoiceItems(final List<InvoiceItem> items) {
+        return this.invoiceItems.addAll(items);
+    }
+
+    @Override
+    public List<InvoiceItem> getInvoiceItems() {
+        return invoiceItems;
+    }
+
+    @Override
+    public <T extends InvoiceItem> List<InvoiceItem> getInvoiceItems(Class<T> clazz) {
+        List<InvoiceItem> results = new ArrayList<InvoiceItem>();
+        for (InvoiceItem item : invoiceItems) {
+            if ( clazz.isInstance(item) ) {
+                results.add(item);
+            }
+        }
+        return results;
+    }
+
+    @Override
+    public int getNumberOfItems() {
+        return invoiceItems.size();
+    }
+
+    @Override
+    public boolean addPayment(final InvoicePayment payment) {
+        return payments.add(payment);
+    }
+
+    @Override
+    public boolean addPayments(final List<InvoicePayment> payments) {
+        return this.payments.addAll(payments);
+    }
+
+    @Override
+    public List<InvoicePayment> getPayments() {
+        return payments;
+    }
+
+    @Override
+    public int getNumberOfPayments() {
+        return payments.size();
+    }
+
+    @Override
+    public UUID getId() {
+        return id;
+    }
+
+    @Override
+    public UUID getAccountId() {
+        return accountId;
+    }
+
+    /**
+     * null until retrieved from the database
+     * @return the invoice number
+     */
+    @Override
+    public Integer getInvoiceNumber() {
+        return invoiceNumber;
+    }
+
+    @Override
+    public DateTime getInvoiceDate() {
+        return invoiceDate;
+    }
+
+    @Override
+    public DateTime getTargetDate() {
+        return targetDate;
+    }
+
+    @Override
+    public Currency getCurrency() {
+        return currency;
+    }
+    
+    @Override
+    public boolean isMigrationInvoice() {
+		return migrationInvoice;
+	}
+
+	@Override
+    public DateTime getLastPaymentAttempt() {
+        DateTime lastPaymentAttempt = null;
+
+        for (final InvoicePayment paymentAttempt : payments) {
+            DateTime paymentAttemptDate = paymentAttempt.getPaymentAttemptDate();
+            if (lastPaymentAttempt == null) {
+                lastPaymentAttempt = paymentAttemptDate;
+            }
+
+            if (lastPaymentAttempt.isBefore(paymentAttemptDate)) {
+                lastPaymentAttempt = paymentAttemptDate;
+            }
+        }
+
+        return lastPaymentAttempt;
+    }
+
+    @Override
+    public BigDecimal getAmountPaid() {
+        BigDecimal amountPaid = BigDecimal.ZERO;
+        for (final InvoicePayment payment : payments) {
+            if (payment.getAmount() != null) {
+                amountPaid = amountPaid.add(payment.getAmount());
+            }
+        }
+        return amountPaid;
+    }
+
+    @Override
+    public BigDecimal getTotalAmount() {
+        BigDecimal result = BigDecimal.ZERO;
+    
+        for(InvoiceItem i : invoiceItems) {
+            result = result.add(i.getAmount());
+        }
+        return result;
+    }
+
+    @Override
+    public BigDecimal getBalance() {
+        return getTotalAmount().subtract(getAmountPaid());
+    }
+
+    @Override
+    public boolean isDueForPayment(final DateTime targetDate, final int numberOfDays) {
+        if (getTotalAmount().compareTo(BigDecimal.ZERO) == 0) {
+            return false;
+        }
+
+        DateTime lastPaymentAttempt = getLastPaymentAttempt();
+        if (lastPaymentAttempt == null) {
+            return true;
+        }
+
+        return !lastPaymentAttempt.plusDays(numberOfDays).isAfter(targetDate);
+    }
+
+    @Override
+    public String toString() {
+        return "DefaultInvoice [items=" + invoiceItems + ", payments=" + payments + ", id=" + id + ", accountId=" + accountId + ", invoiceDate=" + invoiceDate + ", targetDate=" + targetDate + ", currency=" + currency + ", amountPaid=" + getAmountPaid() + ", lastPaymentAttempt=" + getLastPaymentAttempt() + "]";
+    }
+
+    @Override
+    public ObjectType getObjectType() {
+        return ObjectType.RECURRING_INVOICE_ITEM;
+    }
+
+    @Override
+    public void saveFieldValue(String fieldName, String fieldValue, CallContext context) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void saveFields(List<CustomField> fields, CallContext context) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void clearPersistedFields(CallContext context) {
+        throw new UnsupportedOperationException();
+    }
+}
+
diff --git a/payment/src/test/java/com/ning/billing/payment/MockInvoiceCreationEvent.java b/payment/src/test/java/com/ning/billing/payment/MockInvoiceCreationEvent.java
new file mode 100644
index 0000000..9a700cd
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/MockInvoiceCreationEvent.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright 2010-2011 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.payment;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import org.codehaus.jackson.annotate.JsonCreator;
+import org.codehaus.jackson.annotate.JsonIgnore;
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.joda.time.DateTime;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.InvoiceCreationEvent;
+import com.ning.billing.util.bus.BusEvent.BusEventType;
+
+public class MockInvoiceCreationEvent implements InvoiceCreationEvent {
+	
+    private final UUID invoiceId;
+    private final UUID accountId;
+    private final BigDecimal amountOwed;
+    private final Currency currency;
+    private final DateTime invoiceCreationDate;
+    private final UUID userToken;
+
+    @JsonCreator
+    public MockInvoiceCreationEvent(@JsonProperty("invoiceId") UUID invoiceId,
+            @JsonProperty("accountId") UUID accountId,
+            @JsonProperty("amountOwed") BigDecimal amountOwed,
+            @JsonProperty("currency") Currency currency,
+            @JsonProperty("invoiceCreationDate") DateTime invoiceCreationDate,
+            @JsonProperty("userToken") UUID userToken) {
+        this.invoiceId = invoiceId;
+        this.accountId = accountId;
+        this.amountOwed = amountOwed;
+        this.currency = currency;
+        this.invoiceCreationDate = invoiceCreationDate;
+        this.userToken = userToken;
+    }
+
+    @JsonIgnore
+	@Override
+	public BusEventType getBusEventType() {
+		return BusEventType.INVOICE_CREATION;
+	}
+
+	@Override
+	public UUID getUserToken() {
+		return userToken;
+	}
+
+    @Override
+    public UUID getInvoiceId() {
+        return invoiceId;
+    }
+
+    @Override
+    public UUID getAccountId() {
+        return accountId;
+    }
+
+    @Override
+    public BigDecimal getAmountOwed() {
+        return amountOwed;
+    }
+
+    @Override
+    public Currency getCurrency() {
+        return currency;
+    }
+
+    @Override
+    public DateTime getInvoiceCreationDate() {
+        return invoiceCreationDate;
+    }
+
+    @Override
+    public String toString() {
+        return "DefaultInvoiceCreationNotification [invoiceId=" + invoiceId + ", accountId=" + accountId + ", amountOwed=" + amountOwed + ", currency=" + currency + ", invoiceCreationDate=" + invoiceCreationDate + "]";
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result
+                + ((accountId == null) ? 0 : accountId.hashCode());
+        result = prime * result
+                + ((amountOwed == null) ? 0 : amountOwed.hashCode());
+        result = prime * result
+                + ((currency == null) ? 0 : currency.hashCode());
+        result = prime
+                * result
+                + ((invoiceCreationDate == null) ? 0 : invoiceCreationDate
+                        .hashCode());
+        result = prime * result
+                + ((invoiceId == null) ? 0 : invoiceId.hashCode());
+        result = prime * result
+                + ((userToken == null) ? 0 : userToken.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        MockInvoiceCreationEvent other = (MockInvoiceCreationEvent) obj;
+        if (accountId == null) {
+            if (other.accountId != null)
+                return false;
+        } else if (!accountId.equals(other.accountId))
+            return false;
+        if (amountOwed == null) {
+            if (other.amountOwed != null)
+                return false;
+        } else if (!amountOwed.equals(other.amountOwed))
+            return false;
+        if (currency != other.currency)
+            return false;
+        if (invoiceCreationDate == null) {
+            if (other.invoiceCreationDate != null)
+                return false;
+        } else if (invoiceCreationDate.compareTo(other.invoiceCreationDate) != 0)
+            return false;
+        if (invoiceId == null) {
+            if (other.invoiceId != null)
+                return false;
+        } else if (!invoiceId.equals(other.invoiceId))
+            return false;
+        if (userToken == null) {
+            if (other.userToken != null)
+                return false;
+        } else if (!userToken.equals(other.userToken))
+            return false;
+        return true;
+    }
+    
+    
+}
diff --git a/payment/src/test/java/com/ning/billing/payment/MockRecurringInvoiceItem.java b/payment/src/test/java/com/ning/billing/payment/MockRecurringInvoiceItem.java
new file mode 100644
index 0000000..93c90fa
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/MockRecurringInvoiceItem.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright 2010-2011 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.payment;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.util.entity.EntityBase;
+
+import org.apache.commons.lang.NotImplementedException;
+import org.joda.time.DateTime;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import javax.annotation.Nullable;
+
+public class MockRecurringInvoiceItem extends EntityBase implements InvoiceItem {
+    private final BigDecimal rate;
+    private final UUID reversedItemId;
+    protected final UUID invoiceId;
+    protected final UUID accountId;
+    protected final UUID subscriptionId;
+    protected final UUID bundleId;
+    protected final String planName;
+    protected final String phaseName;
+    protected final DateTime startDate;
+    protected final DateTime endDate;
+    protected final BigDecimal amount;
+    protected final Currency currency;
+
+
+    public MockRecurringInvoiceItem(UUID invoiceId, UUID accountId, UUID bundleId, UUID subscriptionId, String planName, String phaseName,
+                                DateTime startDate, DateTime endDate,
+                                BigDecimal amount, BigDecimal rate,
+                                Currency currency) { 
+        this(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, amount, currency, rate, null);
+
+    }
+
+    public MockRecurringInvoiceItem(UUID invoiceId, UUID accountId, UUID bundleId, UUID subscriptionId, String planName, String phaseName,
+                                DateTime startDate, DateTime endDate,
+                                BigDecimal amount, BigDecimal rate,
+                                Currency currency, UUID reversedItemId) {
+        this(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate,
+                amount, currency, rate, reversedItemId);
+    }
+
+    public MockRecurringInvoiceItem(UUID id, UUID invoiceId, UUID accountId, UUID bundleId, UUID subscriptionId, String planName, String phaseName,
+                                DateTime startDate, DateTime endDate,
+                                BigDecimal amount, BigDecimal rate,
+                                Currency currency) {
+        this(id, invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, amount, currency, rate, null);
+
+    }
+
+    public MockRecurringInvoiceItem(UUID id, UUID invoiceId, UUID accountId, UUID bundleId, UUID subscriptionId, String planName, String phaseName,
+                                DateTime startDate, DateTime endDate,
+                                BigDecimal amount, BigDecimal rate,
+                                Currency currency, UUID reversedItemId) {
+        this(id, invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, amount, currency, rate, reversedItemId);
+    }
+    public MockRecurringInvoiceItem(UUID invoiceId, UUID accountId, UUID bundleId, UUID subscriptionId, String planName, String phaseName,
+            DateTime startDate, DateTime endDate, BigDecimal amount, Currency currency, BigDecimal rate, UUID reversedItemId){
+        this(UUID.randomUUID(), invoiceId, accountId, bundleId, subscriptionId, planName, phaseName,
+                startDate, endDate, amount, currency, rate, reversedItemId);
+    }
+
+
+    public MockRecurringInvoiceItem(UUID id, UUID invoiceId, UUID accountId, @Nullable UUID bundleId, @Nullable UUID subscriptionId, String planName, String phaseName,
+            DateTime startDate, DateTime endDate, BigDecimal amount, Currency currency,
+            BigDecimal rate, UUID reversedItemId) {
+        super(id);
+        this.invoiceId = invoiceId;
+        this.accountId = accountId;
+        this.subscriptionId = subscriptionId;
+        this.bundleId = bundleId;
+        this.planName = planName;
+        this.phaseName = phaseName;
+        this.startDate = startDate;
+        this.endDate = endDate;
+        this.amount = amount;
+        this.currency = currency;
+        this.rate = rate;
+        this.reversedItemId = reversedItemId;
+    }
+
+    @Override
+    public UUID getId() {
+        return id;
+    }
+
+    @Override
+    public UUID getInvoiceId() {
+        return invoiceId;
+    }
+
+    @Override
+    public UUID getBundleId() {
+        return bundleId;
+    }
+    
+    public UUID getAccountId() {
+        return accountId;
+    }
+
+    @Override
+    public UUID getSubscriptionId() {
+        return subscriptionId;
+    }
+
+    @Override
+    public String getPlanName() {
+        return planName;
+    }
+
+    @Override
+    public String getPhaseName() {
+        return phaseName;
+    }
+
+    @Override
+    public BigDecimal getAmount() {
+        return amount;
+    }
+
+    @Override
+    public DateTime getStartDate() {
+        return startDate;
+    }
+
+    @Override
+    public DateTime getEndDate() {
+        return endDate;
+    }
+
+    @Override
+    public Currency getCurrency() {
+        return currency;
+    }
+    @Override
+    public InvoiceItem asReversingItem() {
+        throw new NotImplementedException();
+    }
+
+    @Override
+    public String getDescription() {
+        return String.format("%s from %s to %s", phaseName, startDate.toString(), endDate.toString());
+    }
+
+    public UUID getReversedItemId() {
+        return reversedItemId;
+    }
+
+    public boolean reversesItem() {
+        return (reversedItemId != null);
+    }
+
+    public BigDecimal getRate() {
+        return rate;
+    }
+
+    @Override
+    public int compareTo(InvoiceItem item) {
+        if (item == null) {
+            return -1;
+        }
+        if (!(item instanceof MockRecurringInvoiceItem)) {
+            return -1;
+        }
+
+        MockRecurringInvoiceItem that = (MockRecurringInvoiceItem) item;
+        int compareAccounts = getAccountId().compareTo(that.getAccountId());
+        if (compareAccounts == 0 && bundleId != null) {
+            int compareBundles = getBundleId().compareTo(that.getBundleId());
+            if (compareBundles == 0 && subscriptionId != null) {
+
+                int compareSubscriptions = getSubscriptionId().compareTo(that.getSubscriptionId());
+                if (compareSubscriptions == 0) {
+                    int compareStartDates = getStartDate().compareTo(that.getStartDate());
+                    if (compareStartDates == 0) {
+                        return getEndDate().compareTo(that.getEndDate());
+                    } else {
+                        return compareStartDates;
+                    }
+                } else {
+                    return compareSubscriptions;
+                }
+            } else {
+                return compareBundles;
+            }
+        } else {
+            return compareAccounts;
+        }
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        MockRecurringInvoiceItem that = (MockRecurringInvoiceItem) o;
+
+        if (accountId.compareTo(that.accountId) != 0) return false;
+        if (amount.compareTo(that.amount) != 0) return false;
+        if (currency != that.currency) return false;
+        if (startDate.compareTo(that.startDate) != 0) return false;
+        if (endDate.compareTo(that.endDate) != 0) return false;
+        if (!phaseName.equals(that.phaseName)) return false;
+        if (!planName.equals(that.planName)) return false;
+        if (rate.compareTo(that.rate) != 0) return false;
+        if (reversedItemId != null ? !reversedItemId.equals(that.reversedItemId) : that.reversedItemId != null)
+            return false;
+        if (subscriptionId != null ? !subscriptionId.equals(that.subscriptionId) : that.subscriptionId != null)
+            return false;
+        if (bundleId != null ? !bundleId.equals(that.bundleId) : that.bundleId != null)
+            return false;
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = accountId.hashCode();
+        result = 31 * result + (subscriptionId != null ? subscriptionId.hashCode() : 0);
+        result = 31 * result + (bundleId != null ? bundleId.hashCode() : 0);
+        result = 31 * result + planName.hashCode();
+        result = 31 * result + phaseName.hashCode();
+        result = 31 * result + startDate.hashCode();
+        result = 31 * result + endDate.hashCode();
+        result = 31 * result + amount.hashCode();
+        result = 31 * result + rate.hashCode();
+        result = 31 * result + currency.hashCode();
+        result = 31 * result + (reversedItemId != null ? reversedItemId.hashCode() : 0);
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+
+        sb.append(phaseName).append(", ");
+        sb.append(startDate.toString()).append(", ");
+        sb.append(endDate.toString()).append(", ");
+        sb.append(amount.toString()).append(", ");
+        sb.append("subscriptionId = ").append(subscriptionId == null ? null : subscriptionId.toString()).append(", ");
+        sb.append("bundleId = ").append(bundleId == null ? null : bundleId.toString()).append(", ");
+
+        return sb.toString();
+    }
+}
diff --git a/payment/src/test/java/com/ning/billing/payment/provider/MockPaymentProviderPlugin.java b/payment/src/test/java/com/ning/billing/payment/provider/MockPaymentProviderPlugin.java
index 47713bb..f3cb154 100644
--- a/payment/src/test/java/com/ning/billing/payment/provider/MockPaymentProviderPlugin.java
+++ b/payment/src/test/java/com/ning/billing/payment/provider/MockPaymentProviderPlugin.java
@@ -24,26 +24,27 @@ import java.util.UUID;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicBoolean;
 
-import com.google.inject.Inject;
-import com.ning.billing.util.clock.Clock;
 import org.apache.commons.lang.RandomStringUtils;
-import org.joda.time.DateTime;
 
 import com.google.common.base.Predicate;
 import com.google.common.collect.Collections2;
+import com.google.inject.Inject;
 import com.ning.billing.account.api.Account;
 import com.ning.billing.invoice.api.Invoice;
 import com.ning.billing.payment.api.CreditCardPaymentMethodInfo;
+import com.ning.billing.payment.api.DefaultPaymentErrorEvent;
+import com.ning.billing.payment.api.DefaultPaymentInfoEvent;
 import com.ning.billing.payment.api.Either;
-import com.ning.billing.payment.api.PaymentError;
-import com.ning.billing.payment.api.PaymentInfo;
+import com.ning.billing.payment.api.PaymentErrorEvent;
+import com.ning.billing.payment.api.PaymentInfoEvent;
 import com.ning.billing.payment.api.PaymentMethodInfo;
 import com.ning.billing.payment.api.PaymentProviderAccount;
 import com.ning.billing.payment.api.PaypalPaymentMethodInfo;
+import com.ning.billing.util.clock.Clock;
 
 public class MockPaymentProviderPlugin implements PaymentProviderPlugin {
     private final AtomicBoolean makeNextInvoiceFail = new AtomicBoolean(false);
-    private final Map<String, PaymentInfo> payments = new ConcurrentHashMap<String, PaymentInfo>();
+    private final Map<UUID, PaymentInfoEvent> payments = new ConcurrentHashMap<UUID, PaymentInfoEvent>();
     private final Map<String, PaymentProviderAccount> accounts = new ConcurrentHashMap<String, PaymentProviderAccount>();
     private final Map<String, PaymentMethodInfo> paymentMethods = new ConcurrentHashMap<String, PaymentMethodInfo>();
     private final Clock clock;
@@ -58,32 +59,31 @@ public class MockPaymentProviderPlugin implements PaymentProviderPlugin {
     }
 
     @Override
-    public Either<PaymentError, PaymentInfo> processInvoice(Account account, Invoice invoice) {
+    public Either<PaymentErrorEvent, PaymentInfoEvent> processInvoice(Account account, Invoice invoice) {
         if (makeNextInvoiceFail.getAndSet(false)) {
-            return Either.left(new PaymentError("unknown", "test error", account.getId(), invoice.getId()));
+            return Either.left((PaymentErrorEvent) new DefaultPaymentErrorEvent("unknown", "test error", account.getId(), invoice.getId(), null));
         }
         else {
-            PaymentInfo payment = new PaymentInfo.Builder().setPaymentId(UUID.randomUUID().toString())
+            PaymentInfoEvent payment = new DefaultPaymentInfoEvent.Builder().setId(UUID.randomUUID())
                                                  .setAmount(invoice.getBalance())
                                                  .setStatus("Processed")
                                                  .setBankIdentificationNumber("1234")
-                                                 .setCreatedDate(clock.getUTCNow())
                                                  .setEffectiveDate(clock.getUTCNow())
                                                  .setPaymentNumber("12345")
                                                  .setReferenceId("12345")
                                                  .setType("Electronic")
                                                  .build();
-            payments.put(payment.getPaymentId(), payment);
+            payments.put(payment.getId(), payment);
             return Either.right(payment);
         }
     }
 
     @Override
-    public Either<PaymentError, PaymentInfo> getPaymentInfo(String paymentId) {
-        PaymentInfo payment = payments.get(paymentId);
+    public Either<PaymentErrorEvent, PaymentInfoEvent> getPaymentInfo(String paymentId) {
+        PaymentInfoEvent payment = payments.get(paymentId);
 
         if (payment == null) {
-            return Either.left(new PaymentError("notfound", "No payment found for id " + paymentId, null, null));
+            return Either.left((PaymentErrorEvent) new DefaultPaymentErrorEvent("notfound", "No payment found for id " + paymentId, null, null, null));
         }
         else {
             return Either.right(payment);
@@ -91,7 +91,7 @@ public class MockPaymentProviderPlugin implements PaymentProviderPlugin {
     }
 
     @Override
-    public Either<PaymentError, String> createPaymentProviderAccount(Account account) {
+    public Either<PaymentErrorEvent, String> createPaymentProviderAccount(Account account) {
         if (account != null) {
             String id = String.valueOf(RandomStringUtils.randomAlphanumeric(10));
             accounts.put(account.getExternalKey(),
@@ -102,22 +102,22 @@ public class MockPaymentProviderPlugin implements PaymentProviderPlugin {
             return Either.right(id);
         }
         else {
-            return Either.left(new PaymentError("unknown", "Did not get account to create payment provider account", null, null));
+            return Either.left((PaymentErrorEvent)  new DefaultPaymentErrorEvent("unknown", "Did not get account to create payment provider account", null, null, null));
         }
     }
 
     @Override
-    public Either<PaymentError, PaymentProviderAccount> getPaymentProviderAccount(String accountKey) {
+    public Either<PaymentErrorEvent, PaymentProviderAccount> getPaymentProviderAccount(String accountKey) {
         if (accountKey != null) {
             return Either.right(accounts.get(accountKey));
         }
         else {
-            return Either.left(new PaymentError("unknown", "Did not get account for accountKey " + accountKey, null, null));
+            return Either.left((PaymentErrorEvent) new DefaultPaymentErrorEvent("unknown", "Did not get account for accountKey " + accountKey, null, null, null));
         }
     }
 
     @Override
-    public Either<PaymentError, String> addPaymentMethod(String accountKey, PaymentMethodInfo paymentMethod) {
+    public Either<PaymentErrorEvent, String> addPaymentMethod(String accountKey, PaymentMethodInfo paymentMethod) {
         if (paymentMethod != null) {
             PaymentProviderAccount account = accounts.get(accountKey);
 
@@ -144,7 +144,7 @@ public class MockPaymentProviderPlugin implements PaymentProviderPlugin {
                     realPaymentMethod = new CreditCardPaymentMethodInfo.Builder(ccPaymentMethod).setId(paymentMethodId).build();
                 }
                 if (realPaymentMethod == null) {
-                    return Either.left(new PaymentError("unsupported", "Payment method " + paymentMethod.getType() + " not supported by the plugin", null, null));
+                    return Either.left((PaymentErrorEvent)  new DefaultPaymentErrorEvent("unsupported", "Payment method " + paymentMethod.getType() + " not supported by the plugin", null, null, null));
                 }
                 else {
                     if (shouldBeDefault) {
@@ -155,11 +155,11 @@ public class MockPaymentProviderPlugin implements PaymentProviderPlugin {
                 }
             }
                 else {
-                    return Either.left(new PaymentError("noaccount", "Could not retrieve account for accountKey " + accountKey, null, null));
+                    return Either.left((PaymentErrorEvent)  new DefaultPaymentErrorEvent("noaccount", "Could not retrieve account for accountKey " + accountKey, null, null, null));
                 }
         }
         else {
-            return Either.left(new PaymentError("unknown", "Could not create add payment method " + paymentMethod + " for " + accountKey, null, null));
+            return Either.left((PaymentErrorEvent)  new DefaultPaymentErrorEvent("unknown", "Could not create add payment method " + paymentMethod + " for " + accountKey, null, null, null));
         }
     }
 
@@ -190,7 +190,7 @@ public class MockPaymentProviderPlugin implements PaymentProviderPlugin {
     }
 
     @Override
-    public Either<PaymentError, PaymentMethodInfo> updatePaymentMethod(String accountKey, PaymentMethodInfo paymentMethod) {
+    public Either<PaymentErrorEvent, PaymentMethodInfo> updatePaymentMethod(String accountKey, PaymentMethodInfo paymentMethod) {
         if (paymentMethod != null) {
             PaymentMethodInfo realPaymentMethod = null;
 
@@ -203,7 +203,7 @@ public class MockPaymentProviderPlugin implements PaymentProviderPlugin {
                 realPaymentMethod = new CreditCardPaymentMethodInfo.Builder(ccPaymentMethod).build();
             }
             if (realPaymentMethod == null) {
-                return Either.left(new PaymentError("unsupported", "Payment method " + paymentMethod.getType() + " not supported by the plugin", null, null));
+                return Either.left((PaymentErrorEvent)  new DefaultPaymentErrorEvent("unsupported", "Payment method " + paymentMethod.getType() + " not supported by the plugin", null, null, null));
             }
             else {
                 paymentMethods.put(paymentMethod.getId(), paymentMethod);
@@ -211,37 +211,37 @@ public class MockPaymentProviderPlugin implements PaymentProviderPlugin {
             }
         }
         else {
-            return Either.left(new PaymentError("unknown", "Could not create add payment method " + paymentMethod + " for " + accountKey, null, null));
+            return Either.left((PaymentErrorEvent)  new DefaultPaymentErrorEvent("unknown", "Could not create add payment method " + paymentMethod + " for " + accountKey, null, null, null));
         }
     }
 
     @Override
-    public Either<PaymentError, Void> deletePaymentMethod(String accountKey, String paymentMethodId) {
+    public Either<PaymentErrorEvent, Void> deletePaymentMethod(String accountKey, String paymentMethodId) {
         PaymentMethodInfo paymentMethodInfo = paymentMethods.get(paymentMethodId);
         if (paymentMethodInfo != null) {
             if (Boolean.FALSE.equals(paymentMethodInfo.getDefaultMethod()) || paymentMethodInfo.getDefaultMethod() == null) {
                 if (paymentMethods.remove(paymentMethodId) == null) {
-                    return Either.left(new PaymentError("unknown", "Did not get any result back", null, null));
+                    return Either.left((PaymentErrorEvent) new DefaultPaymentErrorEvent("unknown", "Did not get any result back", null, null, null));
                 }
             }
             else {
-                return Either.left(new PaymentError("error", "Cannot delete default payment method", null, null));
+                return Either.left((PaymentErrorEvent) new DefaultPaymentErrorEvent("error", "Cannot delete default payment method", null, null, null));
             }
         }
         return Either.right(null);
     }
 
     @Override
-    public Either<PaymentError, PaymentMethodInfo> getPaymentMethodInfo(String paymentMethodId) {
+    public Either<PaymentErrorEvent, PaymentMethodInfo> getPaymentMethodInfo(String paymentMethodId) {
         if (paymentMethodId == null) {
-            return Either.left(new PaymentError("unknown", "Could not retrieve payment method for paymentMethodId " + paymentMethodId, null, null));
+            return Either.left((PaymentErrorEvent) new DefaultPaymentErrorEvent("unknown", "Could not retrieve payment method for paymentMethodId " + paymentMethodId, null, null, null));
         }
 
         return Either.right(paymentMethods.get(paymentMethodId));
     }
 
     @Override
-    public Either<PaymentError, List<PaymentMethodInfo>> getPaymentMethods(final String accountKey) {
+    public Either<PaymentErrorEvent, List<PaymentMethodInfo>> getPaymentMethods(final String accountKey) {
 
         Collection<PaymentMethodInfo> filteredPaymentMethods = Collections2.filter(paymentMethods.values(), new Predicate<PaymentMethodInfo>() {
             @Override
@@ -254,20 +254,23 @@ public class MockPaymentProviderPlugin implements PaymentProviderPlugin {
     }
 
     @Override
-    public Either<PaymentError, Void> updatePaymentGateway(String accountKey) {
+    public Either<PaymentErrorEvent, Void> updatePaymentGateway(String accountKey) {
         return Either.right(null);
     }
 
     @Override
-    public Either<PaymentError, Void> updatePaymentProviderAccountExistingContact(Account account) {
+    public Either<PaymentErrorEvent, Void> updatePaymentProviderAccountExistingContact(Account account) {
         // nothing to do here
         return Either.right(null);
     }
 
     @Override
-    public Either<PaymentError, Void> updatePaymentProviderAccountWithNewContact(Account account) {
-        // nothing to do here
+    public Either<PaymentErrorEvent, Void> updatePaymentProviderAccountWithNewContact(Account account) {
         return Either.right(null);
     }
 
+    @Override
+    public List<Either<PaymentErrorEvent, PaymentInfoEvent>> processRefund(Account account) {
+        return null;
+    }
 }
diff --git a/payment/src/test/java/com/ning/billing/payment/setup/PaymentTestModuleWithEmbeddedDb.java b/payment/src/test/java/com/ning/billing/payment/setup/PaymentTestModuleWithEmbeddedDb.java
index 97aa31e..da38b34 100644
--- a/payment/src/test/java/com/ning/billing/payment/setup/PaymentTestModuleWithEmbeddedDb.java
+++ b/payment/src/test/java/com/ning/billing/payment/setup/PaymentTestModuleWithEmbeddedDb.java
@@ -20,7 +20,8 @@ import org.apache.commons.collections.MapUtils;
 
 import com.google.common.collect.ImmutableMap;
 import com.google.inject.Provider;
-import com.ning.billing.entitlement.api.billing.EntitlementBillingApi;
+import com.ning.billing.config.PaymentConfig;
+import com.ning.billing.junction.api.BillingApi;
 import com.ning.billing.mock.BrainDeadProxyFactory;
 import com.ning.billing.payment.provider.MockPaymentProviderPluginModule;
 import com.ning.billing.util.bus.Bus;
@@ -29,10 +30,10 @@ import com.ning.billing.util.notificationq.DefaultNotificationQueueService;
 import com.ning.billing.util.notificationq.NotificationQueueService;
 
 public class PaymentTestModuleWithEmbeddedDb extends PaymentModule {
-	public static class MockProvider implements Provider<EntitlementBillingApi> {
+	public static class MockProvider implements Provider<BillingApi> {
 		@Override
-		public EntitlementBillingApi get() {
-			return BrainDeadProxyFactory.createBrainDeadProxyFor(EntitlementBillingApi.class);
+		public BillingApi get() {
+			return BrainDeadProxyFactory.createBrainDeadProxyFor(BillingApi.class);
 		}
 
 	}
@@ -50,7 +51,6 @@ public class PaymentTestModuleWithEmbeddedDb extends PaymentModule {
     protected void configure() {
         super.configure();
         bind(Bus.class).to(InMemoryBus.class).asEagerSingleton();
-        bind(EntitlementBillingApi.class).toProvider(MockProvider.class);
         bind(NotificationQueueService.class).to(DefaultNotificationQueueService.class).asEagerSingleton();
     }
 }
diff --git a/payment/src/test/java/com/ning/billing/payment/setup/PaymentTestModuleWithMocks.java b/payment/src/test/java/com/ning/billing/payment/setup/PaymentTestModuleWithMocks.java
index c8f79bc..14f2ab9 100644
--- a/payment/src/test/java/com/ning/billing/payment/setup/PaymentTestModuleWithMocks.java
+++ b/payment/src/test/java/com/ning/billing/payment/setup/PaymentTestModuleWithMocks.java
@@ -20,25 +20,23 @@ import org.apache.commons.collections.MapUtils;
 
 import com.google.common.collect.ImmutableMap;
 import com.google.inject.Provider;
-import com.ning.billing.account.dao.AccountDao;
-import com.ning.billing.account.dao.MockAccountDao;
-import com.ning.billing.entitlement.api.billing.EntitlementBillingApi;
-import com.ning.billing.invoice.dao.InvoiceDao;
-import com.ning.billing.invoice.dao.MockInvoiceDao;
+import com.ning.billing.config.PaymentConfig;
+import com.ning.billing.junction.api.BillingApi;
 import com.ning.billing.mock.BrainDeadProxyFactory;
+import com.ning.billing.mock.glue.MockInvoiceModule;
+import com.ning.billing.mock.glue.MockNotificationQueueModule;
+import com.ning.billing.mock.glue.TestDbiModule;
 import com.ning.billing.payment.dao.MockPaymentDao;
 import com.ning.billing.payment.dao.PaymentDao;
 import com.ning.billing.payment.provider.MockPaymentProviderPluginModule;
-import com.ning.billing.util.bus.Bus;
-import com.ning.billing.util.bus.InMemoryBus;
-import com.ning.billing.util.notificationq.MockNotificationQueueService;
-import com.ning.billing.util.notificationq.NotificationQueueService;
+import com.ning.billing.util.glue.BusModule;
+import com.ning.billing.util.glue.BusModule.BusType;
 
 public class PaymentTestModuleWithMocks extends PaymentModule {
-	public static class MockProvider implements Provider<EntitlementBillingApi> {
+	public static class MockProvider implements Provider<BillingApi> {
 		@Override
-		public EntitlementBillingApi get() {
-			return BrainDeadProxyFactory.createBrainDeadProxyFor(EntitlementBillingApi.class);
+		public BillingApi get() {
+			return BrainDeadProxyFactory.createBrainDeadProxyFor(BillingApi.class);
 		}
 
 	}
@@ -46,7 +44,7 @@ public class PaymentTestModuleWithMocks extends PaymentModule {
 
     public PaymentTestModuleWithMocks() {
         super(MapUtils.toProperties(ImmutableMap.of("killbill.payment.provider.default", "my-mock",
-                                                    "killbill.payment.engine.events.off", "false")));
+                "killbill.payment.engine.events.off", "false")));
     }
 
     @Override
@@ -62,12 +60,9 @@ public class PaymentTestModuleWithMocks extends PaymentModule {
     @Override
     protected void configure() {
         super.configure();
-        bind(Bus.class).to(InMemoryBus.class).asEagerSingleton();
-        bind(MockAccountDao.class).asEagerSingleton();
-        bind(AccountDao.class).to(MockAccountDao.class);
-        bind(MockInvoiceDao.class).asEagerSingleton();
-        bind(InvoiceDao.class).to(MockInvoiceDao.class);
-        bind(EntitlementBillingApi.class).toProvider( MockProvider.class );
-        bind(NotificationQueueService.class).to(MockNotificationQueueService.class).asEagerSingleton();
+        install(new BusModule(BusType.MEMORY));
+        install(new MockNotificationQueueModule());
+        install(new MockInvoiceModule());
+        install(new TestDbiModule());
     }
 }
diff --git a/payment/src/test/java/com/ning/billing/payment/TestHelper.java b/payment/src/test/java/com/ning/billing/payment/TestHelper.java
index 6112fc2..33dc142 100644
--- a/payment/src/test/java/com/ning/billing/payment/TestHelper.java
+++ b/payment/src/test/java/com/ning/billing/payment/TestHelper.java
@@ -16,83 +16,70 @@
 
 package com.ning.billing.payment;
 
-import java.math.BigDecimal;
 import java.util.UUID;
 
-import com.ning.billing.util.callcontext.CallContext;
-import com.ning.billing.util.callcontext.CallOrigin;
-import com.ning.billing.util.callcontext.UserType;
-import com.ning.billing.util.callcontext.CallContextFactory;
-import com.ning.billing.util.entity.EntityPersistenceException;
 import org.apache.commons.lang.RandomStringUtils;
 import org.joda.time.DateTime;
-import org.joda.time.DateTimeZone;
 
 import com.google.inject.Inject;
 import com.ning.billing.account.api.Account;
-import com.ning.billing.account.api.user.AccountBuilder;
-import com.ning.billing.account.dao.AccountDao;
+import com.ning.billing.account.api.AccountUserApi;
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceCreationEvent;
 import com.ning.billing.invoice.api.InvoiceItem;
-import com.ning.billing.invoice.dao.InvoiceDao;
-import com.ning.billing.invoice.model.DefaultInvoice;
-import com.ning.billing.invoice.model.RecurringInvoiceItem;
+import com.ning.billing.invoice.api.InvoicePaymentApi;
+import com.ning.billing.mock.BrainDeadProxyFactory;
+import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
+import com.ning.billing.util.bus.Bus;
+import com.ning.billing.util.bus.Bus.EventBusException;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.callcontext.CallContextFactory;
+import com.ning.billing.util.callcontext.CallOrigin;
+import com.ning.billing.util.callcontext.UserType;
+import com.ning.billing.util.entity.EntityPersistenceException;
 
 public class TestHelper {
-    protected final AccountDao accountDao;
-    protected final InvoiceDao invoiceDao;
+    protected final AccountUserApi accountUserApi;
+    protected final InvoicePaymentApi invoicePaymentApi;
     private final CallContext context;
+    private final Bus eventBus;
 
     @Inject
-    public TestHelper(CallContextFactory factory, AccountDao accountDao, InvoiceDao invoiceDao) {
-        this.accountDao = accountDao;
-        this.invoiceDao = invoiceDao;
+    public TestHelper(CallContextFactory factory, AccountUserApi accountUserApi, InvoicePaymentApi invoicePaymentApi, Bus eventBus) {
+        this.eventBus = eventBus;
+        this.accountUserApi = accountUserApi;
+        this.invoicePaymentApi = invoicePaymentApi;
         context = factory.createCallContext("Princess Buttercup", CallOrigin.TEST, UserType.TEST);
     }
 
     // These helper methods can be overridden in a plugin implementation
     public Account createTestCreditCardAccount() throws EntityPersistenceException {
-        final String name = "First" + RandomStringUtils.randomAlphanumeric(5) + " " + "Last" + RandomStringUtils.randomAlphanumeric(5);
-        final String externalKey = RandomStringUtils.randomAlphanumeric(10);
-        final Account account = new AccountBuilder(UUID.randomUUID()).name(name)
-                                                                     .firstNameLength(name.length())
-                                                                     .externalKey(externalKey)
-                                                                     .phone("123-456-7890")
-                                                                     .email("ccuser" + RandomStringUtils.randomAlphanumeric(8) + "@example.com")
-                                                                     .currency(Currency.USD)
-                                                                     .billingCycleDay(1)
-                                                                     .build();
-        accountDao.create(account, context);
+        final Account account = createTestAccount("ccuser" + RandomStringUtils.randomAlphanumeric(8) + "@example.com");
+        ((ZombieControl)accountUserApi).addResult("getAccountById", account);
+        ((ZombieControl)accountUserApi).addResult("getAccountByKey", account);
         return account;
     }
 
     public Account createTestPayPalAccount() throws EntityPersistenceException {
-        final String name = "First" + RandomStringUtils.randomAlphanumeric(5) + " " + "Last" + RandomStringUtils.randomAlphanumeric(5);
-        final String externalKey = RandomStringUtils.randomAlphanumeric(10);
-        final Account account = new AccountBuilder(UUID.randomUUID()).name(name)
-                                                                     .firstNameLength(name.length())
-                                                                     .externalKey(externalKey)
-                                                                     .phone("123-456-7890")
-                                                                     .email("ppuser@example.com")
-                                                                     .currency(Currency.USD)
-                                                                     .billingCycleDay(1)
-                                                                     .build();
-        accountDao.create(account, context);
+        final Account account = createTestAccount("ppuser@example.com");
+        ((ZombieControl)accountUserApi).addResult("getAccountById", account);
+        ((ZombieControl)accountUserApi).addResult("getAccountByKey", account);
         return account;
     }
 
     public Invoice createTestInvoice(Account account,
                                      DateTime targetDate,
                                      Currency currency,
-                                     InvoiceItem... items) {
-        Invoice invoice = new DefaultInvoice(account.getId(), new DateTime(), targetDate, currency);
+                                     InvoiceItem... items) throws EventBusException {
+        Invoice invoice = new MockInvoice(account.getId(), new DateTime(), targetDate, currency);
 
         for (InvoiceItem item : items) {
-            if (item instanceof RecurringInvoiceItem) {
-                RecurringInvoiceItem recurringInvoiceItem = (RecurringInvoiceItem) item;
-                invoice.addInvoiceItem(new RecurringInvoiceItem(invoice.getId(),
+            if (item instanceof MockRecurringInvoiceItem) {
+                MockRecurringInvoiceItem recurringInvoiceItem = (MockRecurringInvoiceItem) item;
+                invoice.addInvoiceItem(new MockRecurringInvoiceItem(invoice.getId(),
                                                                account.getId(),
+                                                               recurringInvoiceItem.getBundleId(),
                                                                recurringInvoiceItem.getSubscriptionId(),
                                                                recurringInvoiceItem.getPlanName(),
                                                                recurringInvoiceItem.getPhaseName(),
@@ -103,17 +90,34 @@ public class TestHelper {
                                                                recurringInvoiceItem.getCurrency()));
             }
         }
-        invoiceDao.create(invoice, context);
+
+ //       invoiceTestApi.create(invoice, context);
+        ((ZombieControl)invoicePaymentApi).addResult("getInvoice", invoice);
+        InvoiceCreationEvent event = new MockInvoiceCreationEvent(invoice.getId(), invoice.getAccountId(),
+                invoice.getBalance(), invoice.getCurrency(),
+                invoice.getInvoiceDate(),
+                context.getUserToken());
+        
+        eventBus.post(event);
         return invoice;
     }
 
-    public Invoice createTestInvoice(Account account) {
-        final DateTime now = new DateTime(DateTimeZone.UTC);
-        final UUID subscriptionId = UUID.randomUUID();
-        final BigDecimal amount = new BigDecimal("10.00");
-        final InvoiceItem item = new RecurringInvoiceItem(null, account.getId(), subscriptionId, "test plan", "test phase", now, now.plusMonths(1),
-                amount, new BigDecimal("1.0"), Currency.USD);
+    public Account createTestAccount(String email) {
+        final String name = "First" + RandomStringUtils.randomAlphanumeric(5) + " " + "Last" + RandomStringUtils.randomAlphanumeric(5);
+        final String externalKey = RandomStringUtils.randomAlphanumeric(10);
 
-        return createTestInvoice(account, now, Currency.USD, item);
+        Account account = BrainDeadProxyFactory.createBrainDeadProxyFor(Account.class);
+        ZombieControl zombie = (ZombieControl) account;
+        zombie.addResult("getId", UUID.randomUUID());
+        zombie.addResult("getExternalKey", externalKey);
+        zombie.addResult("getName", name);
+        zombie.addResult("getFirstNameLength", 10);
+        zombie.addResult("getPhone", "123-456-7890");
+        zombie.addResult("getEmail", email);
+        zombie.addResult("getCurrency", Currency.USD);
+        zombie.addResult("getBillCycleDay", 1);
+        zombie.addResult("getPaymentProviderName", "");
+
+        return account;
     }
 }
diff --git a/payment/src/test/java/com/ning/billing/payment/TestRetryService.java b/payment/src/test/java/com/ning/billing/payment/TestRetryService.java
index 1379267..2c519d0 100644
--- a/payment/src/test/java/com/ning/billing/payment/TestRetryService.java
+++ b/payment/src/test/java/com/ning/billing/payment/TestRetryService.java
@@ -25,6 +25,7 @@ import java.util.Arrays;
 import java.util.List;
 import java.util.UUID;
 
+import com.ning.billing.payment.api.DefaultPaymentAttempt;
 import com.ning.billing.util.callcontext.CallContext;
 import com.ning.billing.util.callcontext.CallOrigin;
 import com.ning.billing.util.callcontext.DefaultCallContext;
@@ -39,30 +40,32 @@ import org.testng.annotations.Test;
 
 import com.google.inject.Inject;
 import com.ning.billing.account.api.Account;
-import com.ning.billing.account.glue.AccountModuleWithMocks;
 import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.config.PaymentConfig;
 import com.ning.billing.invoice.api.Invoice;
-import com.ning.billing.invoice.glue.InvoiceModuleWithMocks;
-import com.ning.billing.invoice.model.RecurringInvoiceItem;
-import com.ning.billing.payment.api.Either;
+import com.ning.billing.invoice.api.InvoicePaymentApi;
+import com.ning.billing.mock.BrainDeadProxyFactory;
+import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
+import com.ning.billing.mock.glue.MockJunctionModule;
 import com.ning.billing.payment.api.PaymentApi;
+import com.ning.billing.payment.api.PaymentApiException;
 import com.ning.billing.payment.api.PaymentAttempt;
-import com.ning.billing.payment.api.PaymentError;
-import com.ning.billing.payment.api.PaymentInfo;
+import com.ning.billing.payment.api.PaymentInfoEvent;
 import com.ning.billing.payment.api.PaymentStatus;
 import com.ning.billing.payment.dao.PaymentDao;
 import com.ning.billing.payment.provider.MockPaymentProviderPlugin;
 import com.ning.billing.payment.provider.PaymentProviderPluginRegistry;
-import com.ning.billing.payment.setup.PaymentConfig;
 import com.ning.billing.payment.setup.PaymentTestModuleWithMocks;
 import com.ning.billing.util.bus.Bus;
 import com.ning.billing.util.clock.Clock;
 import com.ning.billing.util.clock.ClockMock;
+import com.ning.billing.util.clock.MockClockModule;
+import com.ning.billing.util.glue.CallContextModule;
 import com.ning.billing.util.notificationq.MockNotificationQueue;
 import com.ning.billing.util.notificationq.Notification;
 import com.ning.billing.util.notificationq.NotificationQueueService;
 
-@Guice(modules = { PaymentTestModuleWithMocks.class, AccountModuleWithMocks.class, InvoiceModuleWithMocks.class })
+@Guice(modules = { PaymentTestModuleWithMocks.class, MockClockModule.class, MockJunctionModule.class, CallContextModule.class })
 @Test(groups = "fast")
 public class TestRetryService {
     @Inject
@@ -72,6 +75,8 @@ public class TestRetryService {
     @Inject
     private PaymentApi paymentApi;
     @Inject
+    private InvoicePaymentApi invoicePaymentApi;
+    @Inject
     private TestHelper testHelper;
     @Inject
     private PaymentProviderPluginRegistry registry;
@@ -102,6 +107,8 @@ public class TestRetryService {
         mockPaymentProviderPlugin = (MockPaymentProviderPlugin)registry.getPlugin(null);
         mockNotificationQueue = (MockNotificationQueue)notificationQueueService.getNotificationQueue(RetryService.SERVICE_NAME, RetryService.QUEUE_NAME);
         context = new DefaultCallContext("RetryServiceTests", CallOrigin.INTERNAL, UserType.TEST, clock);
+        ((ZombieControl)invoicePaymentApi).addResult("notifyOfPaymentAttempt", BrainDeadProxyFactory.ZOMBIE_VOID);
+
     }
 
     @AfterMethod(alwaysRun = true)
@@ -116,12 +123,14 @@ public class TestRetryService {
         final Invoice invoice = testHelper.createTestInvoice(account, clock.getUTCNow(), Currency.USD);
         final BigDecimal amount = new BigDecimal("10.00");
         final UUID subscriptionId = UUID.randomUUID();
+        final UUID bundleId = UUID.randomUUID();
 
         final DateTime startDate = clock.getUTCNow();
         final DateTime endDate = startDate.plusMonths(1);
-        invoice.addInvoiceItem(new RecurringInvoiceItem(invoice.getId(),
+        invoice.addInvoiceItem(new MockRecurringInvoiceItem(invoice.getId(),
                                                        account.getId(),
                                                        subscriptionId,
+                                                       bundleId,
                                                        "test plan", "test phase",
                                                        startDate,
                                                        endDate,
@@ -130,11 +139,13 @@ public class TestRetryService {
                                                        Currency.USD));
 
         mockPaymentProviderPlugin.makeNextInvoiceFail();
-
-        List<Either<PaymentError, PaymentInfo>> results = paymentApi.createPayment(account.getExternalKey(), Arrays.asList(invoice.getId().toString()), context);
-
-        assertEquals(results.size(), 1);
-        assertTrue(results.get(0).isLeft());
+        boolean failed = false;
+        try {
+            paymentApi.createPayment(account.getExternalKey(), Arrays.asList(invoice.getId().toString()), context);
+        } catch (PaymentApiException e) {
+            failed = true;
+        }
+        assertTrue(failed);
 
         List<Notification> pendingNotifications = mockNotificationQueue.getPendingEvents();
 
@@ -144,7 +155,7 @@ public class TestRetryService {
         List<PaymentAttempt> paymentAttempts = paymentApi.getPaymentAttemptsForInvoiceId(invoice.getId().toString());
 
         assertNotNull(paymentAttempts);
-        assertEquals(notification.getNotificationKey(), paymentAttempts.get(0).getPaymentAttemptId().toString());
+        assertEquals(notification.getNotificationKey(), paymentAttempts.get(0).getId().toString());
 
         DateTime expectedRetryDate = paymentAttempts.get(0).getPaymentAttemptDate().plusDays(paymentConfig.getPaymentRetryDays().get(0));
 
@@ -157,12 +168,14 @@ public class TestRetryService {
         final Invoice invoice = testHelper.createTestInvoice(account, clock.getUTCNow(), Currency.USD);
         final BigDecimal amount = new BigDecimal("10.00");
         final UUID subscriptionId = UUID.randomUUID();
+        final UUID bundleId = UUID.randomUUID();
 
         final DateTime now = clock.getUTCNow();
 
-        invoice.addInvoiceItem(new RecurringInvoiceItem(invoice.getId(),
+        invoice.addInvoiceItem(new MockRecurringInvoiceItem(invoice.getId(),
                                                        account.getId(),
                                                        subscriptionId,
+                                                       bundleId,
                                                        "test plan", "test phase",
                                                        now,
                                                        now.plusMonths(1),
@@ -172,7 +185,7 @@ public class TestRetryService {
 
         int numberOfDays = paymentConfig.getPaymentRetryDays().get(0);
         DateTime nextRetryDate = now.plusDays(numberOfDays);
-        PaymentAttempt paymentAttempt = new PaymentAttempt(UUID.randomUUID(), invoice).cloner()
+        PaymentAttempt paymentAttempt = new DefaultPaymentAttempt(UUID.randomUUID(), invoice).cloner()
                                                                                       .setRetryCount(1)
                                                                                       .setPaymentAttemptDate(now)
                                                                                       .build();
@@ -185,14 +198,14 @@ public class TestRetryService {
         List<Notification> pendingNotifications = mockNotificationQueue.getPendingEvents();
         assertEquals(pendingNotifications.size(), 0);
 
-        List<PaymentInfo> paymentInfoList = paymentApi.getPaymentInfo(Arrays.asList(invoice.getId().toString()));
+        List<PaymentInfoEvent> paymentInfoList = paymentApi.getPaymentInfoList(Arrays.asList(invoice.getId().toString()));
         assertEquals(paymentInfoList.size(), 1);
 
-        PaymentInfo paymentInfo = paymentInfoList.get(0);
+        PaymentInfoEvent paymentInfo = paymentInfoList.get(0);
         assertEquals(paymentInfo.getStatus(), PaymentStatus.Processed.toString());
 
         List<PaymentAttempt> updatedAttempts = paymentApi.getPaymentAttemptsForInvoiceId(invoice.getId().toString());
-        assertEquals(paymentInfo.getPaymentId(), updatedAttempts.get(0).getPaymentId());
+        assertEquals(paymentInfo.getId(), updatedAttempts.get(0).getPaymentId());
 
     }
 }

pom.xml 108(+92 -16)

diff --git a/pom.xml b/pom.xml
index a08995c..f8d46a3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -44,11 +44,42 @@
         <module>catalog</module>
         <module>entitlement</module>
         <module>invoice</module>
+        <module>junction</module>
+        <module>overdue</module>
         <module>payment</module>
         <module>util</module>
+        <module>jaxrs</module>
+        <module>server</module>
     </modules>
     <dependencyManagement>
         <dependencies>
+           <dependency>
+                <groupId>javax.ws.rs</groupId>
+                <artifactId>jsr311-api</artifactId>
+                <version>1.1.1</version>
+           </dependency>
+            <dependency>
+                <groupId>com.ning.billing</groupId>
+                <artifactId>killbill-beatrix</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.ning.billing</groupId>
+                <artifactId>killbill-beatrix</artifactId>
+                <version>${project.version}</version>
+                <type>test-jar</type>
+                <scope>test</scope>
+            </dependency>
+            <dependency>
+                <groupId>com.ning.billing</groupId>
+                <artifactId>killbill-analytics</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.ning.billing</groupId>
+                <artifactId>killbill-jaxrs</artifactId>
+                <version>${project.version}</version>
+            </dependency>
             <dependency>
                 <groupId>com.ning.billing</groupId>
                 <artifactId>killbill-api</artifactId>
@@ -63,6 +94,11 @@
             </dependency>
             <dependency>
                 <groupId>com.ning.billing</groupId>
+                <artifactId>killbill-server</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.ning.billing</groupId>
                 <artifactId>killbill-account</artifactId>
                 <version>${project.version}</version>
             </dependency>
@@ -75,6 +111,18 @@
             </dependency>
             <dependency>
                 <groupId>com.ning.billing</groupId>
+                <artifactId>killbill-junction</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.ning.billing</groupId>
+                <artifactId>killbill-junction</artifactId>
+                <version>${project.version}</version>
+                <type>test-jar</type>
+                <scope>test</scope>
+            </dependency>
+            <dependency>
+                <groupId>com.ning.billing</groupId>
                 <artifactId>killbill-entitlement</artifactId>
                 <version>${project.version}</version>
             </dependency>
@@ -128,6 +176,18 @@
             </dependency>
             <dependency>
                 <groupId>com.ning.billing</groupId>
+                <artifactId>killbill-overdue</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.ning.billing</groupId>
+                <artifactId>killbill-overdue</artifactId>
+                <version>${project.version}</version>
+                <type>test-jar</type>
+                <scope>test</scope>
+            </dependency>
+            <dependency>
+                <groupId>com.ning.billing</groupId>
                 <artifactId>killbill-util</artifactId>
                 <version>${project.version}</version>
                 <type>test-jar</type>
@@ -136,17 +196,17 @@
             <dependency>
                 <groupId>org.codehaus.jackson</groupId>
                 <artifactId>jackson-core-asl</artifactId>
-                <version>1.9.2</version>
+                <version>1.9.5</version>
             </dependency>
             <dependency>
                 <groupId>org.codehaus.jackson</groupId>
                 <artifactId>jackson-jaxrs</artifactId>
-                <version>1.9.2</version>
+                <version>1.9.5</version>
             </dependency>
             <dependency>
                 <groupId>org.codehaus.jackson</groupId>
                 <artifactId>jackson-mapper-asl</artifactId>
-                <version>1.9.2</version>
+                <version>1.9.5</version>
             </dependency>
             <dependency>
                 <groupId>com.jolbox</groupId>
@@ -195,14 +255,24 @@
                 <scope>test</scope>
             </dependency>
             <dependency>
+                <groupId>org.apache.commons</groupId>
+                <artifactId>commons-email</artifactId>
+                <version>1.2</version>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.directory.studio</groupId>
+                <artifactId>org.apache.commons.io</artifactId>
+                <version>2.1</version>
+            </dependency>
+            <dependency>
                 <groupId>commons-io</groupId>
                 <artifactId>commons-io</artifactId>
-                <version>2.0.1</version>
+                <version>2.0</version>
             </dependency>
             <dependency>
                 <groupId>commons-lang</groupId>
                 <artifactId>commons-lang</artifactId>
-                <version>2.5</version>
+                <version>2.6</version>
             </dependency>
             <dependency>
                 <groupId>commons-collections</groupId>
@@ -235,32 +305,32 @@
             <dependency>
                 <groupId>org.jdbi</groupId>
                 <artifactId>jdbi</artifactId>
-                <version>2.31.2</version>
+                <version>2.32</version>
             </dependency>
             <dependency>
                 <groupId>org.skife.config</groupId>
                 <artifactId>config-magic</artifactId>
-                <version>0.9</version>
+                <version>0.13</version>
             </dependency>
             <dependency>
                 <groupId>org.slf4j</groupId>
                 <artifactId>slf4j-api</artifactId>
-                <version>1.6.3</version>
+                <version>1.6.4</version>
             </dependency>
             <dependency>
                 <groupId>org.slf4j</groupId>
                 <artifactId>jcl-over-slf4j</artifactId>
-                <version>1.6.3</version>
+                <version>1.6.4</version>
             </dependency>
             <dependency>
                 <groupId>org.slf4j</groupId>
                 <artifactId>jul-to-slf4j</artifactId>
-                <version>1.6.3</version>
+                <version>1.6.4</version>
             </dependency>
             <dependency>
                 <groupId>org.slf4j</groupId>
                 <artifactId>slf4j-log4j12</artifactId>
-                <version>1.6.3</version>
+                <version>1.6.4</version>
             </dependency>
             <dependency>
                 <groupId>org.testng</groupId>
@@ -269,6 +339,11 @@
                 <scope>test</scope>
             </dependency>
             <dependency>
+                <groupId>com.samskivert</groupId>
+                <artifactId>jmustache</artifactId>
+                <version>1.5</version>
+            </dependency>
+            <dependency>
                 <groupId>com.jayway.awaitility</groupId>
                 <artifactId>awaitility</artifactId>
                 <version>1.3.3</version>
@@ -373,6 +448,7 @@
                                 <exclude>**/.project</exclude>
                                 <exclude>.git/**</exclude>
                                 <exclude>.gitignore</exclude>
+                                <exclude>**/.classpath</exclude>
                                 <exclude>ignore/**</exclude>
                                 <exclude>API.txt</exclude>
                                 <exclude>RELEASE.sh</exclude>
@@ -394,12 +470,12 @@
                                 <exclude>**/*.dont-let-git-remove-this-directory</exclude>
                                 <exclude>**/test-output/**</exclude>
                                 <exclude>**/bin/**</exclude>
+                                <exclude>**/target/**</exclude>
+                                <exclude>**/.settings/**</exclude>
                                 <exclude>.travis.yml</exclude>
-                                <!--  until we merge from server branch we disable rat for those -->
-                                <exclude>jaxrs/**</exclude>
-                                <exclude>server/**</exclude>
                                 <exclude>bin/**</exclude>
-
+                                <!-- exclude mustache template files -->
+                                <exclude>**/*.mustache</exclude>
                             </excludes>
                         </configuration>
                     </execution>
@@ -483,7 +559,7 @@
                             <systemPropertyVariables>
                                 <log4j.configuration>file:${project.basedir}/src/test/resources/log4j.xml</log4j.configuration>
                                 <com.ning.billing.dbi.test.useLocalDb>true</com.ning.billing.dbi.test.useLocalDb>
-                                <com.ning.billing.dbi.jdbc.url>jdbc:mysql://127.0.0.1:3306/test_killbill</com.ning.billing.dbi.jdbc.url>
+                                <com.ning.billing.dbi.jdbc.url>jdbc:mysql://127.0.0.1:3306/killbill</com.ning.billing.dbi.jdbc.url>
                                 <file.encoding>UTF-8</file.encoding>
                                 <user.timezone>GMT</user.timezone>
                             </systemPropertyVariables>

server/pom.xml 434(+434 -0)

diff --git a/server/pom.xml b/server/pom.xml
new file mode 100644
index 0000000..e740c10
--- /dev/null
+++ b/server/pom.xml
@@ -0,0 +1,434 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- ~ Copyright 2010-2011 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. -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>com.ning.billing</groupId>
+		<artifactId>killbill</artifactId>
+		<version>0.1.11-SNAPSHOT</version>
+		<relativePath>../pom.xml</relativePath>
+	</parent>
+	<artifactId>killbill-server</artifactId>
+	<name>killbill-server</name>
+	<packaging>war</packaging>
+
+	<properties>
+		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+		<guice.version>3.0</guice.version>
+		<jersey.version>1.12</jersey.version>
+		<jetty.version>8.1.2.v20120308</jetty.version>
+		<logback.version>1.0.1</logback.version>
+		<metrics.version>2.1.2</metrics.version>
+		<slf4j.version>1.6.4</slf4j.version>
+		<skeleton.version>0.1.2</skeleton.version>
+		<async-http-client.version>1.6.5</async-http-client.version>
+	</properties>
+
+	<dependencies>
+                <!-- Jetty provided scope -->
+		<dependency>
+			<groupId>org.eclipse.jetty</groupId>
+			<artifactId>jetty-http</artifactId>
+			<version>${jetty.version}</version>
+			<scope>provided</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.eclipse.jetty</groupId>
+			<artifactId>jetty-io</artifactId>
+			<version>${jetty.version}</version>
+			<scope>provided</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.eclipse.jetty</groupId>
+			<artifactId>jetty-util</artifactId>
+			<version>${jetty.version}</version>
+			<scope>provided</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.eclipse.jetty</groupId>
+			<artifactId>jetty-server</artifactId>
+			<version>${jetty.version}</version>
+			<scope>provided</scope>
+		</dependency>
+
+		<dependency>
+			<groupId>org.eclipse.jetty</groupId>
+			<artifactId>jetty-deploy</artifactId>
+			<version>${jetty.version}</version>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.eclipse.jetty</groupId>
+			<artifactId>jetty-jmx</artifactId>
+			<version>${jetty.version}</version>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.eclipse.jetty</groupId>
+			<artifactId>jetty-xml</artifactId>
+			<version>${jetty.version}</version>
+			<scope>test</scope>
+		</dependency>
+
+		<!-- NOT in master POM; include version as well -->
+		<dependency>
+			<groupId>org.weakref</groupId>
+			<artifactId>jmxutils</artifactId>
+			<version>1.12</version>
+		</dependency>
+		<dependency>
+			<groupId>com.google.inject.extensions</groupId>
+			<artifactId>guice-servlet</artifactId>
+			<version>${guice.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>com.yammer.metrics</groupId>
+			<artifactId>metrics-core</artifactId>
+			<version>${metrics.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>com.yammer.metrics</groupId>
+			<artifactId>metrics-guice</artifactId>
+			<version>${metrics.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>com.sun.jersey</groupId>
+			<artifactId>jersey-server</artifactId>
+			<version>${jersey.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>com.sun.jersey.contribs</groupId>
+			<artifactId>jersey-guice</artifactId>
+			<version>${jersey.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>javax.servlet</groupId>
+			<artifactId>javax.servlet-api</artifactId>
+			<version>3.0.1</version>
+		</dependency>
+		<dependency>
+			<groupId>com.ning.jetty</groupId>
+			<artifactId>ning-service-skeleton-base</artifactId>
+			<version>${skeleton.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>com.ning.jetty</groupId>
+			<artifactId>ning-service-skeleton-jdbi</artifactId>
+			<version>${skeleton.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>com.ning.jetty</groupId>
+			<artifactId>ning-service-skeleton-log4j</artifactId>
+			<version>${skeleton.version}</version>
+			<classifier>selfcontained</classifier>
+			<scope>provided</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.slf4j</groupId>
+			<artifactId>slf4j-api</artifactId>
+			<version>${slf4j.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>org.slf4j</groupId>
+			<artifactId>jcl-over-slf4j</artifactId>
+			<version>${slf4j.version}</version>
+			<scope>runtime</scope>
+		</dependency>
+		<dependency>
+			<groupId>ch.qos.logback</groupId>
+			<artifactId>logback-core</artifactId>
+			<version>${logback.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>ch.qos.logback</groupId>
+			<artifactId>logback-classic</artifactId>
+			<version>${logback.version}</version>
+		</dependency>
+
+		<!-- FROM MASTER POM / LIBRARY -->
+        <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-account</artifactId>
+        </dependency>
+		<dependency>
+			<groupId>com.ning.billing</groupId>
+			<artifactId>killbill-jaxrs</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>com.ning.billing</groupId>
+			<artifactId>killbill-beatrix</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>com.ning.billing</groupId>
+			<artifactId>killbill-util</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>com.ning.billing</groupId>
+			<artifactId>killbill-entitlement</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>com.ning.billing</groupId>
+			<artifactId>killbill-invoice</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>com.ning.billing</groupId>
+			<artifactId>killbill-payment</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>com.ning.billing</groupId>
+			<artifactId>killbill-catalog</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>com.ning.billing</groupId>
+			<artifactId>killbill-analytics</artifactId>
+		</dependency>
+                <dependency>
+                        <groupId>com.ning.billing</groupId>
+                        <artifactId>killbill-junction</artifactId>
+                </dependency>
+		<dependency>
+			<groupId>com.google.guava</groupId>
+			<artifactId>guava</artifactId>
+			<version>11.0.2</version>
+			<scope>compile</scope>
+		</dependency>
+		<dependency>
+			<groupId>com.google.inject</groupId>
+			<artifactId>guice</artifactId>
+			<scope>compile</scope>
+		</dependency>
+		<dependency><!-- Needed by jmxutils -->
+			<groupId>com.google.inject.extensions</groupId>
+			<artifactId>guice-multibindings</artifactId>
+			<scope>compile</scope>
+		</dependency>
+		<dependency>
+			<groupId>mysql</groupId>
+			<artifactId>mysql-connector-java</artifactId>
+			<scope>runtime</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.antlr</groupId>
+			<artifactId>stringtemplate</artifactId>
+			<scope>runtime</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.skife.config</groupId>
+			<artifactId>config-magic</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>javax.ws.rs</groupId>
+			<artifactId>jsr311-api</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>joda-time</groupId>
+			<artifactId>joda-time</artifactId>
+		</dependency>
+                <dependency>
+                        <groupId>commons-io</groupId>
+                        <artifactId>commons-io</artifactId>
+                </dependency>
+		<dependency>
+			<groupId>com.ning</groupId>
+			<artifactId>async-http-client</artifactId>
+			<version>${async-http-client.version}</version>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.testng</groupId>
+			<artifactId>testng</artifactId>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>com.ning.billing</groupId>
+			<artifactId>killbill-util</artifactId>
+			<type>test-jar</type>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>com.ning.billing</groupId>
+			<artifactId>killbill-beatrix</artifactId>
+			<type>test-jar</type>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>com.ning.billing</groupId>
+			<artifactId>killbill-payment</artifactId>
+			<type>test-jar</type>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>mysql</groupId>
+			<artifactId>mysql-connector-mxj</artifactId>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>mysql</groupId>
+			<artifactId>mysql-connector-mxj-db-files</artifactId>
+			<scope>test</scope>
+		</dependency>
+	</dependencies>
+	<build>
+		<resources>
+			<resource>
+				<directory>${basedir}/src/main/resources</directory>
+			</resource>
+		</resources>
+		<plugins>
+			<plugin>
+				<groupId>com.ning.maven.plugins</groupId>
+				<artifactId>maven-dependency-versions-check-plugin</artifactId>
+				<version>2.0.2</version>
+				<configuration>
+					<failBuildInCaseOfConflict>true</failBuildInCaseOfConflict>
+				</configuration>
+				<executions>
+					<execution>
+						<phase>verify</phase>
+						<goals>
+							<goal>check</goal>
+						</goals>
+					</execution>
+				</executions>
+			</plugin>
+			<plugin>
+				<!-- To make eclipse happy -->
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-resources-plugin</artifactId>
+				<version>2.5</version>
+			</plugin>
+			<plugin>
+				<groupId>com.ning.maven.plugins</groupId>
+				<artifactId>maven-duplicate-finder-plugin</artifactId>
+				<version>1.0.2</version>
+				<configuration>
+					<failBuildInCaseOfConflict>false</failBuildInCaseOfConflict>
+					<!-- That's for Jetty -->
+					<ignoredResources>
+						<ignoredResource>about.html</ignoredResource>
+					</ignoredResources>
+				</configuration>
+				<executions>
+					<execution>
+						<phase>verify</phase>
+						<goals>
+							<goal>check</goal>
+						</goals>
+					</execution>
+				</executions>
+			</plugin>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-compiler-plugin</artifactId>
+				<version>2.3.2</version>
+				<configuration>
+					<source>1.6</source>
+					<target>1.6</target>
+				</configuration>
+			</plugin>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-dependency-plugin</artifactId>
+				<version>2.3</version>
+				<executions>
+					<execution>
+						<id>analyze</id>
+						<goals>
+							<goal>analyze-only</goal>
+						</goals>
+						<configuration>
+							<ignoreNonCompile>true</ignoreNonCompile>
+							<failOnWarning>false</failOnWarning>
+						</configuration>
+					</execution>
+				</executions>
+			</plugin>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-release-plugin</artifactId>
+				<version>2.2.1</version>
+				<configuration>
+					<mavenExecutorId>forked-path</mavenExecutorId>
+				</configuration>
+				<dependencies>
+					<dependency>
+						<groupId>org.apache.maven.scm</groupId>
+						<artifactId>maven-scm-provider-gitexe</artifactId>
+						<version>1.4</version>
+					</dependency>
+					<dependency>
+						<groupId>org.codehaus.plexus</groupId>
+						<artifactId>plexus-utils</artifactId>
+						<version>1.5.9</version>
+					</dependency>
+				</dependencies>
+			</plugin>
+			<plugin>
+				<!-- TODO: fix for http://jira.codehaus.org/browse/MSITE-286? -->
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-site-plugin</artifactId>
+				<version>3.0</version>
+			</plugin>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-war-plugin</artifactId>
+				<version>2.1.1</version>
+				<configuration>
+					<attachClasses>true</attachClasses>
+				</configuration>
+			</plugin>
+			<plugin>
+				<groupId>org.mortbay.jetty</groupId>
+				<artifactId>jetty-maven-plugin</artifactId>
+				<version>${jetty.version}</version>
+				<dependencies>
+					<dependency><!-- For LogLevelCounterAppender -->
+						<groupId>com.ning.jetty</groupId>
+						<artifactId>ning-service-skeleton-log4j</artifactId>
+						<version>${skeleton.version}</version>
+						<classifier>selfcontained</classifier>
+						<scope>runtime</scope>
+					</dependency>
+					<!-- Needed to redirect Jetty logs to slf4j -->
+					<dependency>
+						<groupId>org.slf4j</groupId>
+						<artifactId>slf4j-api</artifactId>
+						<version>${slf4j.version}</version>
+					</dependency>
+					<dependency>
+						<groupId>ch.qos.logback</groupId>
+						<artifactId>logback-core</artifactId>
+						<version>${logback.version}</version>
+					</dependency>
+					<dependency>
+						<groupId>ch.qos.logback</groupId>
+						<artifactId>logback-classic</artifactId>
+						<version>${logback.version}</version>
+					</dependency>
+				</dependencies>
+				<configuration>
+					<scanIntervalSeconds>60</scanIntervalSeconds>
+					<systemProperties>
+						<systemProperty>
+							<name>logback.configurationFile</name>
+							<value>file:${basedir}/src/main/resources/logback.xml</value>
+						</systemProperty>
+					</systemProperties>
+				</configuration>
+			</plugin>
+		</plugins>
+	</build>
+</project>
diff --git a/server/src/main/java/com/ning/billing/server/healthchecks/KillbillHealthcheck.java b/server/src/main/java/com/ning/billing/server/healthchecks/KillbillHealthcheck.java
new file mode 100644
index 0000000..62c79bf
--- /dev/null
+++ b/server/src/main/java/com/ning/billing/server/healthchecks/KillbillHealthcheck.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2010-2011 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.server.healthchecks;
+
+import com.yammer.metrics.core.HealthCheck;
+import org.weakref.jmx.Managed;
+
+public class KillbillHealthcheck extends HealthCheck
+{
+    public KillbillHealthcheck()
+    {
+        super(KillbillHealthcheck.class.getSimpleName());
+    }
+
+    @Override
+    public Result check()
+    {
+        try {
+            // STEPH obviously needs more than that
+            return Result.healthy();
+        }
+        catch (Exception e) {
+            return Result.unhealthy(e);
+        }
+    }
+
+    @Managed(description = "Basic killbill healthcheck")
+    public boolean isHealthy()
+    {
+        return check().isHealthy();
+    }
+}
diff --git a/server/src/main/java/com/ning/billing/server/listeners/KillbillGuiceListener.java b/server/src/main/java/com/ning/billing/server/listeners/KillbillGuiceListener.java
new file mode 100644
index 0000000..1aaef9b
--- /dev/null
+++ b/server/src/main/java/com/ning/billing/server/listeners/KillbillGuiceListener.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2010-2011 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.server.listeners;
+
+
+import com.ning.billing.beatrix.lifecycle.DefaultLifecycle;
+import com.ning.billing.jaxrs.util.KillbillEventHandler;
+import com.ning.billing.server.config.KillbillServerConfig;
+import com.ning.billing.server.healthchecks.KillbillHealthcheck;
+import com.ning.billing.server.modules.KillbillServerModule;
+import com.ning.billing.util.bus.Bus;
+import com.ning.billing.util.bus.BusService;
+import com.ning.jetty.base.modules.ServerModuleBuilder;
+import com.ning.jetty.core.listeners.SetupServer;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.PropertyNamingStrategy;
+import com.google.inject.Injector;
+import com.google.inject.Module;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.ServletContextEvent;
+
+public class KillbillGuiceListener extends SetupServer
+{
+    public static final Logger logger = LoggerFactory.getLogger(KillbillGuiceListener.class);
+
+    private DefaultLifecycle killbillLifecycle;
+    private BusService killbillBusService;
+    private KillbillEventHandler killbilleventHandler;
+
+
+    protected Module getModule() {
+    	return new KillbillServerModule();
+    }
+
+    @Override
+    public void contextInitialized(ServletContextEvent event)
+    {
+    	
+
+        final ServerModuleBuilder builder = new ServerModuleBuilder()
+                .addConfig(KillbillServerConfig.class)
+                .addHealthCheck(KillbillHealthcheck.class)
+                .addJMXExport(KillbillHealthcheck.class)
+                .addModule(getModule())
+                .addJerseyResource("com.ning.billing.jaxrs.resources");
+
+
+        guiceModule = builder.build();
+
+        super.contextInitialized(event);
+
+        logger.info("KillbillLifecycleListener : contextInitialized");
+        Injector theInjector = injector(event);
+        killbillLifecycle = theInjector.getInstance(DefaultLifecycle.class);
+        killbillBusService = theInjector.getInstance(BusService.class);
+        killbilleventHandler = theInjector.getInstance(KillbillEventHandler.class); 
+        
+        /*
+        ObjectMapper mapper = theInjector.getInstance(ObjectMapper.class);
+        mapper.setPropertyNamingStrategy(new PropertyNamingStrategy.LowerCaseWithUnderscoresStrategy());
+*/
+        
+        //
+        // Fire all Startup levels up to service start
+        //
+        killbillLifecycle.fireStartupSequencePriorEventRegistration();
+        //
+        // Perform Bus registration
+        //
+        try {
+            killbillBusService.getBus().register(killbilleventHandler);
+        }
+        catch (Bus.EventBusException e) {
+            logger.error("Failed to register for event notifications, this is bad exiting!", e);
+            System.exit(1);
+        }
+        // Let's start!
+        killbillLifecycle.fireStartupSequencePostEventRegistration();
+    }
+
+    @Override
+    public void contextDestroyed(ServletContextEvent sce)
+    {
+        super.contextDestroyed(sce);
+
+        logger.info("IrsKillbillListener : contextDestroyed");
+        // Stop services
+        // Guice error, no need to fill the screen with useless stack traces
+        if (killbillLifecycle == null) {
+            return;
+        }
+
+        killbillLifecycle.fireShutdownSequencePriorEventUnRegistration();
+
+        try {
+            killbillBusService.getBus().unregister(killbilleventHandler);
+        }
+        catch (Bus.EventBusException e) {
+            logger.warn("Failed to unregister for event notifications", e);
+        }
+
+        // Complete shutdown sequence
+        killbillLifecycle.fireShutdownSequencePostEventUnRegistration();
+    }
+}
diff --git a/server/src/main/java/com/ning/billing/server/modules/KillbillServerModule.java b/server/src/main/java/com/ning/billing/server/modules/KillbillServerModule.java
new file mode 100644
index 0000000..d6c3691
--- /dev/null
+++ b/server/src/main/java/com/ning/billing/server/modules/KillbillServerModule.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2010-2011 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.server.modules;
+
+import com.ning.billing.util.email.EmailModule;
+import com.ning.billing.util.email.templates.TemplateModule;
+import com.ning.billing.util.glue.GlobalLockerModule;
+import org.skife.jdbi.v2.DBI;
+import org.skife.jdbi.v2.IDBI;
+
+import com.google.inject.AbstractModule;
+import com.ning.billing.account.glue.AccountModule;
+import com.ning.billing.analytics.setup.AnalyticsModule;
+import com.ning.billing.beatrix.glue.BeatrixModule;
+import com.ning.billing.catalog.glue.CatalogModule;
+import com.ning.billing.entitlement.glue.DefaultEntitlementModule;
+import com.ning.billing.invoice.glue.DefaultInvoiceModule;
+import com.ning.billing.jaxrs.resources.AccountResource;
+import com.ning.billing.jaxrs.resources.BundleResource;
+import com.ning.billing.jaxrs.resources.InvoiceResource;
+import com.ning.billing.jaxrs.resources.PaymentResource;
+import com.ning.billing.jaxrs.resources.SubscriptionResource;
+import com.ning.billing.jaxrs.resources.TagResource;
+import com.ning.billing.jaxrs.util.KillbillEventHandler;
+import com.ning.billing.jaxrs.util.TagHelper;
+import com.ning.billing.junction.glue.DefaultJunctionModule;
+import com.ning.billing.payment.setup.PaymentModule;
+import com.ning.billing.util.glue.BusModule;
+import com.ning.billing.util.glue.CallContextModule;
+import com.ning.billing.util.glue.ClockModule;
+import com.ning.billing.util.glue.FieldStoreModule;
+import com.ning.billing.util.glue.NotificationQueueModule;
+import com.ning.billing.util.glue.TagStoreModule;
+import com.ning.jetty.jdbi.guice.providers.DBIProvider;
+
+public class KillbillServerModule extends AbstractModule
+{
+    @Override
+    protected void configure() {
+        configureDao();
+        configureResources();
+        installKillbillModules();
+    }
+
+    protected void configureDao() {
+        bind(IDBI.class).to(DBI.class).asEagerSingleton();
+        bind(DBI.class).toProvider(DBIProvider.class).asEagerSingleton();
+    }
+
+    protected void configureResources() {
+        bind(TagHelper.class).asEagerSingleton();
+        bind(AccountResource.class).asEagerSingleton();
+        bind(BundleResource.class).asEagerSingleton();
+        bind(SubscriptionResource.class).asEagerSingleton();
+        bind(InvoiceResource.class).asEagerSingleton();
+        bind(PaymentResource.class).asEagerSingleton();
+        bind(TagResource.class).asEagerSingleton();
+        bind(KillbillEventHandler.class).asEagerSingleton();
+    }
+
+    protected void installClock() {
+        install(new ClockModule());    	
+    }
+    
+    protected void installKillbillModules() {
+        install(new EmailModule());
+        install(new GlobalLockerModule());
+        install(new FieldStoreModule());
+        install(new TagStoreModule());
+        install(new CatalogModule());
+    	install(new BusModule());
+        install(new NotificationQueueModule());
+        install(new CallContextModule());
+        install(new AccountModule());
+        install(new DefaultInvoiceModule());
+        install(new TemplateModule());
+        install(new DefaultEntitlementModule());
+        install(new AnalyticsModule());
+        install(new PaymentModule());
+        install(new BeatrixModule());
+        install(new DefaultJunctionModule());        
+        installClock();
+    }
+}
diff --git a/server/src/main/jetty-config/contexts/root.xml b/server/src/main/jetty-config/contexts/root.xml
new file mode 100755
index 0000000..cdf71e1
--- /dev/null
+++ b/server/src/main/jetty-config/contexts/root.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0"  encoding="UTF-8"?>
+<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://jetty.mortbay.org/configure.dtd">
+<Configure class="org.eclipse.jetty.webapp.WebAppContext">
+    <Set name="contextPath">/</Set>
+    <Set name="war">
+        <SystemProperty name="xn.jetty.webapp.path" default="webapps/root"/>
+    </Set>
+    <Set name="defaultsDescriptor">
+        <SystemProperty name="xn.jetty.webapps.defaultsDescriptor" default="etc/webdefault.xml"/>
+    </Set>
+</Configure>
diff --git a/server/src/main/jetty-config/etc/webdefault.xml b/server/src/main/jetty-config/etc/webdefault.xml
new file mode 100755
index 0000000..19ae6ff
--- /dev/null
+++ b/server/src/main/jetty-config/etc/webdefault.xml
@@ -0,0 +1,186 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<web-app
+        xmlns="http://java.sun.com/xml/ns/javaee"
+        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+        xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
+        metadata-complete="true"
+        version="2.5">
+    <description>
+        Default web.xml file.
+        This file is applied to a Web application before it's own WEB_INF/web.xml file
+    </description>
+    <context-param>
+        <param-name>org.mortbay.jetty.webapp.NoTLDJarPattern</param-name>
+        <param-value>
+            start.jar|ant-.*\.jar|dojo-.*\.jar|jetty-.*\.jar|jsp-api-.*\.jar|junit-.*\.jar|servlet-api-.*\.jar|dnsns\.jar|rt\.jar|jsse\.jar|tools\.jar|sunpkcs11\.jar|sunjce_provider\.jar|xerces.*\.jar
+        </param-value>
+    </context-param>
+    <locale-encoding-mapping-list>
+        <locale-encoding-mapping>
+            <locale>ar</locale>
+            <encoding>ISO-8859-6</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>be</locale>
+            <encoding>ISO-8859-5</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>bg</locale>
+            <encoding>ISO-8859-5</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>ca</locale>
+            <encoding>ISO-8859-1</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>cs</locale>
+            <encoding>ISO-8859-2</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>da</locale>
+            <encoding>ISO-8859-1</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>de</locale>
+            <encoding>ISO-8859-1</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>el</locale>
+            <encoding>ISO-8859-7</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>en</locale>
+            <encoding>ISO-8859-1</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>es</locale>
+            <encoding>ISO-8859-1</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>et</locale>
+            <encoding>ISO-8859-1</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>fi</locale>
+            <encoding>ISO-8859-1</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>fr</locale>
+            <encoding>ISO-8859-1</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>hr</locale>
+            <encoding>ISO-8859-2</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>hu</locale>
+            <encoding>ISO-8859-2</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>is</locale>
+            <encoding>ISO-8859-1</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>it</locale>
+            <encoding>ISO-8859-1</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>iw</locale>
+            <encoding>ISO-8859-8</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>ja</locale>
+            <encoding>Shift_JIS</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>ko</locale>
+            <encoding>EUC-KR</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>lt</locale>
+            <encoding>ISO-8859-2</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>lv</locale>
+            <encoding>ISO-8859-2</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>mk</locale>
+            <encoding>ISO-8859-5</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>nl</locale>
+            <encoding>ISO-8859-1</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>no</locale>
+            <encoding>ISO-8859-1</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>pl</locale>
+            <encoding>ISO-8859-2</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>pt</locale>
+            <encoding>ISO-8859-1</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>ro</locale>
+            <encoding>ISO-8859-2</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>ru</locale>
+            <encoding>ISO-8859-5</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>sh</locale>
+            <encoding>ISO-8859-5</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>sk</locale>
+            <encoding>ISO-8859-2</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>sl</locale>
+            <encoding>ISO-8859-2</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>sq</locale>
+            <encoding>ISO-8859-2</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>sr</locale>
+            <encoding>ISO-8859-5</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>sv</locale>
+            <encoding>ISO-8859-1</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>tr</locale>
+            <encoding>ISO-8859-9</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>uk</locale>
+            <encoding>ISO-8859-5</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>zh</locale>
+            <encoding>GB2312</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>zh_TW</locale>
+            <encoding>Big5</encoding>
+        </locale-encoding-mapping>
+    </locale-encoding-mapping-list>
+
+    <security-constraint>
+        <web-resource-collection>
+            <web-resource-name>Disable TRACE</web-resource-name>
+            <url-pattern>/</url-pattern>
+            <http-method>TRACE</http-method>
+        </web-resource-collection>
+        <auth-constraint/>
+    </security-constraint>
+</web-app>
+
diff --git a/server/src/main/jetty-config/ning-jetty-conf.xml b/server/src/main/jetty-config/ning-jetty-conf.xml
new file mode 100644
index 0000000..60764c5
--- /dev/null
+++ b/server/src/main/jetty-config/ning-jetty-conf.xml
@@ -0,0 +1,122 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
+
+<Configure id="Server" class="org.eclipse.jetty.server.Server">
+    <!-- =============================================================== -->
+    <!-- Setup MBean Server early                                        -->
+    <!-- =============================================================== -->
+    <Call id="MBeanServer" class="java.lang.management.ManagementFactory" name="getPlatformMBeanServer"/>
+
+    <New id="MBeanContainer" class="org.eclipse.jetty.jmx.MBeanContainer">
+        <Arg>
+            <Ref id="MBeanServer"/>
+        </Arg>
+    </New>
+
+    <Get id="Container" name="container">
+        <Call name="addEventListener">
+            <Arg>
+                <Ref id="MBeanContainer"/>
+            </Arg>
+        </Call>
+    </Get>
+
+    <!-- =========================================================== -->
+    <!-- Server Thread Pool                                          -->
+    <!-- =========================================================== -->
+    <Set name="ThreadPool">
+        <!-- Default queued blocking threadpool -->
+        <New class="org.eclipse.jetty.util.thread.QueuedThreadPool">
+            <Set name="minThreads">
+                <SystemProperty name="xn.server.threads.min" default="10"/>
+            </Set>
+            <Set name="maxThreads">
+                <SystemProperty name="xn.server.threads.max" default="200"/>
+            </Set>
+        </New>
+    </Set>
+
+    <!-- =========================================================== -->
+    <!-- Set connectors                                              -->
+    <!-- =========================================================== -->
+
+    <!-- Use this connector if NIO is not available. -->
+    <Call name="addConnector">
+        <Arg>
+            <New class="org.eclipse.jetty.server.bio.SocketConnector">
+                <Set name="host">
+                    <SystemProperty name="xn.server.ip"/>
+                </Set>
+                <Set name="port">
+                    <SystemProperty name="xn.server.port" default="8080"/>
+                </Set>
+                <Set name="maxIdleTime">300000</Set>
+                <Set name="Acceptors">2</Set>
+                <Set name="statsOn">true</Set>
+                <Set name="confidentialPort">
+                    <SystemProperty name="xn.server.ssl.port" default="8443"/>
+                </Set>
+            </New>
+        </Arg>
+    </Call>
+
+    <Set name="handler">
+        <New class="org.eclipse.jetty.server.handler.StatisticsHandler">
+            <Set name="handler">
+                <New id="Handlers" class="org.eclipse.jetty.server.handler.HandlerCollection">
+                    <Set name="handlers">
+                        <Array type="org.eclipse.jetty.server.Handler">
+                            <Item>
+                                <New id="Contexts" class="org.eclipse.jetty.server.handler.ContextHandlerCollection"/>
+                            </Item>
+                            <Item>
+                                <New id="DefaultHandler" class="org.eclipse.jetty.server.handler.DefaultHandler"/>
+                            </Item>
+                            <Item>
+                                <New id="RequestLog" class="org.eclipse.jetty.server.handler.RequestLogHandler"/>
+                            </Item>
+                        </Array>
+                    </Set>
+                </New>
+            </Set>
+        </New>
+    </Set>
+
+    <Ref id="RequestLog">
+        <Set name="requestLog">
+            <New id="RequestLogImpl" class="org.eclipse.jetty.server.NCSARequestLog">
+                <Arg>
+                    <SystemProperty name="jetty.logs" default="./logs"/>/yyyy_mm_dd.request.log
+                </Arg>
+                <Set name="retainDays">30</Set>
+                <Set name="append">true</Set>
+                <Set name="extended">false</Set>
+                <Set name="LogTimeZone">GMT</Set>
+            </New>
+        </Set>
+    </Ref>
+
+    <Call name="addLifeCycle">
+        <Arg>
+            <New class="org.eclipse.jetty.deploy.ContextDeployer">
+                <Set name="contexts">
+                    <Ref id="Contexts"/>
+                </Set>
+                <Set name="configurationDir">
+                    <SystemProperty name="xn.jetty.contextDir" default="contexts"/>
+                </Set>
+                <Set name="scanInterval">1</Set>
+            </New>
+        </Arg>
+    </Call>
+
+    <!-- =========================================================== -->
+    <!-- extra options                                               -->
+    <!-- =========================================================== -->
+    <Set name="stopAtShutdown">true</Set>
+    <Set name="sendServerVersion">false</Set>
+    <Set name="sendDateHeader">true</Set>
+    <Set name="gracefulShutdown">
+        <SystemProperty name="xn.jetty.gracefulShutdownTimeoutInMs" default="1000"/>
+    </Set>
+</Configure>
diff --git a/server/src/main/resources/catalog-demo.xml b/server/src/main/resources/catalog-demo.xml
new file mode 100644
index 0000000..8a97d57
--- /dev/null
+++ b/server/src/main/resources/catalog-demo.xml
@@ -0,0 +1,641 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!--
+  ~ Copyright 2010-2011 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.
+  -->
+
+<!-- 
+Use cases covered so far:
+	Tiered Product (Pistol/Shotgun/Assault-Rifle)
+	Multiple changeEvent plan policies
+	Multiple PlanAlignment (see below, trial add-on alignments and rescue discount package)
+	Product transition rules
+	Add on (Scopes, Hoster)
+	Multi-pack addon (Extra-Ammo)
+	Addon Trial aligned to base plan (holster-monthly-regular)
+	Addon Trial aligned to creation (holster-monthly-special)
+	Rescue discount package (assault-rifle-annual-rescue)
+	Plan phase with a reccurring and a one off (refurbish-maintenance)
+	Phan with more than 2 phase (gunclub discount plans)
+		
+Use Cases to do:
+	Tiered Add On
+	Riskfree period
+	
+
+
+ -->
+<catalog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:noNamespaceSchemaLocation="CatalogSchema.xsd ">
+
+	<effectiveDate>2011-01-01T00:00:00+00:00</effectiveDate>
+	<catalogName>Firearms</catalogName>
+
+	<currencies>
+		<currency>USD</currency>
+		<currency>EUR</currency>
+		<currency>GBP</currency>
+	</currencies>
+	
+	<products>
+		<product name="Pistol">
+			<category>BASE</category>
+		</product>
+		<product name="Shotgun">
+			<category>BASE</category>
+            <available>
+                <addonProduct>Telescopic-Scope</addonProduct>
+                <addonProduct>Laser-Scope</addonProduct>
+            </available>
+		</product>
+		<product name="Assault-Rifle">
+			<category>BASE</category>
+			<included> 
+				<addonProduct>Telescopic-Scope</addonProduct>
+			</included>
+			<available>
+				<addonProduct>Laser-Scope</addonProduct>
+			</available>
+		</product>
+		<product name="Telescopic-Scope">
+			<category>ADD_ON</category>
+		</product>
+		<product name="Laser-Scope">
+			<category>ADD_ON</category>
+		</product>
+		<product name="Holster">
+			<category>ADD_ON</category>
+		</product>
+		<product name="Extra-Ammo">
+			<category>ADD_ON</category>
+		</product>
+		<product name="Refurbish-Maintenance">
+			<category>ADD_ON</category>
+		</product>
+	</products>
+	 
+	<rules>
+		<changePolicy>
+			<changePolicyCase> 
+				<phaseType>TRIAL</phaseType>
+				<policy>IMMEDIATE</policy>
+			</changePolicyCase>
+            <changePolicyCase> 
+                <toProduct>Assault-Rifle</toProduct>
+                <policy>IMMEDIATE</policy>
+            </changePolicyCase>
+            <changePolicyCase> 
+                <fromProduct>Pistol</fromProduct>            
+                <toProduct>Shotgun</toProduct>
+                <policy>IMMEDIATE</policy>
+            </changePolicyCase>
+			<changePolicyCase> 
+				<toPriceList>rescue</toPriceList>
+				<policy>END_OF_TERM</policy>
+			</changePolicyCase>
+			<changePolicyCase> 
+				<fromBillingPeriod>MONTHLY</fromBillingPeriod>
+				<toBillingPeriod>ANNUAL</toBillingPeriod>
+				<policy>IMMEDIATE</policy>
+			</changePolicyCase>
+			<changePolicyCase> 
+				<fromBillingPeriod>ANNUAL</fromBillingPeriod>
+				<toBillingPeriod>MONTHLY</toBillingPeriod>
+				<policy>END_OF_TERM</policy>
+			</changePolicyCase>
+			<changePolicyCase> 
+				<policy>END_OF_TERM</policy>
+			</changePolicyCase>
+		</changePolicy>
+		<changeAlignment>
+			<changeAlignmentCase>
+				<toPriceList>rescue</toPriceList>
+				<alignment>CHANGE_OF_PLAN</alignment>
+			</changeAlignmentCase>
+			<changeAlignmentCase>
+				<fromPriceList>rescue</fromPriceList>
+				<toPriceList>rescue</toPriceList>
+				<alignment>CHANGE_OF_PRICELIST</alignment>
+			</changeAlignmentCase>
+            <changeAlignmentCase>
+                <alignment>START_OF_SUBSCRIPTION</alignment>
+            </changeAlignmentCase>
+		</changeAlignment>
+		<cancelPolicy>
+			<cancelPolicyCase>
+				<phaseType>TRIAL</phaseType>
+				<policy>IMMEDIATE</policy>
+			</cancelPolicyCase>
+            <cancelPolicyCase>
+                <policy>END_OF_TERM</policy>
+            </cancelPolicyCase>
+		</cancelPolicy>
+		<createAlignment>
+		    <createAlignmentCase>
+		        <product>Laser-Scope</product>
+                <alignment>START_OF_SUBSCRIPTION</alignment>
+            </createAlignmentCase>
+            <createAlignmentCase>
+                <alignment>START_OF_BUNDLE</alignment>
+            </createAlignmentCase>
+		</createAlignment>
+		<billingAlignment>
+			<billingAlignmentCase>
+				<productCategory>ADD_ON</productCategory>
+				<alignment>BUNDLE</alignment>
+			</billingAlignmentCase>
+			<billingAlignmentCase>
+				<billingPeriod>ANNUAL</billingPeriod>
+				<alignment>SUBSCRIPTION</alignment>
+			</billingAlignmentCase>
+			<billingAlignmentCase>
+				<alignment>ACCOUNT</alignment>
+			</billingAlignmentCase>
+		</billingAlignment>
+		<priceList>
+			<priceListCase>
+				<fromPriceList>rescue</fromPriceList>
+				<toPriceList>DEFAULT</toPriceList>
+			</priceListCase>
+		</priceList>
+	</rules>
+
+	<plans>
+		<plan name="pistol-monthly">
+			<product>Pistol</product>
+			<initialPhases>
+				<phase type="TRIAL">
+					<duration>
+						<unit>DAYS</unit>
+						<number>30</number>
+					</duration>
+					<billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+					<fixedPrice> <!-- empty price implies $0 -->
+					</fixedPrice>
+				</phase>
+			</initialPhases>
+			<finalPhase type="EVERGREEN">
+				<duration>
+					<unit>UNLIMITED</unit>
+				</duration>
+				<billingPeriod>MONTHLY</billingPeriod>
+				<recurringPrice>
+					<price><currency>GBP</currency><value>29.95</value></price>
+					<price><currency>EUR</currency><value>29.95</value></price> 
+					<price><currency>USD</currency><value>29.95</value></price>								
+				</recurringPrice>
+			</finalPhase>
+		</plan>
+		<plan name="shotgun-monthly">
+			<product>Shotgun</product>
+			<initialPhases>
+				<phase type="TRIAL">
+					<duration>
+						<unit>DAYS</unit>
+						<number>30</number>
+					</duration>
+					<billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+					<fixedPrice></fixedPrice>
+				    <!-- no price implies $0 -->
+				</phase>
+			</initialPhases>
+			<finalPhase type="EVERGREEN">
+				<duration>
+					<unit>UNLIMITED</unit>
+					<number>-1</number>
+				</duration>
+				<billingPeriod>MONTHLY</billingPeriod>
+				<recurringPrice>
+					<price><currency>USD</currency><value>249.95</value></price>								
+					<price><currency>EUR</currency><value>149.95</value></price>
+					<price><currency>GBP</currency><value>169.95</value></price>
+				</recurringPrice>
+			</finalPhase>
+		</plan>
+		<plan name="assault-rifle-monthly">
+			<product>Assault-Rifle</product>
+			<initialPhases>
+				<phase type="TRIAL">
+					<duration>
+						<unit>DAYS</unit>
+						<number>30</number>
+					</duration>
+					<billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+					<fixedPrice>
+					</fixedPrice>
+				</phase>
+			</initialPhases>
+			<finalPhase type="EVERGREEN">
+				<duration>
+					<unit>UNLIMITED</unit>
+				</duration>
+				<billingPeriod>MONTHLY</billingPeriod>
+				<recurringPrice>
+					<price><currency>USD</currency><value>599.95</value></price>								
+					<price><currency>EUR</currency><value>349.95</value></price>
+					<price><currency>GBP</currency><value>399.95</value></price>
+				</recurringPrice>
+			</finalPhase>
+		</plan>
+		<plan name="pistol-annual">
+			<product>Pistol</product>
+			<initialPhases>
+				<phase type="TRIAL">
+					<duration>
+						<unit>DAYS</unit>
+						<number>30</number>
+					</duration>
+					<billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+					<fixedPrice>
+					</fixedPrice>
+				</phase>
+			</initialPhases>
+			<finalPhase type="EVERGREEN">
+				<duration>
+					<unit>UNLIMITED</unit>
+				</duration>
+				<billingPeriod>ANNUAL</billingPeriod>
+				<recurringPrice>
+					<price><currency>USD</currency><value>199.95</value></price>								
+					<price><currency>EUR</currency><value>199.95</value></price>
+					<price><currency>GBP</currency><value>199.95</value></price>
+				</recurringPrice>
+			</finalPhase>
+		</plan>
+		<plan name="shotgun-annual">
+			<product>Shotgun</product>
+			<initialPhases>
+				<phase type="TRIAL">
+					<duration>
+						<unit>DAYS</unit>
+						<number>30</number>
+					</duration>
+					<billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+					<fixedPrice>
+					</fixedPrice>
+				</phase>
+			</initialPhases>
+			<finalPhase type="EVERGREEN">
+				<duration>
+					<unit>UNLIMITED</unit>
+				</duration>
+				<billingPeriod>ANNUAL</billingPeriod>
+				<recurringPrice>
+					<price><currency>USD</currency><value>2399.95</value></price>								
+					<price><currency>EUR</currency><value>1499.95</value></price>
+					<price><currency>GBP</currency><value>1699.95</value></price>
+				</recurringPrice>
+			</finalPhase>
+		</plan>
+		<plan name="assault-rifle-annual">
+			<product>Assault-Rifle</product>
+			<initialPhases>
+				<phase type="TRIAL">
+					<duration>
+						<unit>DAYS</unit>
+						<number>30</number>
+					</duration>
+					<billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+					<fixedPrice>
+					</fixedPrice>
+				</phase>
+			</initialPhases>
+			<finalPhase type="EVERGREEN">
+				<duration>
+					<unit>UNLIMITED</unit>
+				</duration>
+				<billingPeriod>ANNUAL</billingPeriod>
+				<recurringPrice>
+					<price><currency>USD</currency><value>5999.95</value></price>								
+					<price><currency>EUR</currency><value>3499.95</value></price>
+					<price><currency>GBP</currency><value>3999.95</value></price>
+				</recurringPrice>
+			</finalPhase>
+		</plan>
+		<plan name="pistol-annual-gunclub-discount">
+			<product>Pistol</product>
+			<initialPhases>
+				<phase type="TRIAL">
+					<duration>
+						<unit>DAYS</unit>
+						<number>30</number>
+					</duration>
+					<billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+					<fixedPrice>
+					</fixedPrice>
+				</phase>
+				<phase type="DISCOUNT">
+					<duration>
+						<unit>MONTHS</unit>
+						<number>6</number>
+					</duration>
+					<billingPeriod>MONTHLY</billingPeriod>
+					<recurringPrice>
+						<price><currency>USD</currency><value>9.95</value></price>								
+						<price><currency>EUR</currency><value>9.95</value></price>
+						<price><currency>GBP</currency><value>9.95</value></price>
+					</recurringPrice>
+				</phase>
+			</initialPhases>
+			<finalPhase type="EVERGREEN">
+				<duration>
+					<unit>UNLIMITED</unit>
+				</duration>
+				<billingPeriod>ANNUAL</billingPeriod>
+				<recurringPrice>
+					<price><currency>USD</currency><value>199.95</value></price>								
+					<price><currency>EUR</currency><value>199.95</value></price>
+					<price><currency>GBP</currency><value>199.95</value></price>
+				</recurringPrice>
+			</finalPhase>
+		</plan>
+		<plan name="shotgun-annual-gunclub-discount">
+			<product>Shotgun</product>
+			<initialPhases>
+				<phase type="TRIAL">
+					<duration>
+						<unit>DAYS</unit>
+						<number>30</number>
+					</duration>
+					<billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+					<fixedPrice>
+					</fixedPrice>
+				</phase>
+				<phase type="DISCOUNT">
+					<duration>
+						<unit>MONTHS</unit>
+						<number>6</number>
+					</duration>
+					<billingPeriod>MONTHLY</billingPeriod>
+					<recurringPrice>
+						<price><currency>USD</currency><value>19.95</value></price>								
+						<price><currency>EUR</currency><value>49.95</value></price>
+						<price><currency>GBP</currency><value>69.95</value></price>
+					</recurringPrice>
+				</phase>
+			</initialPhases>
+			<finalPhase type="EVERGREEN">
+				<duration>
+					<unit>UNLIMITED</unit>
+				</duration>
+				<billingPeriod>ANNUAL</billingPeriod>
+				<recurringPrice>
+					<price><currency>USD</currency><value>2399.95</value></price>								
+					<price><currency>EUR</currency><value>1499.95</value></price>
+					<price><currency>GBP</currency><value>1699.95</value></price>
+				</recurringPrice>
+			</finalPhase>
+		</plan>
+		<plan name="assault-rifle-annual-gunclub-discount">
+			<product>Assault-Rifle</product>
+			<initialPhases>
+				<phase type="TRIAL">
+					<duration>
+						<unit>DAYS</unit>
+						<number>30</number>
+					</duration>
+					<billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+					<fixedPrice>
+					</fixedPrice>
+				</phase>
+				<phase type="DISCOUNT">
+					<duration>
+						<unit>MONTHS</unit>
+						<number>6</number>
+					</duration>
+					<billingPeriod>MONTHLY</billingPeriod>
+					<recurringPrice>
+						<price><currency>USD</currency><value>99.95</value></price>								
+						<price><currency>EUR</currency><value>99.95</value></price>
+						<price><currency>GBP</currency><value>99.95</value></price>
+						</recurringPrice>
+				</phase>
+			</initialPhases>
+			<finalPhase type="EVERGREEN">
+				<duration>
+					<unit>UNLIMITED</unit>
+				</duration>
+				<billingPeriod>ANNUAL</billingPeriod>
+				<recurringPrice>
+					<price><currency>USD</currency><value>5999.95</value></price>								
+					<price><currency>EUR</currency><value>3499.95</value></price>
+					<price><currency>GBP</currency><value>3999.95</value></price>
+				</recurringPrice>
+			</finalPhase>
+		</plan>
+		<plan name="laser-scope-monthly">
+		<product>Laser-Scope</product>
+           <initialPhases>
+              <phase type="DISCOUNT">
+                 <duration>
+                    <unit>MONTHS</unit>
+                    <number>1</number>
+                  </duration>
+                 <billingPeriod>MONTHLY</billingPeriod>
+                    <recurringPrice>
+                      <price><currency>USD</currency><value>999.95</value></price>                             
+                      <price><currency>EUR</currency><value>499.95</value></price>
+                      <price><currency>GBP</currency><value>999.95</value></price>
+                      </recurringPrice>
+                </phase>
+            </initialPhases>
+			<finalPhase type="EVERGREEN">
+				<duration>
+					<unit>UNLIMITED</unit>
+				</duration>
+				<billingPeriod>MONTHLY</billingPeriod>
+				<recurringPrice>
+					<price><currency>USD</currency><value>1999.95</value></price>								
+					<price><currency>EUR</currency><value>1499.95</value></price>
+					<price><currency>GBP</currency><value>1999.95</value></price>
+				</recurringPrice>
+			</finalPhase>
+		</plan>
+		<plan name="telescopic-scope-monthly">
+			<product>Telescopic-Scope</product>
+			<initialPhases>
+              <phase type="DISCOUNT">
+                 <duration>
+                    <unit>MONTHS</unit>
+                    <number>1</number>
+                  </duration>
+                 <billingPeriod>MONTHLY</billingPeriod>
+                    <recurringPrice>
+                      <price><currency>USD</currency><value>399.95</value></price>                             
+                      <price><currency>EUR</currency><value>299.95</value></price>
+                      <price><currency>GBP</currency><value>399.95</value></price>
+                      </recurringPrice>
+                </phase>
+            </initialPhases>
+			<finalPhase type="EVERGREEN">
+				<duration>
+					<unit>UNLIMITED</unit>
+				</duration>
+				<billingPeriod>MONTHLY</billingPeriod>
+				<recurringPrice>
+					<price><currency>USD</currency><value>999.95</value></price>								
+					<price><currency>EUR</currency><value>499.95</value></price>
+					<price><currency>GBP</currency><value>999.95</value></price>
+				</recurringPrice>
+			</finalPhase>
+		</plan>
+		<plan name="extra-ammo-monthly">
+			<product>Extra-Ammo</product>
+			<finalPhase type="EVERGREEN">
+				<duration>
+					<unit>UNLIMITED</unit>
+				</duration>
+				<billingPeriod>MONTHLY</billingPeriod>
+				<recurringPrice>
+					<price><currency>USD</currency><value>999.95</value></price>								
+					<price><currency>EUR</currency><value>499.95</value></price>
+					<price><currency>GBP</currency><value>999.95</value></price>
+				</recurringPrice>
+			</finalPhase>
+			<plansAllowedInBundle>-1</plansAllowedInBundle> <!-- arbitrary number of these (multipack) -->
+		</plan>
+		<plan name="holster-monthly-regular">
+			<product>Holster</product>
+			<initialPhases>
+				<phase type="TRIAL">
+					<duration>
+						<unit>DAYS</unit>
+						<number>30</number>
+					</duration>
+					<billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+					<fixedPrice>
+					</fixedPrice>
+				</phase>
+			</initialPhases>
+			<finalPhase type="EVERGREEN">
+				<duration>
+					<unit>UNLIMITED</unit>
+				</duration>
+				<billingPeriod>ANNUAL</billingPeriod>
+				<recurringPrice>
+					<price><currency>USD</currency><value>199.95</value></price>								
+					<price><currency>EUR</currency><value>199.95</value></price>
+					<price><currency>GBP</currency><value>199.95</value></price>
+				</recurringPrice>
+			</finalPhase>
+		</plan>
+		<plan name="holster-monthly-special">
+			<product>Holster</product>
+			<initialPhases>
+				<phase type="TRIAL">
+					<duration>
+						<unit>DAYS</unit>
+						<number>30</number>
+					</duration>
+					<billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+					<fixedPrice>
+					</fixedPrice>
+				</phase>
+			</initialPhases>
+			<finalPhase type="EVERGREEN">
+				<duration>
+					<unit>UNLIMITED</unit>
+				</duration>
+				<billingPeriod>ANNUAL</billingPeriod>
+				<recurringPrice>
+					<price><currency>USD</currency><value>199.95</value></price>								
+					<price><currency>EUR</currency><value>199.95</value></price>
+					<price><currency>GBP</currency><value>199.95</value></price>
+				</recurringPrice>
+			</finalPhase>
+		</plan>
+		<plan name="assault-rifle-annual-rescue">
+			<product>Assault-Rifle</product>
+			<initialPhases>
+				<phase type="DISCOUNT">
+					<duration>
+						<unit>YEARS</unit>
+						<number>1</number>
+					</duration>
+					<billingPeriod>ANNUAL</billingPeriod>
+					<recurringPrice>
+						<price><currency>USD</currency><value>5999.95</value></price>								
+						<price><currency>EUR</currency><value>3499.95</value></price>
+						<price><currency>GBP</currency><value>3999.95</value></price>
+					</recurringPrice>
+				</phase>
+			</initialPhases>
+			<finalPhase type="EVERGREEN">
+				<duration>
+					<unit>UNLIMITED</unit>
+				</duration>
+				<billingPeriod>ANNUAL</billingPeriod>
+				<recurringPrice>
+					<price><currency>USD</currency><value>5999.95</value></price>								
+					<price><currency>EUR</currency><value>3499.95</value></price>
+					<price><currency>GBP</currency><value>3999.95</value></price>
+				</recurringPrice>
+			</finalPhase>
+		</plan>
+		<plan name="refurbish-maintenance">
+			<product>Refurbish-Maintenance</product>
+			<finalPhase type="FIXEDTERM">
+				<duration>
+					<unit>MONTHS</unit>
+					<number>12</number>
+				</duration>
+				<billingPeriod>MONTHLY</billingPeriod>
+				<recurringPrice>
+					<price><currency>USD</currency><value>199.95</value></price>								
+					<price><currency>EUR</currency><value>199.95</value></price>
+					<price><currency>GBP</currency><value>199.95</value></price>
+				</recurringPrice>
+				<fixedPrice>
+					<price><currency>USD</currency><value>599.95</value></price>								
+					<price><currency>EUR</currency><value>599.95</value></price>
+					<price><currency>GBP</currency><value>599.95</value></price>
+				</fixedPrice>
+			</finalPhase>
+		</plan>
+	</plans>
+	<priceLists>
+		<defaultPriceList name="DEFAULT"> 
+			<plans>
+				<plan>pistol-monthly</plan>
+				<plan>shotgun-monthly</plan>
+				<plan>assault-rifle-monthly</plan>
+				<plan>pistol-annual</plan>
+				<plan>shotgun-annual</plan>
+				<plan>assault-rifle-annual</plan>
+				<plan>laser-scope-monthly</plan>
+				<plan>telescopic-scope-monthly</plan>
+				<plan>extra-ammo-monthly</plan>
+				<plan>holster-monthly-regular</plan>
+				<plan>refurbish-maintenance</plan>
+			</plans>
+		</defaultPriceList>
+		<childPriceList name="gunclubDiscount">
+			<plans>
+				<plan>pistol-monthly</plan>
+				<plan>shotgun-monthly</plan>
+				<plan>assault-rifle-monthly</plan>
+				<plan>pistol-annual-gunclub-discount</plan>
+				<plan>shotgun-annual-gunclub-discount</plan>
+				<plan>assault-rifle-annual-gunclub-discount</plan>
+				<plan>holster-monthly-special</plan>
+			</plans>
+		</childPriceList>
+		<childPriceList name="rescue">
+			<plans>
+				<plan>assault-rifle-annual-rescue</plan>
+			</plans>
+		</childPriceList>
+	</priceLists>
+
+</catalog>
diff --git a/server/src/main/resources/killbill-server.properties b/server/src/main/resources/killbill-server.properties
new file mode 100644
index 0000000..daaa110
--- /dev/null
+++ b/server/src/main/resources/killbill-server.properties
@@ -0,0 +1,12 @@
+# Use skeleton properties for server and configure killbill database
+com.ning.jetty.jdbi.url=jdbc:mysql://127.0.0.1:3306/killbill
+com.ning.jetty.jdbi.user=root
+com.ning.jetty.jdbi.password=root
+
+killbill.catalog.uri=file:src/main/resources/catalog-demo.xml
+
+killbill.entitlement.dao.claim.time=60000
+killbill.entitlement.dao.ready.max=1
+killbill.entitlement.engine.notifications.sleep=500
+user.timezone=UTC
+
diff --git a/server/src/main/resources/logback.xml b/server/src/main/resources/logback.xml
new file mode 100644
index 0000000..3087885
--- /dev/null
+++ b/server/src/main/resources/logback.xml
@@ -0,0 +1,13 @@
+<configuration>
+  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+    <!-- encoders are assigned the type
+         ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
+    <encoder>
+      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
+    </encoder>
+  </appender>
+
+  <root level="info">
+    <appender-ref ref="STDOUT" />
+  </root>
+</configuration>
diff --git a/server/src/main/webapp/WEB-INF/web.xml b/server/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000..9891d30
--- /dev/null
+++ b/server/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<web-app
+        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+        xmlns="http://java.sun.com/xml/ns/javaee"
+        xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
+        xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
+        version="2.5">
+    <display-name>irs</display-name>
+    <filter>
+        <!-- Guice emulates Servlet API with DI -->
+        <filter-name>guiceFilter</filter-name>
+        <filter-class>com.google.inject.servlet.GuiceFilter</filter-class>
+    </filter>
+    <filter-mapping>
+        <filter-name>guiceFilter</filter-name>
+        <url-pattern>/*</url-pattern>
+    </filter-mapping>
+    <listener>
+        <!-- Jersey insists on using java.util.logging (JUL) -->
+        <listener-class>com.ning.jetty.core.listeners.SetupJULBridge</listener-class>
+    </listener>
+    <listener>
+        <!-- Context listener: called at startup time and creates the injector -->
+        <listener-class>com.ning.billing.server.listeners.KillbillGuiceListener</listener-class>
+    </listener>
+    <!-- ServletHandler#handle requires a backend servlet, it won't be used though (handled by Guice) -->
+    <servlet>
+        <servlet-name>log-invalid-resources</servlet-name>
+        <servlet-class>com.ning.jetty.core.servlets.LogInvalidResourcesServlet</servlet-class>
+    </servlet>
+    <servlet-mapping>
+        <servlet-name>log-invalid-resources</servlet-name>
+        <url-pattern>/*</url-pattern>
+    </servlet-mapping>
+</web-app>
diff --git a/server/src/test/java/com/ning/billing/jaxrs/TestAccount.java b/server/src/test/java/com/ning/billing/jaxrs/TestAccount.java
new file mode 100644
index 0000000..414f2a0
--- /dev/null
+++ b/server/src/test/java/com/ning/billing/jaxrs/TestAccount.java
@@ -0,0 +1,204 @@
+/* 
+ * Copyright 2010-2011 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.jaxrs;
+
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+
+import javax.ws.rs.core.Response.Status;
+
+
+import org.codehaus.jackson.map.ObjectMapper;
+import org.codehaus.jackson.map.PropertyNamingStrategy;
+import org.joda.time.DateTime;
+import org.joda.time.Interval;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.jaxrs.json.AccountJson;
+import com.ning.billing.jaxrs.json.AccountTimelineJson;
+import com.ning.billing.jaxrs.json.BundleJsonNoSubsciptions;
+import com.ning.billing.jaxrs.json.CustomFieldJson;
+import com.ning.billing.jaxrs.json.SubscriptionJsonNoEvents;
+import com.ning.billing.jaxrs.json.TagDefinitionJson;
+import com.ning.billing.jaxrs.resources.BaseJaxrsResource;
+import com.ning.http.client.Response;
+
+
+public class TestAccount extends TestJaxrsBase {
+
+	private static final Logger log = LoggerFactory.getLogger(TestAccount.class);
+
+
+	
+	
+	@Test(groups="slow", enabled=true)
+	public void testAccountOk() throws Exception {
+		
+		AccountJson input = createAccount("xoxo", "shdgfhwe", "xoxo@yahoo.com");
+		
+		// Retrieves by external key
+		Map<String, String> queryParams = new HashMap<String, String>();
+		queryParams.put(BaseJaxrsResource.QUERY_EXTERNAL_KEY, "shdgfhwe");
+		Response response = doGet(BaseJaxrsResource.ACCOUNTS_PATH, queryParams, DEFAULT_HTTP_TIMEOUT_SEC);
+		Assert.assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+		String baseJson = response.getResponseBody();
+		AccountJson objFromJson = mapper.readValue(baseJson, AccountJson.class);
+		Assert.assertTrue(objFromJson.equals(input));
+		
+		// Update Account
+		AccountJson newInput = new AccountJson(objFromJson.getAccountId(),
+				"zozo", 4, objFromJson.getExternalKey(), "rr@google.com", 18, "EUR", "none", "UTC", "bl1", "bh2", "", "ca", "usa", "415-255-2991");
+		baseJson = mapper.writeValueAsString(newInput);
+		final String uri = BaseJaxrsResource.ACCOUNTS_PATH + "/" + objFromJson.getAccountId();
+		response = doPut(uri, baseJson, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+		Assert.assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+		baseJson = response.getResponseBody();
+		objFromJson = mapper.readValue(baseJson, AccountJson.class);
+		Assert.assertTrue(objFromJson.equals(newInput));
+	}
+
+
+	@Test(groups="slow", enabled=true)
+	public void testUpdateNonExistentAccount() throws Exception {
+		AccountJson input = getAccountJson("xoxo", "shghaahwe", "xoxo@yahoo.com");
+		String baseJson = mapper.writeValueAsString(input);
+		final String uri = BaseJaxrsResource.ACCOUNTS_PATH + "/" + input.getAccountId();
+		Response response = doPut(uri, baseJson, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+		Assert.assertEquals(response.getStatusCode(), Status.NO_CONTENT.getStatusCode());
+		String body = response.getResponseBody();
+		Assert.assertEquals(body, "");
+	}
+	
+	
+	@Test(groups="slow", enabled=true)
+	public void testAccountNonExistent() throws Exception {
+		final String uri = BaseJaxrsResource.ACCOUNTS_PATH + "/99999999-b103-42f3-8b6e-dd244f1d0747";
+		Response response = doGet(uri, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+		Assert.assertEquals(response.getStatusCode(), Status.NO_CONTENT.getStatusCode());
+	}
+	
+	@Test(groups="slow", enabled=true)
+	public void testAccountBadAccountId() throws Exception {
+		final String uri = BaseJaxrsResource.ACCOUNTS_PATH + "/yo";
+		Response response = doGet(uri, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+		Assert.assertEquals(response.getStatusCode(), Status.NOT_FOUND.getStatusCode());
+	}
+	
+	@Test(groups="slow", enabled=true)
+	public void testAccountTimeline() throws Exception {
+	    
+        clock.setTime(new DateTime(2012, 4, 25, 0, 3, 42, 0));
+        
+        
+	    AccountJson accountJson = createAccount("poney", "shdddqgfhwe", "poney@yahoo.com");
+	    assertNotNull(accountJson);
+	    
+	    BundleJsonNoSubsciptions bundleJson = createBundle(accountJson.getAccountId(), "996599");
+	    assertNotNull(bundleJson);
+	    
+        SubscriptionJsonNoEvents subscriptionJson = createSubscription(bundleJson.getBundleId(), "Shotgun", ProductCategory.BASE.toString(), BillingPeriod.MONTHLY.toString(), true);
+        assertNotNull(subscriptionJson);
+        
+        // MOVE AFTER TRIAL
+        clock.addMonths(3);
+
+        crappyWaitForLackOfProperSynchonization();
+        
+        
+        final String uri = BaseJaxrsResource.ACCOUNTS_PATH + "/" + accountJson.getAccountId() + "/" + BaseJaxrsResource.TIMELINE;
+        
+        Response response = doGet(uri, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+        Assert.assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+        String baseJson = response.getResponseBody();
+        AccountTimelineJson objFromJson = mapper.readValue(baseJson, AccountTimelineJson.class);
+        assertNotNull(objFromJson);
+        log.info(baseJson);
+        
+            Assert.assertEquals(objFromJson.getPayments().size(), 3);
+        Assert.assertEquals(objFromJson.getInvoices().size(), 4);   
+        Assert.assertEquals(objFromJson.getBundles().size(), 1); 
+        Assert.assertEquals(objFromJson.getBundles().get(0).getSubscriptions().size(), 1);
+        Assert.assertEquals(objFromJson.getBundles().get(0).getSubscriptions().get(0).getEvents().size(), 2);        
+ 	}
+
+	@Test(groups="slow", enabled=false)
+	public void testAccountWithTags() throws Exception {
+	    //Create Tag definition
+	    TagDefinitionJson input = new TagDefinitionJson("yoyo", "nothing more to say");
+	    String baseJson = mapper.writeValueAsString(input);
+	    Response response = doPost(BaseJaxrsResource.TAG_DEFINITIONS_PATH, baseJson, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+	    assertEquals(response.getStatusCode(), Status.CREATED.getStatusCode());
+	    
+	    AccountJson accountJson = createAccount("couroucoucou", "shdwdsqgfhwe", "couroucoucou@yahoo.com");
+	    assertNotNull(accountJson);
+	        
+	    Map<String, String> queryParams = new HashMap<String, String>();
+        queryParams.put(BaseJaxrsResource.QUERY_TAGS, input.getName());
+        String uri = BaseJaxrsResource.ACCOUNTS_PATH + "/" + BaseJaxrsResource.TAGS + "/" + accountJson.getAccountId() ;
+	    response = doPost(uri, null, queryParams, DEFAULT_HTTP_TIMEOUT_SEC);
+        assertEquals(response.getStatusCode(), Status.CREATED.getStatusCode());
+        
+        /*
+         * STEPH Some how Location returns the ID twice (confused) :
+         * Location: http://127.0.0.1:8080/1.0/kb/accounts/tags/ebb5f830-6f0a-4521-9553-521d173169be/ebb5f830-6f0a-4521-9553-521d173169be
+         * 
+        String location = response.getHeader("Location");
+        Assert.assertNotNull(location);
+
+        // Retrieves by Id based on Location returned
+        response = doGetWithUrl(location, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+        Assert.assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+        */
+
+	}
+	
+    @Test(groups="slow", enabled=false)
+	public void testAccountWithCustomFields() throws Exception {
+        
+        AccountJson accountJson = createAccount("carafe", "shdwhwgaz", "carafe@yahoo.com");
+        assertNotNull(accountJson);
+        
+        List<CustomFieldJson> customFields =  new LinkedList<CustomFieldJson>();
+        customFields.add(new CustomFieldJson("1", "value1"));
+        customFields.add(new CustomFieldJson("2", "value2"));
+        customFields.add(new CustomFieldJson("3", "value3"));  
+        String baseJson = mapper.writeValueAsString(customFields);
+
+        String uri = BaseJaxrsResource.ACCOUNTS_PATH + "/" + BaseJaxrsResource.CUSTOM_FIELDS + "/" + accountJson.getAccountId() ;
+        Response response = doPost(uri,baseJson, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+        assertEquals(response.getStatusCode(), Status.CREATED.getStatusCode());
+        String location = response.getHeader("Location");
+        Assert.assertNotNull(location);
+
+        // Retrieves by Id based on Location returned
+        response = doGetWithUrl(location, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+        Assert.assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+        
+	}
+}
diff --git a/server/src/test/java/com/ning/billing/jaxrs/TestBundle.java b/server/src/test/java/com/ning/billing/jaxrs/TestBundle.java
new file mode 100644
index 0000000..7795d0c
--- /dev/null
+++ b/server/src/test/java/com/ning/billing/jaxrs/TestBundle.java
@@ -0,0 +1,116 @@
+/* 
+ * Copyright 2010-2011 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.jaxrs;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.ws.rs.core.Response.Status;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.ning.billing.jaxrs.json.AccountJson;
+import com.ning.billing.jaxrs.json.BundleJsonNoSubsciptions;
+import com.ning.billing.jaxrs.resources.BaseJaxrsResource;
+import com.ning.http.client.Response;
+
+public class TestBundle extends TestJaxrsBase {
+
+	private static final Logger log = LoggerFactory.getLogger(TestBundle.class);
+
+
+	
+	@Test(groups="slow", enabled=true)
+	public void testBundleOk() throws Exception {
+
+		AccountJson accountJson = createAccount("xlxl", "shdgfhkkl", "xlxl@yahoo.com");
+		BundleJsonNoSubsciptions bundleJson = createBundle(accountJson.getAccountId(), "12345");
+		
+		// Retrieves by external key
+		Map<String, String> queryParams = new HashMap<String, String>();
+		queryParams.put(BaseJaxrsResource.QUERY_EXTERNAL_KEY, "12345");
+		Response response = doGet(BaseJaxrsResource.BUNDLES_PATH, queryParams, DEFAULT_HTTP_TIMEOUT_SEC);
+		Assert.assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+		String baseJson = response.getResponseBody();
+		BundleJsonNoSubsciptions objFromJson = mapper.readValue(baseJson, BundleJsonNoSubsciptions.class);
+		Assert.assertTrue(objFromJson.equals(bundleJson));
+	}
+	
+	
+	@Test(groups="slow", enabled=true)
+	public void testBundleFromAccount() throws Exception {
+
+		AccountJson accountJson = createAccount("xaxa", "saagfhkkl", "xaxa@yahoo.com");
+		BundleJsonNoSubsciptions bundleJson1 = createBundle(accountJson.getAccountId(), "156567");
+		BundleJsonNoSubsciptions bundleJson2 = createBundle(accountJson.getAccountId(), "265658");
+
+		String uri = BaseJaxrsResource.ACCOUNTS_PATH + "/" + accountJson.getAccountId().toString() + "/" + BaseJaxrsResource.BUNDLES;
+		Response response = doGet(uri, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+		Assert.assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+		String baseJson = response.getResponseBody();
+		List<BundleJsonNoSubsciptions> objFromJson = mapper.readValue(baseJson, new TypeReference<List<BundleJsonNoSubsciptions>>() {});
+		
+		Collections.sort(objFromJson, new Comparator<BundleJsonNoSubsciptions>() {
+			@Override
+			public int compare(BundleJsonNoSubsciptions o1, BundleJsonNoSubsciptions o2) {
+				return o1.getExternalKey().compareTo(o2.getExternalKey());
+			}
+		});
+		Assert.assertEquals(objFromJson.get(0), bundleJson1);
+		Assert.assertEquals(objFromJson.get(1), bundleJson2);		
+	}
+	
+	@Test(groups="slow", enabled=true)
+	public void testBundleNonExistent() throws Exception {
+		AccountJson accountJson = createAccount("dfdf", "dfdfgfhkkl", "dfdf@yahoo.com");	
+		
+		String uri = BaseJaxrsResource.BUNDLES_PATH + "/99999999-b103-42f3-8b6e-dd244f1d0747";
+		Response response = doGet(uri, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+		Assert.assertEquals(response.getStatusCode(), Status.NO_CONTENT.getStatusCode());
+		
+		
+		// Retrieves by external key
+		Map<String, String> queryParams = new HashMap<String, String>();
+		queryParams.put(BaseJaxrsResource.QUERY_EXTERNAL_KEY, "56566");
+		response = doGet(BaseJaxrsResource.BUNDLES_PATH, queryParams, DEFAULT_HTTP_TIMEOUT_SEC);
+		Assert.assertEquals(response.getStatusCode(), Status.NO_CONTENT.getStatusCode());
+		
+		
+		uri = BaseJaxrsResource.ACCOUNTS_PATH + "/" + accountJson.getAccountId().toString() + "/" + BaseJaxrsResource.BUNDLES;
+		response = doGet(uri, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+		Assert.assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+		String baseJson = response.getResponseBody();
+		List<BundleJsonNoSubsciptions> objFromJson = mapper.readValue(baseJson, new TypeReference<List<BundleJsonNoSubsciptions>>() {});
+		Assert.assertNotNull(objFromJson);
+		Assert.assertEquals(objFromJson.size(), 0);
+	}
+
+	@Test(groups="slow", enabled=true)
+	public void testAppNonExistent() throws Exception {
+		String uri = BaseJaxrsResource.ACCOUNTS_PATH + "/99999999-b103-42f3-8b6e-dd244f1d0747/" + BaseJaxrsResource.BUNDLES;
+		Response response = doGet(uri, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+		Assert.assertEquals(response.getStatusCode(), Status.NO_CONTENT.getStatusCode());	
+	}
+	
+	
+}
diff --git a/server/src/test/java/com/ning/billing/jaxrs/TestInvoice.java b/server/src/test/java/com/ning/billing/jaxrs/TestInvoice.java
new file mode 100644
index 0000000..aba67b8
--- /dev/null
+++ b/server/src/test/java/com/ning/billing/jaxrs/TestInvoice.java
@@ -0,0 +1,126 @@
+/* 
+ * Copyright 2010-2011 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.jaxrs;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.ws.rs.core.Response.Status;
+
+import org.joda.time.DateTime;
+import org.joda.time.Interval;
+import org.joda.time.format.DateTimeFormatter;
+import org.joda.time.format.ISODateTimeFormat;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.jaxrs.json.AccountJson;
+import com.ning.billing.jaxrs.json.BundleJsonNoSubsciptions;
+import com.ning.billing.jaxrs.json.InvoiceJsonSimple;
+import com.ning.billing.jaxrs.json.SubscriptionJsonNoEvents;
+import com.ning.billing.jaxrs.resources.BaseJaxrsResource;
+import com.ning.http.client.Response;
+
+public class TestInvoice extends TestJaxrsBase  {
+
+    private final DateTimeFormatter DATE_TIME_FORMATTER = ISODateTimeFormat.dateTime();
+    
+    private static final Logger log = LoggerFactory.getLogger(TestInvoice.class);
+
+
+    @Test(groups="slow", enabled=true)
+    public void testInvoiceOk() throws Exception {
+        
+        DateTime initialDate = new DateTime(2012, 4, 25, 0, 3, 42, 0);
+        clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis());
+        
+        
+        AccountJson accountJson = createAccount("poupou", "qhddffrwe", "poupou@yahoo.com");
+        assertNotNull(accountJson);
+        
+        BundleJsonNoSubsciptions bundleJson = createBundle(accountJson.getAccountId(), "9967599");
+        assertNotNull(bundleJson);
+        
+        SubscriptionJsonNoEvents subscriptionJson = createSubscription(bundleJson.getBundleId(), "Shotgun", ProductCategory.BASE.toString(), BillingPeriod.MONTHLY.toString(), true);
+        assertNotNull(subscriptionJson);
+        
+        // MOVE AFTER TRIAL
+        Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusMonths(3).plusDays(1));
+        clock.addDeltaFromReality(it.toDurationMillis());
+
+        crappyWaitForLackOfProperSynchonization();
+        
+        String uri = BaseJaxrsResource.INVOICES_PATH;
+        Map<String, String> queryParams = new HashMap<String, String>();
+        queryParams.put(BaseJaxrsResource.QUERY_ACCOUNT_ID, accountJson.getAccountId());
+        
+        Response response = doGet(uri, queryParams, DEFAULT_HTTP_TIMEOUT_SEC);
+        Assert.assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+        String baseJson = response.getResponseBody();
+        List<InvoiceJsonSimple> objFromJson = mapper.readValue(baseJson, new TypeReference<List<InvoiceJsonSimple>>() {});
+        assertNotNull(objFromJson);
+        log.info(baseJson);
+        assertEquals(objFromJson.size(), 4);
+        
+        // Check we can retrieve an individual invoice
+        uri = BaseJaxrsResource.INVOICES_PATH + "/" + objFromJson.get(0).getInvoiceId();
+        response = doGet(uri, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+        Assert.assertEquals(response.getStatusCode(), Status.OK.getStatusCode());        
+        baseJson = response.getResponseBody();
+        InvoiceJsonSimple firstInvoiceJson = mapper.readValue(baseJson, InvoiceJsonSimple.class);
+        assertNotNull(objFromJson);    
+        assertEquals(firstInvoiceJson, objFromJson.get(0));
+        
+        // Then create a dryRun Invoice
+        DateTime futureDate = clock.getUTCNow().plusMonths(1).plusDays(3);
+        uri = BaseJaxrsResource.INVOICES_PATH;
+        queryParams.put(BaseJaxrsResource.QUERY_TARGET_DATE, futureDate.toString());
+        queryParams.put(BaseJaxrsResource.QUERY_DRY_RUN, "true");        
+        response = doPost(uri, null, queryParams, DEFAULT_HTTP_TIMEOUT_SEC);
+        Assert.assertEquals(response.getStatusCode(), Status.OK.getStatusCode()); 
+        baseJson = response.getResponseBody();
+        InvoiceJsonSimple futureInvoice = mapper.readValue(baseJson, InvoiceJsonSimple.class);
+        assertNotNull(futureInvoice);    
+        log.info(baseJson);
+        
+        // The one more time with no DryRun
+        queryParams.remove(BaseJaxrsResource.QUERY_DRY_RUN);
+        response = doPost(uri, null, queryParams, DEFAULT_HTTP_TIMEOUT_SEC);
+        Assert.assertEquals(response.getStatusCode(), Status.CREATED.getStatusCode());
+        
+        String location = response.getHeader("Location");
+        Assert.assertNotNull(location);
+        
+        // Check again # invoices, should be 5 this time
+        uri = BaseJaxrsResource.INVOICES_PATH;
+        response = doGet(uri, queryParams, DEFAULT_HTTP_TIMEOUT_SEC);
+        assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+        baseJson = response.getResponseBody();
+        objFromJson = mapper.readValue(baseJson, new TypeReference<List<InvoiceJsonSimple>>() {});
+        assertNotNull(objFromJson);
+        log.info(baseJson);
+        assertEquals(objFromJson.size(), 5);
+    }
+}
diff --git a/server/src/test/java/com/ning/billing/jaxrs/TestJaxrsBase.java b/server/src/test/java/com/ning/billing/jaxrs/TestJaxrsBase.java
new file mode 100644
index 0000000..0a0d619
--- /dev/null
+++ b/server/src/test/java/com/ning/billing/jaxrs/TestJaxrsBase.java
@@ -0,0 +1,497 @@
+/* 
+ * Copyright 2010-2011 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.jaxrs;
+
+import static org.testng.Assert.assertNotNull;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.EventListener;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+
+import javax.ws.rs.core.Response.Status;
+
+import com.ning.billing.util.email.EmailModule;
+import com.ning.billing.util.email.templates.TemplateModule;
+import com.ning.billing.util.glue.GlobalLockerModule;
+import org.apache.commons.io.IOUtils;
+import org.eclipse.jetty.servlet.FilterHolder;
+import org.skife.config.ConfigurationObjectFactory;
+import org.skife.jdbi.v2.IDBI;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.AfterSuite;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.BeforeSuite;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.PropertyNamingStrategy;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.datatype.joda.JodaModule;
+import com.google.inject.Module;
+import com.ning.billing.account.glue.AccountModule;
+import com.ning.billing.analytics.setup.AnalyticsModule;
+import com.ning.billing.api.TestApiListener;
+import com.ning.billing.beatrix.glue.BeatrixModule;
+import com.ning.billing.beatrix.integration.TestIntegration;
+import com.ning.billing.catalog.api.PriceListSet;
+import com.ning.billing.catalog.glue.CatalogModule;
+import com.ning.billing.config.PaymentConfig;
+import com.ning.billing.dbi.DBIProvider;
+import com.ning.billing.dbi.DbiConfig;
+import com.ning.billing.dbi.MysqlTestingHelper;
+import com.ning.billing.entitlement.glue.DefaultEntitlementModule;
+import com.ning.billing.invoice.glue.DefaultInvoiceModule;
+import com.ning.billing.jaxrs.json.AccountJson;
+import com.ning.billing.jaxrs.json.BundleJsonNoSubsciptions;
+import com.ning.billing.jaxrs.json.SubscriptionJsonNoEvents;
+import com.ning.billing.jaxrs.resources.BaseJaxrsResource;
+import com.ning.billing.junction.glue.DefaultJunctionModule;
+import com.ning.billing.payment.provider.MockPaymentProviderPluginModule;
+import com.ning.billing.payment.setup.PaymentModule;
+import com.ning.billing.server.listeners.KillbillGuiceListener;
+import com.ning.billing.server.modules.KillbillServerModule;
+import com.ning.billing.util.clock.Clock;
+import com.ning.billing.util.clock.ClockMock;
+import com.ning.billing.util.glue.BusModule;
+import com.ning.billing.util.glue.CallContextModule;
+import com.ning.billing.util.glue.FieldStoreModule;
+import com.ning.billing.util.glue.NotificationQueueModule;
+import com.ning.billing.util.glue.TagStoreModule;
+import com.ning.http.client.AsyncCompletionHandler;
+import com.ning.http.client.AsyncHttpClient;
+import com.ning.http.client.AsyncHttpClient.BoundRequestBuilder;
+import com.ning.http.client.ListenableFuture;
+import com.ning.http.client.Response;
+import com.ning.jetty.core.CoreConfig;
+import com.ning.jetty.core.server.HttpServer;
+
+public class TestJaxrsBase {
+
+    private final static String PLUGIN_NAME = "noop";
+
+    protected static final int DEFAULT_HTTP_TIMEOUT_SEC =  500000; // 5;
+
+    protected static final Map<String, String> DEFAULT_EMPTY_QUERY = new HashMap<String, String>();
+
+    private static final Logger log = LoggerFactory.getLogger(TestJaxrsBase.class);
+
+    public static final String HEADER_CONTENT_TYPE = "Content-type";
+    public static final String CONTENT_TYPE = "application/json";
+
+    private static TestKillbillGuiceListener listener;
+    
+    
+    private MysqlTestingHelper helper;
+    private HttpServer server;
+
+    protected CoreConfig config;
+    protected AsyncHttpClient httpClient;	
+    protected ObjectMapper mapper;
+    protected ClockMock clock;
+    protected TestApiListener busHandler;
+
+    // Context informtation to be passed around
+    private static final String createdBy = "Toto";
+    private static final String reason = "i am god";
+    private static final String comment = "no comment";    
+    
+    public static void loadSystemPropertiesFromClasspath(final String resource) {
+        final URL url = TestJaxrsBase.class.getResource(resource);
+        assertNotNull(url);
+        try {
+            System.getProperties().load(url.openStream());
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static class TestKillbillGuiceListener extends KillbillGuiceListener {
+        
+        private final MysqlTestingHelper helper;
+        private final Clock clock;
+        
+        public TestKillbillGuiceListener(final MysqlTestingHelper helper, final Clock clock) {
+            super();
+            this.helper = helper;
+            this.clock = clock;
+        }
+        @Override
+        protected Module getModule() {
+            return new TestKillbillServerModule(helper, clock);
+        }
+        
+        //
+        // Listener is created once before Suite and keeps pointer to helper and clock so they can get
+        // reset for each test Class-- needed in order to ONLY start Jetty once across all the test classes
+        // while still being able to start mysql before Jetty is started
+        //
+        public MysqlTestingHelper getMysqlTestingHelper() {
+            return helper;
+        }
+        
+        public Clock getClock() {
+            return clock;
+        }
+    }
+
+    public static class TestKillbillServerModule extends KillbillServerModule {
+
+        private final MysqlTestingHelper helper;
+        private final Clock clock;
+        
+        public TestKillbillServerModule(final MysqlTestingHelper helper, final Clock clock) {
+            super();
+            this.helper = helper;
+            this.clock = clock;
+        }
+        
+        @Override
+        protected void installClock() {
+            bind(Clock.class).toInstance(clock);
+        }
+
+
+        private static final class PaymentMockModule extends PaymentModule {
+            @Override
+            protected void installPaymentProviderPlugins(PaymentConfig config) {
+                install(new MockPaymentProviderPluginModule(PLUGIN_NAME));
+            }
+        }
+
+        protected void installKillbillModules(){
+
+            /*
+             * For a lack of getting module override working, copy all install modules from parent class...
+             * 
+            super.installKillbillModules();
+            Modules.override(new com.ning.billing.payment.setup.PaymentModule()).with(new PaymentMockModule());
+            */
+            install(new EmailModule());
+            install(new GlobalLockerModule());
+            install(new FieldStoreModule());
+            install(new TagStoreModule());
+            install(new CatalogModule());
+            install(new BusModule());
+            install(new NotificationQueueModule());
+            install(new CallContextModule());
+            install(new AccountModule());
+            install(new DefaultInvoiceModule());
+            install(new TemplateModule());
+            install(new DefaultEntitlementModule());
+            install(new AnalyticsModule());
+            install(new PaymentMockModule());
+            install(new BeatrixModule());
+            install(new DefaultJunctionModule());
+            installClock();
+        }
+
+        @Override
+        protected void configureDao() {
+            bind(MysqlTestingHelper.class).toInstance(helper);
+            if (helper.isUsingLocalInstance()) {
+                bind(IDBI.class).toProvider(DBIProvider.class).asEagerSingleton();
+                final DbiConfig config = new ConfigurationObjectFactory(System.getProperties()).build(DbiConfig.class);
+                bind(DbiConfig.class).toInstance(config);
+            } else {
+                final IDBI dbi = helper.getDBI();
+                bind(IDBI.class).toInstance(dbi);
+            }
+        }
+    }
+
+    @BeforeMethod(groups="slow")
+    public void cleanupBeforeMethod() {
+        busHandler.reset();
+        helper.cleanupAllTables();
+    }
+
+    @BeforeClass(groups="slow")
+    public void setupClass() throws IOException {
+        loadConfig();
+        httpClient = new AsyncHttpClient();
+        mapper = new ObjectMapper();
+        mapper.registerModule(new JodaModule());
+        mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
+
+        //mapper.setPropertyNamingStrategy(new PropertyNamingStrategy.LowerCaseWithUnderscoresStrategy());        
+
+        busHandler = new TestApiListener(null);
+        this.helper = listener.getMysqlTestingHelper();
+        this.clock =  (ClockMock) listener.getClock();
+    }
+    
+    private void setupMySQL() throws IOException {
+        final String accountDdl = IOUtils.toString(TestIntegration.class.getResourceAsStream("/com/ning/billing/account/ddl.sql"));
+        final String entitlementDdl = IOUtils.toString(TestIntegration.class.getResourceAsStream("/com/ning/billing/entitlement/ddl.sql"));
+        final String invoiceDdl = IOUtils.toString(TestIntegration.class.getResourceAsStream("/com/ning/billing/invoice/ddl.sql"));
+        final String paymentDdl = IOUtils.toString(TestIntegration.class.getResourceAsStream("/com/ning/billing/payment/ddl.sql"));
+        final String utilDdl = IOUtils.toString(TestIntegration.class.getResourceAsStream("/com/ning/billing/util/ddl.sql"));
+        final String analyticsDdl = IOUtils.toString(TestIntegration.class.getResourceAsStream("/com/ning/billing/analytics/ddl.sql"));        
+        final String junctionDdl = IOUtils.toString(TestIntegration.class.getResourceAsStream("/com/ning/billing/junction/ddl.sql"));        
+
+        helper.startMysql();
+
+        helper.initDb(accountDdl);
+        helper.initDb(entitlementDdl);
+        helper.initDb(invoiceDdl);
+        helper.initDb(paymentDdl);
+        helper.initDb(utilDdl);
+        helper.initDb(analyticsDdl);        
+        helper.initDb(junctionDdl);        
+   }
+
+
+    private void loadConfig() {
+        if (config == null) {
+            config = new ConfigurationObjectFactory(System.getProperties()).build(CoreConfig.class);
+        }
+    }
+
+    @BeforeSuite(groups="slow")
+    public void setup() throws Exception {
+
+        loadSystemPropertiesFromClasspath("/killbill.properties");
+        loadConfig();
+        
+        this.helper = new MysqlTestingHelper();
+        this.clock = new ClockMock();
+        listener = new TestKillbillGuiceListener(helper, clock);
+        server = new HttpServer();
+
+        final Iterable<EventListener> eventListeners = new Iterable<EventListener>() {
+            @Override
+            public Iterator<EventListener> iterator() {
+                ArrayList<EventListener> array = new ArrayList<EventListener>();
+                array.add(listener);
+                return array.iterator();
+            }
+        };
+        server.configure(config, eventListeners, new HashMap<FilterHolder, String>());
+
+        setupMySQL();
+        helper.cleanupAllTables();
+
+        server.start();
+    }
+
+    @AfterSuite(groups="slow")
+    public void tearDown() {
+        try {
+            server.stop();
+        } catch (Exception e) {
+
+        }
+        if (helper != null) {
+            helper.stopMysql();
+        }
+    }
+
+
+    protected AccountJson createAccount(String name, String key, String email) throws Exception {
+        AccountJson input = getAccountJson(name, key, email);
+        String baseJson = mapper.writeValueAsString(input);
+        Response response = doPost(BaseJaxrsResource.ACCOUNTS_PATH, baseJson, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+        Assert.assertEquals(response.getStatusCode(), Status.CREATED.getStatusCode());
+
+        String location = response.getHeader("Location");
+        Assert.assertNotNull(location);
+
+        // Retrieves by Id based on Location returned
+        response = doGetWithUrl(location, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+        Assert.assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+
+        baseJson = response.getResponseBody();
+        AccountJson objFromJson = mapper.readValue(baseJson, AccountJson.class);
+        Assert.assertNotNull(objFromJson);
+        return objFromJson;
+    }
+
+
+
+    protected BundleJsonNoSubsciptions createBundle(String accountId, String key) throws Exception {
+        BundleJsonNoSubsciptions input = new BundleJsonNoSubsciptions(null, accountId, key, null);
+        String baseJson = mapper.writeValueAsString(input);
+        Response response = doPost(BaseJaxrsResource.BUNDLES_PATH, baseJson, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+        Assert.assertEquals(response.getStatusCode(), Status.CREATED.getStatusCode());
+
+        String location = response.getHeader("Location");
+        Assert.assertNotNull(location);
+
+        // Retrieves by Id based on Location returned
+        response = doGetWithUrl(location, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+        Assert.assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+
+        baseJson = response.getResponseBody();
+        BundleJsonNoSubsciptions objFromJson = mapper.readValue(baseJson, BundleJsonNoSubsciptions.class);
+        Assert.assertTrue(objFromJson.equalsNoId(input));
+        return objFromJson;
+    }
+
+    protected SubscriptionJsonNoEvents createSubscription(final String bundleId, final String productName, final String productCategory, final String billingPeriod, final boolean waitCompletion) throws Exception {
+
+        SubscriptionJsonNoEvents input = new SubscriptionJsonNoEvents(null, bundleId, null, productName, productCategory, billingPeriod, PriceListSet.DEFAULT_PRICELIST_NAME, null);
+        String baseJson = mapper.writeValueAsString(input);
+
+
+        Map<String, String> queryParams = waitCompletion ? getQueryParamsForCallCompletion("5") : DEFAULT_EMPTY_QUERY;
+        Response response = doPost(BaseJaxrsResource.SUBSCRIPTIONS_PATH, baseJson, queryParams, DEFAULT_HTTP_TIMEOUT_SEC);
+        Assert.assertEquals(response.getStatusCode(), Status.CREATED.getStatusCode());
+
+        String location = response.getHeader("Location");
+        Assert.assertNotNull(location);
+
+        // Retrieves by Id based on Location returned
+
+        response = doGetWithUrl(location, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+        Assert.assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+
+        baseJson = response.getResponseBody();
+        SubscriptionJsonNoEvents objFromJson = mapper.readValue(baseJson, SubscriptionJsonNoEvents.class);
+        Assert.assertTrue(objFromJson.equalsNoId(input));
+        return objFromJson;
+    }
+
+    protected Map<String, String> getQueryParamsForCallCompletion(final String timeoutSec) {
+        Map<String, String> queryParams = new HashMap<String, String>();
+        queryParams.put(BaseJaxrsResource.QUERY_CALL_COMPLETION, "true");
+        queryParams.put(BaseJaxrsResource.QUERY_CALL_TIMEOUT, timeoutSec);
+        return queryParams;
+    }
+
+    //
+    // HTTP CLIENT HELPERS
+    //
+    protected Response doPost(final String uri, final String body, final Map<String, String> queryParams, final int timeoutSec) {
+        BoundRequestBuilder builder = getBuilderWithHeaderAndQuery("POST", getUrlFromUri(uri), queryParams);
+        if (body != null) {
+            builder.setBody(body);
+        } else {
+            builder.setBody("{}");
+        }
+        return executeAndWait(builder, timeoutSec, true);
+    }
+
+    protected Response doPut(final String uri, final String body, final Map<String, String> queryParams, final int timeoutSec) {
+        final String url = String.format("http://%s:%d%s", config.getServerHost(), config.getServerPort(), uri);
+        BoundRequestBuilder builder = getBuilderWithHeaderAndQuery("PUT", url, queryParams);
+        if (body != null) {
+            builder.setBody(body);
+        } else {
+            builder.setBody("{}");
+        }
+        return executeAndWait(builder, timeoutSec, true);
+    }
+
+    protected Response doDelete(final String uri, final Map<String, String> queryParams, final int timeoutSec) {
+        final String url = String.format("http://%s:%d%s", config.getServerHost(), config.getServerPort(), uri);
+        BoundRequestBuilder builder = getBuilderWithHeaderAndQuery("DELETE", url, queryParams);
+        return executeAndWait(builder, timeoutSec, true);
+    }
+
+    protected Response doGet(final String uri, final Map<String, String> queryParams, final int timeoutSec) {
+        final String url = String.format("http://%s:%d%s", config.getServerHost(), config.getServerPort(), uri);
+        return doGetWithUrl(url, queryParams, timeoutSec);
+    }
+
+    protected Response doGetWithUrl(final String url, final Map<String, String> queryParams, final int timeoutSec) {
+        BoundRequestBuilder builder = getBuilderWithHeaderAndQuery("GET", url, queryParams);
+        return executeAndWait(builder, timeoutSec, false);
+    }
+
+    private Response executeAndWait(final BoundRequestBuilder builder, final int timeoutSec, final boolean addContextHeader) {
+        
+        if (addContextHeader) {
+            builder.addHeader(BaseJaxrsResource.HDR_CREATED_BY, createdBy);
+            builder.addHeader(BaseJaxrsResource.HDR_REASON, reason);
+            builder.addHeader(BaseJaxrsResource.HDR_COMMENT, comment);            
+        }
+        
+        Response response = null;
+        try {
+            ListenableFuture<Response> futureStatus = 
+                builder.execute(new AsyncCompletionHandler<Response>() {
+                    @Override
+                    public Response onCompleted(Response response) throws Exception {
+                        return response;
+                    }
+                });
+            response = futureStatus.get(timeoutSec, TimeUnit.SECONDS);
+        } catch (Exception e) {
+            Assert.fail(e.getMessage());			
+        }
+        Assert.assertNotNull(response);
+        return response;
+    }
+
+    private String getUrlFromUri(final String uri) {
+        return String.format("http://%s:%d%s", config.getServerHost(), config.getServerPort(), uri);
+    }
+
+    private BoundRequestBuilder getBuilderWithHeaderAndQuery(final String verb, final String url, final Map<String, String> queryParams) {
+        BoundRequestBuilder builder = null;
+        if (verb.equals("GET")) {
+            builder = httpClient.prepareGet(url);
+        } else if (verb.equals("POST")) {
+            builder = httpClient.preparePost(url);
+        } else if (verb.equals("PUT")) {
+            builder = httpClient.preparePut(url);			
+        } else if (verb.equals("DELETE")) {
+            builder = httpClient.prepareDelete(url);			
+        }
+        builder.addHeader(HEADER_CONTENT_TYPE, CONTENT_TYPE);
+        for (Entry<String, String> q : queryParams.entrySet()) {
+            builder.addQueryParameter(q.getKey(), q.getValue());
+        }
+        return builder;
+    }
+
+    public AccountJson getAccountJson(final String name, final String externalKey, final String email) {
+        String accountId = UUID.randomUUID().toString();
+        int length = 4;
+        int billCycleDay = 12;
+        String currency = "USD";
+        String paymentProvider = "noop";
+        String timeZone = "UTC";
+        String address1 = "12 rue des ecoles";
+        String address2 = "Poitier";
+        String company = "Renault";
+        String state = "Poitou";
+        String country = "France";
+        String phone = "81 53 26 56";
+
+        AccountJson accountJson = new AccountJson(accountId, name, length, externalKey, email, billCycleDay, currency, paymentProvider, timeZone, address1, address2, company, state, country, phone);
+        return accountJson;
+    }
+    
+    /**
+     * 
+     * We could implement a ClockResource in jaxrs with the ability to sync on user token
+     * but until we have a strong need for it, this is in the TODO list...
+     */
+    protected void crappyWaitForLackOfProperSynchonization() throws Exception {
+        Thread.sleep(5000);
+    }
+
+}
diff --git a/server/src/test/java/com/ning/billing/jaxrs/TestSubscription.java b/server/src/test/java/com/ning/billing/jaxrs/TestSubscription.java
new file mode 100644
index 0000000..d43c858
--- /dev/null
+++ b/server/src/test/java/com/ning/billing/jaxrs/TestSubscription.java
@@ -0,0 +1,132 @@
+/* 
+ * Copyright 2010-2011 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.jaxrs;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import java.util.Map;
+import java.util.UUID;
+
+import javax.ws.rs.core.Response.Status;
+
+import org.joda.time.DateTime;
+import org.joda.time.Interval;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.PriceListSet;
+import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.jaxrs.json.AccountJson;
+import com.ning.billing.jaxrs.json.BundleJsonNoSubsciptions;
+import com.ning.billing.jaxrs.json.SubscriptionJsonNoEvents;
+import com.ning.billing.jaxrs.resources.BaseJaxrsResource;
+import com.ning.http.client.Response;
+
+public class TestSubscription extends TestJaxrsBase {
+
+    private static final Logger log = LoggerFactory.getLogger(TestSubscription.class);
+
+    private static final String CALL_COMPLETION_TIMEOUT_SEC = "5";
+    
+
+    @Test(groups="slow", enabled=true)
+    public void testSubscriptionInTrialOk() throws Exception {
+
+        DateTime initialDate = new DateTime(2012, 4, 25, 0, 3, 42, 0);
+        clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis());
+        
+        AccountJson accountJson = createAccount("xil", "shdxilhkkl", "xil@yahoo.com");
+        BundleJsonNoSubsciptions bundleJson = createBundle(accountJson.getAccountId(), "99999");
+
+        String productName = "Shotgun";
+        BillingPeriod term = BillingPeriod.MONTHLY;
+
+        SubscriptionJsonNoEvents subscriptionJson = createSubscription(bundleJson.getBundleId(), productName, ProductCategory.BASE.toString(), term.toString(), true);
+        Assert.assertNotNull(subscriptionJson.getChargedThroughDate());
+        Assert.assertEquals(subscriptionJson.getChargedThroughDate(), subscriptionJson.getStartDate().plusDays(30));        
+        
+        String uri = BaseJaxrsResource.SUBSCRIPTIONS_PATH + "/" + subscriptionJson.getSubscriptionId().toString();
+ 
+        
+        // Retrieves with GET
+        Response response =  doGet(uri, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+        assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+        String baseJson = response.getResponseBody();
+        SubscriptionJsonNoEvents objFromJson = mapper.readValue(baseJson, SubscriptionJsonNoEvents.class);
+        Assert.assertTrue(objFromJson.equals(subscriptionJson));
+        
+        // Change plan IMM
+        String newProductName = "Assault-Rifle";
+
+        SubscriptionJsonNoEvents newInput = new SubscriptionJsonNoEvents(subscriptionJson.getSubscriptionId(),
+                subscriptionJson.getBundleId(),
+                null,
+                newProductName,
+                subscriptionJson.getProductCategory(), 
+                subscriptionJson.getBillingPeriod(), 
+                subscriptionJson.getPriceList(), null);
+        baseJson = mapper.writeValueAsString(newInput);
+
+        Map<String, String> queryParams = getQueryParamsForCallCompletion(CALL_COMPLETION_TIMEOUT_SEC);
+        response = doPut(uri, baseJson, queryParams, DEFAULT_HTTP_TIMEOUT_SEC);
+        assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+        baseJson = response.getResponseBody();
+        objFromJson = mapper.readValue(baseJson, SubscriptionJsonNoEvents.class);
+        assertTrue(objFromJson.equals(newInput));
+
+        // MOVE AFTER TRIAL
+        Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusMonths(3).plusDays(1));
+        clock.addDeltaFromReality(it.toDurationMillis());
+
+        crappyWaitForLackOfProperSynchonization();
+
+        //      
+        // Cancel EOT
+        uri = BaseJaxrsResource.SUBSCRIPTIONS_PATH + "/" + subscriptionJson.getSubscriptionId().toString();
+        response = doDelete(uri, queryParams, DEFAULT_HTTP_TIMEOUT_SEC);
+        assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+
+        // Uncancel
+        uri = BaseJaxrsResource.SUBSCRIPTIONS_PATH + "/" + subscriptionJson.getSubscriptionId().toString() + "/uncancel";
+        response = doPut(uri, baseJson, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+        Assert.assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+    }
+    
+    @Test(groups="slow", enabled=true)
+    public void testWithNonExistentSubscription() throws Exception {
+        String uri = BaseJaxrsResource.SUBSCRIPTIONS_PATH + "/" + UUID.randomUUID().toString();
+        SubscriptionJsonNoEvents subscriptionJson = new SubscriptionJsonNoEvents(null, UUID.randomUUID().toString(), null, "Pistol", ProductCategory.BASE.toString(), BillingPeriod.MONTHLY.toString(), PriceListSet.DEFAULT_PRICELIST_NAME, null);
+        String baseJson = mapper.writeValueAsString(subscriptionJson);
+        
+        Response response = doPut(uri, baseJson, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+        Assert.assertEquals(response.getStatusCode(), Status.NO_CONTENT.getStatusCode());
+        String body = response.getResponseBody();
+        Assert.assertEquals(body, "");
+        
+        response = doDelete(uri, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+        Assert.assertEquals(response.getStatusCode(), Status.NO_CONTENT.getStatusCode());
+        body = response.getResponseBody();
+        Assert.assertEquals(body, "");
+        
+        response =  doGet(uri, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+        Assert.assertEquals(response.getStatusCode(), Status.NO_CONTENT.getStatusCode());
+    }
+
+}
diff --git a/server/src/test/java/com/ning/billing/jaxrs/TestTag.java b/server/src/test/java/com/ning/billing/jaxrs/TestTag.java
new file mode 100644
index 0000000..c1f460a
--- /dev/null
+++ b/server/src/test/java/com/ning/billing/jaxrs/TestTag.java
@@ -0,0 +1,114 @@
+/* 
+ * Copyright 2010-2011 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.jaxrs;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+
+import java.util.List;
+
+import javax.ws.rs.core.Response.Status;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.Test;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.ning.billing.jaxrs.json.TagDefinitionJson;
+import com.ning.billing.jaxrs.resources.BaseJaxrsResource;
+import com.ning.http.client.Response;
+
+public class TestTag extends TestJaxrsBase {
+
+    private static final Logger log = LoggerFactory.getLogger(TestTag.class);
+
+    @Test(groups="slow", enabled=true)
+    public void testTagDefinitionOk() throws Exception {
+    
+        TagDefinitionJson input = new TagDefinitionJson("blue", "realxing color");
+        String baseJson = mapper.writeValueAsString(input);
+        Response response = doPost(BaseJaxrsResource.TAG_DEFINITIONS_PATH, baseJson, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+        assertEquals(response.getStatusCode(), Status.CREATED.getStatusCode());
+
+        String location = response.getHeader("Location");
+        assertNotNull(location);
+
+        // Retrieves by Id based on Location returned
+        response = doGetWithUrl(location, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+        assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+
+        baseJson = response.getResponseBody();
+        TagDefinitionJson objFromJson = mapper.readValue(baseJson, TagDefinitionJson.class);
+        assertNotNull(objFromJson);
+        assertEquals(objFromJson, input);
+    }
+    
+    @Test(groups="slow", enabled=true)
+    public void testMultipleTagDefinitionOk() throws Exception {
+    
+        Response response = doGet(BaseJaxrsResource.TAG_DEFINITIONS_PATH, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+        assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+        String baseJson = response.getResponseBody();
+        
+        List<TagDefinitionJson> objFromJson = mapper.readValue(baseJson, new TypeReference<List<TagDefinitionJson>>() {});
+        int sizeSystemTag = (objFromJson == null || objFromJson.size() == 0) ? 0 : objFromJson.size();
+        
+        TagDefinitionJson input = new TagDefinitionJson("blue", "realxing color");
+        baseJson = mapper.writeValueAsString(input);
+        response = doPost(BaseJaxrsResource.TAG_DEFINITIONS_PATH, baseJson, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+        assertEquals(response.getStatusCode(), Status.CREATED.getStatusCode());
+
+        input = new TagDefinitionJson("red", "hot color");
+        baseJson = mapper.writeValueAsString(input);
+        response = doPost(BaseJaxrsResource.TAG_DEFINITIONS_PATH, baseJson, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+        assertEquals(response.getStatusCode(), Status.CREATED.getStatusCode());
+
+        input = new TagDefinitionJson("yellow", "vibrant color");
+        baseJson = mapper.writeValueAsString(input);
+        response = doPost(BaseJaxrsResource.TAG_DEFINITIONS_PATH, baseJson, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+        assertEquals(response.getStatusCode(), Status.CREATED.getStatusCode());
+
+        input = new TagDefinitionJson("green", "super realxing color");
+        baseJson = mapper.writeValueAsString(input);
+        response = doPost(BaseJaxrsResource.TAG_DEFINITIONS_PATH, baseJson, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+        assertEquals(response.getStatusCode(), Status.CREATED.getStatusCode());
+
+        response = doGet(BaseJaxrsResource.TAG_DEFINITIONS_PATH, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+        assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+        baseJson = response.getResponseBody();
+        
+        objFromJson = mapper.readValue(baseJson, new TypeReference<List<TagDefinitionJson>>() {});
+        assertNotNull(objFromJson);
+        assertEquals(objFromJson.size(), 4 + sizeSystemTag);
+
+        // STEPH currently broken Tag API does not work as expected...
+        
+        /*
+        String uri = BaseJaxrsResource.TAG_DEFINITIONS_PATH + "/green"; 
+        response = doDelete(uri, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+        assertEquals(response.getStatusCode(), Status.NO_CONTENT.getStatusCode());
+    
+        response = doGet(BaseJaxrsResource.TAG_DEFINITIONS_PATH, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+        assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+        baseJson = response.getResponseBody();
+        
+        objFromJson = mapper.readValue(baseJson, new TypeReference<List<TagDefinitionJson>>() {});
+        assertNotNull(objFromJson);
+        assertEquals(objFromJson.size(), 3 + sizeSystemTag);
+        */
+    }
+    
+}
diff --git a/server/src/test/resources/catalog-weapons.xml b/server/src/test/resources/catalog-weapons.xml
new file mode 100644
index 0000000..8a97d57
--- /dev/null
+++ b/server/src/test/resources/catalog-weapons.xml
@@ -0,0 +1,641 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!--
+  ~ Copyright 2010-2011 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.
+  -->
+
+<!-- 
+Use cases covered so far:
+	Tiered Product (Pistol/Shotgun/Assault-Rifle)
+	Multiple changeEvent plan policies
+	Multiple PlanAlignment (see below, trial add-on alignments and rescue discount package)
+	Product transition rules
+	Add on (Scopes, Hoster)
+	Multi-pack addon (Extra-Ammo)
+	Addon Trial aligned to base plan (holster-monthly-regular)
+	Addon Trial aligned to creation (holster-monthly-special)
+	Rescue discount package (assault-rifle-annual-rescue)
+	Plan phase with a reccurring and a one off (refurbish-maintenance)
+	Phan with more than 2 phase (gunclub discount plans)
+		
+Use Cases to do:
+	Tiered Add On
+	Riskfree period
+	
+
+
+ -->
+<catalog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:noNamespaceSchemaLocation="CatalogSchema.xsd ">
+
+	<effectiveDate>2011-01-01T00:00:00+00:00</effectiveDate>
+	<catalogName>Firearms</catalogName>
+
+	<currencies>
+		<currency>USD</currency>
+		<currency>EUR</currency>
+		<currency>GBP</currency>
+	</currencies>
+	
+	<products>
+		<product name="Pistol">
+			<category>BASE</category>
+		</product>
+		<product name="Shotgun">
+			<category>BASE</category>
+            <available>
+                <addonProduct>Telescopic-Scope</addonProduct>
+                <addonProduct>Laser-Scope</addonProduct>
+            </available>
+		</product>
+		<product name="Assault-Rifle">
+			<category>BASE</category>
+			<included> 
+				<addonProduct>Telescopic-Scope</addonProduct>
+			</included>
+			<available>
+				<addonProduct>Laser-Scope</addonProduct>
+			</available>
+		</product>
+		<product name="Telescopic-Scope">
+			<category>ADD_ON</category>
+		</product>
+		<product name="Laser-Scope">
+			<category>ADD_ON</category>
+		</product>
+		<product name="Holster">
+			<category>ADD_ON</category>
+		</product>
+		<product name="Extra-Ammo">
+			<category>ADD_ON</category>
+		</product>
+		<product name="Refurbish-Maintenance">
+			<category>ADD_ON</category>
+		</product>
+	</products>
+	 
+	<rules>
+		<changePolicy>
+			<changePolicyCase> 
+				<phaseType>TRIAL</phaseType>
+				<policy>IMMEDIATE</policy>
+			</changePolicyCase>
+            <changePolicyCase> 
+                <toProduct>Assault-Rifle</toProduct>
+                <policy>IMMEDIATE</policy>
+            </changePolicyCase>
+            <changePolicyCase> 
+                <fromProduct>Pistol</fromProduct>            
+                <toProduct>Shotgun</toProduct>
+                <policy>IMMEDIATE</policy>
+            </changePolicyCase>
+			<changePolicyCase> 
+				<toPriceList>rescue</toPriceList>
+				<policy>END_OF_TERM</policy>
+			</changePolicyCase>
+			<changePolicyCase> 
+				<fromBillingPeriod>MONTHLY</fromBillingPeriod>
+				<toBillingPeriod>ANNUAL</toBillingPeriod>
+				<policy>IMMEDIATE</policy>
+			</changePolicyCase>
+			<changePolicyCase> 
+				<fromBillingPeriod>ANNUAL</fromBillingPeriod>
+				<toBillingPeriod>MONTHLY</toBillingPeriod>
+				<policy>END_OF_TERM</policy>
+			</changePolicyCase>
+			<changePolicyCase> 
+				<policy>END_OF_TERM</policy>
+			</changePolicyCase>
+		</changePolicy>
+		<changeAlignment>
+			<changeAlignmentCase>
+				<toPriceList>rescue</toPriceList>
+				<alignment>CHANGE_OF_PLAN</alignment>
+			</changeAlignmentCase>
+			<changeAlignmentCase>
+				<fromPriceList>rescue</fromPriceList>
+				<toPriceList>rescue</toPriceList>
+				<alignment>CHANGE_OF_PRICELIST</alignment>
+			</changeAlignmentCase>
+            <changeAlignmentCase>
+                <alignment>START_OF_SUBSCRIPTION</alignment>
+            </changeAlignmentCase>
+		</changeAlignment>
+		<cancelPolicy>
+			<cancelPolicyCase>
+				<phaseType>TRIAL</phaseType>
+				<policy>IMMEDIATE</policy>
+			</cancelPolicyCase>
+            <cancelPolicyCase>
+                <policy>END_OF_TERM</policy>
+            </cancelPolicyCase>
+		</cancelPolicy>
+		<createAlignment>
+		    <createAlignmentCase>
+		        <product>Laser-Scope</product>
+                <alignment>START_OF_SUBSCRIPTION</alignment>
+            </createAlignmentCase>
+            <createAlignmentCase>
+                <alignment>START_OF_BUNDLE</alignment>
+            </createAlignmentCase>
+		</createAlignment>
+		<billingAlignment>
+			<billingAlignmentCase>
+				<productCategory>ADD_ON</productCategory>
+				<alignment>BUNDLE</alignment>
+			</billingAlignmentCase>
+			<billingAlignmentCase>
+				<billingPeriod>ANNUAL</billingPeriod>
+				<alignment>SUBSCRIPTION</alignment>
+			</billingAlignmentCase>
+			<billingAlignmentCase>
+				<alignment>ACCOUNT</alignment>
+			</billingAlignmentCase>
+		</billingAlignment>
+		<priceList>
+			<priceListCase>
+				<fromPriceList>rescue</fromPriceList>
+				<toPriceList>DEFAULT</toPriceList>
+			</priceListCase>
+		</priceList>
+	</rules>
+
+	<plans>
+		<plan name="pistol-monthly">
+			<product>Pistol</product>
+			<initialPhases>
+				<phase type="TRIAL">
+					<duration>
+						<unit>DAYS</unit>
+						<number>30</number>
+					</duration>
+					<billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+					<fixedPrice> <!-- empty price implies $0 -->
+					</fixedPrice>
+				</phase>
+			</initialPhases>
+			<finalPhase type="EVERGREEN">
+				<duration>
+					<unit>UNLIMITED</unit>
+				</duration>
+				<billingPeriod>MONTHLY</billingPeriod>
+				<recurringPrice>
+					<price><currency>GBP</currency><value>29.95</value></price>
+					<price><currency>EUR</currency><value>29.95</value></price> 
+					<price><currency>USD</currency><value>29.95</value></price>								
+				</recurringPrice>
+			</finalPhase>
+		</plan>
+		<plan name="shotgun-monthly">
+			<product>Shotgun</product>
+			<initialPhases>
+				<phase type="TRIAL">
+					<duration>
+						<unit>DAYS</unit>
+						<number>30</number>
+					</duration>
+					<billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+					<fixedPrice></fixedPrice>
+				    <!-- no price implies $0 -->
+				</phase>
+			</initialPhases>
+			<finalPhase type="EVERGREEN">
+				<duration>
+					<unit>UNLIMITED</unit>
+					<number>-1</number>
+				</duration>
+				<billingPeriod>MONTHLY</billingPeriod>
+				<recurringPrice>
+					<price><currency>USD</currency><value>249.95</value></price>								
+					<price><currency>EUR</currency><value>149.95</value></price>
+					<price><currency>GBP</currency><value>169.95</value></price>
+				</recurringPrice>
+			</finalPhase>
+		</plan>
+		<plan name="assault-rifle-monthly">
+			<product>Assault-Rifle</product>
+			<initialPhases>
+				<phase type="TRIAL">
+					<duration>
+						<unit>DAYS</unit>
+						<number>30</number>
+					</duration>
+					<billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+					<fixedPrice>
+					</fixedPrice>
+				</phase>
+			</initialPhases>
+			<finalPhase type="EVERGREEN">
+				<duration>
+					<unit>UNLIMITED</unit>
+				</duration>
+				<billingPeriod>MONTHLY</billingPeriod>
+				<recurringPrice>
+					<price><currency>USD</currency><value>599.95</value></price>								
+					<price><currency>EUR</currency><value>349.95</value></price>
+					<price><currency>GBP</currency><value>399.95</value></price>
+				</recurringPrice>
+			</finalPhase>
+		</plan>
+		<plan name="pistol-annual">
+			<product>Pistol</product>
+			<initialPhases>
+				<phase type="TRIAL">
+					<duration>
+						<unit>DAYS</unit>
+						<number>30</number>
+					</duration>
+					<billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+					<fixedPrice>
+					</fixedPrice>
+				</phase>
+			</initialPhases>
+			<finalPhase type="EVERGREEN">
+				<duration>
+					<unit>UNLIMITED</unit>
+				</duration>
+				<billingPeriod>ANNUAL</billingPeriod>
+				<recurringPrice>
+					<price><currency>USD</currency><value>199.95</value></price>								
+					<price><currency>EUR</currency><value>199.95</value></price>
+					<price><currency>GBP</currency><value>199.95</value></price>
+				</recurringPrice>
+			</finalPhase>
+		</plan>
+		<plan name="shotgun-annual">
+			<product>Shotgun</product>
+			<initialPhases>
+				<phase type="TRIAL">
+					<duration>
+						<unit>DAYS</unit>
+						<number>30</number>
+					</duration>
+					<billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+					<fixedPrice>
+					</fixedPrice>
+				</phase>
+			</initialPhases>
+			<finalPhase type="EVERGREEN">
+				<duration>
+					<unit>UNLIMITED</unit>
+				</duration>
+				<billingPeriod>ANNUAL</billingPeriod>
+				<recurringPrice>
+					<price><currency>USD</currency><value>2399.95</value></price>								
+					<price><currency>EUR</currency><value>1499.95</value></price>
+					<price><currency>GBP</currency><value>1699.95</value></price>
+				</recurringPrice>
+			</finalPhase>
+		</plan>
+		<plan name="assault-rifle-annual">
+			<product>Assault-Rifle</product>
+			<initialPhases>
+				<phase type="TRIAL">
+					<duration>
+						<unit>DAYS</unit>
+						<number>30</number>
+					</duration>
+					<billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+					<fixedPrice>
+					</fixedPrice>
+				</phase>
+			</initialPhases>
+			<finalPhase type="EVERGREEN">
+				<duration>
+					<unit>UNLIMITED</unit>
+				</duration>
+				<billingPeriod>ANNUAL</billingPeriod>
+				<recurringPrice>
+					<price><currency>USD</currency><value>5999.95</value></price>								
+					<price><currency>EUR</currency><value>3499.95</value></price>
+					<price><currency>GBP</currency><value>3999.95</value></price>
+				</recurringPrice>
+			</finalPhase>
+		</plan>
+		<plan name="pistol-annual-gunclub-discount">
+			<product>Pistol</product>
+			<initialPhases>
+				<phase type="TRIAL">
+					<duration>
+						<unit>DAYS</unit>
+						<number>30</number>
+					</duration>
+					<billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+					<fixedPrice>
+					</fixedPrice>
+				</phase>
+				<phase type="DISCOUNT">
+					<duration>
+						<unit>MONTHS</unit>
+						<number>6</number>
+					</duration>
+					<billingPeriod>MONTHLY</billingPeriod>
+					<recurringPrice>
+						<price><currency>USD</currency><value>9.95</value></price>								
+						<price><currency>EUR</currency><value>9.95</value></price>
+						<price><currency>GBP</currency><value>9.95</value></price>
+					</recurringPrice>
+				</phase>
+			</initialPhases>
+			<finalPhase type="EVERGREEN">
+				<duration>
+					<unit>UNLIMITED</unit>
+				</duration>
+				<billingPeriod>ANNUAL</billingPeriod>
+				<recurringPrice>
+					<price><currency>USD</currency><value>199.95</value></price>								
+					<price><currency>EUR</currency><value>199.95</value></price>
+					<price><currency>GBP</currency><value>199.95</value></price>
+				</recurringPrice>
+			</finalPhase>
+		</plan>
+		<plan name="shotgun-annual-gunclub-discount">
+			<product>Shotgun</product>
+			<initialPhases>
+				<phase type="TRIAL">
+					<duration>
+						<unit>DAYS</unit>
+						<number>30</number>
+					</duration>
+					<billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+					<fixedPrice>
+					</fixedPrice>
+				</phase>
+				<phase type="DISCOUNT">
+					<duration>
+						<unit>MONTHS</unit>
+						<number>6</number>
+					</duration>
+					<billingPeriod>MONTHLY</billingPeriod>
+					<recurringPrice>
+						<price><currency>USD</currency><value>19.95</value></price>								
+						<price><currency>EUR</currency><value>49.95</value></price>
+						<price><currency>GBP</currency><value>69.95</value></price>
+					</recurringPrice>
+				</phase>
+			</initialPhases>
+			<finalPhase type="EVERGREEN">
+				<duration>
+					<unit>UNLIMITED</unit>
+				</duration>
+				<billingPeriod>ANNUAL</billingPeriod>
+				<recurringPrice>
+					<price><currency>USD</currency><value>2399.95</value></price>								
+					<price><currency>EUR</currency><value>1499.95</value></price>
+					<price><currency>GBP</currency><value>1699.95</value></price>
+				</recurringPrice>
+			</finalPhase>
+		</plan>
+		<plan name="assault-rifle-annual-gunclub-discount">
+			<product>Assault-Rifle</product>
+			<initialPhases>
+				<phase type="TRIAL">
+					<duration>
+						<unit>DAYS</unit>
+						<number>30</number>
+					</duration>
+					<billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+					<fixedPrice>
+					</fixedPrice>
+				</phase>
+				<phase type="DISCOUNT">
+					<duration>
+						<unit>MONTHS</unit>
+						<number>6</number>
+					</duration>
+					<billingPeriod>MONTHLY</billingPeriod>
+					<recurringPrice>
+						<price><currency>USD</currency><value>99.95</value></price>								
+						<price><currency>EUR</currency><value>99.95</value></price>
+						<price><currency>GBP</currency><value>99.95</value></price>
+						</recurringPrice>
+				</phase>
+			</initialPhases>
+			<finalPhase type="EVERGREEN">
+				<duration>
+					<unit>UNLIMITED</unit>
+				</duration>
+				<billingPeriod>ANNUAL</billingPeriod>
+				<recurringPrice>
+					<price><currency>USD</currency><value>5999.95</value></price>								
+					<price><currency>EUR</currency><value>3499.95</value></price>
+					<price><currency>GBP</currency><value>3999.95</value></price>
+				</recurringPrice>
+			</finalPhase>
+		</plan>
+		<plan name="laser-scope-monthly">
+		<product>Laser-Scope</product>
+           <initialPhases>
+              <phase type="DISCOUNT">
+                 <duration>
+                    <unit>MONTHS</unit>
+                    <number>1</number>
+                  </duration>
+                 <billingPeriod>MONTHLY</billingPeriod>
+                    <recurringPrice>
+                      <price><currency>USD</currency><value>999.95</value></price>                             
+                      <price><currency>EUR</currency><value>499.95</value></price>
+                      <price><currency>GBP</currency><value>999.95</value></price>
+                      </recurringPrice>
+                </phase>
+            </initialPhases>
+			<finalPhase type="EVERGREEN">
+				<duration>
+					<unit>UNLIMITED</unit>
+				</duration>
+				<billingPeriod>MONTHLY</billingPeriod>
+				<recurringPrice>
+					<price><currency>USD</currency><value>1999.95</value></price>								
+					<price><currency>EUR</currency><value>1499.95</value></price>
+					<price><currency>GBP</currency><value>1999.95</value></price>
+				</recurringPrice>
+			</finalPhase>
+		</plan>
+		<plan name="telescopic-scope-monthly">
+			<product>Telescopic-Scope</product>
+			<initialPhases>
+              <phase type="DISCOUNT">
+                 <duration>
+                    <unit>MONTHS</unit>
+                    <number>1</number>
+                  </duration>
+                 <billingPeriod>MONTHLY</billingPeriod>
+                    <recurringPrice>
+                      <price><currency>USD</currency><value>399.95</value></price>                             
+                      <price><currency>EUR</currency><value>299.95</value></price>
+                      <price><currency>GBP</currency><value>399.95</value></price>
+                      </recurringPrice>
+                </phase>
+            </initialPhases>
+			<finalPhase type="EVERGREEN">
+				<duration>
+					<unit>UNLIMITED</unit>
+				</duration>
+				<billingPeriod>MONTHLY</billingPeriod>
+				<recurringPrice>
+					<price><currency>USD</currency><value>999.95</value></price>								
+					<price><currency>EUR</currency><value>499.95</value></price>
+					<price><currency>GBP</currency><value>999.95</value></price>
+				</recurringPrice>
+			</finalPhase>
+		</plan>
+		<plan name="extra-ammo-monthly">
+			<product>Extra-Ammo</product>
+			<finalPhase type="EVERGREEN">
+				<duration>
+					<unit>UNLIMITED</unit>
+				</duration>
+				<billingPeriod>MONTHLY</billingPeriod>
+				<recurringPrice>
+					<price><currency>USD</currency><value>999.95</value></price>								
+					<price><currency>EUR</currency><value>499.95</value></price>
+					<price><currency>GBP</currency><value>999.95</value></price>
+				</recurringPrice>
+			</finalPhase>
+			<plansAllowedInBundle>-1</plansAllowedInBundle> <!-- arbitrary number of these (multipack) -->
+		</plan>
+		<plan name="holster-monthly-regular">
+			<product>Holster</product>
+			<initialPhases>
+				<phase type="TRIAL">
+					<duration>
+						<unit>DAYS</unit>
+						<number>30</number>
+					</duration>
+					<billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+					<fixedPrice>
+					</fixedPrice>
+				</phase>
+			</initialPhases>
+			<finalPhase type="EVERGREEN">
+				<duration>
+					<unit>UNLIMITED</unit>
+				</duration>
+				<billingPeriod>ANNUAL</billingPeriod>
+				<recurringPrice>
+					<price><currency>USD</currency><value>199.95</value></price>								
+					<price><currency>EUR</currency><value>199.95</value></price>
+					<price><currency>GBP</currency><value>199.95</value></price>
+				</recurringPrice>
+			</finalPhase>
+		</plan>
+		<plan name="holster-monthly-special">
+			<product>Holster</product>
+			<initialPhases>
+				<phase type="TRIAL">
+					<duration>
+						<unit>DAYS</unit>
+						<number>30</number>
+					</duration>
+					<billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+					<fixedPrice>
+					</fixedPrice>
+				</phase>
+			</initialPhases>
+			<finalPhase type="EVERGREEN">
+				<duration>
+					<unit>UNLIMITED</unit>
+				</duration>
+				<billingPeriod>ANNUAL</billingPeriod>
+				<recurringPrice>
+					<price><currency>USD</currency><value>199.95</value></price>								
+					<price><currency>EUR</currency><value>199.95</value></price>
+					<price><currency>GBP</currency><value>199.95</value></price>
+				</recurringPrice>
+			</finalPhase>
+		</plan>
+		<plan name="assault-rifle-annual-rescue">
+			<product>Assault-Rifle</product>
+			<initialPhases>
+				<phase type="DISCOUNT">
+					<duration>
+						<unit>YEARS</unit>
+						<number>1</number>
+					</duration>
+					<billingPeriod>ANNUAL</billingPeriod>
+					<recurringPrice>
+						<price><currency>USD</currency><value>5999.95</value></price>								
+						<price><currency>EUR</currency><value>3499.95</value></price>
+						<price><currency>GBP</currency><value>3999.95</value></price>
+					</recurringPrice>
+				</phase>
+			</initialPhases>
+			<finalPhase type="EVERGREEN">
+				<duration>
+					<unit>UNLIMITED</unit>
+				</duration>
+				<billingPeriod>ANNUAL</billingPeriod>
+				<recurringPrice>
+					<price><currency>USD</currency><value>5999.95</value></price>								
+					<price><currency>EUR</currency><value>3499.95</value></price>
+					<price><currency>GBP</currency><value>3999.95</value></price>
+				</recurringPrice>
+			</finalPhase>
+		</plan>
+		<plan name="refurbish-maintenance">
+			<product>Refurbish-Maintenance</product>
+			<finalPhase type="FIXEDTERM">
+				<duration>
+					<unit>MONTHS</unit>
+					<number>12</number>
+				</duration>
+				<billingPeriod>MONTHLY</billingPeriod>
+				<recurringPrice>
+					<price><currency>USD</currency><value>199.95</value></price>								
+					<price><currency>EUR</currency><value>199.95</value></price>
+					<price><currency>GBP</currency><value>199.95</value></price>
+				</recurringPrice>
+				<fixedPrice>
+					<price><currency>USD</currency><value>599.95</value></price>								
+					<price><currency>EUR</currency><value>599.95</value></price>
+					<price><currency>GBP</currency><value>599.95</value></price>
+				</fixedPrice>
+			</finalPhase>
+		</plan>
+	</plans>
+	<priceLists>
+		<defaultPriceList name="DEFAULT"> 
+			<plans>
+				<plan>pistol-monthly</plan>
+				<plan>shotgun-monthly</plan>
+				<plan>assault-rifle-monthly</plan>
+				<plan>pistol-annual</plan>
+				<plan>shotgun-annual</plan>
+				<plan>assault-rifle-annual</plan>
+				<plan>laser-scope-monthly</plan>
+				<plan>telescopic-scope-monthly</plan>
+				<plan>extra-ammo-monthly</plan>
+				<plan>holster-monthly-regular</plan>
+				<plan>refurbish-maintenance</plan>
+			</plans>
+		</defaultPriceList>
+		<childPriceList name="gunclubDiscount">
+			<plans>
+				<plan>pistol-monthly</plan>
+				<plan>shotgun-monthly</plan>
+				<plan>assault-rifle-monthly</plan>
+				<plan>pistol-annual-gunclub-discount</plan>
+				<plan>shotgun-annual-gunclub-discount</plan>
+				<plan>assault-rifle-annual-gunclub-discount</plan>
+				<plan>holster-monthly-special</plan>
+			</plans>
+		</childPriceList>
+		<childPriceList name="rescue">
+			<plans>
+				<plan>assault-rifle-annual-rescue</plan>
+			</plans>
+		</childPriceList>
+	</priceLists>
+
+</catalog>
diff --git a/server/src/test/resources/killbill.properties b/server/src/test/resources/killbill.properties
new file mode 100644
index 0000000..9aa3e66
--- /dev/null
+++ b/server/src/test/resources/killbill.properties
@@ -0,0 +1,22 @@
+# Use killbill util test properties (DbiProvider/MysqltestingHelper) on the test side configured with killbill
+com.ning.billing.dbi.jdbc.url=jdbc:mysql://127.0.0.1:3306/killbill
+
+killbill.catalog.uri=file:src/test/resources/catalog-weapons.xml
+
+killbill.payment.engine.events.off=false
+killbill.payment.retry.days=8,8,8
+
+user.timezone=UTC
+
+com.ning.core.server.jetty.logPath=/var/tmp/.logs
+
+killbill.payment.engine.notifications.sleep=100
+killbill.invoice.engine.notifications.sleep=100
+killbill.entitlement.engine.notifications.sleep=100
+killbill.billing.util.persistent.bus.sleep=100
+killbill.billing.util.persistent.bus.nbThreads=1
+# Local DB 
+#com.ning.billing.dbi.test.useLocalDb=true
+
+
+
diff --git a/server/src/test/resources/log4j.xml b/server/src/test/resources/log4j.xml
new file mode 100644
index 0000000..0c84cc0
--- /dev/null
+++ b/server/src/test/resources/log4j.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright 2010-2011 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.
+  -->
+
+<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
+<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
+    <appender name="stdout" class="org.apache.log4j.ConsoleAppender">
+        <param name="Target" value="System.out"/>
+        <layout class="org.apache.log4j.PatternLayout">
+            <param name="ConversionPattern" value="%p	%d{ISO8601}	%X{trace}	%t	%c	%m%n"/>
+        </layout>
+    </appender>
+
+
+    <logger name="com.ning.billing.jaxrs">
+        <level value="debug"/>
+    </logger>
+    <logger name="com.ning.billing.server">
+        <level value="info"/>
+    </logger>
+
+    <root>
+        <priority value="info"/>
+        <appender-ref ref="stdout"/>
+    </root>
+</log4j:configuration>

util/pom.xml 24(+24 -0)

diff --git a/util/pom.xml b/util/pom.xml
index 806c8c6..23d47a3 100644
--- a/util/pom.xml
+++ b/util/pom.xml
@@ -60,11 +60,27 @@
             <artifactId>joda-time</artifactId>
         </dependency>
         <dependency>
+            <groupId>org.codehaus.jackson</groupId>
+            <artifactId>jackson-core-asl</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.codehaus.jackson</groupId>
+            <artifactId>jackson-mapper-asl</artifactId>
+        </dependency>
+        <dependency>
             <groupId>org.testng</groupId>
             <artifactId>testng</artifactId>
             <scope>test</scope>
         </dependency>
         <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-email</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.directory.studio</groupId>
+            <artifactId>org.apache.commons.io</artifactId>
+        </dependency>
+        <dependency>
             <groupId>commons-io</groupId>
             <artifactId>commons-io</artifactId>
             <scope>test</scope>
@@ -83,6 +99,14 @@
             <artifactId>awaitility</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>com.samskivert</groupId>
+            <artifactId>jmustache</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>commons-io</groupId>
+            <artifactId>commons-io</artifactId>
+        </dependency>
     </dependencies>
     <build>
         <plugins>
diff --git a/util/src/main/java/com/ning/billing/util/bus/dao/BusEventEntry.java b/util/src/main/java/com/ning/billing/util/bus/dao/BusEventEntry.java
new file mode 100644
index 0000000..8e98211
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/bus/dao/BusEventEntry.java
@@ -0,0 +1,98 @@
+/* 
+ * Copyright 2010-2011 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.util.bus.dao;
+
+import org.joda.time.DateTime;
+
+import com.ning.billing.util.queue.PersistentQueueEntryLifecycle;
+
+public class BusEventEntry implements PersistentQueueEntryLifecycle  {
+    
+    private final long id;
+    private final String owner;
+    private final String createdOwner;
+    private final DateTime nextAvailable;
+    private final PersistentQueueEntryLifecycleState processingState;
+    private final String busEventClass;
+    private final String busEventJson;
+
+    public BusEventEntry(final long id, final String createdOwner, final String owner, final DateTime nextAvailable, PersistentQueueEntryLifecycleState processingState, final String busEventClass, final String busEventJson) {
+        this.id = id;
+        this.createdOwner = createdOwner;
+        this.owner = owner;
+        this.nextAvailable = nextAvailable;
+        this.processingState = processingState;
+        this.busEventClass = busEventClass;
+        this.busEventJson = busEventJson;
+    }
+
+    public BusEventEntry(final String createdOwner, final String busEventClass, final String busEventJson) {
+        this(0, createdOwner, null, null, null, busEventClass, busEventJson);
+    }
+
+    
+
+    public long getId() {
+        return id;
+    }
+
+    public String getBusEventClass() {
+        return busEventClass;
+    }
+
+    public String getBusEventJson() {
+        return busEventJson;
+    }
+
+    @Override
+    public String getOwner() {
+        return owner;
+    }
+
+    @Override
+    public String getCreatedOwner() {
+        return createdOwner;
+    }
+
+    @Override
+    public DateTime getNextAvailableDate() {
+        return nextAvailable;
+    }
+
+    @Override
+    public PersistentQueueEntryLifecycleState getProcessingState() {
+        return processingState;
+    }
+
+    @Override
+    public boolean isAvailableForProcessing(DateTime now) {
+        switch(processingState) {
+        case AVAILABLE:
+            break;
+        case IN_PROCESSING:
+            // Somebody already got the event, not available yet
+            if (nextAvailable.isAfter(now)) {
+                return false;
+            }
+            break;
+        case PROCESSED:
+            return false;
+        default:
+            throw new RuntimeException(String.format("Unkwnon IEvent processing state %s", processingState));
+        }
+        return true;
+    }
+}
diff --git a/util/src/main/java/com/ning/billing/util/bus/dao/PersistentBusSqlDao.java b/util/src/main/java/com/ning/billing/util/bus/dao/PersistentBusSqlDao.java
new file mode 100644
index 0000000..ca98098
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/bus/dao/PersistentBusSqlDao.java
@@ -0,0 +1,96 @@
+/* 
+ * Copyright 2010-2011 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.util.bus.dao;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Date;
+
+import org.joda.time.DateTime;
+import org.skife.jdbi.v2.SQLStatement;
+import org.skife.jdbi.v2.StatementContext;
+import org.skife.jdbi.v2.sqlobject.Bind;
+import org.skife.jdbi.v2.sqlobject.Binder;
+import org.skife.jdbi.v2.sqlobject.SqlQuery;
+import org.skife.jdbi.v2.sqlobject.SqlUpdate;
+import org.skife.jdbi.v2.sqlobject.customizers.Mapper;
+import org.skife.jdbi.v2.sqlobject.mixins.CloseMe;
+import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
+import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
+import org.skife.jdbi.v2.tweak.ResultSetMapper;
+
+import com.ning.billing.util.dao.BinderBase;
+import com.ning.billing.util.dao.MapperBase;
+import com.ning.billing.util.queue.PersistentQueueEntryLifecycle.PersistentQueueEntryLifecycleState;
+
+@ExternalizedSqlViaStringTemplate3()
+public interface PersistentBusSqlDao extends Transactional<PersistentBusSqlDao>, CloseMe {
+
+    
+    @SqlQuery
+    @Mapper(PersistentBusSqlMapper.class)
+    public BusEventEntry getNextBusEventEntry(@Bind("max") int max, @Bind("owner") String owner, @Bind("now") Date now);
+    
+    @SqlUpdate
+    public int claimBusEvent(@Bind("owner") String owner, @Bind("nextAvailable") Date nextAvailable,
+                             @Bind("recordId") Long id, @Bind("now") Date now);
+
+    @SqlUpdate
+    public void clearBusEvent(@Bind("recordId") Long id, @Bind("owner") String owner);
+
+    @SqlUpdate
+    public void removeBusEventsById(@Bind("recordId") Long id);
+    
+    @SqlUpdate
+    public void insertBusEvent(@Bind(binder = PersistentBusSqlBinder.class) BusEventEntry evt);
+
+    @SqlUpdate
+    public void insertClaimedHistory(@Bind("ownerId") String owner, @Bind("claimedDate") Date claimedDate,
+                                     @Bind("busEventId") long id);
+
+    
+    public static class PersistentBusSqlBinder extends BinderBase implements Binder<Bind, BusEventEntry> {
+
+        @Override
+        public void bind(@SuppressWarnings("rawtypes") SQLStatement stmt, Bind bind, BusEventEntry evt) {
+            stmt.bind("className", evt.getBusEventClass());
+            stmt.bind("eventJson", evt.getBusEventJson());
+            stmt.bind("createdDate", getDate(new DateTime()));
+            stmt.bind("creatingOwner", evt.getCreatedOwner());
+            stmt.bind("processingAvailableDate", getDate(evt.getNextAvailableDate()));
+            stmt.bind("processingOwner", evt.getOwner());
+            stmt.bind("processingState", PersistentQueueEntryLifecycleState.AVAILABLE.toString());
+        }
+    }
+    
+    public static class PersistentBusSqlMapper extends MapperBase implements ResultSetMapper<BusEventEntry> {
+
+        @Override
+        public BusEventEntry map(int index, ResultSet r, StatementContext ctx)
+                throws SQLException {
+
+            final Long recordId = r.getLong("record_id");
+            final String className = r.getString("class_name"); 
+            final String createdOwner = r.getString("creating_owner");
+            final String eventJson = r.getString("event_json"); 
+            final DateTime nextAvailableDate = getDate(r, "processing_available_date");
+            final String processingOwner = r.getString("processing_owner");
+            final PersistentQueueEntryLifecycleState processingState = PersistentQueueEntryLifecycleState.valueOf(r.getString("processing_state"));
+            
+            return new BusEventEntry(recordId, createdOwner, processingOwner, nextAvailableDate, processingState, className, eventJson);
+        }
+    }
+}
diff --git a/util/src/main/java/com/ning/billing/util/bus/DefaultBusService.java b/util/src/main/java/com/ning/billing/util/bus/DefaultBusService.java
index 2a572ec..ad287e4 100644
--- a/util/src/main/java/com/ning/billing/util/bus/DefaultBusService.java
+++ b/util/src/main/java/com/ning/billing/util/bus/DefaultBusService.java
@@ -22,8 +22,13 @@ import com.ning.billing.lifecycle.LifecycleHandlerType.LifecycleLevel;
 
 public class DefaultBusService implements BusService {
 
-    private final static String EVENT_BUS_SERVICE = "bus-service";
-
+    
+    public final static String EVENT_BUS_GROUP_NAME = "bus-grp";
+    public final static String EVENT_BUS_TH_NAME = "bus-th";
+    
+    public final static String EVENT_BUS_SERVICE = "bus-service";
+    public final static String EVENT_BUS_IDENTIFIER = EVENT_BUS_SERVICE;
+    
     private final Bus eventBus;
 
     @Inject
diff --git a/util/src/main/java/com/ning/billing/util/bus/InMemoryBus.java b/util/src/main/java/com/ning/billing/util/bus/InMemoryBus.java
index 31105c8..5974bfd 100644
--- a/util/src/main/java/com/ning/billing/util/bus/InMemoryBus.java
+++ b/util/src/main/java/com/ning/billing/util/bus/InMemoryBus.java
@@ -28,9 +28,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
 
 public class InMemoryBus implements Bus {
 
-    private final static String EVENT_BUS_IDENTIFIER = "bus-service";
-    private final static String EVENT_BUS_GROUP_NAME = "bus-grp";
-    private final static String EVENT_BUS_TH_NAME = "bus-th";
+ 
 
     private static final Logger log = LoggerFactory.getLogger(InMemoryBus.class);
 
@@ -64,15 +62,15 @@ public class InMemoryBus implements Bus {
 
     public InMemoryBus() {
 
-        final ThreadGroup group = new ThreadGroup(EVENT_BUS_GROUP_NAME);
+        final ThreadGroup group = new ThreadGroup(DefaultBusService.EVENT_BUS_GROUP_NAME);
         Executor executor = Executors.newCachedThreadPool(new ThreadFactory() {
             @Override
             public Thread newThread(Runnable r) {
-                return new Thread(group, r, EVENT_BUS_TH_NAME);
+                return new Thread(group, r, DefaultBusService.EVENT_BUS_TH_NAME);
             }
         });
 
-        this.delegate = new EventBusDelegate(EVENT_BUS_IDENTIFIER, group, executor);
+        this.delegate = new EventBusDelegate(DefaultBusService.EVENT_BUS_IDENTIFIER, group, executor);
         this.isInitialized = new AtomicBoolean(false);
     }
 
diff --git a/util/src/main/java/com/ning/billing/util/bus/PersistentBus.java b/util/src/main/java/com/ning/billing/util/bus/PersistentBus.java
new file mode 100644
index 0000000..92b35d7
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/bus/PersistentBus.java
@@ -0,0 +1,197 @@
+/* 
+ * Copyright 2010-2011 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.util.bus;
+
+
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
+
+import org.codehaus.jackson.map.ObjectMapper;
+import org.codehaus.jackson.map.SerializationConfig;
+import org.skife.jdbi.v2.IDBI;
+import org.skife.jdbi.v2.Transaction;
+import org.skife.jdbi.v2.TransactionStatus;
+import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.eventbus.EventBus;
+import com.google.inject.Inject;
+import com.google.inject.name.Named;
+import com.ning.billing.config.PersistentQueueConfig;
+import com.ning.billing.util.Hostname;
+import com.ning.billing.util.bus.dao.BusEventEntry;
+import com.ning.billing.util.bus.dao.PersistentBusSqlDao;
+import com.ning.billing.util.clock.Clock;
+import com.ning.billing.util.glue.BusModule;
+import com.ning.billing.util.queue.PersistentQueueBase;
+
+
+public class PersistentBus extends PersistentQueueBase implements Bus {
+
+    private final static long DELTA_IN_PROCESSING_TIME_MS = 1000L * 60L * 5L; // 5 minutes
+    private final static int MAX_BUS_EVENTS = 1;
+    
+    private static final Logger log = LoggerFactory.getLogger(PersistentBus.class);
+    
+    private final PersistentBusSqlDao dao;
+    
+    private final ObjectMapper objectMapper;
+    private final EventBusDelegate eventBusDelegate;
+    private final Clock clock;
+    private final String hostname;
+    
+
+    
+    private static final class EventBusDelegate extends EventBus {
+        public EventBusDelegate(String busName) {
+            super(busName);
+        }
+
+        // STEPH we can't override the method because EventHandler is package private scope
+        // Logged a bug against guava (Issue 981)
+        /*
+        @Override
+        protected void dispatch(Object event, EventHandler wrapper) {
+            try {
+              wrapper.handleEvent(event);
+            } catch (InvocationTargetException e) {
+              logger.log(Level.SEVERE,
+                  "Could not dispatch event: " + event + " to handler " + wrapper, e);
+            }
+          }
+          */
+    }
+    
+    @Inject
+    public PersistentBus(final IDBI dbi, final Clock clock, final PersistentBusConfig config) {
+        
+        super("Bus", Executors.newFixedThreadPool(config.getNbThreads(), new ThreadFactory() {
+            @Override
+            public Thread newThread(Runnable r) {
+                return new Thread(new ThreadGroup(DefaultBusService.EVENT_BUS_GROUP_NAME),
+                        r,
+                        DefaultBusService.EVENT_BUS_TH_NAME);
+            }
+        }), config.getNbThreads(), config);
+        this.dao = dbi.onDemand(PersistentBusSqlDao.class);
+        this.clock = clock;
+        this.objectMapper = new ObjectMapper();
+        this.objectMapper.disable(SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS);
+        this.eventBusDelegate = new EventBusDelegate("Killbill EventBus");
+        this.hostname = Hostname.get();
+    }
+    
+    @Override
+    public void start() {
+        startQueue();
+    }
+
+    @Override
+    public void stop() {
+        stopQueue();
+    }
+
+    @Override
+    public int doProcessEvents() {
+
+        List<BusEventEntry> events = getNextBusEvent();
+        if (events.size() == 0) {
+            return 0;
+        }
+
+        int result = 0;
+        for (final BusEventEntry cur : events) {
+            BusEvent evt = deserializeBusEvent(cur.getBusEventClass(), cur.getBusEventJson());
+            result++;
+            // STEPH exception handling is done by GUAVA-- logged a bug Issue-780
+            eventBusDelegate.post(evt);
+            dao.clearBusEvent(cur.getId(), hostname);
+        }
+        return result;
+    }
+
+    private BusEvent deserializeBusEvent(final String className, final String json) {
+        try {
+            Class<?> claz = Class.forName(className);
+            return (BusEvent) objectMapper.readValue(json, claz);
+        } catch (Exception e) {
+            log.error(String.format("Failed to deserialize json object %s for class %s", json, className), e);
+            return null;
+        }
+    }
+
+    
+    private List<BusEventEntry> getNextBusEvent() {
+
+        final Date now = clock.getUTCNow().toDate();
+        final Date nextAvailable = clock.getUTCNow().plus(DELTA_IN_PROCESSING_TIME_MS).toDate();
+
+        BusEventEntry input = dao.getNextBusEventEntry(MAX_BUS_EVENTS, hostname, now);
+        if (input == null) {
+            return Collections.emptyList();
+        }
+        
+        final boolean claimed = (dao.claimBusEvent(hostname, nextAvailable, input.getId(), now) == 1);
+        if (claimed) {
+            dao.insertClaimedHistory(hostname, now, input.getId());
+            return Collections.singletonList(input);
+        }
+        return Collections.emptyList();
+    }
+
+    @Override
+    public void register(Object handlerInstance) throws EventBusException {
+        eventBusDelegate.register(handlerInstance);
+    }
+
+    @Override
+    public void unregister(Object handlerInstance) throws EventBusException {
+        eventBusDelegate.unregister(handlerInstance);
+    }
+
+    @Override
+    public void post(final BusEvent event) throws EventBusException {
+        dao.inTransaction(new Transaction<Void, PersistentBusSqlDao>() {
+            @Override
+            public Void inTransaction(PersistentBusSqlDao transactional,
+                    TransactionStatus status) throws Exception {
+                postFromTransaction(event, transactional);
+                return null;
+            }
+        });
+    }
+
+    @Override
+    public void postFromTransaction(final BusEvent event, Transmogrifier transmogrifier)
+            throws EventBusException {
+        PersistentBusSqlDao transactional = transmogrifier.become(PersistentBusSqlDao.class);
+        postFromTransaction(event, transactional);
+    }
+    
+    private void postFromTransaction(BusEvent event, PersistentBusSqlDao transactional) {
+        try {
+            String json = objectMapper.writeValueAsString(event);
+            BusEventEntry entry  =  new BusEventEntry(hostname, event.getClass().getName(), json);
+            transactional.insertBusEvent(entry);
+        } catch (Exception e) {
+            log.error("Failed to post BusEvent " + event.toString(), e);
+        }
+    }
+}
diff --git a/util/src/main/java/com/ning/billing/util/bus/PersistentBusConfig.java b/util/src/main/java/com/ning/billing/util/bus/PersistentBusConfig.java
new file mode 100644
index 0000000..a2e9879
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/bus/PersistentBusConfig.java
@@ -0,0 +1,33 @@
+/* 
+ * Copyright 2010-2011 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.util.bus;
+
+import org.skife.config.Config;
+import org.skife.config.Default;
+
+import com.ning.billing.config.PersistentQueueConfig;
+
+public interface PersistentBusConfig extends PersistentQueueConfig {
+
+    @Override
+    @Config("killbill.billing.util.persistent.bus.sleep")
+    @Default("500")
+    public long getSleepTimeMs();
+    
+    @Config("killbill.billing.util.persistent.bus.nbThreads")
+    @Default("3")
+    public int getNbThreads();
+}
diff --git a/util/src/main/java/com/ning/billing/util/callcontext/CallContextBase.java b/util/src/main/java/com/ning/billing/util/callcontext/CallContextBase.java
index 8164f76..35dc92f 100644
--- a/util/src/main/java/com/ning/billing/util/callcontext/CallContextBase.java
+++ b/util/src/main/java/com/ning/billing/util/callcontext/CallContextBase.java
@@ -16,15 +16,25 @@
 
 package com.ning.billing.util.callcontext;
 
+import java.util.UUID;
+
 public abstract class CallContextBase implements CallContext {
+	
+	private final UUID userToken;
     private final String userName;
     private final CallOrigin callOrigin;
     private final UserType userType;
 
+
     public CallContextBase(String userName, CallOrigin callOrigin, UserType userType) {
+    	this(userName, callOrigin, userType, null);
+    }
+
+    public CallContextBase(String userName, CallOrigin callOrigin, UserType userType, UUID userToken) {
         this.userName = userName;
         this.callOrigin = callOrigin;
         this.userType = userType;
+        this.userToken = userToken;
     }
 
     @Override
@@ -41,4 +51,9 @@ public abstract class CallContextBase implements CallContext {
     public UserType getUserType() {
         return userType;
     }
+    
+    @Override
+    public UUID getUserToken() {
+    	return userToken;
+    }
 }
diff --git a/util/src/main/java/com/ning/billing/util/callcontext/CallContextBinder.java b/util/src/main/java/com/ning/billing/util/callcontext/CallContextBinder.java
index 3ac8a98..41dd68a 100644
--- a/util/src/main/java/com/ning/billing/util/callcontext/CallContextBinder.java
+++ b/util/src/main/java/com/ning/billing/util/callcontext/CallContextBinder.java
@@ -37,9 +37,10 @@ public @interface CallContextBinder {
             return new Binder<CallContextBinder, CallContext>() {
                 @Override
                 public void bind(SQLStatement q, CallContextBinder bind, CallContext callContext) {
-                    q.bind("userName", callContext.getUserName());
+                	q.bind("userName", callContext.getUserName());
                     q.bind("createdDate", callContext.getCreatedDate().toDate());
                     q.bind("updatedDate", callContext.getUpdatedDate().toDate());
+                	q.bind("userToken", (callContext.getUserToken() != null) ? callContext.getUserToken().toString() : null);                	
                 }
             };
         }
diff --git a/util/src/main/java/com/ning/billing/util/callcontext/CallContextFactory.java b/util/src/main/java/com/ning/billing/util/callcontext/CallContextFactory.java
index 6ffc554..d9f18c9 100644
--- a/util/src/main/java/com/ning/billing/util/callcontext/CallContextFactory.java
+++ b/util/src/main/java/com/ning/billing/util/callcontext/CallContextFactory.java
@@ -16,10 +16,14 @@
 
 package com.ning.billing.util.callcontext;
 
+import java.util.UUID;
+
 import org.joda.time.DateTime;
 
 public interface CallContextFactory {
-    CallContext createCallContext(String userName, CallOrigin callOrigin, UserType userType);
+    CallContext createCallContext(String userName, CallOrigin callOrigin, UserType userType, UUID userToken);
+    
+    CallContext createCallContext(String userName, CallOrigin callOrigin, UserType userType);    
 
     CallContext createMigrationCallContext(String userName, CallOrigin callOrigin, UserType userType, DateTime createdDate, DateTime updatedDate);
 
diff --git a/util/src/main/java/com/ning/billing/util/callcontext/DefaultCallContext.java b/util/src/main/java/com/ning/billing/util/callcontext/DefaultCallContext.java
index 57dc682..4cde143 100644
--- a/util/src/main/java/com/ning/billing/util/callcontext/DefaultCallContext.java
+++ b/util/src/main/java/com/ning/billing/util/callcontext/DefaultCallContext.java
@@ -16,17 +16,24 @@
 
 package com.ning.billing.util.callcontext;
 
+import java.util.UUID;
+
 import com.ning.billing.util.clock.Clock;
 import org.joda.time.DateTime;
 
 public class DefaultCallContext extends CallContextBase {
+	
     private final Clock clock;
 
-    public DefaultCallContext(String userName, CallOrigin callOrigin, UserType userType, Clock clock) {
-        super(userName, callOrigin, userType);
+    public DefaultCallContext(final String userName, final CallOrigin callOrigin, final UserType userType, final UUID userToken, final Clock clock) {
+        super(userName, callOrigin, userType, userToken);
         this.clock = clock;
     }
 
+    public DefaultCallContext(String userName, CallOrigin callOrigin, UserType userType, Clock clock) {
+    	this(userName, callOrigin, userType, null, clock);
+    }
+
     @Override
     public DateTime getCreatedDate() {
         return clock.getUTCNow();
diff --git a/util/src/main/java/com/ning/billing/util/callcontext/DefaultCallContextFactory.java b/util/src/main/java/com/ning/billing/util/callcontext/DefaultCallContextFactory.java
index f75574c..52ac313 100644
--- a/util/src/main/java/com/ning/billing/util/callcontext/DefaultCallContextFactory.java
+++ b/util/src/main/java/com/ning/billing/util/callcontext/DefaultCallContextFactory.java
@@ -16,6 +16,8 @@
 
 package com.ning.billing.util.callcontext;
 
+import java.util.UUID;
+
 import com.google.inject.Inject;
 import com.ning.billing.util.clock.Clock;
 import org.joda.time.DateTime;
@@ -29,8 +31,13 @@ public class DefaultCallContextFactory implements CallContextFactory {
     }
 
     @Override
+    public CallContext createCallContext(String userName, CallOrigin callOrigin, UserType userType, UUID userToken) {
+        return new DefaultCallContext(userName, callOrigin, userType, userToken, clock);
+    }
+
+    @Override
     public CallContext createCallContext(String userName, CallOrigin callOrigin, UserType userType) {
-        return new DefaultCallContext(userName, callOrigin, userType, clock);
+    	return createCallContext(userName, callOrigin, userType, null);
     }
 
     @Override
diff --git a/util/src/main/java/com/ning/billing/util/clock/Clock.java b/util/src/main/java/com/ning/billing/util/clock/Clock.java
index b41a36d..6b65aac 100644
--- a/util/src/main/java/com/ning/billing/util/clock/Clock.java
+++ b/util/src/main/java/com/ning/billing/util/clock/Clock.java
@@ -25,6 +25,5 @@ public interface Clock {
 
     public DateTime getUTCNow();
 
-
     //public DateTime addDuration(DateTime input, IDuration duration);
 }
diff --git a/util/src/main/java/com/ning/billing/util/clock/DefaultClock.java b/util/src/main/java/com/ning/billing/util/clock/DefaultClock.java
index 8280a15..057a58c 100644
--- a/util/src/main/java/com/ning/billing/util/clock/DefaultClock.java
+++ b/util/src/main/java/com/ning/billing/util/clock/DefaultClock.java
@@ -36,6 +36,13 @@ public class DefaultClock implements Clock {
         return getNow(DateTimeZone.UTC);
     }
 
+    public static DateTime toUTCDateTime(DateTime input) {
+        if (input == null) {
+            return null;
+        }
+        DateTime result = input.toDateTime(DateTimeZone.UTC);
+        return truncateMs(result);
+    }
 
     public static DateTime truncateMs(DateTime input) {
         return input.minus(input.getMillisOfSecond());
diff --git a/util/src/main/java/com/ning/billing/util/config/XMLLoader.java b/util/src/main/java/com/ning/billing/util/config/XMLLoader.java
index d0487b8..9d35352 100644
--- a/util/src/main/java/com/ning/billing/util/config/XMLLoader.java
+++ b/util/src/main/java/com/ning/billing/util/config/XMLLoader.java
@@ -71,6 +71,18 @@ public class XMLLoader {
             return null;
         }
     } 
+	
+	public static <T> T getObjectFromStreamNoValidation(InputStream stream, Class<T> clazz) throws SAXException, InvalidConfigException, JAXBException, IOException, TransformerException {
+        Object o = unmarshaller(clazz).unmarshal(stream);
+        if (clazz.isInstance(o)) {
+        	@SuppressWarnings("unchecked")
+			T castObject = (T)o;
+        	return castObject;
+        } else {
+            return null;
+        }
+    } 
+
 
 	public static <T extends ValidatingConfig<T>> void validate(URI uri, T c) throws ValidationException {
             c.initialize(c, uri);
diff --git a/util/src/main/java/com/ning/billing/util/customfield/dao/AuditedCustomFieldDao.java b/util/src/main/java/com/ning/billing/util/customfield/dao/AuditedCustomFieldDao.java
index b7d2de0..b918fac 100644
--- a/util/src/main/java/com/ning/billing/util/customfield/dao/AuditedCustomFieldDao.java
+++ b/util/src/main/java/com/ning/billing/util/customfield/dao/AuditedCustomFieldDao.java
@@ -16,66 +16,90 @@
 
 package com.ning.billing.util.customfield.dao;
 
-import com.ning.billing.util.ChangeType;
-import com.ning.billing.util.callcontext.CallContext;
+import com.google.inject.Inject;
 import com.ning.billing.util.customfield.CustomField;
-import com.ning.billing.util.customfield.CustomFieldHistory;
+import com.ning.billing.util.dao.AuditedCollectionDaoBase;
+import com.ning.billing.util.dao.TableName;
+import com.ning.billing.util.entity.collection.dao.UpdatableEntityCollectionSqlDao;
+import org.skife.jdbi.v2.IDBI;
 import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
 
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-import java.util.UUID;
+public class AuditedCustomFieldDao extends AuditedCollectionDaoBase<CustomField> implements CustomFieldDao {
+    private final CustomFieldSqlDao dao;
 
-public class AuditedCustomFieldDao implements CustomFieldDao {
-    @Override
-    public void saveFields(Transmogrifier dao, UUID objectId, String objectType, List<CustomField> fields, CallContext context) {
-        CustomFieldSqlDao customFieldSqlDao = dao.become(CustomFieldSqlDao.class);
-
-        // get list of existing fields
-        List<CustomField> existingFields = customFieldSqlDao.load(objectId.toString(), objectType);
-        List<CustomField> fieldsToUpdate = new ArrayList<CustomField>();
-
-        // sort into fields to update (fieldsToUpdate), fields to add (fields), and fields to delete (existingFields)
-        Iterator<CustomField> fieldIterator = fields.iterator();
-        while (fieldIterator.hasNext()) {
-            CustomField field = fieldIterator.next();
-
-            Iterator<CustomField> existingFieldIterator = existingFields.iterator();
-            while (existingFieldIterator.hasNext()) {
-                CustomField existingField = existingFieldIterator.next();
-                if (field.getName().equals(existingField.getName())) {
-                    // if the tags match, remove from both lists
-                    fieldsToUpdate.add(field);
-                    fieldIterator.remove();
-                    existingFieldIterator.remove();
-                }
-            }
-        }
-
-        customFieldSqlDao.batchInsertFromTransaction(objectId.toString(), objectType, fields, context);
-        customFieldSqlDao.batchUpdateFromTransaction(objectId.toString(), objectType, fieldsToUpdate, context);
-        customFieldSqlDao.batchDeleteFromTransaction(objectId.toString(), objectType, existingFields, context);
-
-        List<CustomFieldHistory> fieldHistories = new ArrayList<CustomFieldHistory>();
-        fieldHistories.addAll(convertToHistoryEntry(fields, ChangeType.INSERT));
-        fieldHistories.addAll(convertToHistoryEntry(fieldsToUpdate, ChangeType.UPDATE));
-        fieldHistories.addAll(convertToHistoryEntry(existingFields, ChangeType.DELETE));
-
-        CustomFieldHistorySqlDao historyDao = dao.become(CustomFieldHistorySqlDao.class);
-        historyDao.batchAddHistoryFromTransaction(objectId.toString(), objectType, fieldHistories, context);
-
-        CustomFieldAuditSqlDao auditDao = dao.become(CustomFieldAuditSqlDao.class);
-        auditDao.batchInsertAuditLogFromTransaction(objectId.toString(), objectType, fieldHistories, context);
+    @Inject
+    public AuditedCustomFieldDao(final IDBI dbi) {
+        dao = dbi.onDemand(CustomFieldSqlDao.class);
     }
 
-    private List<CustomFieldHistory> convertToHistoryEntry(List<CustomField> fields, ChangeType changeType) {
-        List<CustomFieldHistory> result = new ArrayList<CustomFieldHistory>();
+    @Override
+    protected TableName getTableName() {
+        return TableName.CUSTOM_FIELD_HISTORY;
+    }
 
-        for (CustomField field : fields) {
-            result.add(new CustomFieldHistory(field, changeType));
-        }
+    @Override
+    protected UpdatableEntityCollectionSqlDao<CustomField> transmogrifyDao(Transmogrifier transactionalDao) {
+        return transactionalDao.become(CustomFieldSqlDao.class);
+    }
 
-        return result;
+    @Override
+    protected UpdatableEntityCollectionSqlDao<CustomField> getSqlDao() {
+        return dao;
     }
+
+//    @Override
+//    public void saveEntitiesFromTransaction(Transmogrifier dao, UUID objectId, ObjectType objectType, List<CustomField> fields, CallContext context) {
+//        CustomFieldSqlDao customFieldSqlDao = dao.become(CustomFieldSqlDao.class);
+//
+//        // get list of existing fields
+//        List<CustomField> existingFields = customFieldSqlDao.load(objectId.toString(), objectType);
+//        List<CustomField> fieldsToUpdate = new ArrayList<CustomField>();
+//
+//        // sort into fields to update (fieldsToUpdate), fields to add (fields), and fields to delete (existingFields)
+//        Iterator<CustomField> fieldIterator = fields.iterator();
+//        while (fieldIterator.hasNext()) {
+//            CustomField field = fieldIterator.next();
+//
+//            Iterator<CustomField> existingFieldIterator = existingFields.iterator();
+//            while (existingFieldIterator.hasNext()) {
+//                CustomField existingField = existingFieldIterator.next();
+//                if (field.getName().equals(existingField.getName())) {
+//                    // if the tagStore match, remove from both lists
+//                    fieldsToUpdate.add(field);
+//                    fieldIterator.remove();
+//                    existingFieldIterator.remove();
+//                }
+//            }
+//        }
+//
+//        customFieldSqlDao.batchInsertFromTransaction(objectId.toString(), objectType, fields, context);
+//        customFieldSqlDao.batchUpdateFromTransaction(objectId.toString(), objectType, fieldsToUpdate, context);
+//
+//        // get all custom fields (including those that are about to be deleted) from the database in order to get the record ids
+//        List<Mapper> recordIds = customFieldSqlDao.getRecordIds(objectId.toString(), objectType);
+//        Map<UUID, Long> recordIdMap = new HashMap<UUID, Long>();
+//        for (Mapper recordId : recordIds) {
+//            recordIdMap.put(recordId.getId(), recordId.getRecordId());
+//        }
+//
+//        customFieldSqlDao.batchDeleteFromTransaction(objectId.toString(), objectType, existingFields, context);
+//
+//        List<MappedEntity<CustomField>> fieldHistories = new ArrayList<MappedEntity<CustomField>>();
+//        fieldHistories.addAll(convertToHistory(fields, recordIdMap, ChangeType.INSERT));
+//        fieldHistories.addAll(convertToHistory(fieldsToUpdate, recordIdMap, ChangeType.UPDATE));
+//        fieldHistories.addAll(convertToHistory(existingFields, recordIdMap, ChangeType.DELETE));
+//
+//        customFieldSqlDao.batchAddHistoryFromTransaction(objectId.toString(), objectType, fieldHistories, context);
+//        customFieldSqlDao.batchInsertAuditLogFromTransaction(TableName.CUSTOM_FIELD_HISTORY, objectId.toString(), objectType, fieldHistories, context);
+//    }
+//
+//    private List<MappedEntity<CustomField>> convertToHistory(List<CustomField> fields, Map<UUID, Long> recordIds, ChangeType changeType) {
+//        List<MappedEntity<CustomField>> result = new ArrayList<MappedEntity<CustomField>>();
+//
+//        for (CustomField field : fields) {
+//            result.add(new MappedEntity<CustomField>(recordIds.get(field.getId()), field, changeType));
+//        }
+//
+//        return result;
+//    }
 }
diff --git a/util/src/main/java/com/ning/billing/util/customfield/dao/CustomFieldDao.java b/util/src/main/java/com/ning/billing/util/customfield/dao/CustomFieldDao.java
index a987c4d..66cd734 100644
--- a/util/src/main/java/com/ning/billing/util/customfield/dao/CustomFieldDao.java
+++ b/util/src/main/java/com/ning/billing/util/customfield/dao/CustomFieldDao.java
@@ -16,13 +16,8 @@
 
 package com.ning.billing.util.customfield.dao;
 
-import com.ning.billing.util.callcontext.CallContext;
 import com.ning.billing.util.customfield.CustomField;
-import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
+import com.ning.billing.util.dao.AuditedCollectionDao;
 
-import java.util.List;
-import java.util.UUID;
-
-public interface CustomFieldDao {
-    void saveFields(Transmogrifier dao, UUID objectId, String objectType, List<CustomField> fields, CallContext context);
+public interface CustomFieldDao extends AuditedCollectionDao<CustomField> {
 }
diff --git a/util/src/main/java/com/ning/billing/util/customfield/dao/CustomFieldSqlDao.java b/util/src/main/java/com/ning/billing/util/customfield/dao/CustomFieldSqlDao.java
index 733bfeb..e73dbf4 100644
--- a/util/src/main/java/com/ning/billing/util/customfield/dao/CustomFieldSqlDao.java
+++ b/util/src/main/java/com/ning/billing/util/customfield/dao/CustomFieldSqlDao.java
@@ -20,9 +20,10 @@ import java.util.List;
 
 import com.ning.billing.util.callcontext.CallContext;
 import com.ning.billing.util.callcontext.CallContextBinder;
-import com.ning.billing.util.customfield.CustomFieldBinder;
-import com.ning.billing.util.customfield.CustomFieldMapper;
-import com.ning.billing.util.entity.UpdatableEntityCollectionDao;
+import com.ning.billing.util.dao.EntityHistory;
+import com.ning.billing.util.dao.ObjectType;
+import com.ning.billing.util.dao.ObjectTypeBinder;
+import com.ning.billing.util.entity.collection.dao.UpdatableEntityCollectionSqlDao;
 import org.skife.jdbi.v2.sqlobject.Bind;
 import org.skife.jdbi.v2.sqlobject.SqlBatch;
 import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
@@ -33,25 +34,33 @@ import com.ning.billing.util.customfield.CustomField;
 
 @ExternalizedSqlViaStringTemplate3
 @RegisterMapper(CustomFieldMapper.class)
-public interface CustomFieldSqlDao extends UpdatableEntityCollectionDao<CustomField>, Transactional<CustomFieldSqlDao>, Transmogrifier {
+public interface CustomFieldSqlDao extends UpdatableEntityCollectionSqlDao<CustomField>,
+                                           Transactional<CustomFieldSqlDao>, Transmogrifier {
     @Override
     @SqlBatch(transactional=false)
-    public void batchInsertFromTransaction(@Bind("objectId") final String objectId,
-                                           @Bind("objectType") final String objectType,
-                                           @CustomFieldBinder final List<CustomField> entities,
-                                           @CallContextBinder final CallContext context);
+    public void insertFromTransaction(@Bind("objectId") final String objectId,
+                                      @ObjectTypeBinder final ObjectType objectType,
+                                      @CustomFieldBinder final List<CustomField> entities,
+                                      @CallContextBinder final CallContext context);
 
     @Override
     @SqlBatch(transactional=false)
-    public void batchUpdateFromTransaction(@Bind("objectId") final String objectId,
-                                           @Bind("objectType") final String objectType,
-                                           @CustomFieldBinder final List<CustomField> entities,
-                                           @CallContextBinder final CallContext context);
+    public void updateFromTransaction(@Bind("objectId") final String objectId,
+                                      @ObjectTypeBinder final ObjectType objectType,
+                                      @CustomFieldBinder final List<CustomField> entities,
+                                      @CallContextBinder final CallContext context);
 
     @Override
     @SqlBatch(transactional=false)
-    public void batchDeleteFromTransaction(@Bind("objectId") final String objectId,
-                                           @Bind("objectType") final String objectType,
-                                           @CustomFieldBinder final List<CustomField> entities,
-                                           @CallContextBinder final CallContext context);
+    public void deleteFromTransaction(@Bind("objectId") final String objectId,
+                                      @ObjectTypeBinder final ObjectType objectType,
+                                      @CustomFieldBinder final List<CustomField> entities,
+                                      @CallContextBinder final CallContext context);
+
+    @Override
+    @SqlBatch(transactional=false)
+    public void addHistoryFromTransaction(@Bind("objectId") final String objectId,
+                                               @ObjectTypeBinder final ObjectType objectType,
+                                               @CustomFieldHistoryBinder final List<EntityHistory<CustomField>> entities,
+                                               @CallContextBinder final CallContext context);
 }
diff --git a/util/src/main/java/com/ning/billing/util/customfield/DefaultFieldStore.java b/util/src/main/java/com/ning/billing/util/customfield/DefaultFieldStore.java
index 4a31dd4..8698787 100644
--- a/util/src/main/java/com/ning/billing/util/customfield/DefaultFieldStore.java
+++ b/util/src/main/java/com/ning/billing/util/customfield/DefaultFieldStore.java
@@ -17,14 +17,16 @@
 package com.ning.billing.util.customfield;
 
 import java.util.UUID;
-import com.ning.billing.util.entity.EntityCollectionBase;
+
+import com.ning.billing.util.dao.ObjectType;
+import com.ning.billing.util.entity.collection.EntityCollectionBase;
 
 public class DefaultFieldStore extends EntityCollectionBase<CustomField> implements FieldStore {
-    public DefaultFieldStore(UUID objectId, String objectType) {
+    public DefaultFieldStore(UUID objectId, ObjectType objectType) {
         super(objectId, objectType);
     }
 
-    public static DefaultFieldStore create(UUID objectId, String objectType) {
+    public static DefaultFieldStore create(UUID objectId, ObjectType objectType) {
         return new DefaultFieldStore(objectId, objectType);
     }
 
diff --git a/util/src/main/java/com/ning/billing/util/customfield/StringCustomField.java b/util/src/main/java/com/ning/billing/util/customfield/StringCustomField.java
index f60093c..074f322 100644
--- a/util/src/main/java/com/ning/billing/util/customfield/StringCustomField.java
+++ b/util/src/main/java/com/ning/billing/util/customfield/StringCustomField.java
@@ -18,7 +18,6 @@ package com.ning.billing.util.customfield;
 
 import java.util.UUID;
 import com.ning.billing.util.entity.UpdatableEntityBase;
-import org.joda.time.DateTime;
 
 public class StringCustomField extends UpdatableEntityBase implements CustomField {
     private String name;
@@ -30,9 +29,8 @@ public class StringCustomField extends UpdatableEntityBase implements CustomFiel
         this.value = value;
     }
 
-    public StringCustomField(UUID id, String createdBy, DateTime createdDate,
-                             String updatedBy, DateTime updatedDate, String name, String value) {
-        super(id, createdBy, createdDate, updatedBy, updatedDate);
+    public StringCustomField(UUID id, String name, String value) {
+        super(id);
         this.name = name;
         this.value = value;
     }
@@ -51,4 +49,24 @@ public class StringCustomField extends UpdatableEntityBase implements CustomFiel
     public void setValue(String value) {
         this.value = value;
     }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        StringCustomField that = (StringCustomField) o;
+
+        if (name != null ? !name.equals(that.name) : that.name != null) return false;
+        //if (value != null ? !value.equals(that.value) : that.value != null) return false;
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = name != null ? name.hashCode() : 0;
+        result = 31 * result + (value != null ? value.hashCode() : 0);
+        return result;
+    }
 }
diff --git a/util/src/main/java/com/ning/billing/util/dao/AuditBinder.java b/util/src/main/java/com/ning/billing/util/dao/AuditBinder.java
new file mode 100644
index 0000000..a518578
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/dao/AuditBinder.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2010-2011 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.util.dao;
+
+import org.skife.jdbi.v2.SQLStatement;
+import org.skife.jdbi.v2.sqlobject.Binder;
+import org.skife.jdbi.v2.sqlobject.BinderFactory;
+import org.skife.jdbi.v2.sqlobject.BindingAnnotation;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@BindingAnnotation(AuditBinder.AuditBinderFactory.class)
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.PARAMETER})
+public @interface AuditBinder {
+    public static class AuditBinderFactory implements BinderFactory {
+        @Override
+        public Binder build(Annotation annotation) {
+            return new Binder<AuditBinder, EntityAudit>() {
+                @Override
+                public void bind(SQLStatement q, AuditBinder bind, EntityAudit audit) {
+                    q.bind("tableName", audit.getTableName().toString());
+                    q.bind("recordId", audit.getRecordId());
+                    q.bind("changeType", audit.getChangeType().toString());
+                }
+            };
+        }
+    }
+}
diff --git a/util/src/main/java/com/ning/billing/util/dao/AuditedCollectionDao.java b/util/src/main/java/com/ning/billing/util/dao/AuditedCollectionDao.java
new file mode 100644
index 0000000..d757f70
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/dao/AuditedCollectionDao.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2010-2011 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.util.dao;
+
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.entity.Entity;
+import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
+
+import java.util.List;
+import java.util.UUID;
+
+public interface AuditedCollectionDao<T extends Entity> {
+    void saveEntitiesFromTransaction(Transmogrifier transactionalDao, UUID objectId, ObjectType objectType,
+                                     List<T> entities, CallContext context);
+
+    List<T> loadEntities(UUID objectId, ObjectType objectType);
+
+    List<T> loadEntitiesFromTransaction(Transmogrifier dao, UUID objectId, ObjectType objectType);
+}
diff --git a/util/src/main/java/com/ning/billing/util/dao/AuditedCollectionDaoBase.java b/util/src/main/java/com/ning/billing/util/dao/AuditedCollectionDaoBase.java
new file mode 100644
index 0000000..da6df68
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/dao/AuditedCollectionDaoBase.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2010-2011 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.util.dao;
+
+import com.ning.billing.util.ChangeType;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.entity.Entity;
+import com.ning.billing.util.entity.collection.dao.UpdatableEntityCollectionSqlDao;
+import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+public abstract class AuditedCollectionDaoBase<T extends Entity> implements AuditedCollectionDao<T> {
+    @Override
+    public void saveEntitiesFromTransaction(Transmogrifier transactionalDao, UUID objectId, ObjectType objectType, List<T> entities, CallContext context) {
+        UpdatableEntityCollectionSqlDao<T> dao = transmogrifyDao(transactionalDao);
+
+        // get list of existing entities
+        List<T> existingEntities = dao.load(objectId.toString(), objectType);
+        List<T> entitiesToUpdate = new ArrayList<T>();
+
+        // sort into entities to update (entitiesToUpdate), entities to add (entities), and entities to delete (existingEntities)
+        Iterator<T> entityIterator = entities.iterator();
+        while (entityIterator.hasNext()) {
+            T entity = entityIterator.next();
+
+            Iterator<T> existingEntityIterator = existingEntities.iterator();
+            while (existingEntityIterator.hasNext()) {
+                T existingEntity = existingEntityIterator.next();
+                if (entity.equals(existingEntity)) {
+                    // if the entities match, remove from both lists
+                    entitiesToUpdate.add(entity);
+                    entityIterator.remove();
+                    existingEntityIterator.remove();
+                }
+            }
+        }
+
+        dao.insertFromTransaction(objectId.toString(), objectType, entities, context);
+        dao.updateFromTransaction(objectId.toString(), objectType, entitiesToUpdate, context);
+
+        // get all custom entities (including those that are about to be deleted) from the database in order to get the record ids
+        List<Mapper<UUID, Long>> recordIds = dao.getRecordIds(objectId.toString(), objectType);
+        Map<UUID, Long> recordIdMap = convertToHistoryMap(recordIds);
+
+        dao.deleteFromTransaction(objectId.toString(), objectType, existingEntities, context);
+
+        List<EntityHistory<T>> entityHistories = new ArrayList<EntityHistory<T>>();
+        entityHistories.addAll(convertToHistory(entities, recordIdMap, ChangeType.INSERT));
+        entityHistories.addAll(convertToHistory(entitiesToUpdate, recordIdMap, ChangeType.UPDATE));
+        entityHistories.addAll(convertToHistory(existingEntities, recordIdMap, ChangeType.DELETE));
+
+        Long maxHistoryRecordId = dao.getMaxHistoryRecordId();
+        dao.addHistoryFromTransaction(objectId.toString(), objectType, entityHistories, context);
+
+        // have to fetch history record ids to update audit log
+        List<Mapper<Long, Long>> historyRecordIds = dao.getHistoryRecordIds(maxHistoryRecordId);
+        Map<Long, Long> historyRecordIdMap = convertToAuditMap(historyRecordIds);
+        List<EntityAudit> entityAudits = convertToAudits(entityHistories, historyRecordIdMap);
+
+        dao.insertAuditFromTransaction(entityAudits, context);
+    }
+
+    @Override
+    public List<T> loadEntities(final UUID objectId, final ObjectType objectType) {
+        UpdatableEntityCollectionSqlDao thisDao = getSqlDao();
+        return thisDao.load(objectId.toString(), objectType);
+    }
+
+    @Override
+    public List<T> loadEntitiesFromTransaction(final Transmogrifier dao, final UUID objectId, final ObjectType objectType) {
+        UpdatableEntityCollectionSqlDao<T> thisDao = transmogrifyDao(dao);
+        return thisDao.load(objectId.toString(), objectType);
+    }
+
+    protected List<EntityHistory<T>> convertToHistory(List<T> entities, Map<UUID, Long> recordIds, ChangeType changeType) {
+        List<EntityHistory<T>> histories = new ArrayList<EntityHistory<T>>();
+
+        for (T entity : entities) {
+            UUID id = entity.getId();
+            histories.add(new EntityHistory<T>(id, recordIds.get(id), entity, changeType));
+        }
+
+        return histories;
+    }
+
+    protected List<EntityAudit> convertToAudits(List<EntityHistory<T>> histories, Map<Long, Long> historyRecordIds) {
+        List<EntityAudit> audits = new ArrayList<EntityAudit>();
+
+        for (EntityHistory<T> history : histories) {
+            Long recordId = history.getValue();
+            Long historyRecordId = historyRecordIds.get(recordId);
+            audits.add(new EntityAudit(getTableName(), historyRecordId, history.getChangeType()));
+        }
+
+        return audits;
+    }
+
+    protected Map<UUID, Long> convertToHistoryMap(List<Mapper<UUID, Long>> recordIds) {
+        Map<UUID, Long> recordIdMap = new HashMap<UUID, Long>();
+        for (Mapper<UUID, Long> recordId : recordIds) {
+            recordIdMap.put(recordId.getKey(), recordId.getValue());
+        }
+        return recordIdMap;
+    }
+
+    protected Map<Long, Long> convertToAuditMap(List<Mapper<Long, Long>> historyRecordIds) {
+        Map<Long, Long> historyRecordIdMap = new HashMap<Long, Long>();
+        for (Mapper<Long, Long> historyRecordId : historyRecordIds) {
+            historyRecordIdMap.put(historyRecordId.getKey(), historyRecordId.getValue());
+        }
+        return historyRecordIdMap;
+    }
+
+    protected abstract TableName getTableName();
+    protected abstract UpdatableEntityCollectionSqlDao<T> transmogrifyDao(Transmogrifier transactionalDao);
+    protected abstract UpdatableEntityCollectionSqlDao<T> getSqlDao();
+}
diff --git a/util/src/main/java/com/ning/billing/util/dao/CollectionHistorySqlDao.java b/util/src/main/java/com/ning/billing/util/dao/CollectionHistorySqlDao.java
new file mode 100644
index 0000000..d3a01d5
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/dao/CollectionHistorySqlDao.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2010-2011 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.util.dao;
+
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.entity.Entity;
+import org.skife.jdbi.v2.sqlobject.SqlBatch;
+import org.skife.jdbi.v2.sqlobject.SqlUpdate;
+
+import java.util.List;
+import java.util.UUID;
+
+public interface CollectionHistorySqlDao<T extends Entity> {
+    @SqlBatch(transactional = false)
+    public void addHistoryFromTransaction(String objectId, ObjectType objectType,
+                                           List<EntityHistory<T>> histories,
+                                           CallContext context);
+
+    @SqlUpdate
+    public void addHistoryFromTransaction(String objectId, ObjectType objectType,
+                                          EntityHistory<T> history,
+                                          CallContext context);
+}
diff --git a/util/src/main/java/com/ning/billing/util/dao/EntityAudit.java b/util/src/main/java/com/ning/billing/util/dao/EntityAudit.java
new file mode 100644
index 0000000..15280fa
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/dao/EntityAudit.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2010-2011 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.util.dao;
+
+import com.ning.billing.util.ChangeType;
+
+public class EntityAudit {
+    private final TableName tableName;
+    private final Long recordId;
+    private final ChangeType changeType;
+
+    public EntityAudit(TableName tableName, Long recordId, ChangeType changeType) {
+        this.tableName = tableName;
+        this.recordId = recordId;
+        this.changeType = changeType;
+    }
+
+    public TableName getTableName() {
+        return tableName;
+    }
+
+    public Long getRecordId() {
+        return recordId;
+    }
+
+    public ChangeType getChangeType() {
+        return changeType;
+    }
+}
diff --git a/util/src/main/java/com/ning/billing/util/dao/EntityHistory.java b/util/src/main/java/com/ning/billing/util/dao/EntityHistory.java
new file mode 100644
index 0000000..536de35
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/dao/EntityHistory.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2010-2011 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.util.dao;
+
+import com.ning.billing.util.ChangeType;
+import com.ning.billing.util.entity.Entity;
+
+import java.util.UUID;
+
+public class EntityHistory<T extends Entity> extends MappedEntity<T, UUID, Long> {
+    public EntityHistory(UUID id, Long recordId, T entity, ChangeType changeType) {
+        super(new Mapper<UUID, Long>(id, recordId), entity, changeType);
+    }
+}
diff --git a/util/src/main/java/com/ning/billing/util/dao/HistoryRecordIdMapper.java b/util/src/main/java/com/ning/billing/util/dao/HistoryRecordIdMapper.java
new file mode 100644
index 0000000..3d6e3f5
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/dao/HistoryRecordIdMapper.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2010-2011 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.util.dao;
+
+import org.skife.jdbi.v2.StatementContext;
+import org.skife.jdbi.v2.tweak.ResultSetMapper;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class HistoryRecordIdMapper extends MapperBase implements ResultSetMapper<Mapper> {
+    @Override
+    public Mapper<Long, Long> map(int index, ResultSet resultSet, StatementContext ctx) throws SQLException {
+        Long recordId = resultSet.getLong("record_id");
+        Long historyRecordId = resultSet.getLong("history_record_id");
+
+        return new Mapper<Long, Long>(recordId, historyRecordId);
+    }
+}
\ No newline at end of file
diff --git a/util/src/main/java/com/ning/billing/util/dao/Mapper.java b/util/src/main/java/com/ning/billing/util/dao/Mapper.java
new file mode 100644
index 0000000..e4e178c
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/dao/Mapper.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2010-2011 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.util.dao;
+
+public class Mapper<K, V> {
+    private final K key;
+    private final V value;
+
+    public Mapper(K key, V value) {
+        this.key = key;
+        this.value = value;
+    }
+
+    public K getKey() {
+        return key;
+    }
+
+    public V getValue() {
+        return value;
+    }
+}
diff --git a/util/src/main/java/com/ning/billing/util/dao/MapperBase.java b/util/src/main/java/com/ning/billing/util/dao/MapperBase.java
index d10e2a0..119b727 100644
--- a/util/src/main/java/com/ning/billing/util/dao/MapperBase.java
+++ b/util/src/main/java/com/ning/billing/util/dao/MapperBase.java
@@ -22,11 +22,16 @@ import org.joda.time.DateTimeZone;
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.sql.Timestamp;
-import java.util.Date;
+import java.util.UUID;
 
 public abstract class MapperBase {
     protected DateTime getDate(ResultSet rs, String fieldName) throws SQLException {
         final Timestamp resultStamp = rs.getTimestamp(fieldName);
         return rs.wasNull() ? null : new DateTime(resultStamp).toDateTime(DateTimeZone.UTC);
     }
+
+    protected UUID getUUID(ResultSet resultSet, String fieldName) throws SQLException {
+        String result = resultSet.getString(fieldName);
+        return result == null ? null : UUID.fromString(result);
+    }
 }
diff --git a/util/src/main/java/com/ning/billing/util/dao/ObjectTypeBinder.java b/util/src/main/java/com/ning/billing/util/dao/ObjectTypeBinder.java
new file mode 100644
index 0000000..18f0efc
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/dao/ObjectTypeBinder.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2010-2011 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.util.dao;
+
+import org.skife.jdbi.v2.SQLStatement;
+import org.skife.jdbi.v2.sqlobject.Binder;
+import org.skife.jdbi.v2.sqlobject.BinderFactory;
+import org.skife.jdbi.v2.sqlobject.BindingAnnotation;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@BindingAnnotation(ObjectTypeBinder.ObjectTypeBinderFactory.class)
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.PARAMETER})
+public @interface ObjectTypeBinder {
+    public static class ObjectTypeBinderFactory implements BinderFactory {
+        public Binder build(Annotation annotation) {
+            return new Binder<ObjectTypeBinder, ObjectType>() {
+                public void bind(SQLStatement q, ObjectTypeBinder bind, ObjectType objectType) {
+                    q.bind("objectType", objectType.getObjectName());
+                }
+            };
+        }
+    }
+}
diff --git a/util/src/main/java/com/ning/billing/util/dao/RecordIdMapper.java b/util/src/main/java/com/ning/billing/util/dao/RecordIdMapper.java
new file mode 100644
index 0000000..35cdaf3
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/dao/RecordIdMapper.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2010-2011 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.util.dao;
+
+import org.skife.jdbi.v2.StatementContext;
+import org.skife.jdbi.v2.tweak.ResultSetMapper;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.UUID;
+
+public class RecordIdMapper extends MapperBase implements ResultSetMapper<Mapper> {
+    @Override
+    public Mapper<UUID, Long> map(int index, ResultSet resultSet, StatementContext ctx) throws SQLException {
+        UUID id = getUUID(resultSet, "id");
+        Long recordId = resultSet.getLong("record_id");
+
+        return new Mapper<UUID, Long>(id, recordId);
+    }
+}
diff --git a/util/src/main/java/com/ning/billing/util/dao/TableName.java b/util/src/main/java/com/ning/billing/util/dao/TableName.java
new file mode 100644
index 0000000..28a070a
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/dao/TableName.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2010-2011 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.util.dao;
+
+public enum TableName {
+    ACCOUNT("accounts"),
+    ACCOUNT_HISTORY("account_history"),
+    ACCOUNT_EMAIL_HISTORY("account_email_history"),
+    BUNDLES("bundles"),
+    CUSTOM_FIELD_HISTORY("custom_field_history"),
+    ENTITLEMENT_EVENTS("entitlement_events"),
+    FIXED_INVOICE_ITEMS("fixed_invoice_items"),
+    INVOICE_PAYMENTS("invoice_payments"),
+    INVOICES("invoices"),
+    PAYMENT_ATTEMPTS("payment_attempts"),
+    PAYMENT_HISTORY("payment_history"),
+    PAYMENTS("payments"),
+    RECURRING_INVOICE_ITEMS("recurring_invoice_items"),
+    SUBSCRIPTIONS("subscriptions"),
+    TAG_HISTORY("tag_history");
+    
+    private final String tableName;
+    
+    TableName(String tableName) {
+        this.tableName = tableName;
+    }
+    
+    public String getTableName() {
+        return tableName;
+    }
+}
diff --git a/util/src/main/java/com/ning/billing/util/dao/TableNameBinder.java b/util/src/main/java/com/ning/billing/util/dao/TableNameBinder.java
new file mode 100644
index 0000000..a93d915
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/dao/TableNameBinder.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2010-2011 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.util.dao;
+
+import org.skife.jdbi.v2.SQLStatement;
+import org.skife.jdbi.v2.sqlobject.Binder;
+import org.skife.jdbi.v2.sqlobject.BinderFactory;
+import org.skife.jdbi.v2.sqlobject.BindingAnnotation;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@BindingAnnotation(TableNameBinder.TableNameBinderFactory.class)
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.PARAMETER})
+public @interface TableNameBinder {
+    public static class TableNameBinderFactory implements BinderFactory {
+        public Binder build(Annotation annotation) {
+            return new Binder<TableNameBinder, TableName>() {
+                public void bind(SQLStatement q, TableNameBinder bind, TableName tableName) {
+                    q.bind("tableName", tableName.getTableName());
+                }
+            };
+        }
+    }
+}
\ No newline at end of file
diff --git a/util/src/main/java/com/ning/billing/util/email/DefaultEmailSender.java b/util/src/main/java/com/ning/billing/util/email/DefaultEmailSender.java
new file mode 100644
index 0000000..b4e3587
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/email/DefaultEmailSender.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2010-2011 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.util.email;
+
+import java.util.List;
+
+import org.apache.commons.mail.EmailException;
+import org.apache.commons.mail.HtmlEmail;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.inject.Inject;
+
+public class DefaultEmailSender implements EmailSender { 
+    private final Logger log = LoggerFactory.getLogger(EmailSender.class);
+    private final EmailConfig config;
+
+    @Inject
+    public DefaultEmailSender(EmailConfig emailConfig) {
+        this.config = emailConfig;
+    }
+
+    @Override
+    public void sendSecureEmail(List<String> to, List<String> cc, String subject, String htmlBody) throws EmailApiException {
+        HtmlEmail email;
+        try {
+            email = new HtmlEmail();
+
+            email.setSmtpPort(config.getSmtpPort());
+            email.setAuthentication(config.getSmtpUserName(), config.getSmtpPassword());
+            email.setHostName(config.getSmtpServerName());
+            email.setFrom(config.getSmtpUserName());
+            email.setSubject(subject);
+            email.setHtmlMsg(htmlBody);
+
+            if (to != null) {
+                for (String recipient : to) {
+                    email.addTo(recipient);
+                }
+            }
+
+            if (cc != null) {
+                for (String recipient : cc) {
+                    email.addCc(recipient);
+                }
+            }
+
+            email.setSSL(true);
+            email.send();
+        } catch (EmailException ee)  {
+            log.warn("Failed to send e-mail", ee);
+        }
+    }
+}
diff --git a/util/src/main/java/com/ning/billing/util/email/EmailConfig.java b/util/src/main/java/com/ning/billing/util/email/EmailConfig.java
new file mode 100644
index 0000000..bcbf5e5
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/email/EmailConfig.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2010-2011 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.util.email;
+
+import com.ning.billing.config.KillbillConfig;
+import org.skife.config.Config;
+import org.skife.config.Default;
+
+import java.util.Locale;
+
+public interface EmailConfig extends KillbillConfig {
+    @Config("mail.smtp.host")
+    @Default("smtp.gmail.com")
+    public String getSmtpServerName();
+
+    @Config("mail.smtp.port")
+    @Default("465")
+    public int getSmtpPort();
+
+    @Config("mail.smtp.user")
+    @Default("killbill.ning@gmail.com")
+    public String getSmtpUserName();
+
+    @Config("mail.smtp.password")
+    @Default("killbill@ning!")
+    public String getSmtpPassword();
+}
diff --git a/util/src/main/java/com/ning/billing/util/email/EmailModule.java b/util/src/main/java/com/ning/billing/util/email/EmailModule.java
new file mode 100644
index 0000000..5bb252a
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/email/EmailModule.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2010-2011 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.util.email;
+
+import com.google.inject.AbstractModule;
+import org.skife.config.ConfigurationObjectFactory;
+
+public class EmailModule extends AbstractModule {
+    protected void installEmailConfig() {
+        EmailConfig config = new ConfigurationObjectFactory(System.getProperties()).build(EmailConfig.class);
+        bind(EmailConfig.class).toInstance(config);
+    }
+
+    @Override
+    protected void configure() {
+        installEmailConfig();
+    }
+}
diff --git a/util/src/main/java/com/ning/billing/util/email/templates/MustacheTemplateEngine.java b/util/src/main/java/com/ning/billing/util/email/templates/MustacheTemplateEngine.java
new file mode 100644
index 0000000..dba8757
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/email/templates/MustacheTemplateEngine.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2010-2011 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.util.email.templates;
+
+import com.samskivert.mustache.Mustache;
+import com.samskivert.mustache.Template;
+import org.apache.commons.io.IOUtils;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StringWriter;
+import java.util.Map;
+
+public class MustacheTemplateEngine implements TemplateEngine {
+    @Override
+    public String executeTemplate(String templateName, Map<String, Object> data) throws IOException {
+        String templateText = getTemplateText(templateName);
+        Template template = Mustache.compiler().compile(templateText);
+        return template.execute(data);
+    }
+
+    private String getTemplateText(String templateName) throws IOException {
+        InputStream templateStream = this.getClass().getResourceAsStream(templateName + ".mustache");
+        StringWriter writer = new StringWriter();
+        IOUtils.copy(templateStream, writer, "UTF-8");
+        return writer.toString();
+    }
+}
diff --git a/util/src/main/java/com/ning/billing/util/email/templates/TemplateEngine.java b/util/src/main/java/com/ning/billing/util/email/templates/TemplateEngine.java
new file mode 100644
index 0000000..9a47008
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/email/templates/TemplateEngine.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2010-2011 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.util.email.templates;
+
+import java.io.IOException;
+import java.util.Map;
+
+public interface TemplateEngine {
+    public String executeTemplate(String templateName, Map<String, Object> data) throws IOException;
+}
diff --git a/util/src/main/java/com/ning/billing/util/email/templates/TemplateModule.java b/util/src/main/java/com/ning/billing/util/email/templates/TemplateModule.java
new file mode 100644
index 0000000..86a3124
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/email/templates/TemplateModule.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2010-2011 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.util.email.templates;
+
+import com.google.inject.AbstractModule;
+
+public class TemplateModule extends AbstractModule {
+
+    @Override
+    protected void configure() {
+        bind(TemplateEngine.class).to(MustacheTemplateEngine.class).asEagerSingleton();      
+    }
+
+}
diff --git a/util/src/main/java/com/ning/billing/util/entity/collection/dao/UpdatableEntityCollectionSqlDao.java b/util/src/main/java/com/ning/billing/util/entity/collection/dao/UpdatableEntityCollectionSqlDao.java
new file mode 100644
index 0000000..ae52521
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/entity/collection/dao/UpdatableEntityCollectionSqlDao.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2010-2011 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.util.entity.collection.dao;
+
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.callcontext.CallContextBinder;
+import com.ning.billing.util.dao.AuditSqlDao;
+import com.ning.billing.util.dao.CollectionHistorySqlDao;
+import com.ning.billing.util.dao.HistoryRecordIdMapper;
+import com.ning.billing.util.dao.Mapper;
+import com.ning.billing.util.dao.ObjectType;
+import com.ning.billing.util.dao.ObjectTypeBinder;
+import com.ning.billing.util.entity.Entity;
+import org.skife.jdbi.v2.sqlobject.Bind;
+import org.skife.jdbi.v2.sqlobject.BindBean;
+import org.skife.jdbi.v2.sqlobject.SqlBatch;
+import org.skife.jdbi.v2.sqlobject.SqlQuery;
+import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
+
+import java.util.List;
+
+public interface UpdatableEntityCollectionSqlDao<T extends Entity> extends EntityCollectionSqlDao<T>,
+        CollectionHistorySqlDao<T>,
+        AuditSqlDao {
+    @SqlBatch(transactional=false)
+    public void updateFromTransaction(@Bind("objectId") final String objectId,
+                                      @ObjectTypeBinder final ObjectType objectType,
+                                      @BindBean final List<T> entities,
+                                      @CallContextBinder final CallContext context);
+
+    @SqlQuery
+    public long getMaxHistoryRecordId();
+
+    @SqlQuery
+    @RegisterMapper(HistoryRecordIdMapper.class)
+    public List<Mapper<Long, Long>> getHistoryRecordIds(@Bind("maxHistoryRecordId") final long maxHistoryRecordId);
+}
diff --git a/util/src/main/java/com/ning/billing/util/entity/dao/EntityDao.java b/util/src/main/java/com/ning/billing/util/entity/dao/EntityDao.java
new file mode 100644
index 0000000..1d2d173
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/entity/dao/EntityDao.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2010-2011 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.util.entity.dao;
+
+import java.util.List;
+import java.util.UUID;
+
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.entity.Entity;
+import com.ning.billing.util.entity.EntityPersistenceException;
+
+public interface EntityDao<T extends Entity> {
+    public void create(final T entity, final CallContext context) throws EntityPersistenceException;
+
+    public T getById(final UUID id);
+
+    public List<T> get();
+
+    public void test();
+}
diff --git a/util/src/main/java/com/ning/billing/util/entity/dao/UpdatableEntitySqlDao.java b/util/src/main/java/com/ning/billing/util/entity/dao/UpdatableEntitySqlDao.java
new file mode 100644
index 0000000..59bc69e
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/entity/dao/UpdatableEntitySqlDao.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2010-2011 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.util.entity.dao;
+
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.dao.AuditSqlDao;
+import com.ning.billing.util.dao.EntityHistory;
+import com.ning.billing.util.entity.Entity;
+import org.skife.jdbi.v2.sqlobject.SqlUpdate;
+
+// this interface needs to be extended by an interface that provides (externalized) sql and object binders and mappers
+public interface UpdatableEntitySqlDao<T extends Entity> extends EntitySqlDao<T>, AuditSqlDao {
+    @SqlUpdate
+    public void update(final T entity, final CallContext context);
+
+    @SqlUpdate
+    public void insertHistoryFromTransaction(final EntityHistory<T> account,
+                                            final CallContext context);
+}
diff --git a/util/src/main/java/com/ning/billing/util/entity/EntityBase.java b/util/src/main/java/com/ning/billing/util/entity/EntityBase.java
index 4de0312..6a03828 100644
--- a/util/src/main/java/com/ning/billing/util/entity/EntityBase.java
+++ b/util/src/main/java/com/ning/billing/util/entity/EntityBase.java
@@ -22,35 +22,19 @@ import java.util.UUID;
 
 public abstract class EntityBase implements Entity {
     protected final UUID id;
-    protected final String createdBy;
-    protected final DateTime createdDate;
 
     // used to hydrate objects
-    public EntityBase(UUID id, String createdBy, DateTime createdDate) {
+    public EntityBase(UUID id) {
         this.id = id;
-        this.createdBy = createdBy;
-        this.createdDate = createdDate;
     }
 
     // used to create new objects
     public EntityBase() {
         this.id = UUID.randomUUID();
-        this.createdBy = null;
-        this.createdDate = null;
     }
 
     @Override
     public UUID getId() {
         return id;
     }
-
-    @Override
-    public String getCreatedBy() {
-        return createdBy;
-    }
-
-    @Override
-    public DateTime getCreatedDate() {
-        return createdDate;
-    }
 }
\ No newline at end of file
diff --git a/util/src/main/java/com/ning/billing/util/entity/ExtendedEntityBase.java b/util/src/main/java/com/ning/billing/util/entity/ExtendedEntityBase.java
index 0017397..c598893 100644
--- a/util/src/main/java/com/ning/billing/util/entity/ExtendedEntityBase.java
+++ b/util/src/main/java/com/ning/billing/util/entity/ExtendedEntityBase.java
@@ -21,32 +21,34 @@ import com.ning.billing.util.customfield.CustomField;
 import com.ning.billing.util.customfield.Customizable;
 import com.ning.billing.util.customfield.DefaultFieldStore;
 import com.ning.billing.util.customfield.FieldStore;
+import com.ning.billing.util.dao.ObjectType;
+import com.ning.billing.util.tag.ControlTagType;
+import com.ning.billing.util.tag.DefaultControlTag;
 import com.ning.billing.util.tag.DefaultTagStore;
 import com.ning.billing.util.tag.DescriptiveTag;
 import com.ning.billing.util.tag.Tag;
 import com.ning.billing.util.tag.TagDefinition;
 import com.ning.billing.util.tag.TagStore;
 import com.ning.billing.util.tag.Taggable;
-import org.joda.time.DateTime;
 
-import javax.annotation.Nullable;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.UUID;
 
 public abstract class ExtendedEntityBase extends EntityBase implements Customizable, Taggable {
     protected final FieldStore fields;
-    protected final TagStore tags;
+    protected final TagStore tagStore;
 
     public ExtendedEntityBase() {
         super();
-        this.fields = DefaultFieldStore.create(getId(), getObjectName());
-        this.tags = new DefaultTagStore(id, getObjectName());
+        this.fields = DefaultFieldStore.create(getId(), getObjectType());
+        this.tagStore = new DefaultTagStore(id, getObjectType());
     }
 
-    public ExtendedEntityBase(final UUID id, @Nullable final String createdBy, @Nullable final DateTime createdDate) {
-        super(id, createdBy, createdDate);
-        this.fields = DefaultFieldStore.create(getId(), getObjectName());
-        this.tags = new DefaultTagStore(id, getObjectName());
+    public ExtendedEntityBase(final UUID id) {
+        super(id);
+        this.fields = DefaultFieldStore.create(getId(), getObjectType());
+        this.tagStore = new DefaultTagStore(id, getObjectType());
     }
 
     @Override
@@ -78,49 +80,71 @@ public abstract class ExtendedEntityBase extends EntityBase implements Customiza
 
     @Override
 	public List<Tag> getTagList() {
-		return tags.getEntityList();
+		return tagStore.getEntityList();
 	}
 
 	@Override
-	public boolean hasTag(final String tagName) {
-		return tags.containsTag(tagName);
+	public boolean hasTag(final TagDefinition tagDefinition) {
+		return tagStore.containsTagForDefinition(tagDefinition);
 	}
 
+    @Override
+    public boolean hasTag(ControlTagType controlTagType) {
+        return tagStore.containsTagForControlTagType(controlTagType);
+    }
+
 	@Override
 	public void addTag(final TagDefinition definition) {
 		Tag tag = new DescriptiveTag(definition);
-		tags.add(tag) ;
+		tagStore.add(tag) ;
 	}
 
+    @Override
+    public void addTags(final List<Tag> tags) {
+        this.tagStore.add(tags);
+    }
+
 	@Override
-	public void addTags(final List<Tag> tags) {
-		if (tags != null) {
-			this.tags.add(tags);
+	public void addTagsFromDefinitions(final List<TagDefinition> tagDefinitions) {
+		if (tagStore != null) {
+            List<Tag> tags = new ArrayList<Tag>();
+            if (tagDefinitions != null) {
+                for (TagDefinition tagDefinition : tagDefinitions) {
+                    try {
+                        ControlTagType controlTagType = ControlTagType.valueOf(tagDefinition.getName());
+                        tags.add(new DefaultControlTag(controlTagType));
+                    } catch (IllegalArgumentException ex) {
+                        tags.add(new DescriptiveTag(tagDefinition));
+                    }
+                }
+            }
+
+			this.tagStore.add(tags);
 		}
 	}
 
 	@Override
 	public void clearTags() {
-		this.tags.clear();
+		this.tagStore.clear();
 	}
 
 	@Override
-	public void removeTag(final TagDefinition definition) {
-		tags.remove(definition.getName());
+	public void removeTag(final TagDefinition tagDefinition) {
+		tagStore.remove(tagDefinition);
 	}
 
 	@Override
 	public boolean generateInvoice() {
-		return tags.generateInvoice();
+		return tagStore.generateInvoice();
 	}
 
 	@Override
 	public boolean processPayment() {
-		return tags.processPayment();
+		return tagStore.processPayment();
 	}
 
     @Override
-    public abstract String getObjectName();
+    public abstract ObjectType getObjectType();
 
     @Override
     public abstract void saveFieldValue(String fieldName, String fieldValue, CallContext context);
diff --git a/util/src/main/java/com/ning/billing/util/entity/UpdatableEntityBase.java b/util/src/main/java/com/ning/billing/util/entity/UpdatableEntityBase.java
index 3aaffbc..625aaec 100644
--- a/util/src/main/java/com/ning/billing/util/entity/UpdatableEntityBase.java
+++ b/util/src/main/java/com/ning/billing/util/entity/UpdatableEntityBase.java
@@ -16,33 +16,14 @@
 
 package com.ning.billing.util.entity;
 
-import org.joda.time.DateTime;
-
 import java.util.UUID;
 
 public abstract class UpdatableEntityBase extends EntityBase implements UpdatableEntity {
-    private final String updatedBy;
-    private final DateTime updatedDate;
-
     public UpdatableEntityBase() {
         super();
-        this.updatedBy = null;
-        this.updatedDate = null;
-    }
-
-    public UpdatableEntityBase(UUID id, String createdBy, DateTime createdDate, String updatedBy, DateTime updatedDate) {
-        super(id, createdBy, createdDate);
-        this.updatedBy = updatedBy;
-        this.updatedDate = updatedDate;
-    }
-
-    @Override
-    public String getUpdatedBy() {
-        return updatedBy;
     }
 
-    @Override
-    public DateTime getUpdatedDate() {
-        return updatedDate;
+    public UpdatableEntityBase(UUID id) {
+        super(id);
     }
 }
diff --git a/util/src/main/java/com/ning/billing/util/glue/BusModule.java b/util/src/main/java/com/ning/billing/util/glue/BusModule.java
index d6f7a37..2793208 100644
--- a/util/src/main/java/com/ning/billing/util/glue/BusModule.java
+++ b/util/src/main/java/com/ning/billing/util/glue/BusModule.java
@@ -16,19 +16,65 @@
 
 package com.ning.billing.util.glue;
 
+import org.skife.config.ConfigurationObjectFactory;
+
 import com.google.inject.AbstractModule;
+import com.google.inject.name.Names;
+import com.ning.billing.config.EntitlementConfig;
+import com.ning.billing.config.PersistentQueueConfig;
 import com.ning.billing.util.bus.DefaultBusService;
 import com.ning.billing.util.bus.Bus;
 import com.ning.billing.util.bus.BusService;
+import com.ning.billing.util.bus.PersistentBusConfig;
 import com.ning.billing.util.bus.InMemoryBus;
+import com.ning.billing.util.bus.PersistentBus;
 
 public class BusModule extends AbstractModule {
 
+    private final BusType type;
+    
+    public BusModule() {
+        super();
+        type = BusType.PERSISTENT;        
+    }
+
+    public BusModule(BusType type) {
+        super();
+        this.type = type;
+    }
+
+    public enum BusType {
+        MEMORY,
+        PERSISTENT
+    }
+    
     @Override
     protected void configure() {
         bind(BusService.class).to(DefaultBusService.class);
-        bind(Bus.class).to(InMemoryBus.class).asEagerSingleton();
-
+        switch(type) {
+        case MEMORY:
+            configureInMemoryEventBus();
+            break;
+        case PERSISTENT:
+            configurePersistentEventBus();
+            break;
+        default:
+            new RuntimeException("Unrecognized EventBus type " + type);
+        }
+        
     }
 
+    protected void configurePersistentBusConfig() {
+        final PersistentBusConfig config = new ConfigurationObjectFactory(System.getProperties()).build(PersistentBusConfig.class);
+        bind(PersistentBusConfig.class).toInstance(config);
+    }
+    
+    private void configurePersistentEventBus() {
+        configurePersistentBusConfig();        
+        bind(Bus.class).to(PersistentBus.class).asEagerSingleton();
+    }
+    
+    private void configureInMemoryEventBus() {
+        bind(Bus.class).to(InMemoryBus.class).asEagerSingleton();
+    }
 }
diff --git a/util/src/main/java/com/ning/billing/util/glue/RealImplementation.java b/util/src/main/java/com/ning/billing/util/glue/RealImplementation.java
new file mode 100644
index 0000000..d72a698
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/glue/RealImplementation.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2010-2011 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.util.glue;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
+
+import com.google.inject.BindingAnnotation;
+
+/**
+ * This annotation is used to bing classes that are being intercepted in junction.
+ * 
+ * The real implementation of the class is bound in Guice with this parameter, the Blocking implementation
+ * is left unannotated.
+ *
+ */
+@BindingAnnotation @Target({ FIELD, PARAMETER, METHOD,LOCAL_VARIABLE }) @Retention(RUNTIME)
+public @interface RealImplementation {
+
+}
diff --git a/util/src/main/java/com/ning/billing/util/notificationq/dao/NotificationSqlDao.java b/util/src/main/java/com/ning/billing/util/notificationq/dao/NotificationSqlDao.java
index 3d0a8b1..cbfc564 100644
--- a/util/src/main/java/com/ning/billing/util/notificationq/dao/NotificationSqlDao.java
+++ b/util/src/main/java/com/ning/billing/util/notificationq/dao/NotificationSqlDao.java
@@ -39,7 +39,7 @@ import org.skife.jdbi.v2.tweak.ResultSetMapper;
 
 import com.ning.billing.util.notificationq.DefaultNotification;
 import com.ning.billing.util.notificationq.Notification;
-import com.ning.billing.util.notificationq.NotificationLifecycle.NotificationLifecycleState;
+import com.ning.billing.util.queue.PersistentQueueEntryLifecycle.PersistentQueueEntryLifecycleState;
 
 @ExternalizedSqlViaStringTemplate3()
 public interface NotificationSqlDao extends Transactional<NotificationSqlDao>, CloseMe {
@@ -49,31 +49,36 @@ public interface NotificationSqlDao extends Transactional<NotificationSqlDao>, C
     //
     @SqlQuery
     @Mapper(NotificationSqlMapper.class)
-    public List<Notification> getReadyNotifications(@Bind("now") Date now, @Bind("max") int max, @Bind("queue_name") String queueName);
+    public List<Notification> getReadyNotifications(@Bind("now") Date now, @Bind("owner") String owner, @Bind("max") int max, @Bind("queueName") String queueName);
 
     @SqlUpdate
-    public int claimNotification(@Bind("owner") String owner, @Bind("next_available") Date nextAvailable, @Bind("id") long id, @Bind("now") Date now);
+    public int claimNotification(@Bind("owner") String owner, @Bind("nextAvailable") Date nextAvailable,
+                                 @Bind("id") String id, @Bind("now") Date now);
 
     @SqlUpdate
-    public void clearNotification(@Bind("id") long id, @Bind("owner") String owner);
+    public void clearNotification(@Bind("id") String id, @Bind("owner") String owner);
 
     @SqlUpdate
+    public void removeNotificationsByKey(@Bind("notificationKey") String key);
+    
+    @SqlUpdate
     public void insertNotification(@Bind(binder = NotificationSqlDaoBinder.class) Notification evt);
 
     @SqlUpdate
-    public void insertClaimedHistory(@Bind("sequence_id") int sequenceId, @Bind("owner") String owner, @Bind("claimed_dt") Date clainedDate, @Bind("notification_id") String notificationId);
+    public void insertClaimedHistory(@Bind("ownerId") String ownerId, @Bind("claimedDate") Date claimedDate, @Bind("notificationId") String notificationId);
 
     public static class NotificationSqlDaoBinder extends BinderBase implements Binder<Bind, Notification> {
         @Override
         public void bind(@SuppressWarnings("rawtypes") SQLStatement stmt, Bind bind, Notification evt) {
-            stmt.bind("notification_id", evt.getUUID().toString());
-            stmt.bind("created_dt", getDate(new DateTime()));
-            stmt.bind("notification_key", evt.getNotificationKey());
-            stmt.bind("effective_dt", getDate(evt.getEffectiveDate()));
-            stmt.bind("queue_name", evt.getQueueName());
-            stmt.bind("processing_available_dt", getDate(evt.getNextAvailableDate()));
-            stmt.bind("processing_owner", evt.getOwner());
-            stmt.bind("processing_state", NotificationLifecycleState.AVAILABLE.toString());
+            stmt.bind("id", evt.getId().toString());
+            stmt.bind("createdDate", getDate(new DateTime()));
+            stmt.bind("creatingOwner", evt.getCreatedOwner());            
+            stmt.bind("notificationKey", evt.getNotificationKey());
+            stmt.bind("effectiveDate", getDate(evt.getEffectiveDate()));
+            stmt.bind("queueName", evt.getQueueName());
+            stmt.bind("processingAvailableDate", getDate(evt.getNextAvailableDate()));
+            stmt.bind("processingOwner", evt.getOwner());
+            stmt.bind("processingState", PersistentQueueEntryLifecycleState.AVAILABLE.toString());
         }
     }
 
@@ -83,16 +88,17 @@ public interface NotificationSqlDao extends Transactional<NotificationSqlDao>, C
         public Notification map(int index, ResultSet r, StatementContext ctx)
         throws SQLException {
 
-            final long id = r.getLong("id");
-            final UUID uuid = UUID.fromString(r.getString("notification_id"));
+            final Long ordering = r.getLong("record_id");
+            final UUID id = getUUID(r, "id");
+            final String createdOwner = r.getString("creating_owner");            
             final String notificationKey = r.getString("notification_key");
             final String queueName = r.getString("queue_name");
-            final DateTime effectiveDate = getDate(r, "effective_dt");
-            final DateTime nextAvailableDate = getDate(r, "processing_available_dt");
+            final DateTime effectiveDate = getDate(r, "effective_date");
+            final DateTime nextAvailableDate = getDate(r, "processing_available_date");
             final String processingOwner = r.getString("processing_owner");
-            final NotificationLifecycleState processingState = NotificationLifecycleState.valueOf(r.getString("processing_state"));
+            final PersistentQueueEntryLifecycleState processingState = PersistentQueueEntryLifecycleState.valueOf(r.getString("processing_state"));
 
-            return new DefaultNotification(id, uuid, processingOwner, queueName, nextAvailableDate,
+            return new DefaultNotification(ordering, id, createdOwner, processingOwner, queueName, nextAvailableDate,
                     processingState, notificationKey, effectiveDate);
 
         }
diff --git a/util/src/main/java/com/ning/billing/util/notificationq/DefaultNotification.java b/util/src/main/java/com/ning/billing/util/notificationq/DefaultNotification.java
index 26e6c4e..c71343d 100644
--- a/util/src/main/java/com/ning/billing/util/notificationq/DefaultNotification.java
+++ b/util/src/main/java/com/ning/billing/util/notificationq/DefaultNotification.java
@@ -18,27 +18,27 @@ package com.ning.billing.util.notificationq;
 
 import java.util.UUID;
 
+import com.ning.billing.util.entity.EntityBase;
 import org.joda.time.DateTime;
 
-public class DefaultNotification implements Notification {
-
-    private final long id;
-    private final UUID uuid;
+public class DefaultNotification extends EntityBase implements Notification {
+    private final long ordering;
     private final String owner;
+    private final String createdOwner;
     private final String queueName;
     private final DateTime nextAvailableDate;
-    private final NotificationLifecycleState lifecycleState;
+    private final PersistentQueueEntryLifecycleState lifecycleState;
     private final String notificationKey;
     private final DateTime effectiveDate;
 
 
-    public DefaultNotification(long id, UUID uuid, String owner, String queueName, DateTime nextAvailableDate,
-            NotificationLifecycleState lifecycleState,
+    public DefaultNotification(long ordering, UUID id, String createdOwner, String owner, String queueName, DateTime nextAvailableDate,
+            PersistentQueueEntryLifecycleState lifecycleState,
             String notificationKey, DateTime effectiveDate) {
-        super();
-        this.id = id;
-        this.uuid = uuid;
+        super(id);
+        this.ordering = ordering;
         this.owner = owner;
+        this.createdOwner = createdOwner;
         this.queueName = queueName;
         this.nextAvailableDate = nextAvailableDate;
         this.lifecycleState = lifecycleState;
@@ -46,17 +46,12 @@ public class DefaultNotification implements Notification {
         this.effectiveDate = effectiveDate;
     }
 
-    @Override
-    public long getId() {
-        return id;
-    }
-
-    public DefaultNotification(String queueName, String notificationKey, DateTime effectiveDate) {
-        this(-1L, UUID.randomUUID(), null, queueName, null, NotificationLifecycleState.AVAILABLE, notificationKey, effectiveDate);
+    public DefaultNotification(String queueName, String createdOwner, String notificationKey, DateTime effectiveDate) {
+        this(-1L, UUID.randomUUID(), createdOwner, null, queueName, null, PersistentQueueEntryLifecycleState.AVAILABLE, notificationKey, effectiveDate);
     }
     @Override
-    public UUID getUUID() {
-        return uuid;
+    public Long getOrdering() {
+        return ordering;
     }
 
     @Override
@@ -70,7 +65,7 @@ public class DefaultNotification implements Notification {
     }
 
     @Override
-    public NotificationLifecycleState getProcessingState() {
+    public PersistentQueueEntryLifecycleState getProcessingState() {
         return lifecycleState;
     }
 
@@ -108,4 +103,8 @@ public class DefaultNotification implements Notification {
 		return queueName;
 	}
 
+    @Override
+    public String getCreatedOwner() {
+        return createdOwner;
+    }
 }
diff --git a/util/src/main/java/com/ning/billing/util/notificationq/DefaultNotificationQueue.java b/util/src/main/java/com/ning/billing/util/notificationq/DefaultNotificationQueue.java
index c0c88fe..6b50914 100644
--- a/util/src/main/java/com/ning/billing/util/notificationq/DefaultNotificationQueue.java
+++ b/util/src/main/java/com/ning/billing/util/notificationq/DefaultNotificationQueue.java
@@ -19,11 +19,13 @@ package com.ning.billing.util.notificationq;
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
+import java.util.UUID;
 
 import org.joda.time.DateTime;
 import org.skife.jdbi.v2.IDBI;
 import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
 
+import com.ning.billing.config.NotificationConfig;
 import com.ning.billing.util.clock.Clock;
 import com.ning.billing.util.notificationq.NotificationQueueService.NotificationQueueHandler;
 import com.ning.billing.util.notificationq.dao.NotificationSqlDao;
@@ -39,10 +41,10 @@ public class DefaultNotificationQueue extends NotificationQueueBase {
     }
 
     @Override
-    protected int doProcessEvents(final int sequenceId) {
+    public int doProcessEvents() {
 
         logDebug("ENTER doProcessEvents");
-        List<Notification> notifications = getReadyNotifications(sequenceId);
+        List<Notification> notifications = getReadyNotifications();
         if (notifications.size() == 0) {
             logDebug("EXIT doProcessEvents");
             return 0;
@@ -54,19 +56,19 @@ public class DefaultNotificationQueue extends NotificationQueueBase {
         for (final Notification cur : notifications) {
             nbProcessedEvents.incrementAndGet();
             logDebug("handling notification %s, key = %s for time %s",
-                    cur.getUUID(), cur.getNotificationKey(), cur.getEffectiveDate());
+                    cur.getId(), cur.getNotificationKey(), cur.getEffectiveDate());
             handler.handleReadyNotification(cur.getNotificationKey(), cur.getEffectiveDate());
             result++;
             clearNotification(cur);
             logDebug("done handling notification %s, key = %s for time %s",
-                    cur.getUUID(), cur.getNotificationKey(), cur.getEffectiveDate());
+                    cur.getId(), cur.getNotificationKey(), cur.getEffectiveDate());
         }
         return result;
     }
 
     @Override
     public void recordFutureNotification(DateTime futureNotificationTime, NotificationKey notificationKey) {
-        Notification notification = new DefaultNotification(getFullQName(), notificationKey.toString(), futureNotificationTime);
+        Notification notification = new DefaultNotification(getFullQName(), hostname,  notificationKey.toString(), futureNotificationTime);
         dao.insertNotification(notification);
     }
 
@@ -74,32 +76,32 @@ public class DefaultNotificationQueue extends NotificationQueueBase {
     public void recordFutureNotificationFromTransaction(final Transmogrifier transactionalDao,
             final DateTime futureNotificationTime, final NotificationKey notificationKey) {
         NotificationSqlDao transactionalNotificationDao =  transactionalDao.become(NotificationSqlDao.class);
-        Notification notification = new DefaultNotification(getFullQName(), notificationKey.toString(), futureNotificationTime);
+        Notification notification = new DefaultNotification(getFullQName(), hostname, notificationKey.toString(), futureNotificationTime);
         transactionalNotificationDao.insertNotification(notification);
     }
 
 
     private void clearNotification(final Notification cleared) {
-        dao.clearNotification(cleared.getId(), hostname);
+        dao.clearNotification(cleared.getId().toString(), hostname);
     }
 
-    private List<Notification> getReadyNotifications(final int seqId) {
+    private List<Notification> getReadyNotifications() {
 
         final Date now = clock.getUTCNow().toDate();
-        final Date nextAvailable = clock.getUTCNow().plus(config.getDaoClaimTimeMs()).toDate();
+        final Date nextAvailable = clock.getUTCNow().plus(CLAIM_TIME_MS).toDate();
 
-        List<Notification> input = dao.getReadyNotifications(now, config.getDaoMaxReadyEvents(), getFullQName());
+        List<Notification> input = dao.getReadyNotifications(now, hostname, CLAIM_TIME_MS, getFullQName());
 
         List<Notification> claimedNotifications = new ArrayList<Notification>();
         for (Notification cur : input) {
             logDebug("about to claim notification %s,  key = %s for time %s",
-                    cur.getUUID(), cur.getNotificationKey(), cur.getEffectiveDate());
-            final boolean claimed = (dao.claimNotification(hostname, nextAvailable, cur.getId(), now) == 1);
+                    cur.getId(), cur.getNotificationKey(), cur.getEffectiveDate());
+            final boolean claimed = (dao.claimNotification(hostname, nextAvailable, cur.getId().toString(), now) == 1);
             logDebug("claimed notification %s, key = %s for time %s result = %s",
-                    cur.getUUID(), cur.getNotificationKey(), cur.getEffectiveDate(), Boolean.valueOf(claimed));
+                    cur.getId(), cur.getNotificationKey(), cur.getEffectiveDate(), Boolean.valueOf(claimed));
             if (claimed) {
                 claimedNotifications.add(cur);
-                dao.insertClaimedHistory(seqId, hostname, now, cur.getUUID().toString());
+                dao.insertClaimedHistory(hostname, now, cur.getId().toString());
             }
         }
 
@@ -118,4 +120,10 @@ public class DefaultNotificationQueue extends NotificationQueueBase {
             log.debug(String.format("Thread %d [queue = %s] %s", Thread.currentThread().getId(), getFullQName(), realDebug));
         }
     }
+
+    @Override
+    public void removeNotificationsByKey(UUID key) {
+        dao.removeNotificationsByKey(key.toString());
+        
+    }
 }
diff --git a/util/src/main/java/com/ning/billing/util/notificationq/DefaultNotificationQueueService.java b/util/src/main/java/com/ning/billing/util/notificationq/DefaultNotificationQueueService.java
index 3b96ee4..bf8652c 100644
--- a/util/src/main/java/com/ning/billing/util/notificationq/DefaultNotificationQueueService.java
+++ b/util/src/main/java/com/ning/billing/util/notificationq/DefaultNotificationQueueService.java
@@ -18,6 +18,7 @@ package com.ning.billing.util.notificationq;
 
 import org.skife.jdbi.v2.IDBI;
 import com.google.inject.Inject;
+import com.ning.billing.config.NotificationConfig;
 import com.ning.billing.util.clock.Clock;
 
 public class DefaultNotificationQueueService extends NotificationQueueServiceBase {
diff --git a/util/src/main/java/com/ning/billing/util/notificationq/Notification.java b/util/src/main/java/com/ning/billing/util/notificationq/Notification.java
index d59098b..37caa4e 100644
--- a/util/src/main/java/com/ning/billing/util/notificationq/Notification.java
+++ b/util/src/main/java/com/ning/billing/util/notificationq/Notification.java
@@ -16,22 +16,17 @@
 
 package com.ning.billing.util.notificationq;
 
-import java.util.UUID;
+import com.ning.billing.util.entity.Entity;
+import com.ning.billing.util.queue.PersistentQueueEntryLifecycle;
 
 import org.joda.time.DateTime;
 
-
-public interface Notification extends NotificationLifecycle {
-
-    public long getId();
-
-    public UUID getUUID();
+public interface Notification extends PersistentQueueEntryLifecycle, Entity {
+    public Long getOrdering();
 
     public String getNotificationKey();
 
     public DateTime getEffectiveDate();
 
     public String getQueueName();
-
-
 }
diff --git a/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueue.java b/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueue.java
index fb88d4c..f28ff63 100644
--- a/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueue.java
+++ b/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueue.java
@@ -16,59 +16,58 @@
 
 package com.ning.billing.util.notificationq;
 
+import java.util.UUID;
+
 import org.joda.time.DateTime;
 import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
 
 import com.ning.billing.util.notificationq.NotificationQueueService.NotificationQueueHandler;
+import com.ning.billing.util.queue.QueueLifecycle;
+
+public interface NotificationQueue extends QueueLifecycle {
 
-public interface NotificationQueue {
+    /**
+     *
+     * Record the need to be called back when the notification is ready
+     *
+     * @param futureNotificationTime the time at which the notification is ready
+     * @param notificationKey the key for that notification
+     */
+    public void recordFutureNotification(final DateTime futureNotificationTime, final NotificationKey notificationKey);
 
-   /**
-    *
-    * Record the need to be called back when the notification is ready
-    *
-    * @param futureNotificationTime the time at which the notification is ready
-    * @param notificationKey the key for that notification
-    */
-   public void recordFutureNotification(final DateTime futureNotificationTime, final NotificationKey notificationKey);
+    /**
+     *
+     * Record from within a transaction the need to be called back when the notification is ready
+     *
+     * @param transactionalDao the transactionalDao
+     * @param futureNotificationTime the time at which the notification is ready
+     * @param notificationKey the key for that notification
+     */
+    public void recordFutureNotificationFromTransaction(final Transmogrifier transactionalDao,
+            final DateTime futureNotificationTime, final NotificationKey notificationKey);
 
-   /**
-    *
-    * Record from within a transaction the need to be called back when the notification is ready
-    *
-    * @param transactionalDao the transactionalDao
-    * @param futureNotificationTime the time at which the notification is ready
-    * @param notificationKey the key for that notification
-    */
-   public void recordFutureNotificationFromTransaction(final Transmogrifier transactionalDao,
-           final DateTime futureNotificationTime, final NotificationKey notificationKey);
+  
+    /**
+     * Remove all notifications associated with this key   
+     * 
+     * @param key
+     */
+    public void removeNotificationsByKey(UUID key);
 
-   /**
-    * This is only valid when the queue has been configured with isNotificationProcessingOff is true
-    * In which case, it will callback users for all the ready notifications.
-    *
-    * @return the number of entries we processed
-    */
-   public int processReadyNotification();
+    /**
+     * This is only valid when the queue has been configured with isNotificationProcessingOff is true
+     * In which case, it will callback users for all the ready notifications.
+     *
+     * @return the number of entries we processed
+     */
+    public int processReadyNotification();
 
-   /**
-    * Stops the queue. Blocks until queue is completely stopped.
-    *
-    * @see NotificationQueueHandler.completedQueueStop to be notified when the notification thread exited
-    */
-   public void stopQueue();
+    /**
+     *
+     * @return the name of that queue
+     */
+    public String getFullQName();
 
-   /**
-    * Starts the queue. Blocks until queue has completely started.
-    *
-    * @see NotificationQueueHandler.completedQueueStart to be notified when the notification thread started
-    */
-   public void startQueue();
 
-   /**
-    *
-    * @return the name of that queue
-    */
-   public String getFullQName();
 
 }
diff --git a/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueueBase.java b/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueueBase.java
index a4f4c97..cd3c0c2 100644
--- a/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueueBase.java
+++ b/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueueBase.java
@@ -17,60 +17,45 @@
 package com.ning.billing.util.notificationq;
 
 import java.lang.Thread.UncaughtExceptionHandler;
-import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ThreadFactory;
-import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicLong;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.ning.billing.config.NotificationConfig;
 import com.ning.billing.util.Hostname;
 import com.ning.billing.util.clock.Clock;
 import com.ning.billing.util.notificationq.NotificationQueueService.NotificationQueueHandler;
+import com.ning.billing.util.queue.PersistentQueueBase;
 
 
-public abstract class NotificationQueueBase implements NotificationQueue {
+public abstract class NotificationQueueBase extends PersistentQueueBase implements NotificationQueue {
 
     protected final static Logger log = LoggerFactory.getLogger(NotificationQueueBase.class);
 
-    private static final long MAX_NOTIFICATION_THREAD_WAIT_MS = 10000; // 10 secs
-    private static final long NOTIFICATION_THREAD_WAIT_INCREMENT_MS = 1000; // 1 sec
-    private static final long NANO_TO_MS = (1000 * 1000);
+    public final static int CLAIM_TIME_MS = (5 * 60 * 1000); // 5 minutes
+    
+    private final static String NOTIFICATION_THREAD_PREFIX = "Notification-";
+    private final static int NB_THREADS = 1;
 
-    protected static final String NOTIFICATION_THREAD_PREFIX = "Notification-";
-    protected final long STOP_WAIT_TIMEOUT_MS = 60000;
-
-    protected final String svcName;
-    protected final String queueName;
+    
+    private final String svcName;
+    private final String queueName;
+    
     protected final NotificationQueueHandler handler;
     protected final NotificationConfig config;
 
-    protected final Executor executor;
     protected final Clock clock;
     protected final String hostname;
 
-    protected static final AtomicInteger sequenceId = new AtomicInteger();
-
     protected AtomicLong nbProcessedEvents;
 
-    // Use this object's monitor for synchronization (no need for volatile)
-    protected boolean isProcessingEvents;
-
-    private boolean startedComplete = false;
-    private boolean stoppedComplete = false;
-
     // Package visibility on purpose
     NotificationQueueBase(final Clock clock,  final String svcName, final String queueName, final NotificationQueueHandler handler, final NotificationConfig config) {
-        this.clock = clock;
-        this.svcName = svcName;
-        this.queueName = queueName;
-        this.handler = handler;
-        this.config = config;
-        this.hostname = Hostname.get();
+        super(svcName, Executors.newFixedThreadPool(1, new ThreadFactory() {
 
-        this.executor = Executors.newSingleThreadExecutor(new ThreadFactory() {
             @Override
             public Thread newThread(Runnable r) {
                 Thread th = new Thread(r);
@@ -83,103 +68,36 @@ public abstract class NotificationQueueBase implements NotificationQueue {
                 });
                 return th;
             }
-        });
-    }
+        }), NB_THREADS, config);
 
-
-    @Override
-    public int processReadyNotification() {
-        return doProcessEvents(sequenceId.incrementAndGet());
+        this.clock = clock;
+        this.svcName = svcName;
+        this.queueName = queueName;
+        this.handler = handler;
+        this.config = config;
+        this.hostname = Hostname.get();
+        this.nbProcessedEvents = new AtomicLong();
     }
 
-
     @Override
-    public void stopQueue() {
+    public void startQueue() {
         if (config.isNotificationProcessingOff()) {
-            completedQueueStop();
             return;
         }
-
-        synchronized(this) {
-            isProcessingEvents = false;
-            try {
-                log.info("NotificationQueue requested to stop");
-                wait(STOP_WAIT_TIMEOUT_MS);
-                log.info("NotificationQueue requested should have exited");
-            } catch (InterruptedException e) {
-                log.warn("NotificationQueue got interrupted exception when stopping notifications", e);
-            }
-        }
-        waitForNotificationStopCompletion();
+        super.startQueue();
     }
-
+    
     @Override
-    public void startQueue() {
-
-        this.isProcessingEvents = true;
-        this.nbProcessedEvents = new AtomicLong();
-
-
+    public void stopQueue() {
         if (config.isNotificationProcessingOff()) {
-            log.warn(String.format("KILLBILL NOTIFICATION PROCESSING FOR SVC %s IS OFF !!!", getFullQName()));
-            completedQueueStart();
             return;
         }
-        final NotificationQueueBase notificationQueue = this;
-
-        executor.execute(new Runnable() {
-            @Override
-            public void run() {
-
-                log.info(String.format("NotificationQueue thread %s [%d] started",
-                        Thread.currentThread().getName(),
-                        Thread.currentThread().getId()));
-
-                // Thread is now started, notify the listener
-                completedQueueStart();
-
-                try {
-                    while (true) {
-
-                        synchronized (notificationQueue) {
-                            if (!isProcessingEvents) {
-                                log.info(String.format("NotificationQueue has been requested to stop, thread  %s  [%d] stopping...",
-                                        Thread.currentThread().getName(),
-                                        Thread.currentThread().getId()));
-                                notificationQueue.notify();
-                                break;
-                            }
-                        }
-
-                        // Callback may trigger exceptions in user code so catch anything here and live with it.
-                        try {
-                            doProcessEvents(sequenceId.getAndIncrement());
-                        } catch (Exception e) {
-                            log.error(String.format("NotificationQueue thread  %s  [%d] got an exception..",
-                                    Thread.currentThread().getName(),
-                                    Thread.currentThread().getId()), e);
-                        }
-                        sleepALittle();
-                    }
-                } catch (InterruptedException e) {
-                    log.warn(Thread.currentThread().getName() + " got interrupted ", e);
-                } catch (Throwable e) {
-                    log.error(Thread.currentThread().getName() + " got an exception exiting...", e);
-                    // Just to make it really obvious in the log
-                    e.printStackTrace();
-                } finally {
-                    completedQueueStop();
-                    log.info(String.format("NotificationQueue thread  %s  [%d] exited...",
-                            Thread.currentThread().getName(),
-                            Thread.currentThread().getId()));
-                }
-            }
+        super.stopQueue();
+    }
 
-            private void sleepALittle() throws InterruptedException {
-                Thread.sleep(config.getNotificationSleepTimeMs());
-            }
-        });
-        waitForNotificationStartCompletion();
+    @Override
+    public int processReadyNotification() {
+        return doProcessEvents();
     }
 
     @Override
@@ -187,61 +105,12 @@ public abstract class NotificationQueueBase implements NotificationQueue {
         return getFullQName();
     }
 
-    private void completedQueueStop() {
-    	synchronized (this) {
-    		stoppedComplete = true;
-            this.notifyAll();
-        }
-    }
-
-    private void completedQueueStart() {
-        synchronized (this) {
-        	startedComplete = true;
-            this.notifyAll();
-        }
-    }
-
-    private void waitForNotificationStartCompletion() {
-        waitForNotificationEventCompletion(true);
-    }
-
-    private void waitForNotificationStopCompletion() {
-        waitForNotificationEventCompletion(false);
-    }
-
-    private void waitForNotificationEventCompletion(boolean startEvent) {
-
-        long ini = System.nanoTime();
-        synchronized(this) {
-            do {
-                if ((startEvent ? startedComplete : stoppedComplete)) {
-                    break;
-                }
-                try {
-                    this.wait(NOTIFICATION_THREAD_WAIT_INCREMENT_MS);
-                } catch (InterruptedException e ) {
-                    Thread.currentThread().interrupt();
-                    throw new NotificationError(e);
-                }
-            } while (!(startEvent ? startedComplete : stoppedComplete) &&
-                    (System.nanoTime() - ini) / NANO_TO_MS < MAX_NOTIFICATION_THREAD_WAIT_MS);
-
-            if (!(startEvent ? startedComplete : stoppedComplete)) {
-                log.error("Could not {} notification thread in {} msec !!!",
-                        (startEvent ? "start" : "stop"),
-                        MAX_NOTIFICATION_THREAD_WAIT_MS);
-                throw new NotificationError("Failed to start service!!");
-            }
-            log.info("Notification thread has been {} in {} ms",
-                    (startEvent ? "started" : "stopped"),
-                    (System.nanoTime() - ini) / NANO_TO_MS);
-        }
-    }
 
     @Override
     public String getFullQName() {
         return svcName + ":" +  queueName;
     }
 
-    protected abstract int doProcessEvents(int sequenceId);
+    @Override
+    public abstract int doProcessEvents();
 }
diff --git a/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueueService.java b/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueueService.java
index 4d56b03..828608a 100644
--- a/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueueService.java
+++ b/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueueService.java
@@ -18,6 +18,8 @@ package com.ning.billing.util.notificationq;
 
 import org.joda.time.DateTime;
 
+import com.ning.billing.config.NotificationConfig;
+
 
 public interface NotificationQueueService {
 
diff --git a/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueueServiceBase.java b/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueueServiceBase.java
index 56423a0..90c27b0 100644
--- a/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueueServiceBase.java
+++ b/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueueServiceBase.java
@@ -27,6 +27,7 @@ import org.slf4j.LoggerFactory;
 
 import com.google.common.base.Joiner;
 import com.google.inject.Inject;
+import com.ning.billing.config.NotificationConfig;
 import com.ning.billing.util.clock.Clock;
 
 public abstract class NotificationQueueServiceBase implements NotificationQueueService {
diff --git a/util/src/main/java/com/ning/billing/util/queue/PersistentQueueBase.java b/util/src/main/java/com/ning/billing/util/queue/PersistentQueueBase.java
new file mode 100644
index 0000000..0ffe882
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/queue/PersistentQueueBase.java
@@ -0,0 +1,161 @@
+/* 
+ * Copyright 2010-2011 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.util.queue;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.ning.billing.config.PersistentQueueConfig;
+
+
+public abstract class PersistentQueueBase implements QueueLifecycle {
+
+    private static final Logger log = LoggerFactory.getLogger(PersistentQueueBase.class);
+    
+    private static final long waitTimeoutMs = 15L * 1000L; // 15 seconds
+    
+    private final int nbThreads;
+    private final Executor executor;
+    private final String svcName;
+    private final long sleepTimeMs;
+
+    private boolean isProcessingEvents;
+    private int curActiveThreads;
+    
+    public PersistentQueueBase(final String svcName, final Executor executor, final int nbThreads, final PersistentQueueConfig config) {
+        this.executor = executor;
+        this.nbThreads = nbThreads;
+        this.svcName = svcName;
+        this.sleepTimeMs = config.getSleepTimeMs();
+        this.isProcessingEvents = false;
+        this.curActiveThreads = 0;
+    }
+    
+    @Override
+    public void startQueue() {
+        
+        isProcessingEvents = true;
+        curActiveThreads = 0;
+        
+        final PersistentQueueBase thePersistentQ = this;
+        final CountDownLatch doneInitialization = new CountDownLatch(nbThreads);
+
+        log.info(String.format("%s: Starting with %d threads",
+                svcName, nbThreads));
+        
+        for (int i = 0; i < nbThreads; i++) {
+            executor.execute(new Runnable() {
+                @Override
+                public void run() {
+
+                    log.info(String.format("%s: Thread %s [%d] starting",
+                            svcName,
+                            Thread.currentThread().getName(),
+                            Thread.currentThread().getId()));
+                    
+                    synchronized(thePersistentQ) {
+                        curActiveThreads++;
+                    }
+
+                    doneInitialization.countDown();
+                    
+                    try {
+                        while (true) {
+                            
+                            synchronized(thePersistentQ) {
+                                if (!isProcessingEvents) {
+                                    thePersistentQ.notify();
+                                    break;
+                                }
+                            }
+
+                            try {
+                                doProcessEvents();
+                            } catch (Exception e) {
+                                log.warn(String.format("%s: Thread  %s  [%d] got an exception, catching and moving on...",
+                                        svcName,
+                                        Thread.currentThread().getName(),
+                                        Thread.currentThread().getId()), e);
+                            }
+                            sleepALittle();
+                        }
+                    } catch (InterruptedException e) {
+                        log.info(String.format("%s: Thread %s got interrupted, exting... ", svcName, Thread.currentThread().getName()));
+                    } catch (Throwable e) {
+                        log.error(String.format("%s: Thread %s got an exception, exting... ", svcName, Thread.currentThread().getName()), e);                        
+                    } finally {
+
+                        log.info(String.format("%s: Thread %s has exited", svcName, Thread.currentThread().getName()));                                                
+                        synchronized(thePersistentQ) {
+                            curActiveThreads--;
+                        }
+                    }
+                }
+                
+                private void sleepALittle() throws InterruptedException {
+                    Thread.sleep(sleepTimeMs);
+                }
+            });
+        }
+        try {
+            boolean success = doneInitialization.await(sleepTimeMs, TimeUnit.MILLISECONDS);
+            if (!success) {
+                
+                log.warn(String.format("%s: Failed to wait for all threads to be started, got %d/%d", svcName, (nbThreads - doneInitialization.getCount()), nbThreads));
+            } else {
+                log.info(String.format("%s: Done waiting for all threads to be started, got %d/%d", svcName, (nbThreads - doneInitialization.getCount()), nbThreads));                
+            }
+        } catch (InterruptedException e) {
+            log.warn(String.format("%s: Start sequence, got interrupted", svcName));
+        }
+    }
+    
+    
+    @Override
+    public void stopQueue() {
+        int remaining = 0;
+        try {
+            synchronized(this) {
+                isProcessingEvents = false;
+                long ini = System.currentTimeMillis();
+                long remainingWaitTimeMs = waitTimeoutMs;
+                while (curActiveThreads > 0 && remainingWaitTimeMs > 0) {
+                    wait(1000);
+                    remainingWaitTimeMs = waitTimeoutMs - (System.currentTimeMillis() - ini);
+                }
+                remaining = curActiveThreads;
+            }
+            
+        } catch (InterruptedException ignore) {
+            log.info(String.format("%s: Stop sequence has been interrupted, remaining active threads = %d", svcName, curActiveThreads));
+        } finally {
+            if (remaining > 0) {
+                log.error(String.format("%s: Stop sequence completed with %d active remaing threads", svcName, curActiveThreads));
+            } else {
+                log.info(String.format("%s: Stop sequence completed with %d active remaing threads", svcName, curActiveThreads));                
+            }
+            curActiveThreads = 0;
+        }
+    }
+    
+    
+    @Override
+    public abstract int doProcessEvents();
+}
diff --git a/util/src/main/java/com/ning/billing/util/tag/api/DefaultTagDefinitionService.java b/util/src/main/java/com/ning/billing/util/tag/api/DefaultTagDefinitionService.java
index d24ac9a..5865540 100644
--- a/util/src/main/java/com/ning/billing/util/tag/api/DefaultTagDefinitionService.java
+++ b/util/src/main/java/com/ning/billing/util/tag/api/DefaultTagDefinitionService.java
@@ -21,6 +21,7 @@ import com.ning.billing.util.api.TagDefinitionService;
 import com.ning.billing.util.api.TagUserApi;
 
 public class DefaultTagDefinitionService implements TagDefinitionService {
+    
     private static final String TAG_DEFINITION_SERVICE_NAME = "tag-service";
     private final TagUserApi api;
 
diff --git a/util/src/main/java/com/ning/billing/util/tag/api/DefaultTagDefinitionUserApi.java b/util/src/main/java/com/ning/billing/util/tag/api/DefaultTagDefinitionUserApi.java
index 53d8841..680fb96 100644
--- a/util/src/main/java/com/ning/billing/util/tag/api/DefaultTagDefinitionUserApi.java
+++ b/util/src/main/java/com/ning/billing/util/tag/api/DefaultTagDefinitionUserApi.java
@@ -17,7 +17,9 @@
 package com.ning.billing.util.tag.api;
 
 import java.util.List;
+import java.util.UUID;
 
+import com.ning.billing.util.dao.ObjectType;
 import org.joda.time.DateTime;
 
 import com.google.inject.Inject;
@@ -46,8 +48,8 @@ public class DefaultTagDefinitionUserApi implements TagUserApi {
     }
 
     @Override
-    public TagDefinition create(final String name, final String description, final CallContext context) throws TagDefinitionApiException {
-        return dao.create(name, description, context);
+    public TagDefinition create(final String definitionName, final String description, final CallContext context) throws TagDefinitionApiException {
+        return dao.create(definitionName, description, context);
     }
 
     @Override
@@ -68,24 +70,44 @@ public class DefaultTagDefinitionUserApi implements TagUserApi {
 	}
 
     @Override
-    public Tag createControlTag(String controlTagName) throws TagDefinitionApiException {
-        ControlTagType type = null;
-        for(ControlTagType t : ControlTagType.values()) {
-            if(t.toString().equals(controlTagName)) {
-                type = t;
-            }
-        }
-        
-        if(type == null) {
-            throw new TagDefinitionApiException(ErrorCode.CONTROL_TAG_DOES_NOT_EXIST, controlTagName);
-        }
-        return new DefaultControlTag(type);
+    public List<Tag> createControlTags(UUID objectId, ObjectType objectType, List<TagDefinition> tagDefinitions) throws TagDefinitionApiException {
+        return null;  //To change body of implemented methods use File | Settings | File Templates.
     }
 
     @Override
-    public Tag createDescriptiveTag(String tagDefinitionName) throws TagDefinitionApiException {
-        TagDefinition tagDefinition = getTagDefinition(tagDefinitionName);
-        
-        return new DescriptiveTag(tagDefinition);
+    public Tag createControlTag(UUID objectId, ObjectType objectType, TagDefinition tagDefinition) throws TagDefinitionApiException {
+        return null;  //To change body of implemented methods use File | Settings | File Templates.
     }
+
+    @Override
+    public List<Tag> createDescriptiveTags(UUID objectId, ObjectType objectType, List<TagDefinition> tagDefinitions) throws TagDefinitionApiException {
+        return null;  //To change body of implemented methods use File | Settings | File Templates.
+    }
+
+    @Override
+    public Tag createDescriptiveTag(UUID objectId, ObjectType objectType, TagDefinition tagDefinition) throws TagDefinitionApiException {
+        return null;  //To change body of implemented methods use File | Settings | File Templates.
+    }
+
+//    @Override
+//    public Tag createControlTags(String controlTagName) throws TagDefinitionApiException {
+//        ControlTagType type = null;
+//        for(ControlTagType t : ControlTagType.values()) {
+//            if(t.toString().equals(controlTagName)) {
+//                type = t;
+//            }
+//        }
+//
+//        if(type == null) {
+//            throw new TagDefinitionApiException(ErrorCode.CONTROL_TAG_DOES_NOT_EXIST, controlTagName);
+//        }
+//        return new DefaultControlTag(type);
+//    }
+//
+//    @Override
+//    public Tag createDescriptiveTags(List) throws TagDefinitionApiException {
+//        TagDefinition tagDefinition = getTagDefinition(tagDefinitionName);
+//
+//        return new DescriptiveTag(tagDefinition);
+//    }
 }
diff --git a/util/src/main/java/com/ning/billing/util/tag/dao/AuditedTagDao.java b/util/src/main/java/com/ning/billing/util/tag/dao/AuditedTagDao.java
index 374e0a3..a9a2373 100644
--- a/util/src/main/java/com/ning/billing/util/tag/dao/AuditedTagDao.java
+++ b/util/src/main/java/com/ning/billing/util/tag/dao/AuditedTagDao.java
@@ -20,8 +20,14 @@ import com.google.inject.Inject;
 import com.ning.billing.ErrorCode;
 import com.ning.billing.invoice.api.InvoiceApiException;
 import com.ning.billing.util.ChangeType;
-import com.ning.billing.util.audit.dao.AuditSqlDao;
 import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.dao.AuditedCollectionDaoBase;
+import com.ning.billing.util.dao.EntityAudit;
+import com.ning.billing.util.dao.EntityHistory;
+import com.ning.billing.util.dao.Mapper;
+import com.ning.billing.util.dao.ObjectType;
+import com.ning.billing.util.dao.TableName;
+import com.ning.billing.util.entity.collection.dao.UpdatableEntityCollectionSqlDao;
 import com.ning.billing.util.tag.Tag;
 import org.skife.jdbi.v2.IDBI;
 import org.skife.jdbi.v2.Transaction;
@@ -29,11 +35,11 @@ import org.skife.jdbi.v2.TransactionStatus;
 import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
 
 import java.util.ArrayList;
-import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 import java.util.UUID;
 
-public class AuditedTagDao implements TagDao {
+public class AuditedTagDao extends AuditedCollectionDaoBase<Tag> implements TagDao {
     private final TagSqlDao tagSqlDao;
 
     @Inject
@@ -42,78 +48,31 @@ public class AuditedTagDao implements TagDao {
     }
 
     @Override
-    public void saveTags(final UUID objectId, final String objectType,
-                         final List<Tag> tags, final CallContext context) {
-        saveTagsFromTransaction(tagSqlDao, objectId, objectType, tags, context);
-    }
-
-    @Override
-    public void saveTagsFromTransaction(final Transmogrifier dao, final UUID objectId, final String objectType,
-                                        final List<Tag> tags, final CallContext context) {
-        TagSqlDao tagSqlDao = dao.become(TagSqlDao.class);
-
-        // get list of existing tags
-        List<Tag> existingTags = tagSqlDao.load(objectId.toString(), objectType);
-
-        // sort into tags to update (tagsToUpdate), tags to add (tags), and tags to delete (existingTags)
-        Iterator<Tag> tagIterator = tags.iterator();
-        while (tagIterator.hasNext()) {
-            Tag tag = tagIterator.next();
-
-            Iterator<Tag> existingTagIterator = existingTags.iterator();
-            while (existingTagIterator.hasNext()) {
-                Tag existingTag = existingTagIterator.next();
-                if (tag.getTagDefinitionName().equals(existingTag.getTagDefinitionName())) {
-                    // if the tags match, remove from both lists
-                    // in the case of tag, this just means the tag remains associated
-                    tagIterator.remove();
-                    existingTagIterator.remove();
-                }
-            }
-        }
-
-        tagSqlDao.batchInsertFromTransaction(objectId.toString(), objectType, tags, context);
-        tagSqlDao.batchDeleteFromTransaction(objectId.toString(), objectType, existingTags, context);
-
-        List<String> historyIdsForInsert = getIdList(tags.size());
-        tagSqlDao.batchInsertHistoryFromTransaction(objectId.toString(), objectType, historyIdsForInsert, tags, ChangeType.INSERT, context);
-        List<String> historyIdsForDelete = getIdList(existingTags.size());
-        tagSqlDao.batchInsertHistoryFromTransaction(objectId.toString(), objectType, historyIdsForDelete, existingTags, ChangeType.DELETE, context);
-
-        AuditSqlDao auditSqlDao = tagSqlDao.become(AuditSqlDao.class);
-        auditSqlDao.insertAuditFromTransaction("tag_history", historyIdsForInsert, ChangeType.INSERT, context);
-        auditSqlDao.insertAuditFromTransaction("tag_history", historyIdsForDelete, ChangeType.DELETE, context);
-    }
-
-    private List<String> getIdList(int size) {
-        List<String> results = new ArrayList<String>();
-        for (int i = 0; i < size; i++) {
-            results.add(UUID.randomUUID().toString());
-        }
-        return results;
-    }
-
-    @Override
-    public List<Tag> loadTags(final UUID objectId, final String objectType) {
-        return tagSqlDao.load(objectId.toString(), objectType);
-    }
-
-    @Override
-    public List<Tag> loadTagsFromTransaction(final Transmogrifier dao, final UUID objectId, final String objectType) {
-        TagSqlDao tagSqlDao = dao.become(TagSqlDao.class);
-        return tagSqlDao.load(objectId.toString(), objectType);
-    }
-
-    @Override
-    public void addTag(final String tagName, final UUID objectId, final String objectType, final CallContext context) {
+    public void addTag(final String tagName, final UUID objectId, final ObjectType objectType, final CallContext context) {
         tagSqlDao.inTransaction(new Transaction<Void, TagSqlDao>() {
             @Override
             public Void inTransaction(final TagSqlDao tagSqlDao, final TransactionStatus status) throws Exception {
                 String tagId = UUID.randomUUID().toString();
                 tagSqlDao.addTagFromTransaction(tagId, tagName, objectId.toString(), objectType, context);
 
-                TagAuditSqlDao auditDao = tagSqlDao.become(TagAuditSqlDao.class);
-                auditDao.addTagFromTransaction(tagId, context);
+                Tag tag = tagSqlDao.findTag(tagName, objectId.toString(), objectType);
+                List<Tag> tagList = new ArrayList<Tag>();
+                tagList.add(tag);
+
+                List<Mapper<UUID, Long>> recordIds = tagSqlDao.getRecordIds(objectId.toString(), objectType);
+                Map<UUID, Long> recordIdMap = convertToHistoryMap(recordIds);
+
+                List<EntityHistory<Tag>> entityHistories = new ArrayList<EntityHistory<Tag>>();
+                entityHistories.addAll(convertToHistory(tagList, recordIdMap, ChangeType.INSERT));
+
+                Long maxHistoryRecordId = tagSqlDao.getMaxHistoryRecordId();
+                tagSqlDao.addHistoryFromTransaction(objectId.toString(), objectType, entityHistories, context);
+
+                // have to fetch history record ids to update audit log
+                List<Mapper<Long, Long>> historyRecordIds = tagSqlDao.getHistoryRecordIds(maxHistoryRecordId);
+                Map<Long, Long> historyRecordIdMap = convertToAuditMap(historyRecordIds);
+                List<EntityAudit> entityAudits = convertToAudits(entityHistories, historyRecordIdMap);
+                tagSqlDao.insertAuditFromTransaction(entityAudits, context);
 
                 return null;
             }
@@ -121,7 +80,7 @@ public class AuditedTagDao implements TagDao {
     }
 
     @Override
-    public void removeTag(final String tagName, final UUID objectId, final String objectType, final CallContext context) {
+    public void removeTag(final String tagName, final UUID objectId, final ObjectType objectType, final CallContext context) {
         tagSqlDao.inTransaction(new Transaction<Void, TagSqlDao>() {
             @Override
             public Void inTransaction(final TagSqlDao tagSqlDao, final TransactionStatus status) throws Exception {
@@ -131,13 +90,43 @@ public class AuditedTagDao implements TagDao {
                     throw new InvoiceApiException(ErrorCode.TAG_DOES_NOT_EXIST, tagName);
                 }
 
-                tagSqlDao.removeTagFromTransaction(tagName, objectId.toString(), objectType, context);
+                List<Tag> tagList = new ArrayList<Tag>();
+                tagList.add(tag);
+
+                List<Mapper<UUID, Long>> recordIds = tagSqlDao.getRecordIds(objectId.toString(), objectType);
+                Map<UUID, Long> recordIdMap = convertToHistoryMap(recordIds);
+
+                tagSqlDao.deleteFromTransaction(objectId.toString(), objectType, tagList, context);
+
+                List<EntityHistory<Tag>> entityHistories = new ArrayList<EntityHistory<Tag>>();
+                entityHistories.addAll(convertToHistory(tagList, recordIdMap, ChangeType.DELETE));
+
+                Long maxHistoryRecordId = tagSqlDao.getMaxHistoryRecordId();
+                tagSqlDao.addHistoryFromTransaction(objectId.toString(), objectType, entityHistories, context);
 
-                TagAuditSqlDao auditDao = tagSqlDao.become(TagAuditSqlDao.class);
-                auditDao.removeTagFromTransaction(tag.getId().toString(), context);
+                // have to fetch history record ids to update audit log
+                List<Mapper<Long, Long>> historyRecordIds = tagSqlDao.getHistoryRecordIds(maxHistoryRecordId);
+                Map<Long, Long> historyRecordIdMap = convertToAuditMap(historyRecordIds);
+                List<EntityAudit> entityAudits = convertToAudits(entityHistories, historyRecordIdMap);
+                tagSqlDao.insertAuditFromTransaction(entityAudits, context);
 
                 return null;
             }
         });
     }
+
+    @Override
+    protected TableName getTableName() {
+        return TableName.TAG_HISTORY;
+    }
+
+    @Override
+    protected UpdatableEntityCollectionSqlDao<Tag> transmogrifyDao(Transmogrifier transactionalDao) {
+        return transactionalDao.become(TagSqlDao.class);
+    }
+
+    @Override
+    protected UpdatableEntityCollectionSqlDao<Tag> getSqlDao() {
+        return tagSqlDao;
+    }
 }
diff --git a/util/src/main/java/com/ning/billing/util/tag/dao/DefaultTagDefinitionDao.java b/util/src/main/java/com/ning/billing/util/tag/dao/DefaultTagDefinitionDao.java
index 884dacd..1b6ce2a 100644
--- a/util/src/main/java/com/ning/billing/util/tag/dao/DefaultTagDefinitionDao.java
+++ b/util/src/main/java/com/ning/billing/util/tag/dao/DefaultTagDefinitionDao.java
@@ -44,7 +44,7 @@ public class DefaultTagDefinitionDao implements TagDefinitionDao {
 
         // add control tag definitions
         for (ControlTagType controlTag : ControlTagType.values()) {
-            definitionList.add(new DefaultTagDefinition(controlTag.toString(), controlTag.getDescription()));
+            definitionList.add(new DefaultTagDefinition(controlTag.toString(), controlTag.getDescription(), true));
         }
 
         return definitionList;
@@ -68,7 +68,7 @@ public class DefaultTagDefinitionDao implements TagDefinitionDao {
             throw new TagDefinitionApiException(ErrorCode.TAG_DEFINITION_ALREADY_EXISTS, definitionName);
         }
 
-        TagDefinition definition = new DefaultTagDefinition(definitionName, description);
+        TagDefinition definition = new DefaultTagDefinition(definitionName, description, false);
         dao.create(definition, context);
         return definition;
     }
diff --git a/util/src/main/java/com/ning/billing/util/tag/dao/TagBinder.java b/util/src/main/java/com/ning/billing/util/tag/dao/TagBinder.java
index 2416b21..d2d4a6f 100644
--- a/util/src/main/java/com/ning/billing/util/tag/dao/TagBinder.java
+++ b/util/src/main/java/com/ning/billing/util/tag/dao/TagBinder.java
@@ -22,8 +22,6 @@ import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 
-import com.google.inject.Inject;
-import com.ning.billing.util.clock.Clock;
 import org.skife.jdbi.v2.SQLStatement;
 import org.skife.jdbi.v2.sqlobject.Binder;
 import org.skife.jdbi.v2.sqlobject.BinderFactory;
diff --git a/util/src/main/java/com/ning/billing/util/tag/dao/TagDao.java b/util/src/main/java/com/ning/billing/util/tag/dao/TagDao.java
index 11594ac..a680862 100644
--- a/util/src/main/java/com/ning/billing/util/tag/dao/TagDao.java
+++ b/util/src/main/java/com/ning/billing/util/tag/dao/TagDao.java
@@ -17,6 +17,8 @@
 package com.ning.billing.util.tag.dao;
 
 import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.dao.AuditedCollectionDao;
+import com.ning.billing.util.dao.ObjectType;
 import com.ning.billing.util.tag.ControlTagType;
 import com.ning.billing.util.tag.Tag;
 import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
@@ -24,16 +26,8 @@ import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
 import java.util.List;
 import java.util.UUID;
 
-public interface TagDao {
-    void saveTagsFromTransaction(Transmogrifier dao, UUID objectId, String objectType, List<Tag> tags, CallContext context);
+public interface TagDao extends AuditedCollectionDao<Tag> {
+    void addTag(String tagName, UUID objectId, ObjectType objectType, CallContext context);
 
-    void saveTags(UUID objectId, String objectType, List<Tag> tags, CallContext context);
-
-    List<Tag> loadTags(UUID objectId, String objectType);
-
-    List<Tag> loadTagsFromTransaction(Transmogrifier dao, UUID objectId, String objectType);
-
-    void addTag(String tagName, UUID objectId, String objectType, CallContext context);
-
-    void removeTag(String tagName, UUID objectId, String objectType, CallContext context);
+    void removeTag(String tagName, UUID objectId, ObjectType objectType, CallContext context);
 }
diff --git a/util/src/main/java/com/ning/billing/util/tag/dao/TagDefinitionSqlDao.java b/util/src/main/java/com/ning/billing/util/tag/dao/TagDefinitionSqlDao.java
index 1308ae1..efe7587 100644
--- a/util/src/main/java/com/ning/billing/util/tag/dao/TagDefinitionSqlDao.java
+++ b/util/src/main/java/com/ning/billing/util/tag/dao/TagDefinitionSqlDao.java
@@ -27,10 +27,9 @@ import java.util.UUID;
 
 import com.ning.billing.util.callcontext.CallContext;
 import com.ning.billing.util.callcontext.CallContextBinder;
-import com.ning.billing.util.entity.EntityDao;
+import com.ning.billing.util.entity.dao.EntitySqlDao;
 import com.ning.billing.util.tag.DefaultTagDefinition;
 import com.ning.billing.util.tag.TagDefinition;
-import org.joda.time.DateTime;
 import org.skife.jdbi.v2.SQLStatement;
 import org.skife.jdbi.v2.StatementContext;
 import org.skife.jdbi.v2.sqlobject.Bind;
@@ -45,7 +44,7 @@ import org.skife.jdbi.v2.tweak.ResultSetMapper;
 
 @ExternalizedSqlViaStringTemplate3
 @RegisterMapper(TagDefinitionSqlDao.TagDefinitionMapper.class)
-public interface TagDefinitionSqlDao extends EntityDao<TagDefinition> {
+public interface TagDefinitionSqlDao extends EntitySqlDao<TagDefinition> {
     @Override
     @SqlUpdate
     public void create(@TagDefinitionBinder final TagDefinition entity, @CallContextBinder final CallContext context);
@@ -68,9 +67,7 @@ public interface TagDefinitionSqlDao extends EntityDao<TagDefinition> {
             UUID id = UUID.fromString(result.getString("id"));
             String name = result.getString("name");
             String description = result.getString("description");
-            String createdBy = result.getString("created_by");
-            DateTime createdDate = new DateTime(result.getTimestamp("created_date"));
-            return new DefaultTagDefinition(id, createdBy, createdDate, name, description);
+            return new DefaultTagDefinition(id, name, description);
         }
     }
 
diff --git a/util/src/main/java/com/ning/billing/util/tag/dao/TagHistoryBinder.java b/util/src/main/java/com/ning/billing/util/tag/dao/TagHistoryBinder.java
new file mode 100644
index 0000000..67bb3ca
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/tag/dao/TagHistoryBinder.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2010-2011 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.util.tag.dao;
+
+import com.ning.billing.util.dao.EntityHistory;
+import com.ning.billing.util.dao.MappedEntity;
+import com.ning.billing.util.tag.Tag;
+import org.skife.jdbi.v2.SQLStatement;
+import org.skife.jdbi.v2.sqlobject.Binder;
+import org.skife.jdbi.v2.sqlobject.BinderFactory;
+import org.skife.jdbi.v2.sqlobject.BindingAnnotation;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@BindingAnnotation(TagHistoryBinder.TagHistoryBinderFactory.class)
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.PARAMETER})
+public @interface TagHistoryBinder {
+    public static class TagHistoryBinderFactory implements BinderFactory {
+        @Override
+        public Binder build(Annotation annotation) {
+            return new Binder<TagHistoryBinder, EntityHistory<Tag>>() {
+                @Override
+                public void bind(SQLStatement q, TagHistoryBinder bind, EntityHistory<Tag> tagHistory) {
+                    q.bind("recordId", tagHistory.getValue());
+                    q.bind("changeType", tagHistory.getChangeType().toString());
+                    q.bind("id", tagHistory.getId().toString());
+                    q.bind("tagDefinitionName", tagHistory.getEntity().getTagDefinitionName());
+                }
+            };
+        }
+    }
+}
\ No newline at end of file
diff --git a/util/src/main/java/com/ning/billing/util/tag/dao/TagMapper.java b/util/src/main/java/com/ning/billing/util/tag/dao/TagMapper.java
index 211ae5a..986e49d 100644
--- a/util/src/main/java/com/ning/billing/util/tag/dao/TagMapper.java
+++ b/util/src/main/java/com/ning/billing/util/tag/dao/TagMapper.java
@@ -44,10 +44,8 @@ public class TagMapper extends MapperBase implements ResultSetMapper<Tag> {
 
         if (thisTagType == null) {
             UUID id = UUID.fromString(result.getString("id"));
-            String createdBy = result.getString("created_by");
-            DateTime createdDate = new DateTime(result.getTimestamp("created_date"));
 
-            return new DescriptiveTag(id, createdBy, createdDate, name);
+            return new DescriptiveTag(id, name);
         } else {
             return new DefaultControlTag(thisTagType);
         }
diff --git a/util/src/main/java/com/ning/billing/util/tag/dao/TagSqlDao.java b/util/src/main/java/com/ning/billing/util/tag/dao/TagSqlDao.java
index a20e757..ee62b60 100644
--- a/util/src/main/java/com/ning/billing/util/tag/dao/TagSqlDao.java
+++ b/util/src/main/java/com/ning/billing/util/tag/dao/TagSqlDao.java
@@ -18,10 +18,16 @@ package com.ning.billing.util.tag.dao;
 
 import java.util.List;
 
-import com.ning.billing.util.ChangeType;
 import com.ning.billing.util.callcontext.CallContext;
 import com.ning.billing.util.callcontext.CallContextBinder;
-import com.ning.billing.util.dao.ChangeTypeBinder;
+import com.ning.billing.util.dao.AuditBinder;
+import com.ning.billing.util.dao.EntityAudit;
+import com.ning.billing.util.dao.EntityHistory;
+import com.ning.billing.util.dao.ObjectType;
+import com.ning.billing.util.dao.ObjectTypeBinder;
+import com.ning.billing.util.dao.TableName;
+import com.ning.billing.util.dao.TableNameBinder;
+import com.ning.billing.util.entity.collection.dao.UpdatableEntityCollectionSqlDao;
 import org.skife.jdbi.v2.sqlobject.Bind;
 import org.skife.jdbi.v2.sqlobject.SqlBatch;
 import org.skife.jdbi.v2.sqlobject.SqlQuery;
@@ -30,49 +36,54 @@ import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
 import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
 import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
 import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
-import com.ning.billing.util.entity.EntityCollectionDao;
 import com.ning.billing.util.tag.Tag;
 
 @ExternalizedSqlViaStringTemplate3
 @RegisterMapper(TagMapper.class)
-public interface TagSqlDao extends EntityCollectionDao<Tag>, Transactional<TagSqlDao>, Transmogrifier {
+public interface TagSqlDao extends UpdatableEntityCollectionSqlDao<Tag>, Transactional<TagSqlDao>, Transmogrifier {
     @Override
     @SqlBatch(transactional=false)
-    public void batchInsertFromTransaction(@Bind("objectId") final String objectId,
-                                           @Bind("objectType") final String objectType,
-                                           @TagBinder final List<Tag> entities,
-                                           @CallContextBinder final CallContext context);
+    public void insertFromTransaction(@Bind("objectId") final String objectId,
+                                      @ObjectTypeBinder final ObjectType objectType,
+                                      @TagBinder final List<Tag> tags,
+                                      @CallContextBinder final CallContext context);
+
+    @Override
+    @SqlBatch(transactional=false)
+    public void updateFromTransaction(@Bind("objectId") final String objectId,
+                                      @ObjectTypeBinder final ObjectType objectType,
+                                      @TagBinder final List<Tag> tags,
+                                      @CallContextBinder final CallContext context);
 
     @Override
     @SqlBatch(transactional=false)
-    public void batchDeleteFromTransaction(@Bind("objectId") final String objectId,
-                                           @Bind("objectType") final String objectType,
-                                           @TagBinder final List<Tag> entities,
-                                           @CallContextBinder final CallContext context);
+    public void deleteFromTransaction(@Bind("objectId") final String objectId,
+                                      @ObjectTypeBinder final ObjectType objectType,
+                                      @TagBinder final List<Tag> tags,
+                                      @CallContextBinder final CallContext context);
 
-    @SqlBatch(transactional = false)
-    public void batchInsertHistoryFromTransaction(@Bind("objectId") final String objectId,
-                                                  @Bind("objectType") final String objectType,
-                                                  @Bind("historyRecordId") final List<String> historyRecordIdList,
-                                                  @TagBinder final List<Tag> tags,
-                                                  @ChangeTypeBinder final ChangeType changeType,
-                                                  @CallContextBinder final CallContext context);
+    @Override
+    @SqlBatch(transactional=false)
+    public void addHistoryFromTransaction(@Bind("objectId") final String objectId,
+                                               @ObjectTypeBinder final ObjectType objectType,
+                                               @TagHistoryBinder final List<EntityHistory<Tag>> histories,
+                                               @CallContextBinder final CallContext context);
 
     @SqlUpdate
     public void addTagFromTransaction(@Bind("id") final String tagId,
                                       @Bind("tagDefinitionName") final String tagName,
                                       @Bind("objectId") final String objectId,
-                                      @Bind("objectType") final String objectType,
+                                      @ObjectTypeBinder final ObjectType objectType,
                                       @CallContextBinder final CallContext context);
 
     @SqlUpdate
     public void removeTagFromTransaction(@Bind("tagDefinitionName") final String tagName,
                                          @Bind("objectId") final String objectId,
-                                         @Bind("objectType") final String objectType,
+                                         @ObjectTypeBinder final ObjectType objectType,
                                          @CallContextBinder final CallContext context);
 
     @SqlQuery
     public Tag findTag(@Bind("tagDefinitionName") final String tagName,
                        @Bind("objectId") final String objectId,
-                       @Bind("objectType") final String objectType);
+                       @ObjectTypeBinder final ObjectType objectType);
 }
\ No newline at end of file
diff --git a/util/src/main/java/com/ning/billing/util/tag/DefaultControlTag.java b/util/src/main/java/com/ning/billing/util/tag/DefaultControlTag.java
index 662de60..f41fe12 100644
--- a/util/src/main/java/com/ning/billing/util/tag/DefaultControlTag.java
+++ b/util/src/main/java/com/ning/billing/util/tag/DefaultControlTag.java
@@ -17,7 +17,6 @@
 package com.ning.billing.util.tag;
 
 import java.util.UUID;
-import org.joda.time.DateTime;
 
 public class DefaultControlTag extends DescriptiveTag implements ControlTag {
     private final ControlTagType controlTagType;
@@ -29,9 +28,8 @@ public class DefaultControlTag extends DescriptiveTag implements ControlTag {
     }
 
     // use to hydrate objects when loaded from the persistence layer
-    public DefaultControlTag(final UUID id, final String createdBy,
-                             final DateTime createdDate, final ControlTagType controlTagType) {
-        super(id, createdBy, createdDate, controlTagType.toString());
+    public DefaultControlTag(final UUID id, final ControlTagType controlTagType) {
+        super(id, controlTagType.toString());
         this.controlTagType = controlTagType;
     }
 
@@ -39,4 +37,21 @@ public class DefaultControlTag extends DescriptiveTag implements ControlTag {
     public ControlTagType getControlTagType() {
         return controlTagType;
     }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        DefaultControlTag that = (DefaultControlTag) o;
+
+        if (controlTagType != that.controlTagType) return false;
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        return controlTagType != null ? controlTagType.hashCode() : 0;
+    }
 }
diff --git a/util/src/main/java/com/ning/billing/util/tag/DefaultTagDefinition.java b/util/src/main/java/com/ning/billing/util/tag/DefaultTagDefinition.java
index 0eb0ac9..c16a621 100644
--- a/util/src/main/java/com/ning/billing/util/tag/DefaultTagDefinition.java
+++ b/util/src/main/java/com/ning/billing/util/tag/DefaultTagDefinition.java
@@ -18,20 +18,21 @@ package com.ning.billing.util.tag;
 
 import java.util.UUID;
 import com.ning.billing.util.entity.EntityBase;
-import org.joda.time.DateTime;
 
 public class DefaultTagDefinition extends EntityBase implements TagDefinition {
     private String name;
     private String description;
+    private Boolean isControlTag;
 
-    public DefaultTagDefinition(String name, String description) {
+    public DefaultTagDefinition(String name, String description, Boolean isControlTag) {
         super();
         this.name = name;
         this.description = description;
+        this.isControlTag = isControlTag;
     }
 
-    public DefaultTagDefinition(UUID id, String createdBy, DateTime createdDate, String name, String description) {
-        super(id, createdBy, createdDate);
+    public DefaultTagDefinition(UUID id, String name, String description) {
+        super(id);
         this.name = name;
         this.description = description;
     }
@@ -45,4 +46,9 @@ public class DefaultTagDefinition extends EntityBase implements TagDefinition {
     public String getDescription() {
         return description;
     }
+
+    @Override
+    public Boolean isControlTag() {
+        return isControlTag;
+    }
 }
diff --git a/util/src/main/java/com/ning/billing/util/tag/DefaultTagStore.java b/util/src/main/java/com/ning/billing/util/tag/DefaultTagStore.java
index eace017..a35b424 100644
--- a/util/src/main/java/com/ning/billing/util/tag/DefaultTagStore.java
+++ b/util/src/main/java/com/ning/billing/util/tag/DefaultTagStore.java
@@ -17,10 +17,12 @@
 package com.ning.billing.util.tag;
 
 import java.util.UUID;
-import com.ning.billing.util.entity.EntityCollectionBase;
+
+import com.ning.billing.util.dao.ObjectType;
+import com.ning.billing.util.entity.collection.EntityCollectionBase;
 
 public class DefaultTagStore extends EntityCollectionBase<Tag> implements TagStore {
-    public DefaultTagStore(final UUID objectId, final String objectType) {
+    public DefaultTagStore(final UUID objectId, final ObjectType objectType) {
         super(objectId, objectType);
     }
 
@@ -32,7 +34,7 @@ public class DefaultTagStore extends EntityCollectionBase<Tag> implements TagSto
     @Override
     /***
      * Collates the contents of the TagStore to determine if payments should be processed
-     * @return true is no tags contraindicate payment processing
+     * @return true if no tags contraindicate payment processing
      */
     public boolean processPayment() {
         for (Tag tag : entities.values()) {
@@ -49,7 +51,7 @@ public class DefaultTagStore extends EntityCollectionBase<Tag> implements TagSto
 
     /***
      * Collates the contents of the TagStore to determine if invoices should be generated
-     * @return true is no tags contraindicate invoice generation
+     * @return true if no tags contraindicate invoice generation
      */
     @Override
     public boolean generateInvoice() {
@@ -66,18 +68,30 @@ public class DefaultTagStore extends EntityCollectionBase<Tag> implements TagSto
     }
 
     @Override
-    public void remove(final String tagName) {
-        entities.remove(entities.get(tagName));
+    public boolean containsTagForDefinition(final TagDefinition tagDefinition) {
+        for (Tag tag : entities.values()) {
+            if (tag.getTagDefinitionName().equals(tagDefinition.getName())) {
+                return true;
+            }
+        }
+
+        return false;
     }
 
     @Override
-    public boolean containsTag(final String tagName) {
+    public boolean containsTagForControlTagType(final ControlTagType controlTagType) {
         for (Tag tag : entities.values()) {
-            if (tag.getTagDefinitionName().equals(tagName)) {
+            if (tag.getTagDefinitionName().equals(controlTagType.toString())) {
                 return true;
             }
         }
 
         return false;
     }
+
+    @Override
+    public Tag remove(TagDefinition tagDefinition) {
+        Tag tag = entities.get(tagDefinition.getName());
+        return (tag == null) ? null : entities.remove(tag);
+    }
 }
\ No newline at end of file
diff --git a/util/src/main/java/com/ning/billing/util/tag/DescriptiveTag.java b/util/src/main/java/com/ning/billing/util/tag/DescriptiveTag.java
index 1ad70fe..2633643 100644
--- a/util/src/main/java/com/ning/billing/util/tag/DescriptiveTag.java
+++ b/util/src/main/java/com/ning/billing/util/tag/DescriptiveTag.java
@@ -18,21 +18,14 @@ package com.ning.billing.util.tag;
 
 import java.util.UUID;
 
-import com.google.inject.Inject;
-import com.ning.billing.util.clock.Clock;
 import com.ning.billing.util.entity.EntityBase;
-import com.ning.billing.util.entity.UpdatableEntityBase;
-import org.joda.time.DateTime;
 
 public class DescriptiveTag extends EntityBase implements Tag {
     private final String tagDefinitionName;
 
-    @Inject
-    private Clock clock;
-
     // use to hydrate objects from the persistence layer
-    public DescriptiveTag(UUID id, String createdBy, DateTime createdDate, String tagDefinitionName) {
-        super(id, createdBy, createdDate);
+    public DescriptiveTag(UUID id, String tagDefinitionName) {
+        super(id);
         this.tagDefinitionName = tagDefinitionName;
     }
 
@@ -52,4 +45,22 @@ public class DescriptiveTag extends EntityBase implements Tag {
     public String getTagDefinitionName() {
         return tagDefinitionName;
     }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        DescriptiveTag that = (DescriptiveTag) o;
+
+        if (tagDefinitionName != null ? !tagDefinitionName.equals(that.tagDefinitionName) : that.tagDefinitionName != null)
+            return false;
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        return tagDefinitionName != null ? tagDefinitionName.hashCode() : 0;
+    }
 }
diff --git a/util/src/main/java/com/ning/billing/util/template/translation/DefaultCatalogTranslator.java b/util/src/main/java/com/ning/billing/util/template/translation/DefaultCatalogTranslator.java
new file mode 100644
index 0000000..933b24f
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/template/translation/DefaultCatalogTranslator.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2010-2011 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.util.template.translation;
+
+import com.google.inject.Inject;
+
+public class DefaultCatalogTranslator extends DefaultTranslatorBase {
+    @Inject
+    public DefaultCatalogTranslator(TranslatorConfig config) {
+        super(config);
+    }
+
+    @Override
+    protected String getBundlePath() {
+        return "com/ning/billing/util/template/translation/CatalogTranslation";
+    }
+
+    @Override
+    protected String getTranslationType() {
+        return "catalog";
+    }
+}
diff --git a/util/src/main/java/com/ning/billing/util/template/translation/DefaultTranslatorBase.java b/util/src/main/java/com/ning/billing/util/template/translation/DefaultTranslatorBase.java
new file mode 100644
index 0000000..4d1a7b6
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/template/translation/DefaultTranslatorBase.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2010-2011 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.util.template.translation;
+
+import com.google.inject.Inject;
+import com.ning.billing.ErrorCode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Locale;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+
+public abstract class DefaultTranslatorBase implements Translator {
+    protected final TranslatorConfig config;
+    protected final Logger log = LoggerFactory.getLogger(DefaultTranslatorBase.class);
+
+    @Inject
+    public DefaultTranslatorBase(TranslatorConfig config) {
+        this.config = config;
+    }
+
+    protected abstract String getBundlePath();
+
+    /*
+     * string used for exception handling
+     */
+    protected abstract String getTranslationType();
+
+    @Override
+    public String getTranslation(Locale locale, String originalText) {
+        ResourceBundle bundle = null;
+        try {
+            bundle = ResourceBundle.getBundle(getBundlePath(), locale);
+        } catch (MissingResourceException mrex) {
+            log.warn(String.format(ErrorCode.MISSING_TRANSLATION_RESOURCE.toString(), getTranslationType()));
+        }
+
+        if ((bundle != null) && (bundle.containsKey(originalText))) {
+            return bundle.getString(originalText);
+        } else {
+            try {
+                Locale defaultLocale = new Locale(config.getDefaultLocale());
+                bundle = ResourceBundle.getBundle(getBundlePath(), defaultLocale);
+
+                if ((bundle != null) && (bundle.containsKey(originalText))) {
+                    return bundle.getString(originalText);
+                } else {
+                    return originalText;
+                }
+            } catch (MissingResourceException mrex) {
+                log.warn(String.format(ErrorCode.MISSING_TRANSLATION_RESOURCE.toString(), getTranslationType()));
+                return originalText;
+            }
+        }
+    }
+}
diff --git a/util/src/main/java/com/ning/billing/util/userrequest/CompletionUserRequestBase.java b/util/src/main/java/com/ning/billing/util/userrequest/CompletionUserRequestBase.java
new file mode 100644
index 0000000..03c643d
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/userrequest/CompletionUserRequestBase.java
@@ -0,0 +1,156 @@
+/* 
+ * Copyright 2010-2011 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.util.userrequest;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.TimeoutException;
+
+import com.ning.billing.account.api.AccountChangeEvent;
+import com.ning.billing.account.api.AccountCreationEvent;
+import com.ning.billing.entitlement.api.user.SubscriptionEvent;
+import com.ning.billing.invoice.api.EmptyInvoiceEvent;
+import com.ning.billing.invoice.api.InvoiceCreationEvent;
+import com.ning.billing.payment.api.PaymentErrorEvent;
+import com.ning.billing.payment.api.PaymentInfoEvent;
+import com.ning.billing.util.bus.BusEvent;
+
+public abstract class CompletionUserRequestBase implements CompletionUserRequest {
+
+    private static final long NANO_TO_MILLI_SEC = (1000L * 1000L);
+
+    private final List<BusEvent> events;
+
+    private final UUID userToken;
+    private long timeoutMilliSec;
+
+    private boolean isCompleted;
+    private long initialTimeMilliSec;
+
+
+    public CompletionUserRequestBase(final UUID userToken) {
+        this.events = new LinkedList<BusEvent>();
+        this.userToken = userToken;
+        this.isCompleted = false;
+    }
+
+    @Override
+    public List<BusEvent> waitForCompletion(final long timeoutMilliSec) throws InterruptedException, TimeoutException {
+
+        this.timeoutMilliSec = timeoutMilliSec;
+        initialTimeMilliSec = currentTimeMillis();
+        synchronized(this) {
+            long remainingTimeMillisSec = getRemainingTimeMillis();
+            while (!isCompleted && remainingTimeMillisSec > 0) {
+                wait(remainingTimeMillisSec);
+                if (isCompleted) {
+                    break;
+                }
+                remainingTimeMillisSec = getRemainingTimeMillis();
+            }
+            if (!isCompleted) {
+                throw new TimeoutException();
+            }
+        }
+        return events;
+    }
+
+    @Override
+    public void notifyForCompletion() {
+        synchronized(this) {
+            isCompleted = true;
+            notify();
+        }
+    }
+
+    private long currentTimeMillis() {
+        return System.nanoTime() / NANO_TO_MILLI_SEC;
+    }
+
+    private long getRemainingTimeMillis() {
+        return timeoutMilliSec - (currentTimeMillis() - initialTimeMilliSec);
+    }
+
+    @Override
+    public void onBusEvent(BusEvent curEvent) {
+        // Check if this is for us..
+        if (curEvent.getUserToken() == null ||
+                ! curEvent.getUserToken().equals(userToken)) {
+            return;
+        }
+        
+        events.add(curEvent);
+        
+        switch(curEvent.getBusEventType()) {
+        case ACCOUNT_CREATE:
+            onAccountCreation((AccountCreationEvent) curEvent);
+            break;
+        case ACCOUNT_CHANGE:
+            onAccountChange((AccountChangeEvent) curEvent);
+            break;
+        case SUBSCRIPTION_TRANSITION:
+            onSubscriptionTransition((SubscriptionEvent) curEvent);
+            break;
+        case INVOICE_EMPTY:
+            onEmptyInvoice((EmptyInvoiceEvent) curEvent);
+            break;
+        case INVOICE_CREATION:
+            onInvoiceCreation((InvoiceCreationEvent) curEvent);
+            break;
+        case PAYMENT_INFO:
+            onPaymentInfo((PaymentInfoEvent) curEvent);
+            break;
+        case PAYMENT_ERROR:
+            onPaymentError((PaymentErrorEvent) curEvent);
+            break;
+        default:
+            throw new RuntimeException("Unexpected event type " + curEvent.getBusEventType());
+        }
+    }
+
+    /*
+     * 
+     * Default no-op implementation so as to not have to implement all callbacks
+     */
+    @Override
+    public void onAccountCreation(final AccountCreationEvent curEvent) {
+    }
+
+    @Override
+    public void onAccountChange(final AccountChangeEvent curEvent) {
+    }
+
+    @Override
+    public void onSubscriptionTransition(final SubscriptionEvent curEvent) {
+    }
+
+    @Override
+    public void onEmptyInvoice(final EmptyInvoiceEvent curEvent) {
+    }
+    
+    @Override
+    public void onInvoiceCreation(final InvoiceCreationEvent curEvent) {
+    }
+
+    @Override
+    public void onPaymentInfo(final PaymentInfoEvent curEvent) {
+    }
+
+    @Override
+    public void onPaymentError(final PaymentErrorEvent curEvent) {
+    }
+}
diff --git a/util/src/main/resources/com/ning/billing/util/audit/dao/AuditSqlDao.sql.stg b/util/src/main/resources/com/ning/billing/util/audit/dao/AuditSqlDao.sql.stg
index 25cc9d4..f949ba8 100644
--- a/util/src/main/resources/com/ning/billing/util/audit/dao/AuditSqlDao.sql.stg
+++ b/util/src/main/resources/com/ning/billing/util/audit/dao/AuditSqlDao.sql.stg
@@ -7,10 +7,11 @@ fields(prefix) ::= <<
     <prefix>change_date,
     <prefix>changed_by,
     <prefix>reason_code,
-    <prefix>comments
+    <prefix>comments,
+    <prefix>user_token
 >>
 
 insertAuditFromTransaction() ::= <<
     INSERT INTO audit_log(<fields()>)
-    VALUES(:tableName, :recordId, :changeType, :createdDate, :userName, NULL, NULL);
+    VALUES(:tableName, :recordId, :changeType, :createdDate, :userName, NULL, NULL, :userToken);
 >>
\ No newline at end of file
diff --git a/util/src/main/resources/com/ning/billing/util/bus/dao/PersistentBusSqlDao.sql.stg b/util/src/main/resources/com/ning/billing/util/bus/dao/PersistentBusSqlDao.sql.stg
new file mode 100644
index 0000000..0cbd4ec
--- /dev/null
+++ b/util/src/main/resources/com/ning/billing/util/bus/dao/PersistentBusSqlDao.sql.stg
@@ -0,0 +1,88 @@
+group PersistentBusSqlDao;
+          
+getNextBusEventEntry() ::= <<
+    select
+      record_id
+      , class_name
+      , event_json
+      , created_date
+      , creating_owner
+      , processing_owner
+      , processing_available_date
+      , processing_state
+    from bus_events
+    where
+      processing_state != 'PROCESSED'
+      and processing_state != 'REMOVED'
+      and (processing_owner IS NULL OR processing_available_date \<= :now)
+    order by
+      record_id asc
+    limit :max
+    ;
+>>
+
+
+claimBusEvent() ::= <<
+    update bus_events
+    set
+      processing_owner = :owner
+      , processing_available_date = :nextAvailable
+      , processing_state = 'IN_PROCESSING'
+    where
+      record_id = :recordId
+      and processing_state != 'PROCESSED'
+      and processing_state != 'REMOVED'
+      and (processing_owner IS NULL OR processing_available_date \<= :now)
+    ;
+>>
+
+clearBusEvent() ::= <<
+    update bus_events
+    set
+      processing_state = 'PROCESSED'
+    where
+      record_id = :recordId
+    ;
+>>
+
+removeBusEventsById() ::= <<
+    update bus_events
+    set
+      processing_state = 'REMOVED'
+    where
+      record_id = :recordId
+    ;
+>>
+
+
+insertBusEvent() ::= <<
+    insert into bus_events (
+      class_name
+    , event_json
+    , created_date
+    , creating_owner
+    , processing_owner
+    , processing_available_date
+    , processing_state
+    ) values (
+      :className
+    , :eventJson
+    , :createdDate
+    , :creatingOwner
+    , :processingOwner
+    , :processingAvailableDate
+    , :processingState
+    );   
+>>
+
+insertClaimedHistory() ::= <<
+    insert into claimed_bus_events (
+          owner_id
+        , claimed_date
+        , bus_event_id
+      ) values (
+          :ownerId
+        , :claimedDate
+        , :busEventId
+      );
+>>
diff --git a/util/src/main/resources/com/ning/billing/util/customfield/dao/CustomFieldSqlDao.sql.stg b/util/src/main/resources/com/ning/billing/util/customfield/dao/CustomFieldSqlDao.sql.stg
index 1fbf994..018e1ec 100644
--- a/util/src/main/resources/com/ning/billing/util/customfield/dao/CustomFieldSqlDao.sql.stg
+++ b/util/src/main/resources/com/ning/billing/util/customfield/dao/CustomFieldSqlDao.sql.stg
@@ -1,17 +1,17 @@
 group CustomFieldSqlDao;
 
-batchInsertFromTransaction() ::= <<
+insertFromTransaction() ::= <<
     INSERT INTO custom_fields(id, object_id, object_type, field_name, field_value, created_by, created_date, updated_by, updated_date)
     VALUES (:id, :objectId, :objectType, :fieldName, :fieldValue, :userName, :createdDate, :userName, :updatedDate);
 >>
 
-batchUpdateFromTransaction() ::= <<
+updateFromTransaction() ::= <<
     UPDATE custom_fields
     SET field_value = :fieldValue, updated_by = :userName, updated_date = :updatedDate
     WHERE object_id = :objectId AND object_type = :objectType AND field_name = :fieldName;
 >>
 
-batchDeleteFromTransaction() ::= <<
+deleteFromTransaction() ::= <<
     DELETE FROM custom_fields
     WHERE object_id = :objectId AND object_type = :objectType AND field_name = :fieldName;
 >>
@@ -22,6 +22,56 @@ load() ::= <<
     WHERE object_id = :objectId AND object_type = :objectType;
 >>
 
+getRecordIds() ::= <<
+    SELECT record_id, id
+    FROM custom_fields
+    WHERE object_id = :objectId AND object_type = :objectType;
+>>
+
+historyFields(prefix) ::= <<
+  <prefix>record_id,
+  <prefix>id,
+  <prefix>object_id,
+  <prefix>object_type,
+  <prefix>field_name,
+  <prefix>field_value,
+  <prefix>updated_by,
+  <prefix>date,
+  <prefix>change_type
+>>
+
+addHistoryFromTransaction() ::= <<
+    INSERT INTO custom_field_history(<historyFields()>)
+    VALUES(:recordId, :id, :objectId, :objectType, :fieldName, :fieldValue, :userName, :updatedDate, :changeType);
+>>
+
+getMaxHistoryRecordId() ::= <<
+    SELECT MAX(history_record_id)
+    FROM custom_field_history;
+>>
+
+getHistoryRecordIds() ::= <<
+    SELECT history_record_id, record_id
+    FROM custom_field_history
+    WHERE history_record_id > :maxHistoryRecordId;
+>>
+
+auditFields(prefix) ::= <<
+    <prefix>table_name,
+    <prefix>record_id,
+    <prefix>change_type,
+    <prefix>change_date,
+    <prefix>changed_by,
+    <prefix>reason_code,
+    <prefix>comments,
+    <prefix>user_token
+>>
+
+insertAuditFromTransaction() ::= <<
+    INSERT INTO audit_log(<auditFields()>)
+    VALUES(:tableName, :recordId, :changeType, :createdDate, :userName, NULL, NULL, NULL);
+>>
+
 test() ::= <<
     SELECT 1 FROM custom_fields;
 >>
diff --git a/util/src/main/resources/com/ning/billing/util/ddl.sql b/util/src/main/resources/com/ning/billing/util/ddl.sql
index 77b66c9..9ea7c1a 100644
--- a/util/src/main/resources/com/ning/billing/util/ddl.sql
+++ b/util/src/main/resources/com/ning/billing/util/ddl.sql
@@ -1,120 +1,165 @@
 DROP TABLE IF EXISTS custom_fields;
 CREATE TABLE custom_fields (
-  id char(36) NOT NULL,
-  object_id char(36) NOT NULL,
-  object_type varchar(30) NOT NULL,
-  field_name varchar(30) NOT NULL,
-  field_value varchar(255),
-  created_by varchar(50) NOT NULL,
-  created_date datetime NOT NULL,
-  updated_by varchar(50) DEFAULT NULL,
-  updated_date datetime DEFAULT NULL,
-  PRIMARY KEY(id)
+    record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
+    id char(36) NOT NULL,
+    object_id char(36) NOT NULL,
+    object_type varchar(30) NOT NULL,
+    field_name varchar(30) NOT NULL,
+    field_value varchar(255),
+    created_by varchar(50) NOT NULL,
+    created_date datetime NOT NULL,
+    updated_by varchar(50) DEFAULT NULL,
+    updated_date datetime DEFAULT NULL,
+    PRIMARY KEY(record_id)
 ) ENGINE=innodb;
+CREATE UNIQUE INDEX custom_fields_id ON custom_fields(id);
 CREATE INDEX custom_fields_object_id_object_type ON custom_fields(object_id, object_type);
 CREATE UNIQUE INDEX custom_fields_unique ON custom_fields(object_id, object_type, field_name);
 
 DROP TABLE IF EXISTS custom_field_history;
 CREATE TABLE custom_field_history (
-  history_id char(36) NOT NULL,
-  id char(36) NOT NULL,
-  object_id char(36) NOT NULL,
-  object_type varchar(30) NOT NULL,
-  field_name varchar(30),
-  field_value varchar(255),
-  updated_by varchar(50) NOT NULL,
-  date datetime NOT NULL,
-  change_type char(6) NOT NULL
+    history_record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
+    record_id int(11) unsigned NOT NULL,
+    id char(36) NOT NULL,
+    object_id char(36) NOT NULL,
+    object_type varchar(30) NOT NULL,
+    field_name varchar(30),
+    field_value varchar(255),
+    updated_by varchar(50) NOT NULL,
+    date datetime NOT NULL,
+    change_type char(6) NOT NULL,
+    PRIMARY KEY(history_record_id)
 ) ENGINE=innodb;
+CREATE INDEX custom_field_history_record_id ON custom_field_history(record_id);
 CREATE INDEX custom_field_history_object_id_object_type ON custom_fields(object_id, object_type);
 
 DROP TABLE IF EXISTS tag_descriptions;
 DROP TABLE IF EXISTS tag_definitions;
 CREATE TABLE tag_definitions (
-  id char(36) NOT NULL,
-  name varchar(20) NOT NULL,
-  description varchar(200) NOT NULL,
-  created_by varchar(50) NOT NULL,
-  created_date datetime NOT NULL,
-  PRIMARY KEY(id)
+    record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
+    id char(36) NOT NULL,
+    name varchar(20) NOT NULL,
+    description varchar(200) NOT NULL,
+    created_by varchar(50) NOT NULL,
+    created_date datetime NOT NULL,
+    updated_by varchar(50) NOT NULL,
+    updated_date datetime NOT NULL,
+    PRIMARY KEY(record_id)
 ) ENGINE=innodb;
+CREATE UNIQUE INDEX tag_definitions_id ON tag_definitions(id);
 CREATE UNIQUE INDEX tag_definitions_name ON tag_definitions(name);
 
 DROP TABLE IF EXISTS tag_definition_history;
 CREATE TABLE tag_definition_history (
-  id char(36) NOT NULL,
-  name varchar(30) NOT NULL,
-  created_by varchar(50),
-  description varchar(200),
-  change_type char(6) NOT NULL,
-  updated_by varchar(50) NOT NULL,
-  date datetime NOT NULL
+    history_record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
+    record_id int(11) unsigned NOT NULL,
+    id char(36) NOT NULL,
+    name varchar(30) NOT NULL,
+    created_by varchar(50),
+    description varchar(200),
+    change_type char(6) NOT NULL,
+    updated_by varchar(50) NOT NULL,
+    date datetime NOT NULL,
+    PRIMARY KEY(history_record_id)
 ) ENGINE=innodb;
 CREATE INDEX tag_definition_history_id ON tag_definition_history(id);
+CREATE INDEX tag_definition_history_record_id ON tag_definition_history(record_id);
 CREATE INDEX tag_definition_history_name ON tag_definition_history(name);
 
 DROP TABLE IF EXISTS tags;
 CREATE TABLE tags (
-  id char(36) NOT NULL,
-  tag_definition_name varchar(20) NOT NULL,
-  object_id char(36) NOT NULL,
-  object_type varchar(30) NOT NULL,
-  created_by varchar(50) NOT NULL,
-  created_date datetime NOT NULL,
-  PRIMARY KEY(id)
+    record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
+    id char(36) NOT NULL,
+    tag_definition_name varchar(20) NOT NULL,
+    object_id char(36) NOT NULL,
+    object_type varchar(30) NOT NULL,
+    created_by varchar(50) NOT NULL,
+    created_date datetime NOT NULL,
+    PRIMARY KEY(record_id)
 ) ENGINE = innodb;
+CREATE UNIQUE INDEX tags_id ON tags(id);
 CREATE INDEX tags_by_object ON tags(object_id);
 CREATE UNIQUE INDEX tags_unique ON tags(tag_definition_name, object_id);
 
 DROP TABLE IF EXISTS tag_history;
 CREATE TABLE tag_history (
-  history_record_id char(36) NOT NULL,
-  id char(36) NOT NULL,
-  tag_definition_name varchar(20) NOT NULL,
-  object_id char(36) NOT NULL,
-  object_type varchar(30) NOT NULL,
-  change_type char(6) NOT NULL,
-  updated_by varchar(50) NOT NULL,
-  date datetime NOT NULL
+    history_record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
+    record_id int(11) unsigned NOT NULL,
+    id char(36) NOT NULL,
+    object_id char(36) NOT NULL,
+    object_type varchar(30) NOT NULL,
+    tag_definition_name varchar(20) NOT NULL,
+    updated_by varchar(50) NOT NULL,
+    date datetime NOT NULL,
+    change_type char(6) NOT NULL,
+    PRIMARY KEY(history_record_id)
 ) ENGINE = innodb;
+CREATE INDEX tag_history_record_id ON tag_history(record_id);
 CREATE INDEX tag_history_by_object ON tags(object_id);
 
 DROP TABLE IF EXISTS notifications;
 CREATE TABLE notifications (
-    id int(11) unsigned NOT NULL AUTO_INCREMENT,
-    notification_id char(36) NOT NULL,
-    created_dt datetime NOT NULL,
+    record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
+    id char(36) NOT NULL,
+    created_date datetime NOT NULL,
 	notification_key varchar(256) NOT NULL,
-    effective_dt datetime NOT NULL,
+	creating_owner char(50) NOT NULL,
+    effective_date datetime NOT NULL,
     queue_name char(64) NOT NULL,
     processing_owner char(50) DEFAULT NULL,
-    processing_available_dt datetime DEFAULT NULL,
+    processing_available_date datetime DEFAULT NULL,
     processing_state varchar(14) DEFAULT 'AVAILABLE',
-    PRIMARY KEY(id)
+    PRIMARY KEY(record_id)
 ) ENGINE=innodb;
-CREATE INDEX  `idx_comp_where` ON notifications (`effective_dt`, `queue_name`, `processing_state`,`processing_owner`,`processing_available_dt`);
-CREATE INDEX  `idx_update` ON notifications (`processing_state`,`processing_owner`,`processing_available_dt`);
-CREATE INDEX  `idx_get_ready` ON notifications (`effective_dt`,`created_dt`,`id`);
+CREATE UNIQUE INDEX notifications_id ON notifications(id);
+CREATE INDEX  `idx_comp_where` ON notifications (`effective_date`, `queue_name`, `processing_state`,`processing_owner`,`processing_available_date`);
+CREATE INDEX  `idx_update` ON notifications (`processing_state`,`processing_owner`,`processing_available_date`);
+CREATE INDEX  `idx_get_ready` ON notifications (`effective_date`,`created_date`,`id`);
 
 DROP TABLE IF EXISTS claimed_notifications;
 CREATE TABLE claimed_notifications (
-    id int(11) unsigned NOT NULL AUTO_INCREMENT,
-    sequence_id int(11) unsigned NOT NULL,
+    record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
     owner_id varchar(64) NOT NULL,
-    claimed_dt datetime NOT NULL,
+    claimed_date datetime NOT NULL,
     notification_id char(36) NOT NULL,
-    PRIMARY KEY(id)
+    PRIMARY KEY(record_id)
 ) ENGINE=innodb;
 
 DROP TABLE IF EXISTS audit_log;
 CREATE TABLE audit_log (
     id int(11) unsigned NOT NULL AUTO_INCREMENT,
     table_name varchar(50) NOT NULL,
-    record_id char(36) NOT NULL,
+    record_id int(11) NOT NULL,
     change_type char(6) NOT NULL,
     change_date datetime NOT NULL,
     changed_by varchar(50) NOT NULL,
     reason_code varchar(20) DEFAULT NULL,
     comments varchar(255) DEFAULT NULL,
+    user_token char(36),
     PRIMARY KEY(id)
 ) ENGINE=innodb;
+CREATE INDEX audit_log_fetch_record ON audit_log(table_name, record_id);
+CREATE INDEX audit_log_user_name ON audit_log(changed_by);
+
+DROP TABLE IF EXISTS bus_events;
+CREATE TABLE bus_events (
+    record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
+    class_name varchar(128) NOT NULL, 
+    event_json varchar(2048) NOT NULL,     
+    created_date datetime NOT NULL,
+    creating_owner char(50) NOT NULL,
+    processing_owner char(50) DEFAULT NULL,
+    processing_available_date datetime DEFAULT NULL,
+    processing_state varchar(14) DEFAULT 'AVAILABLE',
+    PRIMARY KEY(record_id)
+) ENGINE=innodb;
+CREATE INDEX  `idx_bus_where` ON bus_events (`processing_state`,`processing_owner`,`processing_available_date`);
+
+DROP TABLE IF EXISTS claimed_bus_events;
+CREATE TABLE claimed_bus_events (
+    record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
+    owner_id varchar(64) NOT NULL,
+    claimed_date datetime NOT NULL,
+    bus_event_id char(36) NOT NULL,
+    PRIMARY KEY(record_id)
+) ENGINE=innodb;
\ No newline at end of file
diff --git a/util/src/main/resources/com/ning/billing/util/email/templates/HtmlInvoiceTemplate.mustache b/util/src/main/resources/com/ning/billing/util/email/templates/HtmlInvoiceTemplate.mustache
new file mode 100644
index 0000000..73c70b3
--- /dev/null
+++ b/util/src/main/resources/com/ning/billing/util/email/templates/HtmlInvoiceTemplate.mustache
@@ -0,0 +1,96 @@
+<html>
+    <head>
+        <style type="text/css">
+            th {align=left; width=225px; border-bottom: solid 2px black;}
+        </style>
+    </head>
+    <body>
+        <h1>{{text.invoiceTitle}}</h1>
+        <table>
+            <tr>
+                <td rowspan=3 width=350px>Insert image here</td>
+                <td width=100px/>
+                <td width=225px/>
+                <td width=225px/>
+            </tr>
+            <tr>
+                <td />
+                <td align=right>{{text.invoiceDate}}</td>
+                <td>{{invoice.formattedInvoiceDate}}</td>
+            </tr>
+            <tr>
+                <td />
+                <td align=right>{{text.invoiceNumber}}</td>
+                <td>{{invoice.invoiceNumber}}</td>
+            </tr>
+            <tr>
+                <td>{{text.companyName}}</td>
+                <td></td>
+                <td align=right>{{text.accountOwnerName}}</td>
+                <td>{{account.name}}</td>
+            </tr>
+            <tr>
+                <td>{{text.companyAddress}}</td>
+                <td />
+                <td />
+                <td>{{account.email}}</td>
+            </tr>
+            <tr>
+                <td>{{text.companyCityProvincePostalCode}}</td>
+                <td />
+                <td />
+                <td>{{account.phone}}</td>
+            </tr>
+            <tr>
+                <td>{{text.companyCountry}}</td>
+                <td />
+                <td />
+                <td />
+            </tr>
+            <tr>
+                <td><{{text.companyUrl}}</td>
+                <td />
+                <td />
+                <td />
+            </tr>
+        </table>
+        <br />
+        <br />
+        <br />
+        <table>
+            <tr>
+                <th>{{text.invoiceItemBundleName}}</td>
+                <th>{{text.invoiceItemDescription}}</td>
+                <th>{{text.invoiceItemServicePeriod}}</td>
+                <th>{{text.invoiceItemAmount}}</td>
+            </tr>
+            {{#invoice.invoiceItems}}
+            <tr>
+                <td>{{description}}</td>
+                <td>{{planName}}</td>
+                <td>{{formattedStartDate}} - {{formattedEndDate}}</td>
+                <td>{{invoice.currency}} {{amount}}</td>
+            </tr>
+            {{/invoice.invoiceItems}}
+            <tr>
+                <td colspan=4 />
+            </tr>
+            <tr>
+                <td colspan=2 />
+                <td align=right><strong>{{text.invoiceAmount}}</strong></td>
+                <td align=right><strong>{{invoice.totalAmount}}</strong></td>
+            </tr>
+            <tr>
+                <td colspan=2 />
+                <td align=right><strong>{{text.invoiceAmountPaid}}</strong></td>
+                <td align=right><strong>{{invoice.amountPaid}}</strong></td>
+            </tr>
+            <tr>
+                <td colspan=2 />
+                <td align=right><strong>{{text.invoiceBalance}}</strong></td>
+                <td align=right><strong>{{invoice.balance}}</strong></td>
+            </tr>
+        </table>
+    </body>
+</html>
+
diff --git a/util/src/main/resources/com/ning/billing/util/notificationq/dao/NotificationSqlDao.sql.stg b/util/src/main/resources/com/ning/billing/util/notificationq/dao/NotificationSqlDao.sql.stg
index 7a7ecab..0731adc 100644
--- a/util/src/main/resources/com/ning/billing/util/notificationq/dao/NotificationSqlDao.sql.stg
+++ b/util/src/main/resources/com/ning/billing/util/notificationq/dao/NotificationSqlDao.sql.stg
@@ -1,45 +1,47 @@
 group NotificationSqlDao;
 
-getReadyNotifications(now, max) ::= <<
+getReadyNotifications() ::= <<
     select
-      id
-      ,  notification_id
+      record_id
+      , id
       , notification_key
-      , created_dt
-      , effective_dt
+      , created_date
+      , creating_owner
+      , effective_date
       , queue_name
       , processing_owner
-      , processing_available_dt
+      , processing_available_date
       , processing_state
     from notifications
     where
-      effective_dt \<= :now
-      and queue_name = :queue_name
+      effective_date \<= :now
+      and queue_name = :queueName
       and processing_state != 'PROCESSED'
-      and (processing_owner IS NULL OR processing_available_dt \<= :now)
+      and processing_state != 'REMOVED'
+      and (processing_owner IS NULL OR processing_available_date \<= :now)
     order by
-      effective_dt asc
-      , created_dt asc
-      , id
+      effective_date asc
+      , created_date asc
+      , record_id
     limit :max
     ;
 >>
 
-
-claimNotification(owner, next_available, id, now) ::= <<
+claimNotification() ::= <<
     update notifications
     set
       processing_owner = :owner
-      , processing_available_dt = :next_available
+      , processing_available_date = :nextAvailable
       , processing_state = 'IN_PROCESSING'
     where
       id = :id
       and processing_state != 'PROCESSED'
-      and (processing_owner IS NULL OR processing_available_dt \<= :now)
+      and processing_state != 'REMOVED'
+      and (processing_owner IS NULL OR processing_available_date \<= :now)
     ;
 >>
 
-clearNotification(id, owner) ::= <<
+clearNotification() ::= <<
     update notifications
     set
       processing_state = 'PROCESSED'
@@ -48,39 +50,47 @@ clearNotification(id, owner) ::= <<
     ;
 >>
 
+removeNotificationsByKey() ::= <<
+    update notifications
+    set
+      processing_state = 'REMOVED'
+    where
+      notification_key = :notificationKey
+    ;
+>>
+
 insertNotification() ::= <<
     insert into notifications (
-      notification_id
-    , notification_key
-      , created_dt
-      , effective_dt
+      id
+      , notification_key
+      , created_date
+      , creating_owner
+      , effective_date
       , queue_name
       , processing_owner
-      , processing_available_dt
+      , processing_available_date
       , processing_state
     ) values (
-      :notification_id
-      , :notification_key
-      , :created_dt
-      , :effective_dt
-      , :queue_name
-      , :processing_owner
-      , :processing_available_dt
-      , :processing_state
+      :id
+      , :notificationKey
+      , :createdDate
+      , :creatingOwner
+      , :effectiveDate
+      , :queueName
+      , :processingOwner
+      , :processingAvailableDate
+      , :processingState
     );   
 >>
 
-
-insertClaimedHistory(sequence_id, owner, hostname, claimed_dt, notification_id) ::= <<
+insertClaimedHistory() ::= <<
     insert into claimed_notifications (
-        sequence_id
-        , owner_id
-        , claimed_dt
+          owner_id
+        , claimed_date
         , notification_id
       ) values (
-        :sequence_id
-        , :owner
-        , :claimed_dt
-        , :notification_id
+          :ownerId
+        , :claimedDate
+        , :notificationId
       );
 >>
diff --git a/util/src/main/resources/com/ning/billing/util/tag/dao/TagDefinitionSqlDao.sql.stg b/util/src/main/resources/com/ning/billing/util/tag/dao/TagDefinitionSqlDao.sql.stg
index 54bdfa9..2a03325 100644
--- a/util/src/main/resources/com/ning/billing/util/tag/dao/TagDefinitionSqlDao.sql.stg
+++ b/util/src/main/resources/com/ning/billing/util/tag/dao/TagDefinitionSqlDao.sql.stg
@@ -5,7 +5,9 @@ fields(prefix) ::= <<
     <prefix>name,
     <prefix>description,
     <prefix>created_by,
-    <prefix>created_date
+    <prefix>created_date ,
+    <prefix>updated_by,
+    <prefix>updated_date
 >>
 
 get() ::= <<
@@ -15,7 +17,7 @@ get() ::= <<
 
 create() ::= <<
   INSERT INTO tag_definitions(<fields()>)
-  VALUES(:id, :name, :description, :userName, :createdDate);
+  VALUES(:id, :name, :description, :userName, :createdDate, :userName, :updatedDate);
 >>
 
 load() ::= <<
diff --git a/util/src/main/resources/com/ning/billing/util/tag/dao/TagSqlDao.sql.stg b/util/src/main/resources/com/ning/billing/util/tag/dao/TagSqlDao.sql.stg
index 8f98452..f0a5805 100644
--- a/util/src/main/resources/com/ning/billing/util/tag/dao/TagSqlDao.sql.stg
+++ b/util/src/main/resources/com/ning/billing/util/tag/dao/TagSqlDao.sql.stg
@@ -9,17 +9,12 @@ fields(prefix) ::= <<
     <prefix>created_date
 >>
 
-batchInsertFromTransaction() ::= <<
+insertFromTransaction() ::= <<
   INSERT INTO tags(<fields()>)
   VALUES (:id, :tagDefinitionName, :objectId, :objectType, :userName, :createdDate);
 >>
 
-batchInsertHistoryFromTransaction() ::= <<
-    INSERT INTO tag_history (history_record_id, id, tag_definition_name, object_id, object_type, change_type, updated_by, date)
-    VALUES (:historyRecordId, :id, :tagDefinitionName, :objectId, :objectType, :changeType, :userName, :updatedDate);
->>
-
-batchDeleteFromTransaction() ::= <<
+deleteFromTransaction() ::= <<
     DELETE FROM tags
     WHERE tag_definition_name = :tagDefinitionName
         AND object_id = :objectId AND object_type = :objectType;
@@ -53,6 +48,55 @@ load() ::= <<
     WHERE t.object_id = :objectId AND t.object_type = :objectType;
 >>
 
+getRecordIds() ::= <<
+    SELECT record_id, id
+    FROM tags
+    WHERE object_id = :objectId AND object_type = :objectType;
+>>
+
+historyFields(prefix) ::= <<
+  <prefix>record_id,
+  <prefix>id,
+  <prefix>object_id,
+  <prefix>object_type,
+  <prefix>tag_definition_name,
+  <prefix>updated_by,
+  <prefix>date,
+  <prefix>change_type
+>>
+
+addHistoryFromTransaction() ::= <<
+    INSERT INTO tag_history(<historyFields()>)
+    VALUES(:recordId, :id, :objectId, :objectType, :tagDefinitionName, :userName, :updatedDate, :changeType);
+>>
+
+getMaxHistoryRecordId() ::= <<
+    SELECT MAX(history_record_id)
+    FROM tag_history;
+>>
+
+getHistoryRecordIds() ::= <<
+    SELECT history_record_id, record_id
+    FROM tag_history
+    WHERE history_record_id > :maxHistoryRecordId;
+>>
+
+auditFields(prefix) ::= <<
+    <prefix>table_name,
+    <prefix>record_id,
+    <prefix>change_type,
+    <prefix>change_date,
+    <prefix>changed_by,
+    <prefix>reason_code,
+    <prefix>comments,
+    <prefix>user_token
+>>
+
+insertAuditFromTransaction() ::= <<
+    INSERT INTO audit_log(<auditFields()>)
+    VALUES(:tableName, :recordId, :changeType, :createdDate, :userName, NULL, NULL, NULL);
+>>
+
 test() ::= <<
   SELECT 1 FROM tags;
 >>
diff --git a/util/src/main/resources/com/ning/billing/util/template/translation/CatalogTranslation_EN_US.properties b/util/src/main/resources/com/ning/billing/util/template/translation/CatalogTranslation_EN_US.properties
new file mode 100644
index 0000000..b05a595
--- /dev/null
+++ b/util/src/main/resources/com/ning/billing/util/template/translation/CatalogTranslation_EN_US.properties
@@ -0,0 +1,2 @@
+ning-pro = Pro
+ning-plus = Plus
\ No newline at end of file
diff --git a/util/src/main/resources/com/ning/billing/util/template/translation/CatalogTranslation_FR_CA.properties b/util/src/main/resources/com/ning/billing/util/template/translation/CatalogTranslation_FR_CA.properties
new file mode 100644
index 0000000..e025a88
--- /dev/null
+++ b/util/src/main/resources/com/ning/billing/util/template/translation/CatalogTranslation_FR_CA.properties
@@ -0,0 +1 @@
+ning-plus = Plus en francais
\ No newline at end of file
diff --git a/util/src/main/resources/com/ning/billing/util/template/translation/InvoiceTranslation_EN_US.properties b/util/src/main/resources/com/ning/billing/util/template/translation/InvoiceTranslation_EN_US.properties
new file mode 100644
index 0000000..0162671
--- /dev/null
+++ b/util/src/main/resources/com/ning/billing/util/template/translation/InvoiceTranslation_EN_US.properties
@@ -0,0 +1,20 @@
+invoiceTitle=INVOICE
+invoiceDate=Date:
+invoiceNumber=Invoice #
+invoiceAmount=New Charges
+invoiceAmountPaid=Payment
+invoiceBalance=Balance
+
+accountOwnerName=Network Creator
+
+companyName=Ning, Inc.
+companyAddress=P.O. Box 1622
+companyCityProvincePostalCode=Palo Alto, CA 94302
+companyCountry=USA
+companyUrl=http://www.ning.com
+
+invoiceItemBundleName=NetworkName
+invoiceItemDescription=Description
+invoiceItemServicePeriod=Service Period
+invoiceItemAmount=Amount
+
diff --git a/util/src/test/java/com/ning/billing/api/TestListenerStatus.java b/util/src/test/java/com/ning/billing/api/TestListenerStatus.java
new file mode 100644
index 0000000..23b8174
--- /dev/null
+++ b/util/src/test/java/com/ning/billing/api/TestListenerStatus.java
@@ -0,0 +1,23 @@
+/* 
+ * Copyright 2010-2011 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.api;
+
+public interface TestListenerStatus {
+    
+    public void failed(String msg);
+
+    public void resetTestListenerStatus();
+}
diff --git a/util/src/test/java/com/ning/billing/dbi/MysqlTestingHelper.java b/util/src/test/java/com/ning/billing/dbi/MysqlTestingHelper.java
index 866289e..c68c759 100644
--- a/util/src/test/java/com/ning/billing/dbi/MysqlTestingHelper.java
+++ b/util/src/test/java/com/ning/billing/dbi/MysqlTestingHelper.java
@@ -20,6 +20,7 @@ import java.io.File;
 import java.io.IOException;
 import java.net.ServerSocket;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 import org.apache.commons.io.FileUtils;
@@ -27,6 +28,7 @@ import org.skife.jdbi.v2.DBI;
 import org.skife.jdbi.v2.Handle;
 import org.skife.jdbi.v2.IDBI;
 import org.skife.jdbi.v2.tweak.HandleCallback;
+import org.skife.jdbi.v2.util.StringMapper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.testng.Assert;
@@ -44,10 +46,12 @@ public class MysqlTestingHelper
 
     private static final Logger log = LoggerFactory.getLogger(MysqlTestingHelper.class);
 
-    private static final String DB_NAME = "test_killbill";
+    private static final String DB_NAME = "killbill";
     private static final String USERNAME = "root";
     private static final String PASSWORD = "root";
 
+    // Discover dynamically list of all tables in that database;
+    private List<String> allTables;    
     private File dbDir;
     private MysqldResource mysqldResource;
     private int port;
@@ -122,13 +126,46 @@ public class MysqlTestingHelper
             }
         });
     }
+    
+    public void cleanupAllTables() {
+    	final List<String> tablesToCleanup = fetchAllTables();
+    	for (String tableName : tablesToCleanup) {
+    		cleanupTable(tableName);
+    	}
+    }
+
+    public synchronized List<String> fetchAllTables() {
+
+    	if (allTables == null) {
+    		final String dbiString = "jdbc:mysql://localhost:" + port + "/information_schema";
+    		IDBI cleanupDbi = new DBI(dbiString, USERNAME, PASSWORD);
+
+    		final List<String> tables=  cleanupDbi.withHandle(new HandleCallback<List<String>>() {
+
+    			@Override
+    			public List<String> withHandle(Handle h) throws Exception {
+    				return h.createQuery("select table_name from tables where table_schema = :table_schema and table_type = 'BASE TABLE';")
+    				.bind("table_schema", DB_NAME)
+    				.map(new StringMapper())
+    				.list();
+    			}
+    		});
+    		allTables = tables;
+    	}
+    	return allTables;
+    }
 
+    
     public void stopMysql()
     {
-        if (mysqldResource != null) {
-            mysqldResource.shutdown();
-            FileUtils.deleteQuietly(dbDir);
-            log.info("MySQLd stopped");
+        try {
+            if (mysqldResource != null) {
+                mysqldResource.shutdown();
+                FileUtils.deleteQuietly(dbDir);
+                log.info("MySQLd stopped");
+            }
+        } catch (Exception ex) {
+            //fail silently
         }
     }
 
diff --git a/util/src/test/java/com/ning/billing/mock/BrainDeadProxyFactory.java b/util/src/test/java/com/ning/billing/mock/BrainDeadProxyFactory.java
index 7404331..7a3d28f 100644
--- a/util/src/test/java/com/ning/billing/mock/BrainDeadProxyFactory.java
+++ b/util/src/test/java/com/ning/billing/mock/BrainDeadProxyFactory.java
@@ -19,12 +19,15 @@ package com.ning.billing.mock;
 import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.Method;
 import java.lang.reflect.Proxy;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Map;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import bsh.This;
+
 public class BrainDeadProxyFactory {
     private static final Logger log = LoggerFactory.getLogger(BrainDeadProxyFactory.class);
     
@@ -39,9 +42,12 @@ public class BrainDeadProxyFactory {
     }
 
     @SuppressWarnings("unchecked")
-    public static <T> T createBrainDeadProxyFor(final Class<T> clazz) {
+    public static <T> T createBrainDeadProxyFor(final Class<T> clazz, final Class<?> ... others) {
+        Class<?>[] clazzes = Arrays.copyOf(others, others.length + 2);
+        clazzes[others.length] = ZombieControl.class;
+        clazzes[others.length + 1] = clazz;
         return (T) Proxy.newProxyInstance(clazz.getClassLoader(),
-                new Class[] { clazz , ZombieControl.class},
+                clazzes,
                 new InvocationHandler() {
             private final Map<String,Object> results = new HashMap<String,Object>();
 
@@ -68,6 +74,8 @@ public class BrainDeadProxyFactory {
                     		throw ((Throwable) result);
                     	}
                         return result;
+                    } else if (method.getName().equals("equals")){
+                       return proxy == args[0];
                     } else {
                         log.error(String.format("No result for Method: '%s' on Class '%s'",method.getName(), method.getDeclaringClass().getName()));
                         throw new UnsupportedOperationException();
diff --git a/util/src/test/java/com/ning/billing/mock/glue/MockClockModule.java b/util/src/test/java/com/ning/billing/mock/glue/MockClockModule.java
new file mode 100644
index 0000000..82605ae
--- /dev/null
+++ b/util/src/test/java/com/ning/billing/mock/glue/MockClockModule.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2010-2011 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.mock.glue;
+
+import com.google.inject.AbstractModule;
+import com.ning.billing.util.clock.Clock;
+import com.ning.billing.util.clock.ClockMock;
+
+public class MockClockModule extends AbstractModule {
+
+	@Override
+	protected void configure() {
+		bind(Clock.class).to(ClockMock.class).asEagerSingleton();
+	}
+}
diff --git a/util/src/test/java/com/ning/billing/mock/glue/MockDbHelperModule.java b/util/src/test/java/com/ning/billing/mock/glue/MockDbHelperModule.java
new file mode 100644
index 0000000..232c4e8
--- /dev/null
+++ b/util/src/test/java/com/ning/billing/mock/glue/MockDbHelperModule.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2010-2011 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.mock.glue;
+
+import org.skife.config.ConfigurationObjectFactory;
+import org.skife.jdbi.v2.IDBI;
+
+import com.google.inject.AbstractModule;
+import com.ning.billing.dbi.DBIProvider;
+import com.ning.billing.dbi.DbiConfig;
+import com.ning.billing.dbi.MysqlTestingHelper;
+
+public class MockDbHelperModule extends AbstractModule {
+
+    
+    @Override
+    protected void configure() {
+        installMysqlTestingHelper();
+    }
+    
+    public void  installMysqlTestingHelper() {
+
+        final MysqlTestingHelper helper = new MysqlTestingHelper();
+        bind(MysqlTestingHelper.class).toInstance(helper);
+        if (helper.isUsingLocalInstance()) {
+            bind(IDBI.class).toProvider(DBIProvider.class).asEagerSingleton();
+            final DbiConfig config = new ConfigurationObjectFactory(System.getProperties()).build(DbiConfig.class);
+            bind(DbiConfig.class).toInstance(config);
+        } else {
+            final IDBI dbi = helper.getDBI(); 
+            bind(IDBI.class).toInstance(dbi);
+        }
+
+    }
+    
+}
diff --git a/util/src/test/java/com/ning/billing/mock/glue/MockEntitlementModule.java b/util/src/test/java/com/ning/billing/mock/glue/MockEntitlementModule.java
new file mode 100644
index 0000000..044618e
--- /dev/null
+++ b/util/src/test/java/com/ning/billing/mock/glue/MockEntitlementModule.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2010-2011 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.mock.glue;
+
+import com.google.inject.AbstractModule;
+import com.ning.billing.entitlement.api.EntitlementService;
+import com.ning.billing.entitlement.api.billing.ChargeThruApi;
+import com.ning.billing.entitlement.api.migration.EntitlementMigrationApi;
+import com.ning.billing.entitlement.api.timeline.EntitlementTimelineApi;
+import com.ning.billing.entitlement.api.user.EntitlementUserApi;
+import com.ning.billing.glue.EntitlementModule;
+import com.ning.billing.mock.BrainDeadProxyFactory;
+import com.ning.billing.util.glue.RealImplementation;
+
+public class MockEntitlementModule extends AbstractModule implements EntitlementModule {
+    
+    /* (non-Javadoc)
+     * @see com.ning.billing.mock.glue.EntitlementModule#installEntitlementService()
+     */
+    @Override
+    public void installEntitlementService() {
+        bind(EntitlementService.class).toInstance(BrainDeadProxyFactory.createBrainDeadProxyFor(EntitlementService.class));
+    }
+    
+    /* (non-Javadoc)
+     * @see com.ning.billing.mock.glue.EntitlementModule#installEntitlementUserApi()
+     */
+    @Override
+    public void installEntitlementUserApi() {
+        bind(EntitlementUserApi.class).annotatedWith(RealImplementation.class).toInstance(BrainDeadProxyFactory.createBrainDeadProxyFor(EntitlementUserApi.class));
+    }
+    
+    /* (non-Javadoc)
+     * @see com.ning.billing.mock.glue.EntitlementModule#installEntitlementMigrationApi()
+     */
+    @Override
+    public void installEntitlementMigrationApi() {
+        bind(EntitlementMigrationApi.class).toInstance(BrainDeadProxyFactory.createBrainDeadProxyFor(EntitlementMigrationApi.class));
+    }
+    
+    /* (non-Javadoc)
+     * @see com.ning.billing.mock.glue.EntitlementModule#installChargeThruApi()
+     */
+    @Override
+    public void installChargeThruApi() {
+        bind(ChargeThruApi.class).toInstance(BrainDeadProxyFactory.createBrainDeadProxyFor(ChargeThruApi.class));
+    }
+
+    @Override
+    protected void configure() {
+        installEntitlementService();
+        installEntitlementUserApi();
+        installEntitlementMigrationApi();
+        installChargeThruApi();
+        installEntitlementTimelineApi();
+    }
+
+    @Override
+    public void installEntitlementTimelineApi() {
+        bind(EntitlementTimelineApi.class).toInstance(BrainDeadProxyFactory.createBrainDeadProxyFor(EntitlementTimelineApi.class));
+    }
+}
diff --git a/util/src/test/java/com/ning/billing/mock/glue/MockInvoiceModule.java b/util/src/test/java/com/ning/billing/mock/glue/MockInvoiceModule.java
new file mode 100644
index 0000000..f499e9a
--- /dev/null
+++ b/util/src/test/java/com/ning/billing/mock/glue/MockInvoiceModule.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2010-2011 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.mock.glue;
+
+import com.google.inject.AbstractModule;
+import com.ning.billing.glue.InvoiceModule;
+import com.ning.billing.invoice.api.InvoiceMigrationApi;
+import com.ning.billing.invoice.api.InvoicePaymentApi;
+import com.ning.billing.invoice.api.InvoiceUserApi;
+import com.ning.billing.invoice.api.test.InvoiceTestApi;
+import com.ning.billing.mock.BrainDeadProxyFactory;
+
+public class MockInvoiceModule extends AbstractModule implements InvoiceModule {
+
+    @Override
+    public void installInvoiceUserApi() {
+       bind(InvoiceUserApi.class).toInstance(BrainDeadProxyFactory.createBrainDeadProxyFor(InvoiceUserApi.class));
+    }
+
+    @Override
+    public void installInvoicePaymentApi() {
+        bind(InvoicePaymentApi.class).toInstance(BrainDeadProxyFactory.createBrainDeadProxyFor(InvoicePaymentApi.class));
+    }
+
+    @Override
+    public void installInvoiceMigrationApi() {
+        bind(InvoiceMigrationApi.class).toInstance(BrainDeadProxyFactory.createBrainDeadProxyFor(InvoiceMigrationApi.class));
+    }
+
+    @Override
+    public void installInvoiceTestApi() {
+        bind(InvoiceTestApi.class).toInstance(BrainDeadProxyFactory.createBrainDeadProxyFor(InvoiceTestApi.class));
+    }
+
+    @Override
+    protected void configure() {
+        installInvoiceUserApi();
+        installInvoicePaymentApi();
+        installInvoiceMigrationApi();
+        installInvoiceTestApi();
+    }
+
+}
diff --git a/util/src/test/java/com/ning/billing/mock/glue/MockJunctionModule.java b/util/src/test/java/com/ning/billing/mock/glue/MockJunctionModule.java
new file mode 100644
index 0000000..1549c0b
--- /dev/null
+++ b/util/src/test/java/com/ning/billing/mock/glue/MockJunctionModule.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2010-2011 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.mock.glue;
+
+import com.google.inject.AbstractModule;
+import com.ning.billing.account.api.AccountUserApi;
+import com.ning.billing.entitlement.api.user.EntitlementUserApi;
+import com.ning.billing.glue.JunctionModule;
+import com.ning.billing.junction.api.BillingApi;
+import com.ning.billing.junction.api.BlockingApi;
+import com.ning.billing.mock.BrainDeadProxyFactory;
+
+public class MockJunctionModule extends AbstractModule implements JunctionModule {
+    private BillingApi billingApi = BrainDeadProxyFactory.createBrainDeadProxyFor(BillingApi.class);
+    private BlockingApi blockingApi = BrainDeadProxyFactory.createBrainDeadProxyFor(BlockingApi.class);
+    private AccountUserApi userApi = BrainDeadProxyFactory.createBrainDeadProxyFor(AccountUserApi.class);
+    private EntitlementUserApi entUserApi = BrainDeadProxyFactory.createBrainDeadProxyFor(EntitlementUserApi.class);
+    
+    @Override
+    protected void configure() {
+        installBlockingApi();
+        installAccountUserApi();
+        installBillingApi();
+        installEntitlementUserApi();
+    }
+
+    @Override
+    public void installBillingApi() {
+        bind(BillingApi.class).toInstance(billingApi);
+    }
+    
+    
+    @Override
+    public void installAccountUserApi() {
+        bind(AccountUserApi.class).toInstance(userApi);
+    }
+    
+    @Override
+    public void installBlockingApi() {
+        bind(BlockingApi.class).toInstance(blockingApi);
+    }
+    
+    @Override
+    public void installEntitlementUserApi() {
+        bind(EntitlementUserApi.class).toInstance(entUserApi);
+    }
+}
diff --git a/util/src/test/java/com/ning/billing/mock/glue/MockNotificationQueueModule.java b/util/src/test/java/com/ning/billing/mock/glue/MockNotificationQueueModule.java
new file mode 100644
index 0000000..899be2b
--- /dev/null
+++ b/util/src/test/java/com/ning/billing/mock/glue/MockNotificationQueueModule.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2010-2011 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.mock.glue;
+
+import com.google.inject.AbstractModule;
+import com.ning.billing.util.notificationq.MockNotificationQueueService;
+import com.ning.billing.util.notificationq.NotificationQueueService;
+
+public class MockNotificationQueueModule extends AbstractModule {
+
+    @Override
+    protected void configure() {
+        bind(NotificationQueueService.class).to(MockNotificationQueueService.class).asEagerSingleton();
+    }
+
+}
diff --git a/util/src/test/java/com/ning/billing/mock/glue/TestDbiModule.java b/util/src/test/java/com/ning/billing/mock/glue/TestDbiModule.java
new file mode 100644
index 0000000..c42717d
--- /dev/null
+++ b/util/src/test/java/com/ning/billing/mock/glue/TestDbiModule.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2010-2011 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.mock.glue;
+
+import org.skife.config.ConfigurationObjectFactory;
+import org.skife.jdbi.v2.IDBI;
+
+import com.google.inject.AbstractModule;
+import com.ning.billing.dbi.DBIProvider;
+import com.ning.billing.dbi.DbiConfig;
+import com.ning.billing.dbi.MysqlTestingHelper;
+
+public class TestDbiModule extends AbstractModule {
+ 
+    protected void configure() {
+
+        final MysqlTestingHelper helper = new MysqlTestingHelper();
+        bind(MysqlTestingHelper.class).toInstance(helper);
+        if (helper.isUsingLocalInstance()) {
+            bind(IDBI.class).toProvider(DBIProvider.class).asEagerSingleton();
+            final DbiConfig config = new ConfigurationObjectFactory(System.getProperties()).build(DbiConfig.class);
+            bind(DbiConfig.class).toInstance(config);
+        } else {
+            final IDBI dbi = helper.getDBI(); 
+            bind(IDBI.class).toInstance(dbi);
+        }
+
+     }
+}
diff --git a/util/src/test/java/com/ning/billing/mock/MockAccountBuilder.java b/util/src/test/java/com/ning/billing/mock/MockAccountBuilder.java
new file mode 100644
index 0000000..8b9c2ee
--- /dev/null
+++ b/util/src/test/java/com/ning/billing/mock/MockAccountBuilder.java
@@ -0,0 +1,383 @@
+/*
+ * Copyright 2010-2011 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.mock;
+
+import java.util.List;
+import java.util.UUID;
+
+import org.apache.commons.lang.NotImplementedException;
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.MutableAccountData;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.junction.api.BlockingState;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.customfield.CustomField;
+import com.ning.billing.util.dao.ObjectType;
+import com.ning.billing.util.tag.ControlTagType;
+import com.ning.billing.util.tag.Tag;
+import com.ning.billing.util.tag.TagDefinition;
+
+public class MockAccountBuilder {
+    private final UUID id;
+    private String externalKey;
+    private String email;
+    private String name;
+    private int firstNameLength;
+    private Currency currency;
+    private int billingCycleDay;
+    private String paymentProviderName;
+    private DateTimeZone timeZone;
+    private String locale;
+    private String address1;
+    private String address2;
+    private String companyName;
+    private String city;
+    private String stateOrProvince;
+    private String country;
+    private String postalCode;
+    private String phone;
+    private boolean migrated;
+    private boolean isNotifiedForInvoices;
+
+    public MockAccountBuilder() {
+        this(UUID.randomUUID());
+    }
+
+    public MockAccountBuilder(final UUID id) {
+        this.id = id;
+    }
+
+    public MockAccountBuilder externalKey(final String externalKey) {
+        this.externalKey = externalKey;
+        return this;
+    }
+
+    public MockAccountBuilder email(final String email) {
+        this.email = email;
+        return this;
+    }
+
+    public MockAccountBuilder name(final String name) {
+        this.name = name;
+        return this;
+    }
+
+    public MockAccountBuilder firstNameLength(final int firstNameLength) {
+        this.firstNameLength = firstNameLength;
+        return this;
+    }
+
+    public MockAccountBuilder billingCycleDay(final int billingCycleDay) {
+        this.billingCycleDay = billingCycleDay;
+        return this;
+    }
+
+    public MockAccountBuilder currency(final Currency currency) {
+        this.currency = currency;
+        return this;
+    }
+
+    public MockAccountBuilder paymentProviderName(final String paymentProviderName) {
+        this.paymentProviderName = paymentProviderName;
+        return this;
+    }
+
+    public MockAccountBuilder timeZone(final DateTimeZone timeZone) {
+        this.timeZone = timeZone;
+        return this;
+    }
+
+    public MockAccountBuilder locale(final String locale) {
+        this.locale = locale;
+        return this;
+    }
+
+    public MockAccountBuilder address1(final String address1) {
+        this.address1 = address1;
+        return this;
+    }
+
+    public MockAccountBuilder address2(final String address2) {
+        this.address2 = address2;
+        return this;
+    }
+
+    public MockAccountBuilder companyName(final String companyName) {
+        this.companyName = companyName;
+        return this;
+    }
+
+    public MockAccountBuilder city(final String city) {
+        this.city = city;
+        return this;
+    }
+
+    public MockAccountBuilder stateOrProvince(final String stateOrProvince) {
+        this.stateOrProvince = stateOrProvince;
+        return this;
+    }
+
+    public MockAccountBuilder postalCode(final String postalCode) {
+        this.postalCode = postalCode;
+        return this;
+    }
+
+    public MockAccountBuilder country(final String country) {
+        this.country = country;
+        return this;
+    }
+
+    public MockAccountBuilder phone(final String phone) {
+        this.phone = phone;
+        return this;
+    }
+
+    public MockAccountBuilder migrated(final boolean migrated) {
+        this.migrated = migrated;
+        return this;
+    }
+
+    public MockAccountBuilder isNotifiedForInvoices(final boolean isNotifiedForInvoices) {
+        this.isNotifiedForInvoices = isNotifiedForInvoices;
+        return this;
+    }
+
+    public Account build() {
+        return new Account(){
+            
+            @Override
+            public String getExternalKey() {
+                return externalKey;
+            }
+
+            @Override
+            public String getName() {
+               
+                return name;
+            }
+
+            @Override
+            public int getFirstNameLength() {
+               
+                return firstNameLength;
+            }
+
+            @Override
+            public String getEmail() {
+               
+                return email;
+            }
+
+            @Override
+            public int getBillCycleDay() {
+               
+                return billingCycleDay;
+            }
+
+            @Override
+            public Currency getCurrency() {
+               
+                return currency;
+            }
+
+            @Override
+            public String getPaymentProviderName() {
+               
+                return paymentProviderName;
+            }
+
+            @Override
+            public DateTimeZone getTimeZone() {
+               
+                return timeZone;
+            }
+
+            @Override
+            public String getLocale() {
+               
+                return locale;
+            }
+
+            @Override
+            public String getAddress1() {
+               
+                return address1;
+            }
+
+            @Override
+            public String getAddress2() {
+               
+                return address2;
+            }
+
+            @Override
+            public String getCompanyName() {
+               
+                return companyName;
+            }
+
+            @Override
+            public String getCity() {
+               
+                return city;
+            }
+
+            @Override
+            public String getStateOrProvince() {
+               
+                return stateOrProvince;
+            }
+
+            @Override
+            public String getPostalCode() {
+               
+                return postalCode;
+            }
+
+            @Override
+            public String getCountry() {
+               
+                return country;
+            }
+
+            @Override
+            public String getPhone() {
+               
+                return phone;
+            }
+
+            @Override
+            public boolean isMigrated() {
+               
+                return migrated;
+            }
+
+            @Override
+            public boolean isNotifiedForInvoices() {
+               
+                return isNotifiedForInvoices;
+            }
+
+            @Override
+            public String getFieldValue(String fieldName) {
+               
+                return null;
+            }
+
+            @Override
+            public void setFieldValue(String fieldName, String fieldValue) {
+               
+                
+            }
+
+            @Override
+            public void saveFieldValue(String fieldName, String fieldValue, CallContext context) {
+               
+                
+            }
+
+            @Override
+            public List<CustomField> getFieldList() {
+                return null;
+            }
+
+            @Override
+            public void setFields(List<CustomField> fields) {
+            }
+
+            @Override
+            public void saveFields(List<CustomField> fields, CallContext context) {
+            }
+
+            @Override
+            public void clearFields() {
+            }
+
+            @Override
+            public void clearPersistedFields(CallContext context) {
+            }
+
+            @Override
+            public ObjectType getObjectType() {
+                throw new UnsupportedOperationException();
+            }
+
+            @Override
+            public UUID getId() {
+                return id;
+            }
+
+            @Override
+            public List<Tag> getTagList() {
+                return null;
+            }
+
+            @Override
+            public boolean hasTag(TagDefinition tagDefinition) {
+                return false;
+            }
+
+            @Override
+            public boolean hasTag(ControlTagType controlTagType) {
+                return false;
+            }
+
+            @Override
+            public void addTag(TagDefinition definition) {
+            }
+
+            @Override
+            public void addTags(List<Tag> tags) {
+            }
+
+            @Override
+            public void addTagsFromDefinitions(List<TagDefinition> tagDefinitions) {
+            }
+
+            @Override
+            public void clearTags() {
+            }
+
+            @Override
+            public void removeTag(TagDefinition definition) {
+            }
+
+            @Override
+            public boolean generateInvoice() {
+                return true;
+            }
+
+            @Override
+            public boolean processPayment() {
+                return true;
+            }
+
+            @Override
+            public BlockingState getBlockingState() {
+                return null;
+            }
+
+            @Override
+            public MutableAccountData toMutableAccountData() {
+                throw new NotImplementedException();
+            }
+        };
+    }
+}
diff --git a/util/src/test/java/com/ning/billing/util/bus/TestEventBus.java b/util/src/test/java/com/ning/billing/util/bus/TestEventBus.java
index 2310f1c..a64aa3e 100644
--- a/util/src/test/java/com/ning/billing/util/bus/TestEventBus.java
+++ b/util/src/test/java/com/ning/billing/util/bus/TestEventBus.java
@@ -16,123 +16,26 @@
 
 package com.ning.billing.util.bus;
 
-import com.google.common.eventbus.Subscribe;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.testng.Assert;
-import org.testng.annotations.AfterClass;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
 
-public class TestEventBus {
-
-    private static final Logger log = LoggerFactory.getLogger(TestEventBus.class);
-
-    private Bus eventBus;
+@Test(groups={"slow"})
+public class TestEventBus extends TestEventBusBase {
 
 
     @BeforeClass(groups = "slow")
-    public void setup() {
+    public void setup() throws Exception {
         eventBus = new InMemoryBus();
-        eventBus.start();
-    }
-
-    @AfterClass(groups = "slow")
-    public void tearDown() {
-        eventBus.stop();
-    }
-
-    public static final class MyEvent implements BusEvent {
-        String name;
-        Long value;
-
-        public MyEvent(String name, Long value) {
-            this.name = name;
-            this.value = value;
-        }
-    }
-
-    public static final class MyOtherEvent implements BusEvent {
-        String name;
-        Long value;
-
-        public MyOtherEvent(String name, Long value) {
-            this.name = name;
-            this.value = value;
-        }
-    }
-
-    public static class MyEventHandler {
-
-        private final int expectedEvents;
-
-        private int gotEvents;
-
-
-        public MyEventHandler(int exp) {
-            this.expectedEvents = exp;
-            this.gotEvents = 0;
-        }
-
-        public synchronized int getEvents() {
-            return gotEvents;
-        }
-
-        @Subscribe
-        public synchronized void processEvent(MyEvent event) {
-            gotEvents++;
-            //log.debug("Got event {} {}", event.name, event.value);
-        }
-
-        public synchronized boolean waitForCompletion(long timeoutMs) {
-
-            while (gotEvents < expectedEvents) {
-                try {
-                    wait(timeoutMs);
-                    break;
-                } catch (InterruptedException ignore) {
-                }
-            }
-            return (gotEvents == expectedEvents);
-        }
+        super.setup();
     }
-
+    
     @Test(groups = "slow")
     public void testSimple() {
-        try {
-
-            int nbEvents = 127;
-            MyEventHandler handler = new MyEventHandler(nbEvents);
-            eventBus.register(handler);
-
-            for (int i = 0; i < nbEvents; i++) {
-                eventBus.post(new MyEvent("my-event", (long) i));
-            }
-
-            boolean completed = handler.waitForCompletion(3000);
-            Assert.assertEquals(completed, true);
-        } catch (Exception e) {
-            Assert.fail("",e);
-        }
+        super.testSimple();
     }
 
     @Test(groups = "slow")
     public void testDifferentType() {
-        try {
-
-            MyEventHandler handler = new MyEventHandler(1);
-            eventBus.register(handler);
-
-            for (int i = 0; i < 10; i++) {
-                eventBus.post(new MyOtherEvent("my-other-event", (long) i));
-            }
-            eventBus.post(new MyEvent("my-event", 11l));
-
-            boolean completed = handler.waitForCompletion(3000);
-            Assert.assertEquals(completed, true);
-        } catch (Exception e) {
-            Assert.fail("",e);
-        }
-
+        super.testDifferentType();
     }
 }
diff --git a/util/src/test/java/com/ning/billing/util/bus/TestEventBusBase.java b/util/src/test/java/com/ning/billing/util/bus/TestEventBusBase.java
new file mode 100644
index 0000000..ddc89ed
--- /dev/null
+++ b/util/src/test/java/com/ning/billing/util/bus/TestEventBusBase.java
@@ -0,0 +1,253 @@
+/* 
+ * Copyright 2010-2011 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.util.bus;
+
+import java.util.UUID;
+
+import org.codehaus.jackson.annotate.JsonCreator;
+import org.codehaus.jackson.annotate.JsonIgnore;
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.google.common.eventbus.Subscribe;
+import com.google.inject.Inject;
+import com.ning.billing.util.bus.BusEvent.BusEventType;
+
+
+public class TestEventBusBase {
+
+    protected static final Logger log = LoggerFactory.getLogger(TestEventBusBase.class);
+
+    @Inject
+    protected Bus eventBus;
+
+    @BeforeClass(groups = "slow")
+    public void setup() throws Exception {
+        eventBus.start();
+    }
+    
+    @AfterClass(groups = "slow")
+    public void tearDown() {
+        eventBus.stop();
+    }
+
+    
+    public static  class MyEvent implements BusEvent {
+        
+        private String name;
+        private Long value;
+        private UUID userToken;
+        private String type;
+
+        @JsonCreator
+        public MyEvent(@JsonProperty("name") String name,
+                @JsonProperty("value") Long value,
+                @JsonProperty("token") UUID token,
+                @JsonProperty("type") String type) {
+                
+            this.name = name;
+            this.value = value;
+            this.userToken = token;
+            this.type = type;
+        }
+
+        @JsonIgnore
+        @Override
+        public BusEventType getBusEventType() {
+            return BusEventType.valueOf(type);
+        }
+
+        @Override
+        public UUID getUserToken() {
+            return userToken;
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        public Long getValue() {
+            return value;
+        }
+
+        public String getType() {
+            return type;
+        }
+    }
+    
+    public static final class MyEventWithException extends MyEvent {
+        
+        @JsonCreator
+        public MyEventWithException(@JsonProperty("name") String name,
+                @JsonProperty("value") Long value,
+                @JsonProperty("token") UUID token,
+                @JsonProperty("type") String type) {
+            super(name, value, token, type);
+        }        
+    }
+
+
+    public static final class MyOtherEvent implements BusEvent {
+
+        private String name;
+        private Double value;
+        private UUID userToken;
+        private String type;
+
+
+        @JsonCreator
+        public MyOtherEvent(@JsonProperty("name") String name,
+                @JsonProperty("value") Double value,
+                @JsonProperty("token") UUID token,
+                @JsonProperty("type") String type) {
+                
+            this.name = name;
+            this.value = value;
+            this.userToken = token;
+            this.type = type;
+        }
+       
+        @JsonIgnore
+        @Override
+        public BusEventType getBusEventType() {
+            return BusEventType.valueOf(type);
+        }
+
+        @Override
+        public UUID getUserToken() {
+            return userToken;
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        public Double getValue() {
+            return value;
+        }
+
+        public String getType() {
+            return type;
+        }
+    }
+    
+    public static class MyEventHandlerException extends RuntimeException {
+        public MyEventHandlerException(String msg) {
+            super(msg);
+        }
+    }
+
+    public static class MyEventHandler {
+
+        private final int expectedEvents;
+
+        private volatile int gotEvents;
+
+
+        public MyEventHandler(int exp) {
+            this.expectedEvents = exp;
+            this.gotEvents = 0;
+        }
+
+        public synchronized int getEvents() {
+            return gotEvents;
+        }
+
+        @Subscribe
+        public synchronized void processEvent(MyEvent event) {
+            gotEvents++;
+            //log.debug("Got event {} {}", event.name, event.value);
+        }
+
+        @Subscribe
+        public synchronized void processEvent(MyEventWithException event) {
+            throw new MyEventHandlerException("FAIL");
+        }
+        
+        public synchronized boolean waitForCompletion(long timeoutMs) {
+
+            long ini = System.currentTimeMillis();
+            long remaining = timeoutMs;
+            while (gotEvents < expectedEvents && remaining > 0) {
+                try {
+                    wait(1000);
+                    if (gotEvents == expectedEvents) {
+                        break;
+                    }
+                    remaining = timeoutMs - (System.currentTimeMillis() - ini);
+                } catch (InterruptedException ignore) {
+                }
+            }
+            return (gotEvents == expectedEvents);
+        }
+    }
+
+    public void testSimpleWithException() {
+        try {
+        MyEventHandler handler = new MyEventHandler(1);
+        eventBus.register(handler);
+
+        eventBus.post(new MyEventWithException("my-event", 1L, UUID.randomUUID(), BusEventType.ACCOUNT_CHANGE.toString()));
+        
+        Thread.sleep(50000);
+        } catch (Exception e) {
+            
+        }
+        
+    }
+    
+    public void testSimple() {
+        try {
+
+            int nbEvents = 5;
+            MyEventHandler handler = new MyEventHandler(nbEvents);
+            eventBus.register(handler);
+
+            for (int i = 0; i < nbEvents; i++) {
+                eventBus.post(new MyEvent("my-event", (long) i, UUID.randomUUID(), BusEventType.ACCOUNT_CHANGE.toString()));
+            }
+
+            boolean completed = handler.waitForCompletion(10000);
+            Assert.assertEquals(completed, true);
+        } catch (Exception e) {
+            Assert.fail("",e);
+        }
+    }
+
+    public void testDifferentType() {
+        try {
+
+            MyEventHandler handler = new MyEventHandler(1);
+            eventBus.register(handler);
+
+            for (int i = 0; i < 5; i++) {
+                eventBus.post(new MyOtherEvent("my-other-event", (double) i, UUID.randomUUID(), BusEventType.BUNDLE_REPAIR.toString()));
+            }
+            eventBus.post(new MyEvent("my-event", 11l, UUID.randomUUID(), BusEventType.ACCOUNT_CHANGE.toString()));
+
+            boolean completed = handler.waitForCompletion(10000);
+            Assert.assertEquals(completed, true);
+        } catch (Exception e) {
+            Assert.fail("",e);
+        }
+
+    }
+}
diff --git a/util/src/test/java/com/ning/billing/util/bus/TestPersistentEventBus.java b/util/src/test/java/com/ning/billing/util/bus/TestPersistentEventBus.java
new file mode 100644
index 0000000..b42b694
--- /dev/null
+++ b/util/src/test/java/com/ning/billing/util/bus/TestPersistentEventBus.java
@@ -0,0 +1,97 @@
+/* 
+ * Copyright 2010-2011 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.util.bus;
+
+
+import java.io.IOException;
+
+import org.apache.commons.io.IOUtils;
+import org.skife.config.ConfigurationObjectFactory;
+import org.skife.jdbi.v2.IDBI;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Guice;
+import org.testng.annotations.Test;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Inject;
+import com.ning.billing.dbi.DBIProvider;
+import com.ning.billing.dbi.DbiConfig;
+import com.ning.billing.dbi.MysqlTestingHelper;
+import com.ning.billing.util.clock.Clock;
+import com.ning.billing.util.clock.ClockMock;
+import com.ning.billing.util.glue.BusModule;
+import com.ning.billing.util.glue.BusModule.BusType;
+
+@Guice(modules = TestPersistentEventBus.PersistentBusModuleTest.class)
+public class TestPersistentEventBus extends TestEventBusBase {
+    
+    @Inject
+    private MysqlTestingHelper helper;
+    
+    @BeforeClass(groups = {"slow"})
+    public void setup() throws Exception {
+        helper.startMysql();
+        final String ddl = IOUtils.toString(TestPersistentEventBus.class.getResourceAsStream("/com/ning/billing/util/ddl.sql"));
+        helper.initDb(ddl);
+        cleanup();
+        super.setup();
+    }
+    
+    @BeforeMethod(groups = {"slow"})
+    public void cleanup() {
+        helper.cleanupTable("bus_events");
+        helper.cleanupTable("claimed_bus_events");
+    }
+    
+    public static class PersistentBusModuleTest extends AbstractModule {
+
+        @Override
+        protected void configure() {
+            
+            //System.setProperty("com.ning.billing.dbi.test.useLocalDb", "true");
+
+            bind(Clock.class).to(ClockMock.class).asEagerSingleton();
+            bind(ClockMock.class).asEagerSingleton();
+
+            final MysqlTestingHelper helper = new MysqlTestingHelper();
+            bind(MysqlTestingHelper.class).toInstance(helper);
+            if (helper.isUsingLocalInstance()) {
+                bind(IDBI.class).toProvider(DBIProvider.class).asEagerSingleton();
+                final DbiConfig config = new ConfigurationObjectFactory(System.getProperties()).build(DbiConfig.class);
+                bind(DbiConfig.class).toInstance(config);
+            } else {
+                final IDBI dbi = helper.getDBI();
+                bind(IDBI.class).toInstance(dbi);
+            }
+            install(new BusModule(BusType.PERSISTENT));
+        }
+    }
+    
+    @Test(groups = {"slow"})
+    public void testSimple() {
+        super.testSimple();
+    }
+    
+    // Until Guava fixes exception handling, r13?
+    @Test(groups={"slow"}, enabled=false)
+    public void testSimpleWithException() {
+        super.testSimpleWithException();
+        
+    }
+ 
+}
diff --git a/util/src/test/java/com/ning/billing/util/callcontext/TestCallContext.java b/util/src/test/java/com/ning/billing/util/callcontext/TestCallContext.java
index b632f2e..0185a69 100644
--- a/util/src/test/java/com/ning/billing/util/callcontext/TestCallContext.java
+++ b/util/src/test/java/com/ning/billing/util/callcontext/TestCallContext.java
@@ -16,25 +16,27 @@
 
 package com.ning.billing.util.callcontext;
 
+import java.util.UUID;
+
 import com.ning.billing.util.clock.DefaultClock;
 import org.joda.time.DateTime;
 
 public class TestCallContext implements CallContext {
+	
     private final String userName;
     private final DateTime updatedDate;
     private final DateTime createdDate;
-
+    private final UUID userToken;
+    
     public TestCallContext(String userName) {
-        this.userName = userName;
-        DateTime now = new DefaultClock().getUTCNow();
-        this.updatedDate = now;
-        this.createdDate = now;
+    	this(userName, new DefaultClock().getUTCNow(), new DefaultClock().getUTCNow());
     }
 
     public TestCallContext(String userName, DateTime createdDate, DateTime updatedDate) {
         this.userName = userName;
         this.createdDate = createdDate;
         this.updatedDate = updatedDate;
+        this.userToken = UUID.randomUUID();
     }
 
     @Override
@@ -61,4 +63,9 @@ public class TestCallContext implements CallContext {
     public DateTime getUpdatedDate() {
         return updatedDate;
     }
+
+	@Override
+	public UUID getUserToken() {
+		return userToken;
+	}
 }
diff --git a/util/src/test/java/com/ning/billing/util/clock/ClockMock.java b/util/src/test/java/com/ning/billing/util/clock/ClockMock.java
index 8787cd1..ab702cb 100644
--- a/util/src/test/java/com/ning/billing/util/clock/ClockMock.java
+++ b/util/src/test/java/com/ning/billing/util/clock/ClockMock.java
@@ -16,141 +16,134 @@
 
 package com.ning.billing.util.clock;
 
-import com.ning.billing.catalog.api.Duration;
 import org.joda.time.DateTime;
 import org.joda.time.DateTimeZone;
+import org.joda.time.Days;
+import org.joda.time.Months;
+import org.joda.time.MutablePeriod;
+import org.joda.time.Period;
+import org.joda.time.ReadablePeriod;
+import org.joda.time.Weeks;
+import org.joda.time.Years;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.util.ArrayList;
-import java.util.List;
-
-// STEPH should really be in tests but not accessible from other sub modules
-public class ClockMock extends DefaultClock {
+import com.ning.billing.catalog.api.Duration;
+import com.ning.billing.catalog.api.TimeUnit;
 
+public class ClockMock implements Clock {
+    
+    private MutablePeriod delta = new MutablePeriod();
     private static final Logger log = LoggerFactory.getLogger(ClockMock.class);
 
-    private enum DeltaType {
-        DELTA_NONE,
-        DELTA_DURATION,
-        DELTA_ABS
-    }
-
-    private long deltaFromRealityMs;
-    private List<Duration> deltaFromRealityDuration;
-    private long deltaFromRealityDurationEpsilon;
-    private DeltaType deltaType;
-
-    public ClockMock() {
-        deltaType = DeltaType.DELTA_NONE;
-        deltaFromRealityMs = 0;
-        deltaFromRealityDurationEpsilon = 0;
-        deltaFromRealityDuration = null;
-    }
-
+     
     @Override
     public synchronized DateTime getNow(DateTimeZone tz) {
-        return adjust(super.getNow(tz));
+        return getUTCNow().toDateTime(tz);
     }
 
     @Override
     public synchronized DateTime getUTCNow() {
-        return getNow(DateTimeZone.UTC);
+        return truncate(adjust(now()));
+    }
+    
+    private DateTime adjust(DateTime now) {
+        return now.plus(delta);
     }
 
-    private void logClockAdjustment(DateTime prev, DateTime next) {
-        log.info(String.format("            ************      ADJUSTING CLOCK FROM %s to %s     ********************", prev, next));
+    public synchronized void setTime(DateTime time) {
+        DateTime prev = getUTCNow();
+        delta = new MutablePeriod(now(), time);
+        logChange(prev);
+    }
+    
+    public synchronized void addDays(int days) {
+        adjustTo(Days.days(days));
+    }
+    
+    public synchronized void addWeeks(int weeks) {
+        adjustTo(Weeks.weeks(weeks));
+    }
+    
+    public synchronized void addMonths(int months) {
+        adjustTo(Months.months(months));
+    }
+    
+    public synchronized void addYears(int years) {
+        adjustTo(Years.years(years));
+    }
+    
+    public synchronized void reset() {
+        delta = new MutablePeriod();
+    }
+    
+    @Override
+    public String toString() {
+        return getUTCNow().toString();
+    }
+    
+    private void adjustTo(ReadablePeriod period) {
+        DateTime prev = getUTCNow();
+        delta.add(period);
+        logChange(prev);
+    }
+    
+    private void logChange(DateTime prev) {     
+        DateTime now = getUTCNow();
+        log.info(String.format("            ************      ADJUSTING CLOCK FROM %s to %s     ********************", prev, now));
+    }
+    
+    private DateTime now() {
+        return new DateTime(DateTimeZone.UTC);
     }
 
-    public synchronized void setDeltaFromReality(Duration delta, long epsilon) {
+    private DateTime truncate(DateTime time) {
+        return time.minus(time.getMillisOfSecond());
+    }
+   
+    //
+    //Backward compatibility stuff
+    //
+    public synchronized void setDeltaFromReality(Duration duration, long epsilon) {
         DateTime prev = getUTCNow();
-        deltaType = DeltaType.DELTA_DURATION;
-        deltaFromRealityDuration = new ArrayList<Duration>();
-        deltaFromRealityDuration.add(delta);
-        deltaFromRealityDurationEpsilon = epsilon;
-        deltaFromRealityMs = 0;
-        logClockAdjustment(prev, getUTCNow());
+        delta.addMillis((int)epsilon);
+        addDeltaFromReality(duration);
+        logChange(prev);
+        
     }
 
     public synchronized void addDeltaFromReality(Duration delta) {
-        DateTime prev = getUTCNow();
-        if (deltaType != DeltaType.DELTA_DURATION) {
-            throw new RuntimeException("ClockMock should be set with type DELTA_DURATION");
-        }
-        deltaFromRealityDuration.add(delta);
-        logClockAdjustment(prev, getUTCNow());
+        adjustTo(periodFromDuration(delta));
     }
 
     public synchronized void setDeltaFromReality(long delta) {
-        DateTime prev = getUTCNow();
-        deltaType = DeltaType.DELTA_ABS;
-        deltaFromRealityDuration = null;
-        deltaFromRealityDurationEpsilon = 0;
-        deltaFromRealityMs = delta;
-        logClockAdjustment(prev, getUTCNow());
+        adjustTo(new Period(delta));
     }
 
     public synchronized void addDeltaFromReality(long delta) {
-        DateTime prev = getUTCNow();
-        if (deltaType != DeltaType.DELTA_ABS) {
-            throw new RuntimeException("ClockMock should be set with type DELTA_ABS");
-        }
-        deltaFromRealityDuration = null;
-        deltaFromRealityDurationEpsilon = 0;
-        deltaFromRealityMs += delta;
-        logClockAdjustment(prev, getUTCNow());
+        adjustTo(new Period(delta));
     }
 
     public synchronized void resetDeltaFromReality() {
-        deltaType = DeltaType.DELTA_NONE;
-        deltaFromRealityDuration = null;
-        deltaFromRealityDurationEpsilon = 0;
-        deltaFromRealityMs = 0;
-    }
-
-    private DateTime adjust(DateTime realNow) {
-        switch(deltaType) {
-            case DELTA_NONE:
-                return realNow;
-            case DELTA_ABS:
-                return adjustFromAbsolute(realNow);
-            case DELTA_DURATION:
-                return adjustFromDuration(realNow);
-            default:
-                return null;
-        }
+        reset();
     }
+    
+    public ReadablePeriod periodFromDuration(Duration duration) {
+        if (duration.getUnit() != TimeUnit.UNLIMITED) {return new Period();}
 
-    private DateTime adjustFromDuration(DateTime input) {
-
-        DateTime result = input;
-        for (Duration cur : deltaFromRealityDuration) {
-            switch (cur.getUnit()) {
+        switch (duration.getUnit()) {
             case DAYS:
-                result = result.plusDays(cur.getNumber());
-                break;
-
+                return Days.days(duration.getNumber());
             case MONTHS:
-                result = result.plusMonths(cur.getNumber());
-                break;
-
+                return Months.months(duration.getNumber());
             case YEARS:
-                result = result.plusYears(cur.getNumber());
-                break;
-
+                return Years.years(duration.getNumber());
             case UNLIMITED:
-            default:
-                throw new RuntimeException("ClockMock is adjusting an unlimited time period");
-            }
-        }
-        if (deltaFromRealityDurationEpsilon != 0) {
-            result = result.plus(deltaFromRealityDurationEpsilon);
+                return Years.years(100);
+           default:
+                return new Period();
         }
-        return result;
-    }
-
-    private DateTime adjustFromAbsolute(DateTime input) {
-        return truncateMs(input.plus(deltaFromRealityMs));
     }
+    
 
 }
diff --git a/util/src/test/java/com/ning/billing/util/clock/MockClockModule.java b/util/src/test/java/com/ning/billing/util/clock/MockClockModule.java
index 89de280..ece94b0 100644
--- a/util/src/test/java/com/ning/billing/util/clock/MockClockModule.java
+++ b/util/src/test/java/com/ning/billing/util/clock/MockClockModule.java
@@ -23,7 +23,9 @@ public class MockClockModule extends AbstractModule {
 
 	@Override
 	protected void configure() {
-		bind(Clock.class).to(ClockMock.class).asEagerSingleton();
+	    bind(Clock.class).to(ClockMock.class).asEagerSingleton();
+        bind(ClockMock.class).asEagerSingleton();
 	}
 
 }
+ 
\ No newline at end of file
diff --git a/util/src/test/java/com/ning/billing/util/clock/OldClockMock.java b/util/src/test/java/com/ning/billing/util/clock/OldClockMock.java
new file mode 100644
index 0000000..b31db77
--- /dev/null
+++ b/util/src/test/java/com/ning/billing/util/clock/OldClockMock.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright 2010-2011 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.util.clock;
+
+import com.ning.billing.catalog.api.Duration;
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+
+// STEPH should really be in tests but not accessible from other sub modules
+public class OldClockMock extends DefaultClock {
+
+    private static final Logger log = LoggerFactory.getLogger(OldClockMock.class);
+
+    private enum DeltaType {
+        DELTA_NONE,
+        DELTA_DURATION,
+        DELTA_ABS
+    }
+
+    private long deltaFromRealityMs;
+    private List<Duration> deltaFromRealityDuration;
+    private long deltaFromRealityDurationEpsilon;
+    private DeltaType deltaType;
+
+    public OldClockMock() {
+        deltaType = DeltaType.DELTA_NONE;
+        deltaFromRealityMs = 0;
+        deltaFromRealityDurationEpsilon = 0;
+        deltaFromRealityDuration = null;
+    }
+
+    @Override
+    public synchronized DateTime getNow(DateTimeZone tz) {
+        return adjust(super.getNow(tz));
+    }
+
+    @Override
+    public synchronized DateTime getUTCNow() {
+        return getNow(DateTimeZone.UTC);
+    }
+
+    private void logClockAdjustment(DateTime prev, DateTime next) {
+        log.info(String.format("            ************      ADJUSTING CLOCK FROM %s to %s     ********************", prev, next));
+    }
+
+    public synchronized void setDeltaFromReality(Duration delta, long epsilon) {
+        DateTime prev = getUTCNow();
+        deltaType = DeltaType.DELTA_DURATION;
+        deltaFromRealityDuration = new ArrayList<Duration>();
+        deltaFromRealityDuration.add(delta);
+        deltaFromRealityDurationEpsilon = epsilon;
+        deltaFromRealityMs = 0;
+        logClockAdjustment(prev, getUTCNow());
+    }
+
+    public synchronized void addDeltaFromReality(Duration delta) {
+        DateTime prev = getUTCNow();
+        if (deltaType != DeltaType.DELTA_DURATION) {
+            throw new RuntimeException("ClockMock should be set with type DELTA_DURATION");
+        }
+        deltaFromRealityDuration.add(delta);
+        logClockAdjustment(prev, getUTCNow());
+    }
+
+    public synchronized void setDeltaFromReality(long delta) {
+        DateTime prev = getUTCNow();
+        deltaType = DeltaType.DELTA_ABS;
+        deltaFromRealityDuration = null;
+        deltaFromRealityDurationEpsilon = 0;
+        deltaFromRealityMs = delta;
+        logClockAdjustment(prev, getUTCNow());
+    }
+
+    public synchronized void addDeltaFromReality(long delta) {
+        DateTime prev = getUTCNow();
+        if (deltaType != DeltaType.DELTA_ABS) {
+            throw new RuntimeException("ClockMock should be set with type DELTA_ABS");
+        }
+        deltaFromRealityDuration = null;
+        deltaFromRealityDurationEpsilon = 0;
+        deltaFromRealityMs += delta;
+        logClockAdjustment(prev, getUTCNow());
+    }
+
+    public synchronized void resetDeltaFromReality() {
+        deltaType = DeltaType.DELTA_NONE;
+        deltaFromRealityDuration = null;
+        deltaFromRealityDurationEpsilon = 0;
+        deltaFromRealityMs = 0;
+    }
+
+    private DateTime adjust(DateTime realNow) {
+        switch(deltaType) {
+            case DELTA_NONE:
+                return realNow;
+            case DELTA_ABS:
+                return adjustFromAbsolute(realNow);
+            case DELTA_DURATION:
+                return adjustFromDuration(realNow);
+            default:
+                return null;
+        }
+    }
+
+    private DateTime adjustFromDuration(DateTime input) {
+
+        DateTime result = input;
+        for (Duration cur : deltaFromRealityDuration) {
+            switch (cur.getUnit()) {
+            case DAYS:
+                result = result.plusDays(cur.getNumber());
+                break;
+
+            case MONTHS:
+                result = result.plusMonths(cur.getNumber());
+                break;
+
+            case YEARS:
+                result = result.plusYears(cur.getNumber());
+                break;
+
+            case UNLIMITED:
+            default:
+                throw new RuntimeException("ClockMock is adjusting an unlimited time period");
+            }
+        }
+        if (deltaFromRealityDurationEpsilon != 0) {
+            result = result.plus(deltaFromRealityDurationEpsilon);
+        }
+        return result;
+    }
+
+    private DateTime adjustFromAbsolute(DateTime input) {
+        return truncateMs(input.plus(deltaFromRealityMs));
+    }
+
+    @Override
+    public String toString() {
+        return getUTCNow().toString();
+    }
+
+    
+}
diff --git a/util/src/test/java/com/ning/billing/util/customfield/TestFieldStore.java b/util/src/test/java/com/ning/billing/util/customfield/TestFieldStore.java
index 8a2385b..7a31ab8 100644
--- a/util/src/test/java/com/ning/billing/util/customfield/TestFieldStore.java
+++ b/util/src/test/java/com/ning/billing/util/customfield/TestFieldStore.java
@@ -19,18 +19,14 @@ package com.ning.billing.util.customfield;
 import java.io.IOException;
 import java.util.UUID;
 
-import com.google.inject.Guice;
-import com.google.inject.Injector;
-import com.google.inject.Stage;
 import com.ning.billing.util.callcontext.CallContext;
 import com.ning.billing.util.callcontext.CallOrigin;
 import com.ning.billing.util.callcontext.UserType;
 import com.ning.billing.util.callcontext.DefaultCallContextFactory;
-import com.ning.billing.util.clock.Clock;
-import com.ning.billing.util.clock.MockClockModule;
+import com.ning.billing.util.clock.ClockMock;
 import com.ning.billing.util.customfield.dao.AuditedCustomFieldDao;
 import com.ning.billing.util.customfield.dao.CustomFieldDao;
-import com.ning.billing.util.glue.FieldStoreModule;
+import com.ning.billing.util.dao.ObjectType;
 import org.apache.commons.io.IOUtils;
 import org.skife.jdbi.v2.IDBI;
 import org.slf4j.Logger;
@@ -62,13 +58,8 @@ public class TestFieldStore {
             helper.initDb(utilDdl);
 
             dbi = helper.getDBI();
-            customFieldDao = new AuditedCustomFieldDao();
-
-            FieldStoreModule module = new FieldStoreModule();
-            final Injector injector = Guice.createInjector(Stage.DEVELOPMENT, module, new MockClockModule());
-            Clock clock = injector.getInstance(Clock.class);
-            context = new DefaultCallContextFactory(clock).createCallContext("Fezzik", CallOrigin.TEST, UserType.TEST);
-
+            customFieldDao = new AuditedCustomFieldDao(dbi);
+            context = new DefaultCallContextFactory(new ClockMock()).createCallContext("Fezzik", CallOrigin.TEST, UserType.TEST);
         }
         catch (Throwable t) {
             log.error("Setup failed", t);
@@ -79,13 +70,15 @@ public class TestFieldStore {
     @AfterClass(groups = {"util", "slow"})
     public void stopMysql()
     {
-        helper.stopMysql();
+        if (helper!= null) {
+            helper.stopMysql();
+        }
     }
 
     @Test
     public void testFieldStore() {
         final UUID id = UUID.randomUUID();
-        final String objectType = "Test widget";
+        final ObjectType objectType = ObjectType.ACCOUNT;
 
         final FieldStore fieldStore1 = new DefaultFieldStore(id, objectType);
 
@@ -94,7 +87,7 @@ public class TestFieldStore {
         fieldStore1.setValue(fieldName, fieldValue);
 
         CustomFieldSqlDao customFieldSqlDao = dbi.onDemand(CustomFieldSqlDao.class);
-        customFieldDao.saveFields(customFieldSqlDao, id, objectType, fieldStore1.getEntityList(), context);
+        customFieldDao.saveEntitiesFromTransaction(customFieldSqlDao, id, objectType, fieldStore1.getEntityList(), context);
 
         final FieldStore fieldStore2 = DefaultFieldStore.create(id, objectType);
         fieldStore2.add(customFieldSqlDao.load(id.toString(), objectType));
@@ -104,7 +97,7 @@ public class TestFieldStore {
         fieldValue = "Cape Canaveral";
         fieldStore2.setValue(fieldName, fieldValue);
         assertEquals(fieldStore2.getValue(fieldName), fieldValue);
-        customFieldDao.saveFields(customFieldSqlDao, id, objectType, fieldStore2.getEntityList(), context);
+        customFieldDao.saveEntitiesFromTransaction(customFieldSqlDao, id, objectType, fieldStore2.getEntityList(), context);
 
         final FieldStore fieldStore3 = DefaultFieldStore.create(id, objectType);
         assertEquals(fieldStore3.getValue(fieldName), null);
diff --git a/util/src/test/java/com/ning/billing/util/email/DefaultCatalogTranslationTest.java b/util/src/test/java/com/ning/billing/util/email/DefaultCatalogTranslationTest.java
new file mode 100644
index 0000000..0be05ac
--- /dev/null
+++ b/util/src/test/java/com/ning/billing/util/email/DefaultCatalogTranslationTest.java
@@ -0,0 +1,84 @@
+package com.ning.billing.util.email;/*
+ * Copyright 2010-2011 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.
+ */
+
+import com.ning.billing.util.template.translation.DefaultCatalogTranslator;
+import com.ning.billing.util.template.translation.Translator;
+import com.ning.billing.util.template.translation.TranslatorConfig;
+import org.skife.config.ConfigurationObjectFactory;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import java.util.Locale;
+
+import static org.testng.Assert.assertEquals;
+
+@Test(groups = {"fast", "email"})
+public class DefaultCatalogTranslationTest {
+    private Translator translation;
+
+    @BeforeClass(groups={"fast", "email"})
+    public void setup() {
+        final TranslatorConfig config = new ConfigurationObjectFactory(System.getProperties()).build(TranslatorConfig.class);
+        translation = new DefaultCatalogTranslator(config);
+    }
+
+    @Test(groups = {"fast", "email"})
+    public void testInitialization() {
+        String ningPlusText = "ning-plus";
+        String ningProText = "ning-pro";
+        String badText = "Bad text";
+
+        assertEquals(translation.getTranslation(Locale.US, ningPlusText), "Plus");
+        assertEquals(translation.getTranslation(Locale.US, ningProText), "Pro");
+        assertEquals(translation.getTranslation(Locale.US, badText), badText);
+
+        assertEquals(translation.getTranslation(Locale.CANADA_FRENCH, ningPlusText), "Plus en francais");
+        assertEquals(translation.getTranslation(Locale.CANADA_FRENCH, ningProText), "Pro");
+        assertEquals(translation.getTranslation(Locale.CANADA_FRENCH, badText), badText);
+
+        assertEquals(translation.getTranslation(Locale.CHINA, ningPlusText), "Plus");
+        assertEquals(translation.getTranslation(Locale.CHINA, ningProText), "Pro");
+        assertEquals(translation.getTranslation(Locale.CHINA, badText), badText);
+    }
+
+    @Test
+    public void testExistingTranslation() {
+        // if the translation exists, return the translation
+        String originalText = "ning-plus";
+        assertEquals(translation.getTranslation(Locale.US,  originalText), "Plus");
+    }
+
+    @Test
+    public void testMissingTranslation() {
+        // if the translation is missing from the file, return the original text
+        String originalText = "missing translation";
+        assertEquals(translation.getTranslation(Locale.US, originalText), originalText);
+    }
+
+    @Test
+    public void testMissingTranslationFileWithEnglishText() {
+        // if the translation file doesn't exist, return the "English" translation
+        String originalText = "ning-plus";
+        assertEquals(translation.getTranslation(Locale.CHINA, originalText), "Plus");
+    }
+
+    @Test
+    public void testMissingFileAndText() {
+        // if the file is missing, and the "English" translation is missing, return the original text
+        String originalText = "missing translation";
+        assertEquals(translation.getTranslation(Locale.CHINA, originalText), originalText);
+    }
+}
diff --git a/util/src/test/java/com/ning/billing/util/email/EmailSenderTest.java b/util/src/test/java/com/ning/billing/util/email/EmailSenderTest.java
new file mode 100644
index 0000000..1685628
--- /dev/null
+++ b/util/src/test/java/com/ning/billing/util/email/EmailSenderTest.java
@@ -0,0 +1,42 @@
+package com.ning.billing.util.email;/*
+ * Copyright 2010-2011 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.
+ */
+
+import org.skife.config.ConfigurationObjectFactory;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Test(groups = {"slow", "email"})
+public class EmailSenderTest {
+    private EmailConfig config;
+
+    @BeforeClass
+    public void setup() {
+        config = new ConfigurationObjectFactory(System.getProperties()).build(EmailConfig.class);
+    }
+
+    @Test
+    public void testSendEmail() throws Exception {
+        String html = "<html><body><h1>Test E-mail</h1></body></html>";
+        List<String> recipients = new ArrayList<String>();
+        recipients.add("killbill.ning@gmail.com");
+
+        EmailSender sender = new DefaultEmailSender(config);
+        sender.sendSecureEmail(recipients, null, "Test message", html);
+    }
+}
diff --git a/util/src/test/java/com/ning/billing/util/globallocker/TestMysqlGlobalLocker.java b/util/src/test/java/com/ning/billing/util/globallocker/TestMysqlGlobalLocker.java
index 6f2facd..be642f8 100644
--- a/util/src/test/java/com/ning/billing/util/globallocker/TestMysqlGlobalLocker.java
+++ b/util/src/test/java/com/ning/billing/util/globallocker/TestMysqlGlobalLocker.java
@@ -58,7 +58,9 @@ public class TestMysqlGlobalLocker {
 
     @AfterClass(groups = "slow")
     public void tearDown() {
-        helper.stopMysql();
+        if (helper != null) {
+            helper.stopMysql();
+        }
     }
 
     // Used as a manual test to validate the simple DAO by stepping through that locking is done and release correctly
diff --git a/util/src/test/java/com/ning/billing/util/notificationq/dao/TestNotificationSqlDao.java b/util/src/test/java/com/ning/billing/util/notificationq/dao/TestNotificationSqlDao.java
index 891cadf..7c61363 100644
--- a/util/src/test/java/com/ning/billing/util/notificationq/dao/TestNotificationSqlDao.java
+++ b/util/src/test/java/com/ning/billing/util/notificationq/dao/TestNotificationSqlDao.java
@@ -20,11 +20,10 @@ import java.io.IOException;
 import java.sql.SQLException;
 import java.util.List;
 import java.util.UUID;
-import java.util.concurrent.atomic.AtomicInteger;
+
 import org.apache.commons.io.IOUtils;
 import org.joda.time.DateTime;
 import org.joda.time.DateTimeZone;
-import org.skife.jdbi.v2.DBI;
 import org.skife.jdbi.v2.Handle;
 import org.skife.jdbi.v2.IDBI;
 import org.skife.jdbi.v2.tweak.HandleCallback;
@@ -39,8 +38,8 @@ import com.google.inject.Inject;
 import com.ning.billing.dbi.MysqlTestingHelper;
 import com.ning.billing.util.notificationq.DefaultNotification;
 import com.ning.billing.util.notificationq.Notification;
-import com.ning.billing.util.notificationq.NotificationLifecycle.NotificationLifecycleState;
 import com.ning.billing.util.notificationq.dao.NotificationSqlDao.NotificationSqlMapper;
+import com.ning.billing.util.queue.PersistentQueueEntryLifecycle.PersistentQueueEntryLifecycleState;
 
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertNotNull;
@@ -49,8 +48,8 @@ import static org.testng.Assert.assertNotNull;
 @Guice(modules = TestNotificationSqlDao.TestNotificationSqlDaoModule.class)
 public class TestNotificationSqlDao {
 
-    private static AtomicInteger sequenceId = new AtomicInteger();
-
+    private final static String hostname = "Yop";
+    
     @Inject
     private IDBI dbi;
 
@@ -78,7 +77,9 @@ public class TestNotificationSqlDao {
     @AfterSuite(groups = "slow")
     public void stopMysql()
     {
-        helper.stopMysql();
+        if (helper != null) {
+            helper.stopMysql();
+        }
     }
 
 
@@ -102,12 +103,12 @@ public class TestNotificationSqlDao {
 
         String notificationKey = UUID.randomUUID().toString();
         DateTime effDt = new DateTime();
-        Notification notif = new DefaultNotification("testBasic",notificationKey, effDt);
+        Notification notif = new DefaultNotification("testBasic", hostname, notificationKey, effDt);
         dao.insertNotification(notif);
 
         Thread.sleep(1000);
         DateTime now = new DateTime();
-        List<Notification> notifications = dao.getReadyNotifications(now.toDate(), 3, "testBasic");
+        List<Notification> notifications = dao.getReadyNotifications(now.toDate(), hostname, 3, "testBasic");
         assertNotNull(notifications);
         assertEquals(notifications.size(), 1);
 
@@ -115,28 +116,28 @@ public class TestNotificationSqlDao {
         assertEquals(notification.getNotificationKey(), notificationKey);
         validateDate(notification.getEffectiveDate(), effDt);
         assertEquals(notification.getOwner(), null);
-        assertEquals(notification.getProcessingState(), NotificationLifecycleState.AVAILABLE);
+        assertEquals(notification.getProcessingState(), PersistentQueueEntryLifecycleState.AVAILABLE);
         assertEquals(notification.getNextAvailableDate(), null);
 
         DateTime nextAvailable = now.plusMinutes(5);
-        int res = dao.claimNotification(ownerId, nextAvailable.toDate(), notification.getId(), now.toDate());
+        int res = dao.claimNotification(ownerId, nextAvailable.toDate(), notification.getId().toString(), now.toDate());
         assertEquals(res, 1);
-        dao.insertClaimedHistory(sequenceId.incrementAndGet(), ownerId, now.toDate(), notification.getUUID().toString());
+        dao.insertClaimedHistory(ownerId, now.toDate(), notification.getId().toString());
 
-        notification = fetchNotification(notification.getUUID().toString());
+        notification = fetchNotification(notification.getId().toString());
         assertEquals(notification.getNotificationKey(), notificationKey);
         validateDate(notification.getEffectiveDate(), effDt);
         assertEquals(notification.getOwner().toString(), ownerId);
-        assertEquals(notification.getProcessingState(), NotificationLifecycleState.IN_PROCESSING);
+        assertEquals(notification.getProcessingState(), PersistentQueueEntryLifecycleState.IN_PROCESSING);
         validateDate(notification.getNextAvailableDate(), nextAvailable);
 
-        dao.clearNotification(notification.getId(), ownerId);
+        dao.clearNotification(notification.getId().toString(), ownerId);
 
-        notification = fetchNotification(notification.getUUID().toString());
+        notification = fetchNotification(notification.getId().toString());
         assertEquals(notification.getNotificationKey(), notificationKey);
         validateDate(notification.getEffectiveDate(), effDt);
         //assertEquals(notification.getOwner(), null);
-        assertEquals(notification.getProcessingState(), NotificationLifecycleState.PROCESSED);
+        assertEquals(notification.getProcessingState(), PersistentQueueEntryLifecycleState.PROCESSED);
         validateDate(notification.getNextAvailableDate(), nextAvailable);
 
     }
@@ -147,18 +148,19 @@ public class TestNotificationSqlDao {
             @Override
             public Notification withHandle(Handle handle) throws Exception {
                 Notification res = handle.createQuery("   select" +
-                        " id " +
-                		", notification_id" +
+                        " record_id " +
+                		", id" +
                 		", notification_key" +
-                		", created_dt" +
-                		", effective_dt" +
+                		", created_date" +
+                		", creating_owner" +
+                		", effective_date" +
                 		", queue_name" +
                 		", processing_owner" +
-                		", processing_available_dt" +
+                		", processing_available_date" +
                 		", processing_state" +
                 		"    from notifications " +
                 		" where " +
-                		" notification_id = '" + notificationId + "';")
+                		" id = '" + notificationId + "';")
                 		.map(new NotificationSqlMapper())
                 		.first();
                 return res;
@@ -195,12 +197,6 @@ public class TestNotificationSqlDao {
             bind(MysqlTestingHelper.class).toInstance(helper);
             IDBI dbi = helper.getDBI();
             bind(IDBI.class).toInstance(dbi);
-
-            /*
-            bind(DBI.class).toProvider(DBIProvider.class).asEagerSingleton();
-            final DbiConfig config = new ConfigurationObjectFactory(System.getProperties()).build(DbiConfig.class);
-            bind(DbiConfig.class).toInstance(config);
-            */
         }
     }
 }
diff --git a/util/src/test/java/com/ning/billing/util/notificationq/MockNotificationQueue.java b/util/src/test/java/com/ning/billing/util/notificationq/MockNotificationQueue.java
index 1c40988..8e9cce4 100644
--- a/util/src/test/java/com/ning/billing/util/notificationq/MockNotificationQueue.java
+++ b/util/src/test/java/com/ning/billing/util/notificationq/MockNotificationQueue.java
@@ -21,13 +21,15 @@ import java.util.Comparator;
 import java.util.Iterator;
 import java.util.List;
 import java.util.TreeSet;
+import java.util.UUID;
 
 import org.joda.time.DateTime;
 import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
 
+import com.ning.billing.config.NotificationConfig;
 import com.ning.billing.util.clock.Clock;
-import com.ning.billing.util.notificationq.NotificationLifecycle.NotificationLifecycleState;
 import com.ning.billing.util.notificationq.NotificationQueueService.NotificationQueueHandler;
+import com.ning.billing.util.queue.PersistentQueueEntryLifecycle.PersistentQueueEntryLifecycleState;
 
 public class MockNotificationQueue extends NotificationQueueBase implements NotificationQueue {
     private final TreeSet<Notification> notifications;
@@ -48,7 +50,7 @@ public class MockNotificationQueue extends NotificationQueueBase implements Noti
 
     @Override
     public void recordFutureNotification(DateTime futureNotificationTime, NotificationKey notificationKey) {
-        Notification notification = new DefaultNotification("MockQueue", notificationKey.toString(), futureNotificationTime);
+        Notification notification = new DefaultNotification("MockQueue", hostname, notificationKey.toString(), futureNotificationTime);
         synchronized(notifications) {
             notifications.add(notification);
         }
@@ -65,7 +67,7 @@ public class MockNotificationQueue extends NotificationQueueBase implements Noti
         List<Notification> result = new ArrayList<Notification>();
 
         for (Notification notification : notifications) {
-            if (notification.getProcessingState() == NotificationLifecycleState.AVAILABLE) {
+            if (notification.getProcessingState() == PersistentQueueEntryLifecycleState.AVAILABLE) {
                 result.add(notification);
             }
         }
@@ -73,7 +75,7 @@ public class MockNotificationQueue extends NotificationQueueBase implements Noti
     }
 
     @Override
-    protected int doProcessEvents(int sequenceId) {
+    public int doProcessEvents() {
 
         int result = 0;
 
@@ -94,7 +96,7 @@ public class MockNotificationQueue extends NotificationQueueBase implements Noti
         result = readyNotifications.size();
         for (Notification cur : readyNotifications) {
             handler.handleReadyNotification(cur.getNotificationKey(), cur.getEffectiveDate());
-            DefaultNotification processedNotification = new DefaultNotification(-1L, cur.getUUID(), hostname, "MockQueue", clock.getUTCNow().plus(config.getDaoClaimTimeMs()), NotificationLifecycleState.PROCESSED, cur.getNotificationKey(), cur.getEffectiveDate());
+            DefaultNotification processedNotification = new DefaultNotification(-1L, cur.getId(), hostname, hostname, "MockQueue", clock.getUTCNow().plus(CLAIM_TIME_MS), PersistentQueueEntryLifecycleState.PROCESSED, cur.getNotificationKey(), cur.getEffectiveDate());
             oldNotifications.add(cur);
             processedNotifications.add(processedNotification);
         }
@@ -109,4 +111,20 @@ public class MockNotificationQueue extends NotificationQueueBase implements Noti
         }
         return result;
     }
+
+    @Override
+    public void removeNotificationsByKey(UUID key) {
+        List<Notification> toClearNotifications = new ArrayList<Notification>();
+        for (Notification notification : notifications) {
+            if (notification.getNotificationKey().equals(key.toString())) {
+                    toClearNotifications.add(notification);
+            }
+        }
+        synchronized(notifications) {
+            if (toClearNotifications.size() > 0) {
+                notifications.removeAll(toClearNotifications);
+            }
+        }
+        
+    }
 }
diff --git a/util/src/test/java/com/ning/billing/util/notificationq/MockNotificationQueueService.java b/util/src/test/java/com/ning/billing/util/notificationq/MockNotificationQueueService.java
index e9ce90e..9af43c1 100644
--- a/util/src/test/java/com/ning/billing/util/notificationq/MockNotificationQueueService.java
+++ b/util/src/test/java/com/ning/billing/util/notificationq/MockNotificationQueueService.java
@@ -17,6 +17,7 @@
 package com.ning.billing.util.notificationq;
 
 import com.google.inject.Inject;
+import com.ning.billing.config.NotificationConfig;
 import com.ning.billing.util.clock.Clock;
 
 public class MockNotificationQueueService extends NotificationQueueServiceBase {
diff --git a/util/src/test/java/com/ning/billing/util/notificationq/TestNotificationQueue.java b/util/src/test/java/com/ning/billing/util/notificationq/TestNotificationQueue.java
index fefbcdb..50f0a68 100644
--- a/util/src/test/java/com/ning/billing/util/notificationq/TestNotificationQueue.java
+++ b/util/src/test/java/com/ning/billing/util/notificationq/TestNotificationQueue.java
@@ -50,6 +50,7 @@ import com.google.common.collect.Collections2;
 import com.google.inject.AbstractModule;
 import com.google.inject.Inject;
 import com.google.inject.name.Names;
+import com.ning.billing.config.NotificationConfig;
 import com.ning.billing.dbi.MysqlTestingHelper;
 import com.ning.billing.util.clock.Clock;
 import com.ning.billing.util.clock.ClockMock;
@@ -91,7 +92,9 @@ public class TestNotificationQueue {
 
     @AfterClass(groups="slow")
     public void tearDown() {
-        helper.stopMysql();
+        if (helper != null) {
+            helper.stopMysql();
+        }
     }
 
     @BeforeTest(groups="slow")
@@ -108,6 +111,7 @@ public class TestNotificationQueue {
         });
         // Reset time to real value
         ((ClockMock) clock).resetDeltaFromReality();
+        eventsReceived=0;
     }
 
 
@@ -129,7 +133,7 @@ public class TestNotificationQueue {
                 synchronized (expectedNotifications) {
                     log.info("Handler received key: " + notificationKey);
 
-                    expectedNotifications.put(notificationKey.toString(), Boolean.TRUE);
+                    expectedNotifications.put(notificationKey, Boolean.TRUE);
                     expectedNotifications.notify();
                 }
             }
@@ -284,17 +288,9 @@ public class TestNotificationQueue {
                 return false;
             }
             @Override
-            public long getNotificationSleepTimeMs() {
+            public long getSleepTimeMs() {
                 return 10;
             }
-            @Override
-            public int getDaoMaxReadyEvents() {
-                return 1;
-            }
-            @Override
-            public long getDaoClaimTimeMs() {
-                return 60000;
-            }
         };
 
 
@@ -393,21 +389,89 @@ public class TestNotificationQueue {
                 return off;
             }
             @Override
-            public long getNotificationSleepTimeMs() {
+            public long getSleepTimeMs() {
                 return sleepTime;
             }
+        };
+    }
+    
+    
+    @Test(groups="slow")
+    public void testRemoveNotifications() throws InterruptedException {
+        
+        final UUID key = UUID.randomUUID();
+        final NotificationKey notificationKey = new NotificationKey() {
+            @Override
+            public String toString() {
+                return key.toString();
+            }
+        };        
+        final UUID key2 = UUID.randomUUID();
+        final NotificationKey notificationKey2 = new NotificationKey() {
             @Override
-            public int getDaoMaxReadyEvents() {
-                return maxReadyEvents;
+            public String toString() {
+                return key2.toString();
             }
+        };        
+
+        final DefaultNotificationQueue queue = new DefaultNotificationQueue(dbi, clock, "test-svc", "many",
+                new NotificationQueueHandler() {
             @Override
-            public long getDaoClaimTimeMs() {
-                return claimTimeMs;
+            public void handleReadyNotification(String key, DateTime eventDateTime) {
+                    if(key.equals(notificationKey) || key.equals(notificationKey2)) { //ignore stray events from other tests
+                        log.info("Received notification with key: " + notificationKey);
+                        eventsReceived++;
+                    }
             }
-        };
+        },
+        getNotificationConfig(false, 100, 10, 10000));
+
+
+        queue.startQueue();
+
+        final DateTime start = clock.getUTCNow().plusHours(1);
+        final int nextReadyTimeIncrementMs = 1000;
+ 
+        // add 3 events
+
+        dao.inTransaction(new Transaction<Void, DummySqlTest>() {
+            @Override
+            public Void inTransaction(DummySqlTest transactional,
+                    TransactionStatus status) throws Exception {
+
+                queue.recordFutureNotificationFromTransaction(transactional,
+                        start.plus(nextReadyTimeIncrementMs), notificationKey);
+                queue.recordFutureNotificationFromTransaction(transactional,
+                        start.plus(2 *nextReadyTimeIncrementMs), notificationKey);
+                queue.recordFutureNotificationFromTransaction(transactional,
+                        start.plus(3 * nextReadyTimeIncrementMs), notificationKey2);
+                return null;
+            }
+        });
+    
+    
+      queue.removeNotificationsByKey(key); // should remove 2 of the 3
+
+    // Move time in the future after the notification effectiveDate
+        ((ClockMock) clock).setDeltaFromReality(4000000 + nextReadyTimeIncrementMs * 3 );
+        
+        try {
+            await().atMost(10, TimeUnit.SECONDS).until(new Callable<Boolean>() {
+                @Override
+                public Boolean call() throws Exception {
+                    return eventsReceived >= 2;
+                }
+            });
+            Assert.fail("There should only have been only one event left in the queue we got: " + eventsReceived);
+        } catch (Exception e) {
+            // expected behavior
+        }
+        log.info("Received " + eventsReceived + " events");
+        queue.stopQueue();
     }
 
 
+
     public static class TestNotificationQueueModule extends AbstractModule {
         @Override
         protected void configure() {
diff --git a/util/src/test/java/com/ning/billing/util/tag/dao/MockTagDao.java b/util/src/test/java/com/ning/billing/util/tag/dao/MockTagDao.java
index 553bc3c..b64d7ff 100644
--- a/util/src/test/java/com/ning/billing/util/tag/dao/MockTagDao.java
+++ b/util/src/test/java/com/ning/billing/util/tag/dao/MockTagDao.java
@@ -16,11 +16,9 @@
 
 package com.ning.billing.util.tag.dao;
 
-import com.google.inject.Inject;
 import com.ning.billing.util.callcontext.CallContext;
-import com.ning.billing.util.clock.Clock;
+import com.ning.billing.util.dao.ObjectType;
 import com.ning.billing.util.tag.Tag;
-import org.joda.time.DateTime;
 import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
 
 import java.util.HashMap;
@@ -31,39 +29,27 @@ import java.util.UUID;
 
 public class MockTagDao implements TagDao {
     private Map<UUID, List<Tag>> tagStore = new HashMap<UUID, List<Tag>>();
-    private final Clock clock;
-
-    @Inject
-    public MockTagDao(Clock clock) {
-        this.clock = clock;
-    }
 
     @Override
-    public void saveTagsFromTransaction(final Transmogrifier dao, final UUID objectId, final String objectType,
+    public void saveEntitiesFromTransaction(final Transmogrifier dao, final UUID objectId, final ObjectType objectType,
                                         final List<Tag> tags, final CallContext context) {
         tagStore.put(objectId, tags);
     }
 
     @Override
-    public void saveTags(UUID objectId, String objectType, List<Tag> tags, CallContext context) {
-        tagStore.put(objectId, tags);
-    }
-
-    @Override
-    public List<Tag> loadTags(UUID objectId, String objectType) {
+    public List<Tag> loadEntities(UUID objectId, ObjectType objectType) {
         return tagStore.get(objectId);
     }
 
     @Override
-    public List<Tag> loadTagsFromTransaction(Transmogrifier dao, UUID objectId, String objectType) {
+    public List<Tag> loadEntitiesFromTransaction(Transmogrifier dao, UUID objectId, ObjectType objectType) {
         return tagStore.get(objectId);
     }
 
     @Override
-    public void addTag(final String tagName, final UUID objectId, final String objectType, final CallContext context) {
+    public void addTag(final String tagName, final UUID objectId, final ObjectType objectType, final CallContext context) {
         Tag tag = new Tag() {
             private UUID id = UUID.randomUUID();
-            private DateTime createdDate = clock.getUTCNow();
 
             @Override
             public String getTagDefinitionName() {
@@ -74,24 +60,13 @@ public class MockTagDao implements TagDao {
             public UUID getId() {
                 return id;
             }
-
-            @Override
-            public String getCreatedBy() {
-                return context.getUserName();
-            }
-
-            @Override
-            public DateTime getCreatedDate() {
-                return createdDate;
-            }
         };
 
-
         tagStore.get(objectId).add(tag);
     }
 
     @Override
-    public void removeTag(String tagName, UUID objectId, String objectType, CallContext context) {
+    public void removeTag(String tagName, UUID objectId, ObjectType objectType, CallContext context) {
         List<Tag> tags = tagStore.get(objectId);
         if (tags != null) {
             Iterator<Tag> tagIterator = tags.iterator();
diff --git a/util/src/test/java/com/ning/billing/util/tag/dao/MockTagDefinitionDao.java b/util/src/test/java/com/ning/billing/util/tag/dao/MockTagDefinitionDao.java
index adcb848..5b5956b 100644
--- a/util/src/test/java/com/ning/billing/util/tag/dao/MockTagDefinitionDao.java
+++ b/util/src/test/java/com/ning/billing/util/tag/dao/MockTagDefinitionDao.java
@@ -42,7 +42,7 @@ public class MockTagDefinitionDao implements TagDefinitionDao {
     @Override
     public TagDefinition create(final String definitionName, final String description,
                                 final CallContext context) throws TagDefinitionApiException {
-        TagDefinition tag = new DefaultTagDefinition(definitionName, description);
+        TagDefinition tag = new DefaultTagDefinition(definitionName, description, false);
 
         tags.put(definitionName, tag);
         return tag;
diff --git a/util/src/test/java/com/ning/billing/util/tag/TestTagStore.java b/util/src/test/java/com/ning/billing/util/tag/TestTagStore.java
index a8d353f..c9b40d1 100644
--- a/util/src/test/java/com/ning/billing/util/tag/TestTagStore.java
+++ b/util/src/test/java/com/ning/billing/util/tag/TestTagStore.java
@@ -21,12 +21,12 @@ import java.util.List;
 import java.util.Map;
 import java.util.UUID;
 
-import com.ning.billing.account.api.Account;
 import com.ning.billing.util.callcontext.CallContext;
 import com.ning.billing.util.callcontext.CallOrigin;
 import com.ning.billing.util.callcontext.UserType;
 import com.ning.billing.util.callcontext.DefaultCallContextFactory;
 import com.ning.billing.util.clock.Clock;
+import com.ning.billing.util.dao.ObjectType;
 import com.ning.billing.util.tag.dao.TagDao;
 import org.apache.commons.io.IOUtils;
 import org.joda.time.DateTime;
@@ -103,7 +103,9 @@ public class TestTagStore {
     @AfterClass(groups="slow")
     public void stopMysql()
     {
-        helper.stopMysql();
+        if (helper != null) {
+            helper.stopMysql();
+        }
     }
 
     private void cleanupTags() {
@@ -113,7 +115,7 @@ public class TestTagStore {
                 public Void withHandle(Handle handle) throws Exception {
                     handle.createScript("delete from tag_definitions").execute();
                     handle.createScript("delete from tag_definition_history").execute();
-                    handle.createScript("delete from tags").execute();
+                    handle.createScript("delete from tagStore").execute();
                     handle.createScript("delete from tag_history").execute();
                     return null;
                 }
@@ -125,13 +127,13 @@ public class TestTagStore {
     public void testTagCreationAndRetrieval() {
         UUID accountId = UUID.randomUUID();
 
-        TagStore tagStore = new DefaultTagStore(accountId, Account.ObjectType);
+        TagStore tagStore = new DefaultTagStore(accountId, ObjectType.ACCOUNT);
         Tag tag = new DescriptiveTag(testTag);
         tagStore.add(tag);
 
-        tagDao.saveTagsFromTransaction(dao, accountId, Account.ObjectType, tagStore.getEntityList(), context);
+        tagDao.saveEntitiesFromTransaction(dao, accountId, ObjectType.ACCOUNT, tagStore.getEntityList(), context);
 
-        List<Tag> savedTags = tagDao.loadTags(accountId, Account.ObjectType);
+        List<Tag> savedTags = tagDao.loadEntities(accountId, ObjectType.ACCOUNT);
         assertEquals(savedTags.size(), 1);
 
         Tag savedTag = savedTags.get(0);
@@ -143,19 +145,19 @@ public class TestTagStore {
     @Test(groups="slow")
     public void testControlTagCreation() {
         UUID accountId = UUID.randomUUID();
-        TagStore tagStore = new DefaultTagStore(accountId, Account.ObjectType);
+        TagStore tagStore = new DefaultTagStore(accountId, ObjectType.ACCOUNT);
 
         ControlTag tag = new DefaultControlTag(ControlTagType.AUTO_INVOICING_OFF);
         tagStore.add(tag);
         assertEquals(tagStore.generateInvoice(), false);
 
         List<Tag> tagList = tagStore.getEntityList();
-        tagDao.saveTagsFromTransaction(dao, accountId, Account.ObjectType, tagList, context);
+        tagDao.saveEntitiesFromTransaction(dao, accountId, ObjectType.ACCOUNT, tagList, context);
 
         tagStore.clear();
         assertEquals(tagStore.getEntityList().size(), 0);
 
-        tagList = tagDao.loadTags(accountId, Account.ObjectType);
+        tagList = tagDao.loadEntities(accountId, ObjectType.ACCOUNT);
         tagStore.add(tagList);
         assertEquals(tagList.size(), 1);
 
@@ -165,7 +167,7 @@ public class TestTagStore {
     @Test(groups="slow")
     public void testDescriptiveTagCreation() {
         UUID accountId = UUID.randomUUID();
-        TagStore tagStore = new DefaultTagStore(accountId, Account.ObjectType);
+        TagStore tagStore = new DefaultTagStore(accountId, ObjectType.ACCOUNT);
 
         String definitionName = "SomeTestTag";
         TagDefinition tagDefinition = null;
@@ -179,12 +181,12 @@ public class TestTagStore {
         tagStore.add(tag);
         assertEquals(tagStore.generateInvoice(), true);
 
-        tagDao.saveTagsFromTransaction(dao, accountId, Account.ObjectType, tagStore.getEntityList(), context);
+        tagDao.saveEntitiesFromTransaction(dao, accountId, ObjectType.ACCOUNT, tagStore.getEntityList(), context);
 
         tagStore.clear();
         assertEquals(tagStore.getEntityList().size(), 0);
 
-        List<Tag> tagList = tagDao.loadTags(accountId, Account.ObjectType);
+        List<Tag> tagList = tagDao.loadEntities(accountId, ObjectType.ACCOUNT);
         tagStore.add(tagList);
         assertEquals(tagList.size(), 1);
 
@@ -194,7 +196,7 @@ public class TestTagStore {
     @Test(groups="slow")
     public void testMixedTagCreation() {
         UUID accountId = UUID.randomUUID();
-        TagStore tagStore = new DefaultTagStore(accountId, Account.ObjectType);
+        TagStore tagStore = new DefaultTagStore(accountId, ObjectType.ACCOUNT);
 
         String definitionName = "MixedTagTest";
         TagDefinition tagDefinition = null;
@@ -212,12 +214,12 @@ public class TestTagStore {
         tagStore.add(controlTag);
         assertEquals(tagStore.generateInvoice(), false);
 
-        tagDao.saveTagsFromTransaction(dao, accountId, Account.ObjectType, tagStore.getEntityList(), context);
+        tagDao.saveEntitiesFromTransaction(dao, accountId, ObjectType.ACCOUNT, tagStore.getEntityList(), context);
 
         tagStore.clear();
         assertEquals(tagStore.getEntityList().size(), 0);
 
-        List<Tag> tagList = tagDao.loadTags(accountId, Account.ObjectType);
+        List<Tag> tagList = tagDao.loadEntities(accountId, ObjectType.ACCOUNT);
         tagStore.add(tagList);
         assertEquals(tagList.size(), 2);
 
@@ -227,7 +229,7 @@ public class TestTagStore {
     @Test(groups="slow")
     public void testControlTags() {
         UUID accountId = UUID.randomUUID();
-        TagStore tagStore = new DefaultTagStore(accountId, Account.ObjectType);
+        TagStore tagStore = new DefaultTagStore(accountId, ObjectType.ACCOUNT);
         assertEquals(tagStore.generateInvoice(), true);
         assertEquals(tagStore.processPayment(), true);
 
@@ -270,13 +272,13 @@ public class TestTagStore {
         assertNotNull(tagDefinition);
 
         UUID objectId = UUID.randomUUID();
-        TagStore tagStore = new DefaultTagStore(objectId, Account.ObjectType);
+        TagStore tagStore = new DefaultTagStore(objectId, ObjectType.ACCOUNT);
         Tag tag = new DescriptiveTag(tagDefinition);
         tagStore.add(tag);
 
-        tagDao.saveTags(objectId, Account.ObjectType, tagStore.getEntityList(), context);
+        tagDao.saveEntitiesFromTransaction(dao, objectId, ObjectType.ACCOUNT, tagStore.getEntityList(), context);
 
-        List<Tag> tags = tagDao.loadTags(objectId, Account.ObjectType);
+        List<Tag> tags = tagDao.loadEntities(objectId, ObjectType.ACCOUNT);
         assertEquals(tags.size(), 1);
 
         tagDefinitionDao.deleteTagDefinition(definitionName, context);
@@ -295,19 +297,19 @@ public class TestTagStore {
         assertNotNull(tagDefinition);
 
         UUID objectId = UUID.randomUUID();
-        TagStore tagStore = new DefaultTagStore(objectId, Account.ObjectType);
+        TagStore tagStore = new DefaultTagStore(objectId, ObjectType.ACCOUNT);
         Tag tag = new DescriptiveTag(tagDefinition);
         tagStore.add(tag);
 
-        tagDao.saveTags(objectId, Account.ObjectType, tagStore.getEntityList(), context);
+        tagDao.saveEntitiesFromTransaction(dao, objectId, ObjectType.ACCOUNT, tagStore.getEntityList(), context);
 
-        List<Tag> tags = tagDao.loadTags(objectId, Account.ObjectType);
+        List<Tag> tags = tagDao.loadEntities(objectId, ObjectType.ACCOUNT);
         assertEquals(tags.size(), 1);
 
         try {
             tagDefinitionDao.deleteAllTagsForDefinition(definitionName, context);
         } catch (TagDefinitionApiException e) {
-            fail("Could not delete tags for tag definition", e);
+            fail("Could not delete tagStore for tag definition", e);
         }
 
         try {
@@ -332,7 +334,7 @@ public class TestTagStore {
         try {
             tagDefinitionDao.deleteAllTagsForDefinition(definitionName, context);
         } catch (TagDefinitionApiException e) {
-            fail("Could not delete tags for tag definition", e);
+            fail("Could not delete tagStore for tag definition", e);
         }
 
         try {
@@ -374,13 +376,13 @@ public class TestTagStore {
     public void testTagInsertAudit() {
         UUID accountId = UUID.randomUUID();
 
-        TagStore tagStore = new DefaultTagStore(accountId, Account.ObjectType);
+        TagStore tagStore = new DefaultTagStore(accountId, ObjectType.ACCOUNT);
         Tag tag = new DescriptiveTag(testTag);
         tagStore.add(tag);
 
-        tagDao.saveTagsFromTransaction(dao, accountId, Account.ObjectType, tagStore.getEntityList(), context);
+        tagDao.saveEntitiesFromTransaction(dao, accountId, ObjectType.ACCOUNT, tagStore.getEntityList(), context);
 
-        List<Tag> savedTags = tagDao.loadTags(accountId, Account.ObjectType);
+        List<Tag> savedTags = tagDao.loadEntities(accountId, ObjectType.ACCOUNT);
         assertEquals(savedTags.size(), 1);
 
         Tag savedTag = savedTags.get(0);
@@ -391,6 +393,8 @@ public class TestTagStore {
         String query = String.format("select * from audit_log a inner join tag_history th on a.record_id = th.history_record_id where a.table_name = 'tag_history' and th.id='%s' and a.change_type='INSERT'",
                                      tag.getId().toString());
         List<Map<String, Object>> result = handle.select(query);
+        handle.close();
+
         assertNotNull(result);
         assertEquals(result.size(), 1);
         assertEquals(result.get(0).get("change_type"), "INSERT");
@@ -404,22 +408,24 @@ public class TestTagStore {
     public void testTagDeleteAudit() {
         UUID accountId = UUID.randomUUID();
 
-        TagStore tagStore = new DefaultTagStore(accountId, Account.ObjectType);
+        TagStore tagStore = new DefaultTagStore(accountId, ObjectType.ACCOUNT);
         Tag tag = new DescriptiveTag(testTag);
         tagStore.add(tag);
 
-        tagDao.saveTags(accountId, Account.ObjectType, tagStore.getEntityList(), context);
+        tagDao.saveEntitiesFromTransaction(dao, accountId, ObjectType.ACCOUNT, tagStore.getEntityList(), context);
 
         tagStore.remove(tag);
-        tagDao.saveTags(accountId, Account.ObjectType, tagStore.getEntityList(), context);
+        tagDao.saveEntitiesFromTransaction(dao, accountId, ObjectType.ACCOUNT, tagStore.getEntityList(), context);
 
-        List<Tag> savedTags = tagDao.loadTags(accountId, Account.ObjectType);
+        List<Tag> savedTags = tagDao.loadEntities(accountId, ObjectType.ACCOUNT);
         assertEquals(savedTags.size(), 0);
 
         Handle handle = dbi.open();
         String query = String.format("select * from audit_log a inner join tag_history th on a.record_id = th.history_record_id where a.table_name = 'tag_history' and th.id='%s' and a.change_type='DELETE'",
                                      tag.getId().toString());
         List<Map<String, Object>> result = handle.select(query);
+        handle.close();
+
         assertNotNull(result);
         assertEquals(result.size(), 1);
         assertNotNull(result.get(0).get("change_date"));
diff --git a/util/src/test/java/com/ning/billing/util/validation/TestValidationManager.java b/util/src/test/java/com/ning/billing/util/validation/TestValidationManager.java
index 1848950..4e845db 100644
--- a/util/src/test/java/com/ning/billing/util/validation/TestValidationManager.java
+++ b/util/src/test/java/com/ning/billing/util/validation/TestValidationManager.java
@@ -67,7 +67,9 @@ public class TestValidationManager {
     }
 
     private void stopDatabase() {
-        helper.stopMysql();
+        if (helper != null) {
+            helper.stopMysql();
+        }
     }
 
     @Test(groups = "slow")