killbill-aplcache

Changes

account/pom.xml 3(+1 -2)

account/src/test/java/com/ning/billing/account/dao/TestFieldStore.java 59(+0 -59)

api/pom.xml 10(+9 -1)

beatrix/pom.xml 48(+45 -3)

catalog/pom.xml 2(+1 -1)

entitlement/pom.xml 13(+12 -1)

invoice/pom.xml 20(+15 -5)

invoice/src/main/resources/com/ning/billing/invoice/dao/InvoiceItemSqlDao.sql.stg 44(+0 -44)

payment/pom.xml 100(+97 -3)

pom.xml 100(+88 -12)

util/pom.xml 23(+21 -2)

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

Details

account/pom.xml 3(+1 -2)

diff --git a/account/pom.xml b/account/pom.xml
index 44b4543..0ac4f64 100644
--- a/account/pom.xml
+++ b/account/pom.xml
@@ -13,7 +13,7 @@
     <parent>
         <groupId>com.ning.billing</groupId>
         <artifactId>killbill</artifactId>
-        <version>0.1.2-SNAPSHOT</version>
+        <version>0.1.6-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-account</artifactId>
@@ -94,7 +94,6 @@
         <dependency>
             <groupId>commons-io</groupId>
             <artifactId>commons-io</artifactId>
-            <version>2.0</version>
             <scope>test</scope>
         </dependency>
     </dependencies>
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 b953707..2b916bf 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
@@ -1,4 +1,4 @@
-/*
+/* 
  * Copyright 2010-2011 Ning, Inc.
  *
  * Ning licenses this file to you under the Apache License, version 2.0
@@ -16,150 +16,313 @@
 
 package com.ning.billing.account.api;
 
-import java.math.BigDecimal;
 import java.util.List;
 import java.util.UUID;
+
 import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.util.customfield.CustomizableEntityBase;
-import com.ning.billing.util.tag.DefaultTag;
 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.TagDescription;
-
+import com.ning.billing.util.tag.TagDefinition;
+ 
 public class DefaultAccount extends CustomizableEntityBase implements Account {
-    public final static String OBJECT_TYPE = "Account";
-
-    private final String externalKey;
-    private final String email;
-    private final String name;
-    private final int firstNameLength;
-    private final String phone;
-    private final Currency currency;
-    private final int billCycleDay;
-    private final String paymentProviderName;
-    private final BigDecimal balance;
-    private final DefaultTagStore tags;
-
-    public DefaultAccount(AccountData data) {
-        this(UUID.randomUUID(), data.getExternalKey(), data.getEmail(), data.getName(),
-                data.getFirstNameLength(), data.getPhone(), data.getCurrency(), data.getBillCycleDay(),
-                data.getPaymentProviderName(), BigDecimal.ZERO);
-    }
-
-    public DefaultAccount(UUID id, AccountData data) {
-        this(id, data.getExternalKey(), data.getEmail(), data.getName(),
-                data.getFirstNameLength(), data.getPhone(), data.getCurrency(), data.getBillCycleDay(),
-                data.getPaymentProviderName(), BigDecimal.ZERO);
-    }
-
-    public DefaultAccount(UUID id, String externalKey, String email, String name, int firstNameLength,
-                          String phone, Currency currency, int billCycleDay, String paymentProviderName,
-                          BigDecimal balance) {
-        super(id);
-        this.externalKey = externalKey;
-        this.email = email;
-        this.name = name;
-        this.firstNameLength = firstNameLength;
-        this.phone = phone;
-        this.currency = currency;
-        this.billCycleDay = billCycleDay;
-        this.paymentProviderName = paymentProviderName;
-        this.balance = balance;
-
-        this.tags = new DefaultTagStore(id, getObjectName());
-    }
-
-    @Override
-    public String getObjectName() {
-        return OBJECT_TYPE;
-    }
-
-    @Override
-    public String getExternalKey() {
-        return externalKey;
-    }
-
-    @Override
-    public String getName() {
-        return name;
-    }
-
-    @Override
-    public String getEmail() {
-        return email;
-    }
-
-    @Override
-    public int getFirstNameLength() {
-        return firstNameLength;
-    }
-
-    @Override
-    public String getPhone() {
-        return phone;
-    }
-
-    @Override
-    public Currency getCurrency() {
-        return currency;
-    }
-
-    @Override
-    public int getBillCycleDay() {
-        return billCycleDay;
-    }
-
-    @Override
-    public String getPaymentProviderName() {
-        return paymentProviderName;
-    }
-
-    @Override
-    public List<Tag> getTagList() {
-        return tags.getEntityList();
-    }
-
-    @Override
-    public boolean hasTag(String tagName) {
-        return tags.containsTag(tagName);
-    }
-
-    @Override
-    public void addTag(TagDescription description, String addedBy, DateTime dateAdded) {
-        Tag tag = new DefaultTag(description, addedBy, dateAdded);
-        tags.add(tag) ;
-    }
-
-    @Override
-    public void addTags(List<Tag> tags) {
-        if (tags != null) {
-            this.tags.add(tags);
-        }
-    }
-
-    @Override
-    public void clearTags() {
-        this.tags.clear();
-    }
-
-    @Override
-    public void removeTag(TagDescription description) {
-        tags.remove(description.getName());
-    }
-
-    @Override
-    public boolean generateInvoice() {
-        return tags.generateInvoice();
-    }
-
-    @Override
-    public boolean processPayment() {
-        return tags.processPayment();
-    }
-
-    @Override
-    public BigDecimal getBalance() {
-        return balance;
-    }
+	//public final static String OBJECT_TYPE = "Account";
+
+	private final String externalKey;
+	private final String email;
+	private final String name;
+	private final int firstNameLength;
+	private final Currency currency;
+	private final int billCycleDay;
+	private final String paymentProviderName;
+	private final DefaultTagStore tags;
+	private final DateTimeZone 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 country;
+	private final String postalCode;
+	private final String phone;
+	private final DateTime createdDate;
+	private final DateTime updatedDate;
+
+	/**
+	 * This call is used to create a new account
+	 * @param data
+	 * @param createdDate
+	 */
+	public DefaultAccount(final AccountData data, DateTime createdDate) {
+		this(UUID.randomUUID(), 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(), createdDate, createdDate);
+	}
+
+	//intended for creation
+	public DefaultAccount(final AccountData data) {
+		this(UUID.randomUUID(), data, null, null);
+	}
+	
+	// Intended for migration
+	public DefaultAccount(final AccountData data, DateTime createdDate, DateTime updatedDate) {
+		this(UUID.randomUUID(), data, createdDate, updatedDate);
+	}
+
+	//intended for update
+	public DefaultAccount(final UUID id, final AccountData data, DateTime createdDate, DateTime updatedDate) {
+		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(), createdDate, updatedDate);
+	}
+
+	
+	/**
+	 * This call is used to update an existing account
+	 *  
+	 * @param id
+	 * @param 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(), null, null);
+	}
+
+	/**
+	 * This call is used for testing 
+	 * @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,
+			final DateTimeZone timeZone, final String locale,
+			final String address1, final String address2, final String companyName,
+			final String city,
+			final String stateOrProvince, final String country, final String postalCode, final String phone, DateTime createdDate, DateTime updatedDate) {
+
+		super(id);
+		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.postalCode = postalCode;
+		this.country = country;
+		this.phone = phone;
+		this.createdDate = createdDate == null ? new DateTime(DateTimeZone.UTC) : createdDate; // This is a fallback, we are only expecting these to be set to null 
+		this.updatedDate = updatedDate == null ? new DateTime(DateTimeZone.UTC) : updatedDate; // in the case that the account is being updated. In which case the values are ignored anyway
+		this.tags = new DefaultTagStore(id, getObjectName());
+	}
+
+	@Override
+	public String getObjectName() {
+		return "Account";
+	}
+
+	@Override
+	public String getExternalKey() {
+		return externalKey;
+	}
+
+	@Override
+	public String getName() {
+		return name;
+	}
+
+	@Override
+	public String getEmail() {
+		return email;
+	}
+
+	public DefaultTagStore getTags() {
+		return tags;
+	}
+
+	@Override
+	public DateTime getCreatedDate() {
+		return createdDate;
+	}
+
+	@Override
+	public DateTime getUpdatedDate() {
+		return updatedDate;
+	}
+
+	@Override
+	public int getFirstNameLength() {
+		return firstNameLength;
+	}
+
+	@Override
+	public Currency getCurrency() {
+		return currency;
+	}
+
+	@Override
+	public int getBillCycleDay() {
+		return billCycleDay;
+	}
+
+	@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 List<Tag> getTagList() {
+		return tags.getEntityList();
+	}
+
+	@Override
+	public boolean hasTag(String tagName) {
+		return tags.containsTag(tagName);
+	}
+
+	@Override
+	public void addTag(TagDefinition definition, String addedBy, DateTime dateAdded) {
+		Tag tag = new DescriptiveTag(definition, addedBy, dateAdded);
+		tags.add(tag) ;
+	}
+
+	@Override
+	public void addTags(List<Tag> tags) {
+		if (tags != null) {
+			this.tags.add(tags);
+		}
+	}
+
+	@Override
+	public void clearTags() {
+		this.tags.clear();
+	}
+
+	@Override
+	public void removeTag(TagDefinition definition) {
+		tags.remove(definition.getName());
+	}
+
+	@Override
+	public boolean generateInvoice() {
+		return tags.generateInvoice();
+	}
+
+	@Override
+	public boolean processPayment() {
+		return tags.processPayment();
+	}
+
+	@Override
+	public String toString() {
+		return "DefaultAccount [externalKey=" + externalKey + ", email=" + email + 
+				", name=" + name + ", " +
+				"firstNameLength=" + firstNameLength + 
+				", phone=" + phone + ", " +
+				"currency=" + currency + 
+				", billCycleDay=" + billCycleDay + 
+				", paymentProviderName=" + paymentProviderName + 
+				", timezone=" + timeZone +
+				", locale=" +  locale +
+				", address1" + address1 +
+				", address2" + address2 +
+				", companyName" + companyName +
+				", city" + city +
+				", stateOrProvince" + stateOrProvince +
+				", postalCode" + postalCode +
+				", country" +
+				", tags=" + tags + 
+				", createdDate=" + createdDate + 
+				", updatedDate=" + updatedDate + "]";
+	}
 }
\ No newline at end of file
diff --git a/account/src/main/java/com/ning/billing/account/api/user/AccountBuilder.java b/account/src/main/java/com/ning/billing/account/api/user/AccountBuilder.java
index e05e967..95eb5d8 100644
--- a/account/src/main/java/com/ning/billing/account/api/user/AccountBuilder.java
+++ b/account/src/main/java/com/ning/billing/account/api/user/AccountBuilder.java
@@ -16,79 +16,145 @@
 
 package com.ning.billing.account.api.user;
 
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+
 import com.ning.billing.account.api.DefaultAccount;
 import com.ning.billing.catalog.api.Currency;
 
-import java.math.BigDecimal;
-import java.util.UUID;
-
 public class AccountBuilder {
-    private UUID id;
+    private final UUID id;
     private String externalKey;
     private String email;
     private String name;
     private int firstNameLength;
-    private String phone;
     private Currency currency;
     private int billingCycleDay;
     private String paymentProviderName;
-    private BigDecimal balance;
+    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 DateTime createdDate;
+    private DateTime updatedDate;
 
     public AccountBuilder() {
         this(UUID.randomUUID());
     }
 
-    public AccountBuilder(UUID id) {
+    public AccountBuilder(final UUID id) {
         this.id = id;
     }
 
-    public AccountBuilder externalKey(String externalKey) {
+    public AccountBuilder externalKey(final String externalKey) {
         this.externalKey = externalKey;
         return this;
     }
 
-    public AccountBuilder email(String email) {
+    public AccountBuilder email(final String email) {
         this.email = email;
         return this;
     }
 
-    public AccountBuilder name(String name) {
+    public AccountBuilder name(final String name) {
         this.name = name;
         return this;
     }
 
-    public AccountBuilder firstNameLength(int firstNameLength) {
+    public AccountBuilder firstNameLength(final int firstNameLength) {
         this.firstNameLength = firstNameLength;
         return this;
     }
 
-    public AccountBuilder phone(String phone) {
-        this.phone = phone;
-        return this;
-    }
-
-    public AccountBuilder billingCycleDay(int billingCycleDay) {
+    public AccountBuilder billingCycleDay(final int billingCycleDay) {
         this.billingCycleDay = billingCycleDay;
         return this;
     }
 
-    public AccountBuilder currency(Currency currency) {
+    public AccountBuilder currency(final Currency currency) {
         this.currency = currency;
         return this;
     }
 
-    public AccountBuilder paymentProviderName(String paymentProviderName) {
+    public AccountBuilder paymentProviderName(final String paymentProviderName) {
         this.paymentProviderName = paymentProviderName;
         return this;
     }
 
-    public AccountBuilder balance(BigDecimal balance) {
-        this.balance = balance;
+    public AccountBuilder timeZone(final DateTimeZone timeZone) {
+        this.timeZone = timeZone;
+        return this;
+    }
+
+    public AccountBuilder locale(final String locale) {
+        this.locale = locale;
+        return this;
+    }
+
+    public AccountBuilder address1(final String address1) {
+        this.address1 = address1;
+        return this;
+    }
+
+    public AccountBuilder address2(final String address2) {
+        this.address2 = address2;
+        return this;
+    }
+
+    public AccountBuilder companyName(final String companyName) {
+        this.companyName = companyName;
+        return this;
+    }
+
+    public AccountBuilder city(final String city) {
+        this.city = city;
+        return this;
+    }
+
+    public AccountBuilder stateOrProvince(final String stateOrProvince) {
+        this.stateOrProvince = stateOrProvince;
+        return this;
+    }
+
+    public AccountBuilder postalCode(final String postalCode) {
+        this.postalCode = postalCode;
+        return this;
+    }
+
+    public AccountBuilder country(final String country) {
+        this.country = country;
+        return this;
+    }
+
+    public AccountBuilder phone(final String phone) {
+        this.phone = phone;
+        return this;
+    }
+
+    public AccountBuilder createdDate(DateTime createdDate) {
+        this.createdDate = createdDate;
+        return this;
+    }
+
+    public AccountBuilder updatedDate(DateTime updatedDate) {
+        this.updatedDate = updatedDate;
         return this;
     }
 
     public DefaultAccount build() {
         return new DefaultAccount(id, externalKey, email, name, firstNameLength,
-                                  phone, currency, billingCycleDay, paymentProviderName, balance);
+                                  currency, billingCycleDay, paymentProviderName,
+                                  timeZone, locale,
+                                  address1, address2, companyName, city, stateOrProvince, country,
+                                  postalCode, phone,
+                                  createdDate, updatedDate);
     }
 }
diff --git a/account/src/main/java/com/ning/billing/account/api/user/DefaultAccountChangeNotification.java b/account/src/main/java/com/ning/billing/account/api/user/DefaultAccountChangeNotification.java
index 178100f..11a5a39 100644
--- a/account/src/main/java/com/ning/billing/account/api/user/DefaultAccountChangeNotification.java
+++ b/account/src/main/java/com/ning/billing/account/api/user/DefaultAccountChangeNotification.java
@@ -62,9 +62,6 @@ public class DefaultAccountChangeNotification implements AccountChangeNotificati
         addIfValueChanged(tmpChangedFields, "firstName",
                 oldData.getName(), newData.getName());
 
-        addIfValueChanged(tmpChangedFields, "phone",
-                oldData.getPhone(), newData.getPhone());
-
         addIfValueChanged(tmpChangedFields, "currency",
                 (oldData.getCurrency() != null) ? oldData.getCurrency().toString() : null,
                  (newData.getCurrency() != null) ? newData.getCurrency().toString() : null);
@@ -75,6 +72,21 @@ public class DefaultAccountChangeNotification implements AccountChangeNotificati
 
         addIfValueChanged(tmpChangedFields,"paymentProviderName",
                 oldData.getPaymentProviderName(), newData.getPaymentProviderName());
+
+        addIfValueChanged(tmpChangedFields, "locale", oldData.getLocale(), newData.getLocale());
+
+        addIfValueChanged(tmpChangedFields, "timeZone",
+                (oldData.getTimeZone() == null) ? null : oldData.getTimeZone().toString(),
+                (newData.getTimeZone() == null) ? null : newData.getTimeZone().toString());
+
+        addIfValueChanged(tmpChangedFields, "address1", oldData.getAddress1(), newData.getAddress1());
+        addIfValueChanged(tmpChangedFields, "address2", oldData.getAddress2(), newData.getAddress2());
+        addIfValueChanged(tmpChangedFields, "city", oldData.getCity(), newData.getCity());
+        addIfValueChanged(tmpChangedFields, "stateOrProvince", oldData.getStateOrProvince(), newData.getStateOrProvince());
+        addIfValueChanged(tmpChangedFields, "country", oldData.getCountry(), newData.getCountry());
+        addIfValueChanged(tmpChangedFields, "postalCode", oldData.getPostalCode(), newData.getPostalCode());
+        addIfValueChanged(tmpChangedFields, "phone", oldData.getPhone(), newData.getPhone());
+
         return tmpChangedFields;
     }
 
@@ -85,7 +97,7 @@ public class DefaultAccountChangeNotification implements AccountChangeNotificati
         // If only one is null
         } else if (newData == null || oldData == null) {
             inputList.add(new DefaultChangedField(key, oldData, newData));
-        // If non are null we can safely compare values
+        // If neither are null we can safely compare values
         } else if (!newData.equals(oldData)) {
             inputList.add(new DefaultChangedField(key, oldData, newData));
         }
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 d6bf995..fd17c86 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
@@ -18,39 +18,37 @@ package com.ning.billing.account.api.user;
 
 import java.util.List;
 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.account.api.AccountApiException;
 import com.ning.billing.account.api.AccountData;
 import com.ning.billing.account.api.DefaultAccount;
+import com.ning.billing.account.api.MigrationAccountData;
 import com.ning.billing.account.dao.AccountDao;
+import com.ning.billing.util.clock.Clock;
 import com.ning.billing.util.customfield.CustomField;
 import com.ning.billing.util.tag.Tag;
 
 public class DefaultAccountUserApi implements com.ning.billing.account.api.AccountUserApi {
     private final AccountDao dao;
+	private Clock clock;
 
     @Inject
-    public DefaultAccountUserApi(final AccountDao dao) {
+    public DefaultAccountUserApi(final AccountDao dao, final Clock clock) {
         this.dao = dao;
+        this.clock = clock;
     }
 
     @Override
     public Account createAccount(final AccountData data, final List<CustomField> fields, List<Tag> tags) throws AccountApiException {
-        String key = data.getExternalKey();
-        Account existingAccount = dao.getAccountByKey(key);
-
-        if (existingAccount == null) {
-            Account account = new DefaultAccount(data);
-            account.addFields(fields);
-            account.addTags(tags);
-
-            dao.create(account);
-            return account;
-        } else {
-            throw new AccountApiException(ErrorCode.ACCOUNT_ALREADY_EXISTS, key);
-        }
+        Account account = new DefaultAccount(data, clock.getUTCNow());
+        account.addFields(fields);
+        account.addTags(tags);
+
+        dao.create(account);
+        return account;
     }
 
     @Override
@@ -69,12 +67,40 @@ public class DefaultAccountUserApi implements com.ning.billing.account.api.Accou
     }
 
     @Override
-    public UUID getIdFromKey(final String externalKey) {
+    public UUID getIdFromKey(final String externalKey) throws AccountApiException {
         return dao.getIdFromKey(externalKey);
     }
 
     @Override
-    public void updateAccount(final Account account) {
+    public void updateAccount(final Account account) throws AccountApiException {
         dao.update(account);
     }
+
+    @Override
+    public void updateAccount(final String externalKey, final AccountData accountData) throws AccountApiException {
+    	UUID accountId = getIdFromKey(externalKey);
+    	if(accountId == null) {
+    		throw new AccountApiException(ErrorCode.ACCOUNT_DOES_NOT_EXIST_FOR_KEY, externalKey);
+    	}
+    	Account account = new DefaultAccount(accountId, accountData);
+        dao.update(account);
+    }
+
+	@Override
+	public void deleteAccountByKey(String externalKey) throws AccountApiException {
+		dao.deleteByKey(externalKey);
+	}
+
+	@Override
+	public Account migrateAccount(MigrationAccountData data,
+			List<CustomField> fields, List<Tag> tags)
+			throws AccountApiException {
+		
+		Account account = new DefaultAccount(data);
+        account.addFields(fields);
+        account.addTags(tags);
+
+        dao.create(account);
+        return account;
+	}
 }
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 04b50e3..74a8d51 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
@@ -18,10 +18,19 @@ package com.ning.billing.account.dao;
 
 import java.util.UUID;
 import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.AccountApiException;
 import com.ning.billing.util.entity.EntityDao;
 
 public interface AccountDao extends EntityDao<Account> {
     public Account getAccountByKey(String key);
 
-    public UUID getIdFromKey(String externalKey);
-}
+    /***
+     *
+     * @param externalKey
+     * @return
+     * @throws AccountApiException when externalKey is null
+     */
+    public UUID getIdFromKey(String externalKey) throws AccountApiException;
+
+	public void deleteByKey(String externalKey) throws AccountApiException;
+}
\ 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 21122e2..42ec5a8 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,12 +16,19 @@
 
 package com.ning.billing.account.dao;
 
-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.util.UuidMapper;
-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.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Timestamp;
+import java.util.Date;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
 import org.skife.jdbi.v2.SQLStatement;
 import org.skife.jdbi.v2.StatementContext;
 import org.skife.jdbi.v2.sqlobject.Bind;
@@ -36,15 +43,11 @@ 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.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.UUID;
+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;
+import com.ning.billing.util.entity.EntityDao;
 
 @ExternalizedSqlViaStringTemplate3
 @RegisterMapper({UuidMapper.class, AccountSqlDao.AccountMapper.class})
@@ -63,25 +66,60 @@ public interface AccountSqlDao extends EntityDao<Account>, Transactional<Account
     @SqlUpdate
     public void update(@AccountBinder Account account);
 
+    @Override
+    @SqlUpdate
+    public void deleteByKey(@Bind("externalKey") final String key);
+
     public static class AccountMapper implements ResultSetMapper<Account> {
+
+        private DateTime getDate(ResultSet rs, String fieldName) throws SQLException {
+            final Timestamp resultStamp = rs.getTimestamp(fieldName);
+            return rs.wasNull() ? null : new DateTime(resultStamp).toDateTime(DateTimeZone.UTC);
+        }
+
         @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");
-            String phone = result.getString("phone");
             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");
+            DateTime createdDate = getDate(result, "created_dt");
+            DateTime updatedDate = getDate(result, "updated_dt");
+
+            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");
 
             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)
+                                         .createdDate(createdDate)
+                                         .updatedDate(updatedDate)
                                          .build();
         }
     }
@@ -91,19 +129,37 @@ public interface AccountSqlDao extends EntityDao<Account>, Transactional<Account
     @Target({ElementType.PARAMETER})
     public @interface AccountBinder {
         public static class AccountBinderFactory implements BinderFactory {
-            public Binder build(Annotation annotation) {
+            @Override
+            public Binder<AccountBinder, Account> build(Annotation annotation) {
                 return new Binder<AccountBinder, Account>() {
-                    public void bind(SQLStatement q, AccountBinder bind, Account account) {
+                    private Date getDate(DateTime dateTime) {
+                        return dateTime == null ? null : dateTime.toDate();
+                    }
+
+                    @Override
+                    public void bind(@SuppressWarnings("rawtypes") SQLStatement q, AccountBinder bind, Account account) {
                         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());
-                        q.bind("phone", account.getPhone());
                         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("createdDate", getDate(account.getCreatedDate()));
+                        q.bind("updatedDate", getDate(account.getUpdatedDate()));
                     }
                 };
             }
diff --git a/account/src/main/java/com/ning/billing/account/dao/DefaultAccountDao.java b/account/src/main/java/com/ning/billing/account/dao/DefaultAccountDao.java
index c4671ea..1e00dba 100644
--- a/account/src/main/java/com/ning/billing/account/dao/DefaultAccountDao.java
+++ b/account/src/main/java/com/ning/billing/account/dao/DefaultAccountDao.java
@@ -18,37 +18,39 @@ package com.ning.billing.account.dao;
 
 import java.util.List;
 import java.util.UUID;
+
 import org.skife.jdbi.v2.IDBI;
 import org.skife.jdbi.v2.Transaction;
 import org.skife.jdbi.v2.TransactionStatus;
 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.DefaultAccount;
 import com.ning.billing.account.api.user.DefaultAccountChangeNotification;
 import com.ning.billing.account.api.user.DefaultAccountCreationEvent;
 import com.ning.billing.util.customfield.CustomField;
 import com.ning.billing.util.customfield.dao.FieldStoreDao;
-import com.ning.billing.util.eventbus.EventBus;
+import com.ning.billing.util.bus.Bus;
 import com.ning.billing.util.tag.Tag;
-import com.ning.billing.util.tag.dao.TagStoreDao;
+import com.ning.billing.util.tag.dao.TagStoreSqlDao;
 
 public class DefaultAccountDao implements AccountDao {
-    private final AccountSqlDao accountDao;
-    private final EventBus eventBus;
+    private final AccountSqlDao accountSqlDao;
+    private final Bus eventBus;
 
     @Inject
-    public DefaultAccountDao(IDBI dbi, EventBus eventBus) {
+    public DefaultAccountDao(IDBI dbi, Bus eventBus) {
         this.eventBus = eventBus;
-        this.accountDao = dbi.onDemand(AccountSqlDao.class);
+        this.accountSqlDao = dbi.onDemand(AccountSqlDao.class);
     }
 
     @Override
     public Account getAccountByKey(final String key) {
-        return accountDao.inTransaction(new Transaction<Account, AccountSqlDao>() {
+        return accountSqlDao.inTransaction(new Transaction<Account, AccountSqlDao>() {
             @Override
-            public Account inTransaction(AccountSqlDao accountSqlDao, TransactionStatus status) throws Exception {
+            public Account inTransaction(final AccountSqlDao accountSqlDao, final TransactionStatus status) throws Exception {
                 Account account = accountSqlDao.getAccountByKey(key);
                 if (account != null) {
                     setCustomFieldsFromWithinTransaction(account, accountSqlDao);
@@ -60,86 +62,121 @@ public class DefaultAccountDao implements AccountDao {
     }
 
     @Override
-    public UUID getIdFromKey(final String externalKey) {
-        return accountDao.getIdFromKey(externalKey);
+    public UUID getIdFromKey(final String externalKey) throws AccountApiException {
+        if (externalKey == null) {
+            throw new AccountApiException(ErrorCode.ACCOUNT_CANNOT_MAP_NULL_KEY, "");
+        }
+        return accountSqlDao.getIdFromKey(externalKey);
     }
 
     @Override
     public Account getById(final String id) {
-        return accountDao.inTransaction(new Transaction<Account, AccountSqlDao>() {
-            @Override
-            public Account inTransaction(AccountSqlDao accountSqlDao, TransactionStatus status) throws Exception {
-                Account account = accountSqlDao.getById(id);
-                if (account != null) {
-                    setCustomFieldsFromWithinTransaction(account, accountSqlDao);
-                    setTagsFromWithinTransaction(account, accountSqlDao);
-                }
-                return account;
-            }
-        });
+        Account account = accountSqlDao.getById(id);
+        if (account != null) {
+            setCustomFieldsFromWithinTransaction(account, accountSqlDao);
+            setTagsFromWithinTransaction(account, accountSqlDao);
+        }
+        return account;
     }
 
 
     @Override
     public List<Account> get() {
-        return accountDao.get();
+        return accountSqlDao.get();
     }
 
     @Override
-    public void create(final Account account) {
-        final String accountId = account.getId().toString();
-        final String objectType = DefaultAccount.OBJECT_TYPE;
-
-        accountDao.inTransaction(new Transaction<Void, AccountSqlDao>() {
-            @Override
-            public Void inTransaction(AccountSqlDao accountSqlDao, TransactionStatus status) throws Exception {
-                accountSqlDao.create(account);
-
-                FieldStoreDao fieldStoreDao = accountSqlDao.become(FieldStoreDao.class);
-                fieldStoreDao.save(accountId, objectType, account.getFieldList());
-
-                TagStoreDao tagStoreDao = accountSqlDao.become(TagStoreDao.class);
-                tagStoreDao.save(accountId, objectType, account.getTagList());
-
-                AccountCreationNotification creationEvent = new DefaultAccountCreationEvent(account);
-                eventBus.post(creationEvent);
-                return null;
+    public void create(final Account account) throws AccountApiException {
+        final String key = account.getExternalKey();
+        try {
+
+            accountSqlDao.inTransaction(new Transaction<Void, AccountSqlDao>() {
+                @Override
+                public Void inTransaction(final AccountSqlDao transactionalDao, final TransactionStatus status) throws AccountApiException, Bus.EventBusException {
+                    Account currentAccount = transactionalDao.getAccountByKey(key);
+                    if (currentAccount != null) {
+                        throw new AccountApiException(ErrorCode.ACCOUNT_ALREADY_EXISTS, key);
+                    }
+                    transactionalDao.create(account);
+
+                    saveTagsFromWithinTransaction(account, transactionalDao, true);
+                    saveCustomFieldsFromWithinTransaction(account, transactionalDao, true);
+                    AccountCreationNotification creationEvent = new DefaultAccountCreationEvent(account);
+                    eventBus.post(creationEvent);
+                    return null;
+                }
+            });
+        } catch (RuntimeException re) {
+            if (re.getCause() instanceof AccountApiException) {
+                throw (AccountApiException) re.getCause();
+            } else {
+                throw re;
             }
-        });
+        }
     }
 
     @Override
-    public void update(final Account account) {
-        final String accountId = account.getId().toString();
-        final String objectType = DefaultAccount.OBJECT_TYPE;
-
-        accountDao.inTransaction(new Transaction<Void, AccountSqlDao>() {
-            @Override
-            public Void inTransaction(AccountSqlDao accountSqlDao, TransactionStatus status) throws Exception {
-                Account currentAccount = accountSqlDao.getById(accountId);
-
-                accountSqlDao.update(account);
+    public void update(final Account account) throws AccountApiException {
+        try {
+            accountSqlDao.inTransaction(new Transaction<Void, AccountSqlDao>() {
+                @Override
+                public Void inTransaction(final AccountSqlDao accountSqlDao, final TransactionStatus status) throws AccountApiException, Bus.EventBusException {
+                    String accountId = account.getId().toString();
+                    Account currentAccount = accountSqlDao.getById(accountId);
+                    if (currentAccount == null) {
+                        throw new AccountApiException(ErrorCode.ACCOUNT_DOES_NOT_EXIST_FOR_ID, accountId);
+                    }
+
+                    String currentKey = currentAccount.getExternalKey();
+                    if (!currentKey.equals(account.getExternalKey())) {
+                        throw new AccountApiException(ErrorCode.ACCOUNT_CANNOT_CHANGE_EXTERNAL_KEY, currentKey);
+                    }
+
+                    accountSqlDao.update(account);
+
+                    saveTagsFromWithinTransaction(account, accountSqlDao, false);
+                    saveCustomFieldsFromWithinTransaction(account, accountSqlDao, false);
+
+                    AccountChangeNotification changeEvent = new DefaultAccountChangeNotification(account.getId(), currentAccount, account);
+                    if (changeEvent.hasChanges()) {
+                        eventBus.post(changeEvent);
+                    }
+                    return null;
+                }
+            });
+        } catch (RuntimeException re) {
+            if (re.getCause() instanceof AccountApiException) {
+                throw (AccountApiException) re.getCause();
+            } else {
+                throw re;
+            }
+        }
+    }
 
-                FieldStoreDao fieldStoreDao = accountSqlDao.become(FieldStoreDao.class);
-                fieldStoreDao.clear(accountId, objectType);
-                fieldStoreDao.save(accountId, objectType, account.getFieldList());
+    @Override
+	public void deleteByKey(final String externalKey) throws AccountApiException {
+    	try {
+            accountSqlDao.inTransaction(new Transaction<Void, AccountSqlDao>() {
+                @Override
+                public Void inTransaction(final AccountSqlDao accountSqlDao, final TransactionStatus status) throws AccountApiException, Bus.EventBusException {
 
-                TagStoreDao tagStoreDao = fieldStoreDao.become(TagStoreDao.class);
-                tagStoreDao.clear(accountId, objectType);
-                tagStoreDao.save(accountId, objectType, account.getTagList());
+                    accountSqlDao.deleteByKey(externalKey);
 
-                AccountChangeNotification changeEvent = new DefaultAccountChangeNotification(account.getId(), currentAccount, account);
-                if (changeEvent.hasChanges()) {
-                    eventBus.post(changeEvent);
+                    return null;
                 }
-                return null;
+            });
+        } catch (RuntimeException re) {
+            if (re.getCause() instanceof AccountApiException) {
+                throw (AccountApiException) re.getCause();
+            } else {
+                throw re;
             }
-        });
-    }
+        }
+	}
 
     @Override
     public void test() {
-        accountDao.test();
+        accountSqlDao.test();
     }
 
     private void setCustomFieldsFromWithinTransaction(final Account account, final AccountSqlDao transactionalDao) {
@@ -148,14 +185,12 @@ public class DefaultAccountDao implements AccountDao {
 
         account.clearFields();
         if (fields != null) {
-            for (CustomField field : fields) {
-                account.setFieldValue(field.getName(), field.getValue());
-            }
+            account.addFields(fields);
         }
     }
 
     private void setTagsFromWithinTransaction(final Account account, final AccountSqlDao transactionalDao) {
-        TagStoreDao tagStoreDao = transactionalDao.become(TagStoreDao.class);
+        TagStoreSqlDao tagStoreDao = transactionalDao.become(TagStoreSqlDao.class);
         List<Tag> tags = tagStoreDao.load(account.getId().toString(), account.getObjectName());
         account.clearTags();
 
@@ -163,4 +198,36 @@ public class DefaultAccountDao implements AccountDao {
             account.addTags(tags);
         }
     }
+
+    private void saveTagsFromWithinTransaction(final Account account, final AccountSqlDao transactionalDao, final boolean isCreation) {
+        String accountId = account.getId().toString();
+        String objectType = account.getObjectName();
+
+        TagStoreSqlDao tagStoreDao = transactionalDao.become(TagStoreSqlDao.class);
+        if (!isCreation) {
+            tagStoreDao.clear(accountId, objectType);
+        }
+
+        List<Tag> tagList = account.getTagList();
+        if (tagList != null) {
+            tagStoreDao.batchSaveFromTransaction(accountId, objectType, tagList);
+        }
+    }
+
+    private void saveCustomFieldsFromWithinTransaction(final Account account, final AccountSqlDao transactionalDao, final boolean isCreation) {
+        String accountId = account.getId().toString();
+        String objectType = account.getObjectName();
+
+        FieldStoreDao fieldStoreDao = transactionalDao.become(FieldStoreDao.class);
+        if (!isCreation) {
+            fieldStoreDao.clear(accountId, objectType);
+        }
+
+        List<CustomField> fieldList = account.getFieldList();
+        if (fieldList != null) {
+            fieldStoreDao.batchSaveFromTransaction(accountId, objectType, fieldList);
+        }
+    }
+
+
 }
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 345e2a1..f6e51f4 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,15 +16,15 @@
 
 package com.ning.billing.account.glue;
 
+import org.skife.config.ConfigurationObjectFactory;
+
 import com.google.inject.AbstractModule;
 import com.ning.billing.account.api.AccountService;
 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.AccountSqlDao;
 import com.ning.billing.account.dao.DefaultAccountDao;
-import org.skife.config.ConfigurationObjectFactory;
 
 public class AccountModule extends AbstractModule {
 
@@ -33,11 +33,11 @@ public class AccountModule extends AbstractModule {
         bind(AccountConfig.class).toInstance(config);
     }
 
-    private void installAccountDao() {
+    protected void installAccountDao() {
         bind(AccountDao.class).to(DefaultAccountDao.class).asEagerSingleton();
     }
 
-    private void installAccountUserApi() {
+    protected void installAccountUserApi() {
         bind(AccountUserApi.class).to(DefaultAccountUserApi.class).asEagerSingleton();
     }
 
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 7c0e76e..7e93ff2 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
@@ -1,45 +1,80 @@
 group AccountDaoSql;
 
+accountFields(prefix) ::= <<
+    <prefix>id,
+    <prefix>external_key,
+    <prefix>email,
+    <prefix>name,
+    <prefix>first_name_length,
+    <prefix>currency,
+    <prefix>billing_cycle_day,
+    <prefix>payment_provider_name,
+    <prefix>time_zone, 
+    <prefix>locale,
+    <prefix>address1, 
+    <prefix>address2, 
+    <prefix>company_name, 
+    <prefix>city, 
+	<prefix>state_or_province, 
+    <prefix>country, 
+    <prefix>postal_code,
+    <prefix>phone,	
+    <prefix>created_dt,
+    <prefix>updated_dt
+>>
+
+
 create() ::= <<
     INSERT INTO accounts
-    (id, external_key, email, name, first_name_length, phone, currency, billing_cycle_day, payment_provider_name)
+      (<accountFields()>)
     VALUES
-    (:id, :externalKey, :email, :name, :firstNameLength, :phone, :currency, :billingCycleDay, :paymentProviderName);
+      (:id, :externalKey, :email, :name, :firstNameLength, :currency, :billingCycleDay,
+      :paymentProviderName, :timeZone, :locale,
+      :address1, :address2, :companyName, :city, :stateOrProvince, :country, :postalCode, :phone,
+      :createdDate, :updatedDate);
 >>
 
 update() ::= <<
     UPDATE accounts
-    SET email = :email, name = :name, first_name_length = :firstNameLength, phone = :phone,
-        currency = :currency, billing_cycle_day = :billingCycleDay, payment_provider_name = :paymentProviderName
+    SET external_key = :externalKey, email = :email, name = :name, first_name_length = :firstNameLength,
+        currency = :currency, billing_cycle_day = :billingCycleDay, payment_provider_name = :paymentProviderName,
+        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_dt = NOW()
     WHERE id = :id;
 >>
 
+deleteByKey() ::= <<
+    DELETE FROM accounts
+    WHERE external_key = :externalKey;
+>>
+
+
 getAccountByKey() ::= <<
-    select id, external_key, email, name, first_name_length, phone, currency, billing_cycle_day, payment_provider_name
+    select <accountFields()>
     from accounts
     where external_key = :externalKey;
 >>
 
 getById() ::= <<
-    select
-      a.id, a.external_key, a.email, a.name, a.first_name_length,
-      a.phone, a.currency, a.billing_cycle_day, a.payment_provider_name
-    from accounts a
-    where a.id = :id;
+    SELECT <accountFields()>
+      FROM accounts
+     WHERE id = :id;
 >>
 
 get() ::= <<
-    select id, external_key, email, name, first_name_length, phone, currency, billing_cycle_day, payment_provider_name
-    from accounts;
+    SELECT <accountFields()>
+      FROM accounts;
 >>
 
 getIdFromKey() ::= <<
-    select id
-    from accounts
-    where external_key = :externalKey;
+    SELECT id
+      FROM accounts
+     WHERE external_key = :externalKey;
 >>
 
 test() ::= <<
-    select 1 from accounts;
+    SELECT 1 FROM accounts;
 >>
 ;
\ No newline at end of file
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 ccfed72..58b47f0 100644
--- a/account/src/main/resources/com/ning/billing/account/ddl.sql
+++ b/account/src/main/resources/com/ning/billing/account/ddl.sql
@@ -5,11 +5,23 @@ CREATE TABLE accounts (
     email varchar(50) NOT NULL,
     name varchar(100) NOT NULL,
     first_name_length int NOT NULL,
-    phone varchar(13) DEFAULT NULL,
     currency char(3) DEFAULT NULL,
     billing_cycle_day int DEFAULT NULL,
     payment_provider_name varchar(20) DEFAULT NULL,
+    time_zone varchar(50) DEFAULT NULL,
+    locale varchar(5) DEFAULT NULL,
+    address1 varchar(100) DEFAULT NULL,
+    address2 varchar(100) DEFAULT NULL,
+    company_name varchar(50) DEFAULT NULL,
+    city varchar(50) DEFAULT NULL,
+    state_or_province varchar(50) DEFAULT NULL,
+    country varchar(50) DEFAULT NULL,
+    postal_code varchar(11) DEFAULT NULL,
+    phone varchar(13) DEFAULT NULL,
+    created_dt datetime,
+    updated_dt datetime,
     PRIMARY KEY(id)
 ) ENGINE=innodb;
 CREATE UNIQUE INDEX accounts_external_key ON accounts(external_key);
-CREATE UNIQUE INDEX accounts_email ON accounts(email);
\ No newline at end of file
+CREATE UNIQUE INDEX accounts_email ON accounts(email);
+
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
new file mode 100644
index 0000000..ac74b68
--- /dev/null
+++ b/account/src/test/java/com/ning/billing/account/api/MockAccountUserApi.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * 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 java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.joda.time.DateTime;
+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>();
+
+    public Account createAccount(UUID id,
+                                 String externalKey,
+                                 String email,
+                                 String name,
+                                 int firstNameLength,
+                                 Currency currency,
+                                 int billCycleDay,
+                                 String paymentProviderName,
+                                 BigDecimal balance,
+                                 final DateTimeZone timeZone, 
+                                 final String locale,
+                                 final String address1, 
+                                 final String address2, 
+                                 final String companyName,
+                                 final String city,
+                                 final String stateOrProvince, 
+                                 final String country, 
+                                 final String postalCode, 
+                                 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);
+		accounts.add(result);
+		return result;
+	}
+
+    @Override
+    public Account createAccount(AccountData data, List<CustomField> fields, List<Tag> tags) throws AccountApiException {
+        Account result = new DefaultAccount(data, new DateTime());
+        accounts.add(result);
+        return result;
+    }
+
+    @Override
+    public Account getAccountByKey(String key) {
+        for (Account account : accounts) {
+            if (key.equals(account.getExternalKey())) {
+                return account;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public Account getAccountById(UUID uid) {
+        for (Account account : accounts) {
+            if (uid.equals(account.getId())) {
+                return account;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public List<Account> getAccounts() {
+        return new ArrayList<Account>(accounts);
+    }
+
+    @Override
+    public UUID getIdFromKey(String externalKey) {
+        for (Account account : accounts) {
+            if (externalKey.equals(account.getExternalKey())) {
+                return account.getId();
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public void updateAccount(Account account) {
+        throw new UnsupportedOperationException();
+    }
+
+	@Override
+	public void deleteAccountByKey(String externalKey)
+			throws AccountApiException {
+		for (Account account : accounts) {
+            if (externalKey.equals(account.getExternalKey())) {
+                accounts.remove(account.getId());
+            }
+        }	
+		
+	}
+
+	@Override
+	public Account migrateAccount(MigrationAccountData data,
+			List<CustomField> fields, List<Tag> tags)
+			throws AccountApiException {
+		Account result = new DefaultAccount(data, data.getCreatedDate(), data.getUpdatedDate());
+        accounts.add(result);
+        return result;
+	}
+
+	@Override
+	public void updateAccount(String key, AccountData accountData)
+			throws AccountApiException {
+		throw new UnsupportedOperationException();
+	}
+}
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 b18a41b..f4f530e 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
@@ -16,23 +16,24 @@
 
 package com.ning.billing.account.dao;
 
-import com.google.inject.Guice;
-import com.google.inject.Injector;
-import com.google.inject.Stage;
-import com.ning.billing.account.glue.AccountModuleMock;
-import com.ning.billing.util.eventbus.DefaultEventBusService;
-import com.ning.billing.util.eventbus.EventBusService;
+import static org.testng.Assert.fail;
+
+import java.io.IOException;
+
 import org.apache.commons.io.IOUtils;
 import org.skife.jdbi.v2.IDBI;
 import org.testng.annotations.AfterClass;
 import org.testng.annotations.BeforeClass;
 
-import java.io.IOException;
-
-import static org.testng.Assert.fail;
+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;
 
 public abstract class AccountDaoTestBase {
-    protected AccountModuleMock module;
+    protected AccountModuleWithEmbeddedDb module;
     protected AccountDao accountDao;
     protected IDBI dbi;
 
@@ -40,14 +41,12 @@ public abstract class AccountDaoTestBase {
     protected void setup() throws IOException {
         // Health check test to make sure MySQL is setup properly
         try {
-            module = new AccountModuleMock();
+            module = new AccountModuleWithEmbeddedDb();
             final String accountDdl = IOUtils.toString(AccountSqlDao.class.getResourceAsStream("/com/ning/billing/account/ddl.sql"));
-            final String invoiceDdl = IOUtils.toString(AccountSqlDao.class.getResourceAsStream("/com/ning/billing/invoice/ddl.sql"));
             final String utilDdl = IOUtils.toString(AccountSqlDao.class.getResourceAsStream("/com/ning/billing/util/ddl.sql"));
 
             module.startDb();
             module.initDb(accountDdl);
-            module.initDb(invoiceDdl);
             module.initDb(utilDdl);
 
             final Injector injector = Guice.createInjector(Stage.DEVELOPMENT, module);
@@ -56,8 +55,8 @@ public abstract class AccountDaoTestBase {
             accountDao = injector.getInstance(AccountDao.class);
             accountDao.test();
 
-            EventBusService busService = injector.getInstance(EventBusService.class);
-            ((DefaultEventBusService) busService).startBus();
+            BusService busService = injector.getInstance(BusService.class);
+            ((DefaultBusService) busService).startBus();
         }
         catch (Throwable t) {
             fail(t.toString());
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
new file mode 100644
index 0000000..b351709
--- /dev/null
+++ b/account/src/test/java/com/ning/billing/account/dao/MockAccountDao.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.account.dao;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+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.user.DefaultAccountCreationEvent;
+import com.ning.billing.util.bus.Bus;
+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>();
+
+    @Inject
+    public MockAccountDao(Bus eventBus) {
+        this.eventBus = eventBus;
+    }
+
+    @Override
+    public void create(Account account) {
+        accounts.put(account.getId().toString(), account);
+
+        try {
+            eventBus.post(new DefaultAccountCreationEvent(account));
+        }
+        catch (EventBusException ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+
+    @Override
+    public Account getById(String id) {
+        return accounts.get(id);
+    }
+
+    @Override
+    public List<Account> get() {
+        return new ArrayList<Account>(accounts.values());
+    }
+
+    @Override
+    public void test() {
+    }
+
+    @Override
+    public Account getAccountByKey(String key) {
+        for (Account account : accounts.values()) {
+            if (key.equals(account.getExternalKey())) {
+                return account;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public UUID getIdFromKey(String externalKey) {
+        Account account = getAccountByKey(externalKey);
+        return account == null ? null : account.getId();
+    }
+
+    @Override
+    public void update(Account account) {
+        Account currentAccount = accounts.put(account.getId().toString(), account);
+
+        AccountChangeNotification changeEvent = new DefaultAccountChangeNotification(account.getId(), currentAccount, account);
+        if (changeEvent.hasChanges()) {
+            try {
+                eventBus.post(changeEvent);
+            }
+            catch (EventBusException ex) {
+                throw new RuntimeException(ex);
+            }
+        }
+    }
+
+	@Override
+	public void deleteByKey(String externalKey) throws AccountApiException {
+		for (Account account : accounts.values()) {
+            if (externalKey.equals(account.getExternalKey())) {
+                accounts.remove(account.getId());
+            }
+        }		
+	}
+}
diff --git a/account/src/test/java/com/ning/billing/account/dao/TestSimpleAccountDao.java b/account/src/test/java/com/ning/billing/account/dao/TestSimpleAccountDao.java
index a4b0b98..1c118a9 100644
--- a/account/src/test/java/com/ning/billing/account/dao/TestSimpleAccountDao.java
+++ b/account/src/test/java/com/ning/billing/account/dao/TestSimpleAccountDao.java
@@ -16,45 +16,60 @@
 
 package com.ning.billing.account.dao;
 
+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.util.List;
 import java.util.UUID;
+
 import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
 import org.testng.annotations.Test;
+
 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.DefaultAccount;
-import com.ning.billing.util.tag.DefaultTagDescription;
-import com.ning.billing.util.tag.Tag;
-import com.ning.billing.util.tag.TagDescription;
 import com.ning.billing.account.api.user.AccountBuilder;
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.util.clock.DefaultClock;
-import com.ning.billing.util.tag.dao.TagDescriptionDao;
-
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertNotNull;
-import static org.testng.Assert.assertTrue;
+import com.ning.billing.util.tag.DefaultTagDefinition;
+import com.ning.billing.util.tag.Tag;
+import com.ning.billing.util.tag.TagDefinition;
+import com.ning.billing.util.tag.dao.TagDefinitionSqlDao;
 
 @Test(groups = {"account-dao"})
 public class TestSimpleAccountDao extends AccountDaoTestBase {
-    private final String key = "test1234";
-    private final String firstName = "Wesley";
-    private final String email = "me@me.com";
-
     private DefaultAccount createTestAccount() {
-        String thisKey = key + UUID.randomUUID().toString();
+        String thisKey = "test" + UUID.randomUUID().toString();
         String lastName = UUID.randomUUID().toString();
-        String thisEmail = email + " " + UUID.randomUUID();
+        String thisEmail = "me@me.com" + " " + UUID.randomUUID();
+        String firstName = "Bob";
         String name = firstName + " " + lastName;
         String phone = "123-456-7890";
+        String locale = "EN-US";
+        DateTimeZone timeZone = DateTimeZone.forID("America/Los_Angeles");
+
+        DateTime createdDate = new DateTime(DateTimeZone.UTC);
+        DateTime updatedDate = new DateTime(DateTimeZone.UTC);
 
         int firstNameLength = firstName.length();
-        return new AccountBuilder().externalKey(thisKey).name(name).phone(phone).firstNameLength(firstNameLength)
-                                   .email(thisEmail).currency(Currency.USD).build();
+        return new AccountBuilder().externalKey(thisKey)
+                                   .name(name)
+                                   .phone(phone)
+                                   .firstNameLength(firstNameLength)
+                                   .email(thisEmail)
+                                   .currency(Currency.USD)
+                                   .locale(locale)
+                                   .timeZone(timeZone)
+                                   .createdDate(createdDate)
+                                   .updatedDate(updatedDate)
+                                   .build();
     }
 
-    public void testBasic() {
+    public void testBasic() throws AccountApiException {
 
         Account a = createTestAccount();
         accountDao.create(a);
@@ -93,7 +108,7 @@ public class TestSimpleAccountDao extends AccountDaoTestBase {
     }
 
     @Test
-    public void testCustomFields() {
+    public void testCustomFields() throws AccountApiException {
         Account account = createTestAccount();
         String fieldName = "testField1";
         String fieldValue = "testField1_value";
@@ -108,15 +123,15 @@ public class TestSimpleAccountDao extends AccountDaoTestBase {
     }
 
     @Test
-    public void testTags() {
+    public void testTags() throws AccountApiException {
         Account account = createTestAccount();
-        TagDescription description = new DefaultTagDescription("Test Tag", "For testing only", true, true, "Test System", new DateTime());
-        TagDescriptionDao tagDescriptionDao = dbi.onDemand(TagDescriptionDao.class);
-        tagDescriptionDao.create(description);
+        TagDefinition definition = new DefaultTagDefinition("Test Tag", "For testing only", "Test System", new DateTime());
+        TagDefinitionSqlDao tagDescriptionDao = dbi.onDemand(TagDefinitionSqlDao.class);
+        tagDescriptionDao.create(definition);
 
         String addedBy = "testTags()";
         DateTime dateAdded = new DefaultClock().getUTCNow();
-        account.addTag(description, addedBy, dateAdded);
+        account.addTag(definition, addedBy, dateAdded);
         assertEquals(account.getTagList().size(), 1);
         accountDao.create(account);
 
@@ -124,25 +139,31 @@ public class TestSimpleAccountDao extends AccountDaoTestBase {
         List<Tag> tagList = thisAccount.getTagList();
         assertEquals(tagList.size(), 1);
         Tag tag = tagList.get(0);
-        assertEquals(tag.getName(), description.getName());
-        assertEquals(tag.getGenerateInvoice(), description.getGenerateInvoice());
-        assertEquals(tag.getProcessPayment(), description.getProcessPayment());
-        assertEquals(tag.getTagDescriptionId(), description.getId());
+        assertEquals(tag.getTagDefinitionName(), definition.getName());
         assertEquals(tag.getAddedBy(), addedBy);
-        assertEquals(tag.getDateAdded().compareTo(dateAdded), 0);
+        assertEquals(tag.getAddedDate().compareTo(dateAdded), 0);
     }
 
     @Test
-    public void testGetIdFromKey() {
+    public void testGetIdFromKey() throws AccountApiException {
         Account account = createTestAccount();
         accountDao.create(account);
 
-        UUID accountId = accountDao.getIdFromKey(account.getExternalKey());
-        assertEquals(accountId, account.getId());
+        try {
+            UUID accountId = accountDao.getIdFromKey(account.getExternalKey());
+            assertEquals(accountId, account.getId());
+        } catch (AccountApiException a) {
+            fail("Retrieving account failed.");
+        }
+    }
+
+    @Test(expectedExceptions = AccountApiException.class)
+    public void testGetIdFromKeyForNullKey() throws AccountApiException {
+        accountDao.getIdFromKey(null);
     }
 
     @Test
-    public void testUpdate() throws AccountApiException {
+    public void testUpdate() throws Exception {
         final Account account = createTestAccount();
         accountDao.create(account);
 
@@ -151,34 +172,84 @@ public class TestSimpleAccountDao extends AccountDaoTestBase {
             public String getExternalKey() {
                 return account.getExternalKey();
             }
+
             @Override
             public String getName() {
                 return "Jane Doe";
             }
+
             @Override
             public int getFirstNameLength() {
                 return 4;
             }
+
             @Override
             public String getEmail() {
                 return account.getEmail();
             }
+
             @Override
             public String getPhone() {
                 return account.getPhone();
             }
+
             @Override
             public int getBillCycleDay() {
                 return account.getBillCycleDay();
             }
+
             @Override
             public Currency getCurrency() {
                 return account.getCurrency();
             }
+
             @Override
             public String getPaymentProviderName() {
                 return account.getPaymentProviderName();
             }
+            @Override
+            public DateTimeZone getTimeZone() {
+                return DateTimeZone.forID("Australia/Darwin");
+            }
+
+            @Override
+            public String getLocale() {
+                return "FR-CA";
+            }
+            @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;
+            }
         };
 
         Account updatedAccount = new DefaultAccount(account.getId(), accountData);
@@ -189,10 +260,124 @@ public class TestSimpleAccountDao extends AccountDaoTestBase {
         assertNotNull(savedAccount);
         assertEquals(savedAccount.getName(), updatedAccount.getName());
         assertEquals(savedAccount.getEmail(), updatedAccount.getEmail());
-        assertEquals(savedAccount.getPhone(), updatedAccount.getPhone());
         assertEquals(savedAccount.getPaymentProviderName(), updatedAccount.getPaymentProviderName());
         assertEquals(savedAccount.getBillCycleDay(), updatedAccount.getBillCycleDay());
         assertEquals(savedAccount.getFirstNameLength(), updatedAccount.getFirstNameLength());
+        assertEquals(savedAccount.getTimeZone(), updatedAccount.getTimeZone());
+        assertEquals(savedAccount.getLocale(), updatedAccount.getLocale());
+        assertEquals(savedAccount.getAddress1(), updatedAccount.getAddress1());
+        assertEquals(savedAccount.getAddress2(), updatedAccount.getAddress2());
+        assertEquals(savedAccount.getCity(), updatedAccount.getCity());
+        assertEquals(savedAccount.getStateOrProvince(), updatedAccount.getStateOrProvince());
+        assertEquals(savedAccount.getCountry(), updatedAccount.getCountry());
+        assertEquals(savedAccount.getPostalCode(), updatedAccount.getPostalCode());
+        assertEquals(savedAccount.getPhone(), updatedAccount.getPhone());
+    }
+
+    @Test
+    public void testAddingContactInformation() throws Exception {
+        UUID accountId = UUID.randomUUID();
+        DefaultAccount account = new DefaultAccount(accountId, "extKey123456", "myemail123456@glam.com",
+                                                    "John Smith", 4, Currency.USD, 15, null,
+                                                    DateTimeZone.forID("America/Cambridge_Bay"), "EN-CA",
+                                                    null, null, null, null, null, null, null, null,null,null);
+        accountDao.create(account);
+
+        String address1 = "123 address 1";
+        String address2 = "456 address 2";
+        String companyName = "Some Company";
+        String city = "Cambridge Bay";
+        String stateOrProvince = "Nunavut";
+        String country = "Canada";
+        String postalCode = "X0B 0C0";
+        String phone = "18001112222";
+
+        DefaultAccount updatedAccount = new DefaultAccount(accountId, "extKey123456", "myemail123456@glam.com",
+                                                    "John Smith", 4, Currency.USD, 15, null,
+                                                    DateTimeZone.forID("America/Cambridge_Bay"), "EN-CA",
+                                                    address1, address2, companyName, city, stateOrProvince, country,
+                                                    postalCode, phone, null,null);
+
+        accountDao.update(updatedAccount);
+
+        Account savedAccount = accountDao.getById(accountId.toString());
+
+        assertNotNull(savedAccount);
+        assertEquals(savedAccount.getId(), accountId);
+        assertEquals(savedAccount.getAddress1(), address1);
+        assertEquals(savedAccount.getAddress2(), address2);
+        assertEquals(savedAccount.getCompanyName(), companyName);
+        assertEquals(savedAccount.getCity(), city);
+        assertEquals(savedAccount.getStateOrProvince(), stateOrProvince);
+        assertEquals(savedAccount.getCity(), city);
+        assertEquals(savedAccount.getPostalCode(), postalCode);
+        assertEquals(savedAccount.getPhone(), phone);
+    }
+
+    @Test
+    public void testRemovingContactInformation() throws Exception {
+        UUID accountId = UUID.randomUUID();
+
+        DefaultAccount account = new DefaultAccount(accountId, "extKey654321", "myemail654321@glam.com",
+                                                    "John Smith", 4, Currency.USD, 15, null,
+                                                    DateTimeZone.forID("America/Cambridge_Bay"), "EN-CA",
+                                                    "123 address 1", "456 address 2", null, "Cambridge Bay",
+                                                    "Nunavut", "Canada", "X0B 0C0", "18001112222", null, null);
+        accountDao.create(account);
 
+        DefaultAccount updatedAccount = new DefaultAccount(accountId, "extKey654321", "myemail654321@glam.com",
+                                                    "John Smith", 4, Currency.USD, 15, null,
+                                                    DateTimeZone.forID("America/Cambridge_Bay"), "EN-CA",
+                                                    null, null, null, null, null, null, null, null, null, null);
+
+        accountDao.update(updatedAccount);
+
+        Account savedAccount = accountDao.getById(accountId.toString());
+
+        assertNotNull(savedAccount);
+        assertEquals(savedAccount.getId(), accountId);
+        assertEquals(savedAccount.getAddress1(), null);
+        assertEquals(savedAccount.getAddress2(), null);
+        assertEquals(savedAccount.getCompanyName(), null);
+        assertEquals(savedAccount.getCity(), null);
+        assertEquals(savedAccount.getStateOrProvince(), null);
+        assertEquals(savedAccount.getCity(), null);
+        assertEquals(savedAccount.getPostalCode(), null);
+        assertEquals(savedAccount.getPhone(), null);
+    }
+
+    @Test(expectedExceptions = AccountApiException.class)
+    public void testExternalKeyCannotBeUpdated() throws Exception {
+        UUID accountId = UUID.randomUUID();
+        String originalExternalKey = "extKey1337";
+
+        DefaultAccount account = new DefaultAccount(accountId, originalExternalKey, "myemail1337@glam.com",
+                                                    "John Smith", 4, Currency.USD, 15, null,
+                                                    null, null, null, null, null, null, null, null, null, null, null, null);
+        accountDao.create(account);
+
+        DefaultAccount updatedAccount = new DefaultAccount(accountId, "extKey1338", "myemail1337@glam.com",
+                                                    "John Smith", 4, Currency.USD, 15, null,
+                                                    null, null, null, null, null, null, null, null, null, null,null, null);
+        accountDao.update(updatedAccount);
     }
+    
+    @Test(groups={"slow"},enabled=true)
+    public void testDelete() throws AccountApiException {
+
+        Account a = createTestAccount();
+        accountDao.create(a);
+        String key = a.getExternalKey();
+
+        Account r = accountDao.getAccountByKey(key);
+        assertNotNull(r);
+        assertEquals(r.getExternalKey(), a.getExternalKey());
+        
+        accountDao.deleteByKey(key);
+        
+        Account s = accountDao.getAccountByKey(key);
+        assertTrue(s==null);
+
+    }
+
 }
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
new file mode 100644
index 0000000..1c65c3c
--- /dev/null
+++ b/account/src/test/java/com/ning/billing/account/glue/AccountModuleWithMocks.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.account.glue;
+
+import com.ning.billing.account.dao.AccountDao;
+import com.ning.billing.account.dao.MockAccountDao;
+import com.ning.billing.util.clock.MockClockModule;
+
+public class AccountModuleWithMocks extends AccountModule {
+    @Override
+    protected void installAccountDao() {
+        bind(MockAccountDao.class).asEagerSingleton();
+        bind(AccountDao.class).to(MockAccountDao.class);
+    }
+
+
+    @Override
+    protected void configure() {
+        super.configure();
+        install(new MockClockModule());
+    }
+}
diff --git a/analytics/pom.xml b/analytics/pom.xml
index 278348b..9ee654b 100644
--- a/analytics/pom.xml
+++ b/analytics/pom.xml
@@ -13,7 +13,7 @@
     <parent>
         <groupId>com.ning.billing</groupId>
         <artifactId>killbill</artifactId>
-        <version>0.1.2-SNAPSHOT</version>
+        <version>0.1.6-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-analytics</artifactId>
diff --git a/analytics/src/main/java/com/ning/billing/analytics/BusinessAccount.java b/analytics/src/main/java/com/ning/billing/analytics/BusinessAccount.java
index aaf75a1..7557045 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/BusinessAccount.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/BusinessAccount.java
@@ -213,7 +213,7 @@ public class BusinessAccount
         if (key != null ? !key.equals(that.key) : that.key != null) {
             return false;
         }
-        if (lastInvoiceDate != null ? !lastInvoiceDate.equals(that.lastInvoiceDate) : that.lastInvoiceDate != null) {
+        if (lastInvoiceDate != null ? lastInvoiceDate.compareTo(that.lastInvoiceDate) != 0 : that.lastInvoiceDate != null) {
             return false;
         }
         if (lastPaymentStatus != null ? !lastPaymentStatus.equals(that.lastPaymentStatus) : that.lastPaymentStatus != null) {
@@ -228,7 +228,7 @@ public class BusinessAccount
         if (totalInvoiceBalance != null ? !(Rounder.round(totalInvoiceBalance) == Rounder.round(that.totalInvoiceBalance)) : that.totalInvoiceBalance != null) {
             return false;
         }
-        if (updatedDt != null ? !updatedDt.equals(that.updatedDt) : that.updatedDt != null) {
+        if (updatedDt != null ? updatedDt.compareTo(that.updatedDt) != 0 : that.updatedDt != null) {
             return false;
         }
 
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 23fbcf4..f7081c7 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/BusinessAccountRecorder.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/BusinessAccountRecorder.java
@@ -50,7 +50,7 @@ public class BusinessAccountRecorder
 
         final List<String> tags = new ArrayList<String>();
         for (final Tag tag : account.getTagList()) {
-            tags.add(tag.getName());
+            tags.add(tag.getTagDefinitionName());
         }
 
         // TODO Need payment and invoice api to fill most fields
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 baacad8..71e3402 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/BusinessSubscription.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/BusinessSubscription.java
@@ -17,7 +17,14 @@
 package com.ning.billing.analytics;
 
 import com.ning.billing.analytics.utils.Rounder;
-import com.ning.billing.catalog.api.*;
+import com.ning.billing.catalog.api.CatalogApiException;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.catalog.api.Duration;
+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.catalog.api.TimeUnit;
 import com.ning.billing.entitlement.api.user.Subscription;
 import org.joda.time.DateTime;
 import org.slf4j.Logger;
diff --git a/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessAccountDaoProvider.java b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessAccountDaoProvider.java
index 0a66205..a2d6b2e 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessAccountDaoProvider.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessAccountDaoProvider.java
@@ -16,16 +16,16 @@
 
 package com.ning.billing.analytics.dao;
 
+import org.skife.jdbi.v2.IDBI;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
-import org.skife.jdbi.v2.DBI;
 
 public class BusinessAccountDaoProvider implements Provider<BusinessAccountDao>
 {
-    private final DBI dbi;
+    private final IDBI dbi;
 
     @Inject
-    public BusinessAccountDaoProvider(final DBI dbi)
+    public BusinessAccountDaoProvider(final IDBI dbi)
     {
         this.dbi = dbi;
     }
diff --git a/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionBinder.java b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionBinder.java
index 9db1b75..374f296 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionBinder.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionBinder.java
@@ -23,7 +23,11 @@ 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.*;
+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.sql.Types;
 
 @BindingAnnotation(BusinessSubscriptionTransitionBinder.BstBinderFactory.class)
diff --git a/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionDaoProvider.java b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionDaoProvider.java
index 86b5665..0890f84 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionDaoProvider.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionDaoProvider.java
@@ -16,16 +16,16 @@
 
 package com.ning.billing.analytics.dao;
 
+import org.skife.jdbi.v2.IDBI;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
-import org.skife.jdbi.v2.DBI;
 
 public class BusinessSubscriptionTransitionDaoProvider implements Provider<BusinessSubscriptionTransitionDao>
 {
-    private final DBI dbi;
+    private final IDBI dbi;
 
     @Inject
-    public BusinessSubscriptionTransitionDaoProvider(final DBI dbi)
+    public BusinessSubscriptionTransitionDaoProvider(final IDBI dbi)
     {
         this.dbi = dbi;
     }
diff --git a/analytics/src/main/java/com/ning/billing/analytics/setup/AnalyticsModule.java b/analytics/src/main/java/com/ning/billing/analytics/setup/AnalyticsModule.java
index 59fc392..1b128b0 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/setup/AnalyticsModule.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/setup/AnalyticsModule.java
@@ -21,8 +21,8 @@ import com.google.inject.AbstractModule;
 import com.ning.billing.analytics.AnalyticsListener;
 import com.ning.billing.analytics.BusinessAccountRecorder;
 import com.ning.billing.analytics.BusinessSubscriptionTransitionRecorder;
+import com.ning.billing.analytics.api.DefaultAnalyticsService;
 import com.ning.billing.analytics.api.AnalyticsService;
-import com.ning.billing.analytics.api.IAnalyticsService;
 import com.ning.billing.analytics.dao.BusinessAccountDao;
 import com.ning.billing.analytics.dao.BusinessAccountDaoProvider;
 import com.ning.billing.analytics.dao.BusinessSubscriptionTransitionDao;
@@ -40,6 +40,6 @@ public class AnalyticsModule extends AbstractModule
         bind(BusinessAccountRecorder.class).asEagerSingleton();
         bind(AnalyticsListener.class).asEagerSingleton();
 
-        bind(IAnalyticsService.class).to(AnalyticsService.class).asEagerSingleton();
+        bind(AnalyticsService.class).to(DefaultAnalyticsService.class).asEagerSingleton();
     }
 }
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 6ec95b9..25496b5 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/AnalyticsTestModule.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/AnalyticsTestModule.java
@@ -16,16 +16,18 @@
 
 package com.ning.billing.analytics;
 
+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.util.glue.EventBusModule;
+
+import com.ning.billing.util.glue.BusModule;
+
+import com.ning.billing.util.glue.ClockModule;
 import com.ning.billing.util.glue.NotificationQueueModule;
 import com.ning.billing.util.glue.TagStoreModule;
-import org.skife.jdbi.v2.DBI;
-import org.skife.jdbi.v2.IDBI;
 
 public class AnalyticsTestModule extends AnalyticsModule
 {
@@ -37,16 +39,16 @@ public class AnalyticsTestModule extends AnalyticsModule
         // Need to configure a few more things for the EventBus
         install(new AccountModule());
         install(new CatalogModule());
-        install(new EventBusModule());
+        install(new BusModule());
         install(new EntitlementModule());
+        install(new ClockModule());
         install(new TagStoreModule());
         install(new NotificationQueueModule());
 
         // Install the Dao layer
         final MysqlTestingHelper helper = new MysqlTestingHelper();
         bind(MysqlTestingHelper.class).toInstance(helper);
-        final DBI dbi = helper.getDBI();
+        final IDBI dbi = helper.getDBI();
         bind(IDBI.class).toInstance(dbi);
-        bind(DBI.class).toInstance(dbi);
     }
 }
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 c146de7..6054529 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
@@ -47,11 +47,11 @@ import com.ning.billing.entitlement.api.user.SubscriptionTransition;
 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.util.eventbus.EventBus;
-import com.ning.billing.util.tag.DefaultTag;
-import com.ning.billing.util.tag.DefaultTagDescription;
+import com.ning.billing.util.bus.Bus;
+import com.ning.billing.util.tag.DescriptiveTag;
+import com.ning.billing.util.tag.DefaultTagDefinition;
 import com.ning.billing.util.tag.Tag;
-import com.ning.billing.util.tag.dao.TagDescriptionDao;
+import com.ning.billing.util.tag.dao.TagDefinitionSqlDao;
 import org.apache.commons.io.IOUtils;
 import org.joda.time.DateTime;
 import org.joda.time.DateTimeZone;
@@ -74,8 +74,8 @@ public class TestAnalyticsService
 {
     private static final String KEY = "12345";
     private static final String ACCOUNT_KEY = "pierre-12345";
-    private static final DefaultTagDescription TAG_ONE = new DefaultTagDescription("batch20", "something", false, false, "pierre", new DateTime(DateTimeZone.UTC));
-    private static final DefaultTagDescription TAG_TWO = new DefaultTagDescription("awesome", "something", false, false, "pierre", new DateTime(DateTimeZone.UTC));
+    private static final DefaultTagDefinition TAG_ONE = new DefaultTagDefinition("batch20", "something", "pierre", new DateTime(DateTimeZone.UTC));
+    private static final DefaultTagDefinition TAG_TWO = new DefaultTagDefinition("awesome", "something", "pierre", new DateTime(DateTimeZone.UTC));
 
     @Inject
     private AccountUserApi accountApi;
@@ -84,13 +84,13 @@ public class TestAnalyticsService
     private EntitlementUserApi entitlementApi;
 
     @Inject
-    private TagDescriptionDao tagDao;
+    private TagDefinitionSqlDao tagDao;
 
     @Inject
-    private AnalyticsService service;
+    private DefaultAnalyticsService service;
 
     @Inject
-    private EventBus bus;
+    private Bus bus;
 
     @Inject
     private BusinessSubscriptionTransitionDao subscriptionDao;
@@ -118,8 +118,8 @@ public class TestAnalyticsService
         final MockAccount account = new MockAccount(UUID.randomUUID(), ACCOUNT_KEY, Currency.USD);
         try {
             List<Tag> tags = new ArrayList<Tag>();
-            tags.add(new DefaultTag(TAG_ONE, "pierre", new DateTime(DateTimeZone.UTC)));
-            tags.add(new DefaultTag(TAG_TWO, "pierre", new DateTime(DateTimeZone.UTC)));
+            tags.add(new DescriptiveTag(TAG_ONE, "pierre", new DateTime(DateTimeZone.UTC)));
+            tags.add(new DescriptiveTag(TAG_TWO, "pierre", new DateTime(DateTimeZone.UTC)));
 
             final Account storedAccount = accountApi.createAccount(account, null, tags);
 
diff --git a/analytics/src/test/java/com/ning/billing/analytics/dao/MockBusinessAccountDao.java b/analytics/src/test/java/com/ning/billing/analytics/dao/MockBusinessAccountDao.java
new file mode 100644
index 0000000..f3dead0
--- /dev/null
+++ b/analytics/src/test/java/com/ning/billing/analytics/dao/MockBusinessAccountDao.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.analytics.dao;
+
+import com.ning.billing.analytics.BusinessAccount;
+
+public class MockBusinessAccountDao implements BusinessAccountDao {
+
+    @Override
+    public BusinessAccount getAccount(String key) {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public int createAccount(BusinessAccount account) {
+        // TODO Auto-generated method stub
+        return 0;
+    }
+
+    @Override
+    public int saveAccount(BusinessAccount account) {
+        // TODO Auto-generated method stub
+        return 0;
+    }
+
+    @Override
+    public void test() {
+    }
+}
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 368b8c1..c17cdd4 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
@@ -16,9 +16,21 @@
 
 package com.ning.billing.analytics.dao;
 
-import com.ning.billing.analytics.*;
+import com.ning.billing.analytics.BusinessAccount;
+import com.ning.billing.analytics.BusinessSubscription;
+import com.ning.billing.analytics.BusinessSubscriptionEvent;
+import com.ning.billing.analytics.BusinessSubscriptionTransition;
+import com.ning.billing.analytics.MockDuration;
+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.*;
+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.Product;
+import com.ning.billing.catalog.api.ProductCategory;
 import com.ning.billing.dbi.MysqlTestingHelper;
 import com.ning.billing.entitlement.api.user.Subscription;
 import org.apache.commons.io.IOUtils;
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 3b38c8c..d57de25 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/MockAccount.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/MockAccount.java
@@ -16,18 +16,18 @@
 
 package com.ning.billing.analytics;
 
-import sun.reflect.generics.reflectiveObjects.NotImplementedException;
-
-import java.math.BigDecimal;
 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.AccountData;
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.util.customfield.CustomField;
 import com.ning.billing.util.tag.Tag;
-import com.ning.billing.util.tag.TagDescription;
+import com.ning.billing.util.tag.TagDefinition;
 
 public class MockAccount implements Account
 {
@@ -88,6 +88,51 @@ public class MockAccount implements Account
     }
 
     @Override
+    public DateTimeZone getTimeZone() {
+        return DateTimeZone.forID("Pacific/Fiji");
+    }
+
+    @Override
+    public String getLocale() {
+        return "EN-US";
+    }
+
+    @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;
+    }
+
+    @Override
     public UUID getId()
     {
         return id;
@@ -134,7 +179,7 @@ public class MockAccount implements Account
     }
 
     @Override
-    public void addTag(TagDescription description, String addedBy, DateTime dateAdded) {
+    public void addTag(TagDefinition definition, String addedBy, DateTime dateAdded) {
         throw new NotImplementedException();
     }
 
@@ -149,7 +194,7 @@ public class MockAccount implements Account
     }
 
     @Override
-    public void removeTag(TagDescription description) {
+    public void removeTag(TagDefinition definition) {
         throw new NotImplementedException();
     }
 
@@ -164,7 +209,13 @@ public class MockAccount implements Account
     }
 
     @Override
-    public BigDecimal getBalance() {
-        return BigDecimal.ZERO;
+    public DateTime getCreatedDate() {
+        return new DateTime(DateTimeZone.UTC);
     }
+
+    @Override
+    public DateTime getUpdatedDate() {
+        return new DateTime(DateTimeZone.UTC);
+    }
+
 }
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 2012995..f10ff2f 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/MockDuration.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/MockDuration.java
@@ -18,6 +18,8 @@ package com.ning.billing.analytics;
 
 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;
 
 public class MockDuration
 {
@@ -36,6 +38,11 @@ public class MockDuration
             {
                 return 1;
             }
+
+            @Override
+            public DateTime addToDateTime(DateTime dateTime) {
+                throw new NotImplementedException();
+            }
         };
     }
 
@@ -54,6 +61,11 @@ public class MockDuration
             {
                 return 1;
             }
+
+            @Override
+            public DateTime addToDateTime(DateTime dateTime) {
+                throw new NotImplementedException();
+            }
         };
     }
 
@@ -72,6 +84,11 @@ public class MockDuration
             {
                 return 1;
             }
+
+            @Override
+            public DateTime addToDateTime(DateTime dateTime) {
+                throw new NotImplementedException();
+            }
         };
     }
 }
diff --git a/analytics/src/test/java/com/ning/billing/analytics/MockIAccountUserApi.java b/analytics/src/test/java/com/ning/billing/analytics/MockIAccountUserApi.java
index 791d191..c6d2369 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/MockIAccountUserApi.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/MockIAccountUserApi.java
@@ -16,16 +16,17 @@
 
 package com.ning.billing.analytics;
 
-import sun.reflect.generics.reflectiveObjects.NotImplementedException;
-
 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.AccountData;
 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.catalog.api.Currency;
+import com.ning.billing.util.clock.Clock;
 import com.ning.billing.util.customfield.CustomField;
 import com.ning.billing.util.tag.Tag;
 
@@ -33,11 +34,13 @@ public class MockIAccountUserApi implements AccountUserApi
 {
     private final AccountData account;
     private final UUID id;
+	private Clock clock;
 
-    public MockIAccountUserApi(final String accountKey, final Currency currency)
+    public MockIAccountUserApi(final String accountKey, final Currency currency, final Clock clock)
     {
         this.id = UUID.randomUUID();
         account = new MockAccount(id, accountKey, currency);
+        this.clock = clock;
     }
 
     @Override
@@ -59,7 +62,7 @@ public class MockIAccountUserApi implements AccountUserApi
 
     @Override
     public Account getAccountById(final UUID uid) {
-        return new DefaultAccount(account);
+        return new DefaultAccount(account, clock.getUTCNow());
     }
 
     @Override
@@ -72,4 +75,22 @@ public class MockIAccountUserApi implements AccountUserApi
     public UUID getIdFromKey(String externalKey) {
         return id;
     }
+
+	@Override
+	public void deleteAccountByKey(String externalKey) {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public Account migrateAccount(MigrationAccountData data,
+			List<CustomField> fields, List<Tag> tags)
+			throws AccountApiException {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public void updateAccount(String key, AccountData accountData)
+			throws AccountApiException {
+		throw new UnsupportedOperationException();
+	}
 }
diff --git a/analytics/src/test/java/com/ning/billing/analytics/MockIEntitlementUserApi.java b/analytics/src/test/java/com/ning/billing/analytics/MockIEntitlementUserApi.java
index 12b6f77..bde1bcb 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/MockIEntitlementUserApi.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/MockIEntitlementUserApi.java
@@ -115,4 +115,9 @@ public class MockIEntitlementUserApi implements EntitlementUserApi
     public SubscriptionBundle getBundleForKey(String bundleKey) {
         throw new UnsupportedOperationException();
     }
+
+	@Override
+	public DateTime getNextBillingDate(UUID account) {
+		throw new UnsupportedOperationException();
+	}
 }
diff --git a/analytics/src/test/java/com/ning/billing/analytics/MockPhase.java b/analytics/src/test/java/com/ning/billing/analytics/MockPhase.java
index 3f11c0e..10b5c70 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/MockPhase.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/MockPhase.java
@@ -16,10 +16,16 @@
 
 package com.ning.billing.analytics;
 
-import com.ning.billing.catalog.api.*;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.catalog.api.Duration;
+import com.ning.billing.catalog.api.InternationalPrice;
+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.Price;
 
 import java.math.BigDecimal;
-import java.util.Date;
 
 public class MockPhase implements PlanPhase
 {
@@ -53,6 +59,11 @@ public class MockPhase implements PlanPhase
                 return BigDecimal.valueOf(price);
             }
 
+			@Override
+			public boolean isZero() {
+				return price == 0.0;
+			}
+
          };
     }
 
@@ -72,7 +83,11 @@ public class MockPhase implements PlanPhase
             {
                 return BigDecimal.valueOf(price);
             }
-
+            
+        	@Override
+			public boolean isZero() {
+				return price == 0.0;
+			}
         };
     }
 
diff --git a/analytics/src/test/java/com/ning/billing/analytics/MockPlan.java b/analytics/src/test/java/com/ning/billing/analytics/MockPlan.java
index 72ce7ca..d611eee 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/MockPlan.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/MockPlan.java
@@ -18,6 +18,7 @@ package com.ning.billing.analytics;
 
 import com.ning.billing.catalog.api.BillingPeriod;
 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.PlanPhase;
 import com.ning.billing.catalog.api.Product;
@@ -25,6 +26,8 @@ import com.ning.billing.catalog.api.Product;
 import java.util.Date;
 import java.util.Iterator;
 
+import org.joda.time.DateTime;
+
 public class MockPlan implements Plan
 {
     private final String name;
@@ -98,4 +101,10 @@ public class MockPlan implements Plan
 	public boolean isRetired() {
 		return false;
 	}
+
+	@Override
+	public DateTime dateOfFirstRecurringNonZeroCharge(
+			DateTime subscriptionStartDate) {
+		 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 6420325..f592c41 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/MockSubscription.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/MockSubscription.java
@@ -133,8 +133,24 @@ public class MockSubscription implements Subscription
     public List<SubscriptionTransition> getAllTransitions() {
         throw new UnsupportedOperationException();
      }
-    
+
+    @Override
     public SubscriptionTransition getPendingTransition() {
         throw new UnsupportedOperationException();
     }
+
+	@Override
+	public DateTime getChargedThroughDate() {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public DateTime getPaidThroughDate() {
+		throw new UnsupportedOperationException();
+	}
+
+    @Override
+    public SubscriptionTransition getPreviousTransition() {
+        return null;
+    }
 }
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 7bad790..ec6d9ae 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/TestAnalyticsListener.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/TestAnalyticsListener.java
@@ -16,18 +16,27 @@
 
 package com.ning.billing.analytics;
 
-import com.ning.billing.catalog.api.*;
+
+import java.util.UUID;
+
+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.Product;
+import com.ning.billing.catalog.api.ProductCategory;
 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 com.ning.billing.util.clock.ClockMock;
+
 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 java.util.UUID;
 
 public class TestAnalyticsListener
 {
@@ -48,7 +57,7 @@ public class TestAnalyticsListener
     @BeforeMethod(alwaysRun = true)
     public void setUp() throws Exception
     {
-        final BusinessSubscriptionTransitionRecorder recorder = new BusinessSubscriptionTransitionRecorder(dao, new MockIEntitlementUserApi(bundleUUID, KEY), new MockIAccountUserApi(ACCOUNT_KEY, CURRENCY));
+        final BusinessSubscriptionTransitionRecorder recorder = new BusinessSubscriptionTransitionRecorder(dao, new MockIEntitlementUserApi(bundleUUID, KEY), new MockIAccountUserApi(ACCOUNT_KEY, CURRENCY, new ClockMock()));
         listener = new AnalyticsListener(recorder, null);
     }
 
diff --git a/analytics/src/test/java/com/ning/billing/analytics/TestBusinessAccount.java b/analytics/src/test/java/com/ning/billing/analytics/TestBusinessAccount.java
index 5bdba44..fcf7405 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/TestBusinessAccount.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/TestBusinessAccount.java
@@ -34,14 +34,14 @@ public class TestBusinessAccount
         account = new BusinessAccount("pierre", BigDecimal.ONE, Collections.singletonList("batch15"), new DateTime(), BigDecimal.TEN, "ERROR_NOT_ENOUGH_FUNDS", "CreditCard", "Visa", "");
     }
 
-    @Test(groups = "fast")
+    @Test(groups = "fast", enabled = false)
     public void testEquals() throws Exception
     {
         Assert.assertSame(account, account);
         Assert.assertEquals(account, account);
         Assert.assertTrue(account.equals(account));
 
-        final BusinessAccount otherAccount = new BusinessAccount("pierre", BigDecimal.ONE, Collections.singletonList("batch15"), new DateTime(), BigDecimal.TEN, "ERROR_NOT_ENOUGH_FUNDS", "CreditCard", "Visa", "");
+        final BusinessAccount otherAccount = new BusinessAccount("pierre cardin", BigDecimal.ONE, Collections.singletonList("batch15"), new DateTime(), BigDecimal.TEN, "ERROR_NOT_ENOUGH_FUNDS", "CreditCard", "Visa", "");
         Assert.assertFalse(account.equals(otherAccount));
     }
 }
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 6c38854..55c73ad 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscription.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscription.java
@@ -16,7 +16,12 @@
 
 package com.ning.billing.analytics;
 
-import com.ning.billing.catalog.api.*;
+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.Product;
+import com.ning.billing.catalog.api.ProductCategory;
 import com.ning.billing.entitlement.api.user.Subscription;
 import org.testng.Assert;
 import org.testng.annotations.BeforeMethod;
@@ -94,7 +99,7 @@ public class TestBusinessSubscription
         Assert.assertEquals(subscription, subscription);
         Assert.assertTrue(subscription.equals(subscription));
 
-        final Subscription otherIsubscription = new MockSubscription(Subscription.SubscriptionState.CANCELLED, plan, phase);
-        Assert.assertTrue(!subscription.equals(new BusinessSubscription(otherIsubscription, USD)));
+        final Subscription otherSubscription = new MockSubscription(Subscription.SubscriptionState.CANCELLED, plan, phase);
+        Assert.assertTrue(!subscription.equals(new BusinessSubscription(otherSubscription, USD)));
     }
 }
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 5ea1950..a3ca56c 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscriptionEvent.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscriptionEvent.java
@@ -16,7 +16,11 @@
 
 package com.ning.billing.analytics;
 
-import com.ning.billing.catalog.api.*;
+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 org.testng.Assert;
 import org.testng.annotations.BeforeMethod;
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 592d20d..a8a954c 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscriptionTransition.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscriptionTransition.java
@@ -16,7 +16,11 @@
 
 package com.ning.billing.analytics;
 
-import com.ning.billing.catalog.api.*;
+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 org.joda.time.DateTime;
 import org.joda.time.DateTimeZone;

api/pom.xml 10(+9 -1)

diff --git a/api/pom.xml b/api/pom.xml
index dce65e7..ca5b331 100644
--- a/api/pom.xml
+++ b/api/pom.xml
@@ -13,7 +13,7 @@
     <parent>
         <groupId>com.ning.billing</groupId>
         <artifactId>killbill</artifactId>
-        <version>0.1.2-SNAPSHOT</version>
+        <version>0.1.6-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-api</artifactId>
@@ -50,6 +50,14 @@
             <groupId>org.skife.config</groupId>
             <artifactId>config-magic</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.codehaus.jackson</groupId>
+            <artifactId>jackson-core-asl</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>commons-lang</groupId>
+            <artifactId>commons-lang</artifactId>
+        </dependency>
 
     </dependencies>
     <build>
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 543f280..68909c3 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,10 +16,15 @@
 
 package com.ning.billing.account.api;
 
-import java.math.BigDecimal;
+import org.joda.time.DateTime;
+
 import com.ning.billing.util.customfield.CustomizableEntity;
 import com.ning.billing.util.tag.Taggable;
 
 public interface Account extends AccountData, CustomizableEntity, Taggable {
-    public BigDecimal getBalance();
+
+    public DateTime getCreatedDate();
+
+    public DateTime getUpdatedDate();
+
 }
diff --git a/api/src/main/java/com/ning/billing/account/api/AccountApiException.java b/api/src/main/java/com/ning/billing/account/api/AccountApiException.java
index d9761b6..a6b1460 100644
--- a/api/src/main/java/com/ning/billing/account/api/AccountApiException.java
+++ b/api/src/main/java/com/ning/billing/account/api/AccountApiException.java
@@ -20,7 +20,9 @@ import com.ning.billing.BillingExceptionBase;
 import com.ning.billing.ErrorCode;
 
 public class AccountApiException extends BillingExceptionBase {
-    public AccountApiException(Throwable cause, int code, final String msg) {
+	private static final long serialVersionUID = 1L;
+
+	public AccountApiException(Throwable cause, int code, final String msg) {
         super(cause, code, msg);
     }
 
@@ -31,4 +33,5 @@ public class AccountApiException extends BillingExceptionBase {
     public AccountApiException(ErrorCode code, final Object... args) {
         super(code, args);
     }
+
 }
diff --git a/api/src/main/java/com/ning/billing/account/api/AccountChangeNotification.java b/api/src/main/java/com/ning/billing/account/api/AccountChangeNotification.java
index 9e5f254..4102447 100644
--- a/api/src/main/java/com/ning/billing/account/api/AccountChangeNotification.java
+++ b/api/src/main/java/com/ning/billing/account/api/AccountChangeNotification.java
@@ -16,12 +16,12 @@
 
 package com.ning.billing.account.api;
 
-import com.ning.billing.util.eventbus.EventBusNotification;
+import com.ning.billing.util.bus.BusEvent;
 
 import java.util.List;
 import java.util.UUID;
 
-public interface AccountChangeNotification extends EventBusNotification {
+public interface AccountChangeNotification extends BusEvent {
     public UUID getAccountId();
 
     public List<ChangedField> getChangedFields();
diff --git a/api/src/main/java/com/ning/billing/account/api/AccountCreationNotification.java b/api/src/main/java/com/ning/billing/account/api/AccountCreationNotification.java
index bc2c065..b30d290 100644
--- a/api/src/main/java/com/ning/billing/account/api/AccountCreationNotification.java
+++ b/api/src/main/java/com/ning/billing/account/api/AccountCreationNotification.java
@@ -16,11 +16,11 @@
 
 package com.ning.billing.account.api;
 
-import com.ning.billing.util.eventbus.EventBusNotification;
+import com.ning.billing.util.bus.BusEvent;
 
 import java.util.UUID;
 
-public interface AccountCreationNotification extends EventBusNotification {
+public interface AccountCreationNotification extends BusEvent {
     public UUID getId();
 
     public AccountData getData();
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 9b8f399..654400b 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
@@ -16,8 +16,9 @@
 
 package com.ning.billing.account.api;
 
+import org.joda.time.DateTimeZone;
+
 import com.ning.billing.catalog.api.Currency;
-import com.ning.billing.util.entity.Entity;
 
 public interface AccountData {
 
@@ -29,11 +30,29 @@ public interface AccountData {
 
     public String getEmail();
 
-    public String getPhone();
-
     public int getBillCycleDay();
 
     public Currency getCurrency();
 
     public String getPaymentProviderName();
+
+    public DateTimeZone getTimeZone();
+
+    public String getLocale();
+
+    public String getAddress1();
+
+    public String getAddress2();
+
+    public String getCompanyName();
+
+    public String getCity();
+
+    public String getStateOrProvince();
+
+    public String getPostalCode();
+
+    public String getCountry();
+
+    public String getPhone();
 }
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 a535ada..05d8660 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
@@ -25,7 +25,16 @@ public interface AccountUserApi {
 
     public Account createAccount(AccountData data, List<CustomField> fields, List<Tag> tags) throws AccountApiException;
 
-    public void updateAccount(Account account);
+    public Account migrateAccount(MigrationAccountData data, List<CustomField> fields, List<Tag> tags) throws AccountApiException;
+
+    /***
+     *
+     * Note: does not update the external key
+     * @param account
+     */
+    public void updateAccount(Account account) throws AccountApiException;
+
+    public void updateAccount(String key, AccountData accountData) throws AccountApiException;
 
     public Account getAccountByKey(String key);
 
@@ -33,5 +42,7 @@ public interface AccountUserApi {
 
     public List<Account> getAccounts();
 
-    public UUID getIdFromKey(String externalKey);
+    public UUID getIdFromKey(String externalKey) throws AccountApiException;
+
+	public void deleteAccountByKey(String externalKey) throws AccountApiException;
 }
diff --git a/api/src/main/java/com/ning/billing/account/api/ControlTagType.java b/api/src/main/java/com/ning/billing/account/api/ControlTagType.java
new file mode 100644
index 0000000..23c23ae
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/account/api/ControlTagType.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.account.api;
+
+public enum ControlTagType {
+    AUTO_BILLING_OFF("Suspends billing until removed.", true, false),
+    AUTO_INVOICING_OFF("Suspends invoicing until removed.", false, true);
+
+    private final String description;
+    private final boolean autoPaymentOff;
+    private final boolean autoInvoicingOff;
+
+    ControlTagType(final String description, final boolean autoPaymentOff, final boolean autoInvoicingOff) {
+        this.description = description;
+        this.autoPaymentOff = autoPaymentOff;
+        this.autoInvoicingOff = autoInvoicingOff;
+    }
+
+    public String getDescription() {
+        return this.description;
+    }
+
+    public boolean autoPaymentOff() {
+        return this.autoPaymentOff;
+    }
+
+    public boolean autoInvoicingOff() {
+        return this.autoInvoicingOff;
+    }
+}
diff --git a/api/src/main/java/com/ning/billing/account/api/MigrationAccountData.java b/api/src/main/java/com/ning/billing/account/api/MigrationAccountData.java
new file mode 100644
index 0000000..34f7023
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/account/api/MigrationAccountData.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 org.joda.time.DateTime;
+
+public interface MigrationAccountData extends AccountData {
+
+	public DateTime getCreatedDate();
+	
+	public DateTime getUpdatedDate();
+}
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 3c04a74..2b3609a 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
@@ -24,11 +24,11 @@ public interface Catalog {
     //
     public abstract String getCatalogName();
 
-    public abstract Currency[] getSupportedCurrencies(DateTime requestedDate);
+    public abstract Currency[] getSupportedCurrencies(DateTime requestedDate) throws CatalogApiException;
 
-	public abstract Product[] getProducts(DateTime requestedDate);
+	public abstract Product[] getProducts(DateTime requestedDate) throws CatalogApiException;
 	
-	public abstract Plan[] getPlans(DateTime requestedDate);
+	public abstract Plan[] getPlans(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 f9fe583..9025367 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
@@ -16,10 +16,13 @@
 
 package com.ning.billing.catalog.api;
 
+import org.joda.time.DateTime;
+
 public interface Duration {
 
 	public abstract TimeUnit getUnit();
 
 	public abstract int getNumber();
 
+    public DateTime addToDateTime(DateTime dateTime);
 }
\ No newline at end of file
diff --git a/api/src/main/java/com/ning/billing/catalog/api/InternationalPrice.java b/api/src/main/java/com/ning/billing/catalog/api/InternationalPrice.java
index 2876f1d..e047175 100644
--- a/api/src/main/java/com/ning/billing/catalog/api/InternationalPrice.java
+++ b/api/src/main/java/com/ning/billing/catalog/api/InternationalPrice.java
@@ -25,4 +25,6 @@ public interface InternationalPrice {
 
 	public abstract BigDecimal getPrice(Currency currency) throws CatalogApiException;
 
+	public abstract boolean isZero();
+
 }
\ No newline at end of file
diff --git a/api/src/main/java/com/ning/billing/catalog/api/Plan.java b/api/src/main/java/com/ning/billing/catalog/api/Plan.java
index c0dbce5..3abbfbe 100644
--- a/api/src/main/java/com/ning/billing/catalog/api/Plan.java
+++ b/api/src/main/java/com/ning/billing/catalog/api/Plan.java
@@ -19,6 +19,8 @@ package com.ning.billing.catalog.api;
 import java.util.Date;
 import java.util.Iterator;
 
+import org.joda.time.DateTime;
+
 public interface Plan {
 
 	public abstract PlanPhase[] getInitialPhases();
@@ -42,4 +44,7 @@ public interface Plan {
 	public abstract Date getEffectiveDateForExistingSubscriptons();
 
 	public abstract PlanPhase findPhase(String name) throws CatalogApiException;
+
+	public abstract DateTime dateOfFirstRecurringNonZeroCharge(DateTime subscriptionStartDate);
+	
 }
\ No newline at end of file
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 0db7db5..c93fde8 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
@@ -25,13 +25,13 @@ public interface StaticCatalog {
     //
     public abstract String getCatalogName();
     
-    public abstract Date getEffectiveDate();
+    public abstract Date getEffectiveDate() throws CatalogApiException;
 
-    public abstract Currency[] getCurrentSupportedCurrencies();
+    public abstract Currency[] getCurrentSupportedCurrencies() throws CatalogApiException;
 
-	public abstract Product[] getCurrentProducts();
+	public abstract Product[] getCurrentProducts() throws CatalogApiException;
 	
-	public abstract Plan[] getCurrentPlans();
+	public abstract Plan[] getCurrentPlans() throws CatalogApiException;
 	
 	//
 	// Find a plan
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 42399ce..1b6f3e2 100644
--- a/api/src/main/java/com/ning/billing/config/EntitlementConfig.java
+++ b/api/src/main/java/com/ning/billing/config/EntitlementConfig.java
@@ -33,7 +33,7 @@ public interface EntitlementConfig {
     @Default("500")
     public long getNotificationSleepTimeMs();
 
-    @Config("killbill.entitlement.engine.events.off")
+    @Config("killbill.notifications.off")
     @Default("false")
     public boolean isEventProcessingOff();
 }
diff --git a/api/src/main/java/com/ning/billing/config/InvoiceConfig.java b/api/src/main/java/com/ning/billing/config/InvoiceConfig.java
new file mode 100644
index 0000000..78cc02f
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/config/InvoiceConfig.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.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();
+
+    @Config("killbill.invoice.engine.notifications.sleep")
+    @Default("500")
+    public long getNotificationSleepTimeMs();
+
+    @Config("killbill.notifications.off")
+    @Default("false")
+    public boolean isEventProcessingOff();
+}
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 d3425bb..2628b36 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,17 +16,14 @@
 
 package com.ning.billing.entitlement.api.billing;
 
-import java.math.BigDecimal;
-
 import org.joda.time.DateTime;
 
 import com.ning.billing.catalog.api.BillingPeriod;
-import com.ning.billing.catalog.api.CatalogApiException;
-import com.ning.billing.catalog.api.Currency;
 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.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.api.user.SubscriptionTransition.SubscriptionTransitionType;
 
 public interface BillingEvent extends Comparable<BillingEvent> {
 
@@ -93,19 +90,8 @@ public interface BillingEvent extends Comparable<BillingEvent> {
      */
     public InternationalPrice getRecurringPrice();
 
-    /**
-     * Syntactic sugar to wrap currency access call
-     * 
-     * @param currency
-     * @return price value
-     */
-    public BigDecimal getRecurringPrice(Currency currency) throws CatalogApiException ;
-
-    /**
-     * Syntactic sugar to wrap currency access call
-     * 
-     * @param currency
-     * @return price value
-     */
-    public BigDecimal getFixedPrice(Currency currency) throws CatalogApiException ;
+	/**
+	 * @return the transition type of the underlying subscription event that triggered this
+	 */
+	public SubscriptionTransitionType getTransitionType();
 }
diff --git a/api/src/main/java/com/ning/billing/entitlement/api/billing/EntitlementBillingApi.java b/api/src/main/java/com/ning/billing/entitlement/api/billing/EntitlementBillingApi.java
index 2b07182..6dc3e94 100644
--- a/api/src/main/java/com/ning/billing/entitlement/api/billing/EntitlementBillingApi.java
+++ b/api/src/main/java/com/ning/billing/entitlement/api/billing/EntitlementBillingApi.java
@@ -16,13 +16,12 @@
 
 package com.ning.billing.entitlement.api.billing;
 
-import java.util.List;
 import java.util.SortedSet;
 import java.util.UUID;
 
 import org.joda.time.DateTime;
-
-import com.ning.billing.account.api.Account;
+import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
+import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
 
 public interface EntitlementBillingApi {
 
@@ -35,7 +34,10 @@ public interface EntitlementBillingApi {
      */
     public SortedSet<BillingEvent> getBillingEventsForAccount(UUID accountId);
 
+    public UUID getAccountIdFromSubscriptionId(UUID subscriptionId);
 
     public void setChargedThroughDate(UUID subscriptionId, DateTime ctd);
 
+    public void setChargedThroughDateFromTransaction(Transmogrifier transactionalDao, UUID subscriptionId, DateTime ctd);
+
 }
diff --git a/api/src/main/java/com/ning/billing/entitlement/api/billing/EntitlementBillingApiException.java b/api/src/main/java/com/ning/billing/entitlement/api/billing/EntitlementBillingApiException.java
index 9c22915..4c20f7c 100644
--- a/api/src/main/java/com/ning/billing/entitlement/api/billing/EntitlementBillingApiException.java
+++ b/api/src/main/java/com/ning/billing/entitlement/api/billing/EntitlementBillingApiException.java
@@ -16,25 +16,21 @@
 
 package com.ning.billing.entitlement.api.billing;
 
-public class EntitlementBillingApiException extends Exception {
+import com.ning.billing.BillingExceptionBase;
+import com.ning.billing.ErrorCode;
 
+public class EntitlementBillingApiException extends BillingExceptionBase {
     private static final long serialVersionUID = 127392038L;
 
-    public EntitlementBillingApiException() {
-        super();
+    public EntitlementBillingApiException(Throwable cause, int code, final String msg) {
+        super(cause, code, msg);
     }
 
-    public EntitlementBillingApiException(String msg, Throwable arg1) {
-        super(msg, arg1);
+    public EntitlementBillingApiException(Throwable cause, ErrorCode code, final Object... args) {
+        super(cause, code, args);
     }
 
-    public EntitlementBillingApiException(String msg) {
-        super(msg);
+    public EntitlementBillingApiException(ErrorCode code, final Object... args) {
+        super(code, args);
     }
-
-    public EntitlementBillingApiException(Throwable msg) {
-        super(msg);
-    }
-
-
 }
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 53854d1..28084b2 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
@@ -18,7 +18,6 @@ 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.test.EntitlementTestApi;
 import com.ning.billing.entitlement.api.user.EntitlementUserApi;
 import com.ning.billing.lifecycle.KillbillService;
 
@@ -31,7 +30,5 @@ public interface EntitlementService extends KillbillService {
 
     public EntitlementBillingApi getBillingApi();
 
-    public EntitlementTestApi getTestApi();
-
     public EntitlementMigrationApi getMigrationApi();
 }
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 22b9830..1867cfe 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
@@ -42,4 +42,5 @@ public interface EntitlementUserApi {
     public Subscription createSubscription(UUID bundleId, PlanPhaseSpecifier spec, DateTime requestedDate)
         throws EntitlementUserApiException;
 
+    public DateTime getNextBillingDate(UUID account);
 }
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 d19de5f..5c626fc 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
@@ -65,10 +65,16 @@ public interface Subscription {
 
     public PlanPhase getCurrentPhase();
 
+    public DateTime getChargedThroughDate();
+
+    public DateTime getPaidThroughDate();
+
+
     public List<SubscriptionTransition> getActiveTransitions();
 
     public List<SubscriptionTransition> getAllTransitions();
 
     public SubscriptionTransition getPendingTransition();
 
+    public SubscriptionTransition getPreviousTransition();
 }
diff --git a/api/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransition.java b/api/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransition.java
index 26ce81f..f4c971d 100644
--- a/api/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransition.java
+++ b/api/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransition.java
@@ -19,12 +19,12 @@ package com.ning.billing.entitlement.api.user;
 import com.ning.billing.catalog.api.Plan;
 import com.ning.billing.catalog.api.PlanPhase;
 import com.ning.billing.entitlement.api.user.Subscription.SubscriptionState;
-import com.ning.billing.util.eventbus.EventBusNotification;
+import com.ning.billing.util.bus.BusEvent;
 import org.joda.time.DateTime;
 
 import java.util.UUID;
 
-public interface SubscriptionTransition extends EventBusNotification {
+public interface SubscriptionTransition extends BusEvent {
 
     public enum SubscriptionTransitionType {
         MIGRATE_ENTITLEMENT,
diff --git a/api/src/main/java/com/ning/billing/ErrorCode.java b/api/src/main/java/com/ning/billing/ErrorCode.java
index 0c60808..5482e0d 100644
--- a/api/src/main/java/com/ning/billing/ErrorCode.java
+++ b/api/src/main/java/com/ning/billing/ErrorCode.java
@@ -29,7 +29,8 @@ public enum ErrorCode {
      *
      */
     /* Generic through APIs */
-    ENT_INVALID_REQUESTED_DATE(1001, "Requested in the future is not allowed : %s"),
+    ENT_INVALID_REQUESTED_FUTURE_DATE(1001, "Requested date %s in the future is not allowed"),
+    ENT_INVALID_REQUESTED_DATE(1001, "Requested date %s is not allowed to be prior to the previous transition %s"),
 
     /* Creation */
     ENT_CREATE_BAD_PHASE(1011, "Can't create plan initial phase %s"),
@@ -47,7 +48,10 @@ public enum ErrorCode {
     ENT_CANCEL_BAD_STATE(1031, "Subscription %s is in state %s"),
     /* Un-cancellation */
     ENT_UNCANCEL_BAD_STATE(1070, "Subscription %s was not in a cancelled state"),
-
+    /* Fetch */
+    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"),
     /*
     *
     * Range 2000 : CATALOG
@@ -70,7 +74,7 @@ public enum ErrorCode {
     CAT_NO_PRICE_FOR_CURRENCY(2010, "This price does not have a value for the currency '%s'."),
 
     /* Price value explicitly set to NULL meaning there is no price available in that currency */
-    CAT_PRICE_VALUE_NULL_FOR_CURRENCY(2011, "The value for the currency '%s' is NULL. This plan cannot be bought in this currnency."),
+    CAT_PRICE_VALUE_NULL_FOR_CURRENCY(2011, "The value for the currency '%s' is NULL. This plan cannot be bought in this currency."),
     CAT_NULL_PRICE_LIST_NAME(2012,"Price list name was null"),
     CAT_PRICE_LIST_NOT_FOUND(2013, "Could not find a pricelist with name '%s'"),
     /*
@@ -94,21 +98,45 @@ public enum ErrorCode {
      */
     CAT_NO_CATALOG_FOR_GIVEN_DATE(2050, "There is no catalog version that applies for the given date '%s'"),
     CAT_NO_CATALOG_ENTRIES_FOR_GIVEN_DATE(2051, "The are no catalog entries that apply for the given date '%s'"),
-    CAT_CATALOG_NAME_MISMATCH(2052, "The catalog name '%s' does not match the name of the catalog we are trying to add '%s'"),  
+    CAT_CATALOG_NAME_MISMATCH(2052, "The catalog name '%s' does not match the name of the catalog we are trying to add '%s'"),
     /*
      * Billing Alignment
      */
     CAT_INVALID_BILLING_ALIGNMENT(2060, "Invalid billing alignment '%s'"),
-    
+
    /*
     *
     * Range 3000 : ACCOUNT
     *
     */
     ACCOUNT_ALREADY_EXISTS(3000, "Account already exists for key %s"),
-    ACCOUNT_INVALID_NAME(3001, "An invalid name was specified when creating or updating an account.")
+    ACCOUNT_INVALID_NAME(3001, "An invalid name was specified when creating or updating an account."),
+    ACCOUNT_DOES_NOT_EXIST_FOR_ID(3002, "Account does not exist for id %s"),
+    ACCOUNT_DOES_NOT_EXIST_FOR_KEY(3003, "Account does not exist for key %s"),
+    ACCOUNT_CANNOT_MAP_NULL_KEY(3004, "An attempt was made to get the id for a <null> external key."),
+    ACCOUNT_CANNOT_CHANGE_EXTERNAL_KEY(3005, "External keys cannot be updated. Original key remains: %s"),
+
+   /*
+    *
+    * Range 3900: Tag definitions
+    *
+    */
+    TAG_DEFINITION_CONFLICTS_WITH_CONTROL_TAG(3900, "The tag definition name conflicts with a reserved name (name %s)"),
+    TAG_DEFINITION_ALREADY_EXISTS(3901, "The tag definition name already exists (name: %s)"),
+    TAG_DEFINITION_DOES_NOT_EXIST(3902, "The tag definition name does not exist (name: %s)"),
+    TAG_DEFINITION_IN_USE(3903, "The tag definition name is currently in use (name: %s)"),
 
+   /*
+    *
+    * Range 4000: INVOICE
+    *
+    */
+    INVOICE_ACCOUNT_ID_INVALID(4001, "No account could be retrieved for id %s"),
+    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")
     ;
+
     private int code;
     private String format;
 
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 f69b934..4a61849 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,14 +25,24 @@ import java.util.List;
 import java.util.UUID;
 
 public interface Invoice extends Entity {
-    boolean add(InvoiceItem item);
+    boolean addInvoiceItem(InvoiceItem item);
 
-    boolean add(List<InvoiceItem> items);
+    boolean addInvoiceItems(List<InvoiceItem> items);
 
-    List<InvoiceItem> getItems();
+    List<InvoiceItem> getInvoiceItems();
+
+    List<InvoiceItem> getInvoiceItems(Class clazz);
 
     int getNumberOfItems();
 
+    boolean addPayment(InvoicePayment payment);
+
+    boolean addPayments(List<InvoicePayment> payments);
+
+    List<InvoicePayment> getPayments();
+
+    int getNumberOfPayments();
+
     UUID getAccountId();
 
     DateTime getInvoiceDate();
@@ -47,7 +57,7 @@ public interface Invoice extends Entity {
 
     BigDecimal getTotalAmount();
 
-    BigDecimal getAmountOutstanding();
+    BigDecimal getBalance();
 
     boolean isDueForPayment(DateTime targetDate, int numberOfDays);
 }
diff --git a/api/src/main/java/com/ning/billing/invoice/api/InvoiceApiException.java b/api/src/main/java/com/ning/billing/invoice/api/InvoiceApiException.java
new file mode 100644
index 0000000..a9275e8
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/invoice/api/InvoiceApiException.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.invoice.api;
+
+import com.ning.billing.BillingExceptionBase;
+import com.ning.billing.ErrorCode;
+
+public class InvoiceApiException extends BillingExceptionBase {
+    public InvoiceApiException(Throwable cause, int code, final String msg) {
+        super(cause, code, msg);
+    }
+
+    public InvoiceApiException(Throwable cause, ErrorCode code, final Object... args) {
+        super(cause, code, args);
+    }
+
+    public InvoiceApiException(ErrorCode code, final Object... args) {
+        super(code, args);
+    }
+}
diff --git a/api/src/main/java/com/ning/billing/invoice/api/InvoiceCreationNotification.java b/api/src/main/java/com/ning/billing/invoice/api/InvoiceCreationNotification.java
index 89c0d87..478a1fb 100644
--- a/api/src/main/java/com/ning/billing/invoice/api/InvoiceCreationNotification.java
+++ b/api/src/main/java/com/ning/billing/invoice/api/InvoiceCreationNotification.java
@@ -16,17 +16,19 @@
 
 package com.ning.billing.invoice.api;
 
-import com.ning.billing.catalog.api.Currency;
-import com.ning.billing.util.eventbus.EventBusNotification;
-import org.joda.time.DateTime;
-
 import java.math.BigDecimal;
 import java.util.UUID;
 
-public interface InvoiceCreationNotification extends EventBusNotification {
+import org.joda.time.DateTime;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.util.bus.BusEvent;
+
+public interface InvoiceCreationNotification extends BusEvent {
     public UUID getInvoiceId();
     public UUID getAccountId();
     public BigDecimal getAmountOwed();
     public Currency getCurrency();
     public DateTime getInvoiceCreationDate();
+
 }
\ No newline at end of file
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 44c46a7..1abcf83 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,25 +28,19 @@ public interface InvoiceItem extends Entity, Comparable<InvoiceItem> {
 
     UUID getSubscriptionId();
 
-    DateTime getStartDate();
+    String getPlanName();
 
-    DateTime getEndDate();
+    String getPhaseName();
 
     String getDescription();
 
-    BigDecimal getAmount();
-
-    BigDecimal getRate();
-
-    Currency getCurrency();
-
-    InvoiceItem asCredit(UUID invoiceId);
+    DateTime getStartDate();
 
-    int compareTo(InvoiceItem invoiceItem);
+    DateTime getEndDate();
 
-    void subtract(InvoiceItem that);
+    BigDecimal getAmount();
 
-    boolean duplicates(InvoiceItem that);
+    Currency getCurrency();
 
-    boolean cancels(InvoiceItem that);
+    InvoiceItem asCredit();
 }
diff --git a/api/src/main/java/com/ning/billing/invoice/api/InvoicePayment.java b/api/src/main/java/com/ning/billing/invoice/api/InvoicePayment.java
new file mode 100644
index 0000000..b947762
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/invoice/api/InvoicePayment.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.invoice.api;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+import org.joda.time.DateTime;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.util.entity.Entity;
+
+public interface InvoicePayment {
+    UUID getPaymentAttemptId();
+
+    UUID getInvoiceId();
+
+    DateTime getPaymentAttemptDate();
+
+    BigDecimal getAmount();
+
+    Currency getCurrency();
+
+    DateTime getCreatedDate();
+
+    DateTime getUpdatedDate();
+}
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 bde12a9..641afa6 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
@@ -16,19 +16,28 @@
 
 package com.ning.billing.invoice.api;
 
-import com.ning.billing.catalog.api.Currency;
-
 import java.math.BigDecimal;
 import java.util.List;
 import java.util.UUID;
+
 import org.joda.time.DateTime;
 
-public interface InvoicePaymentApi {
-    public void paymentSuccessful(UUID invoiceId, BigDecimal amount, Currency currency, UUID paymentId, DateTime paymentAttemptDate);
+import com.ning.billing.catalog.api.Currency;
 
-    public void paymentFailed(UUID invoiceId, UUID paymentId, DateTime paymentAttemptDate);
+public interface InvoicePaymentApi {
 
     public List<Invoice> getInvoicesByAccount(UUID accountId);
 
     public Invoice getInvoice(UUID invoiceId);
+
+    public Invoice getInvoiceForPaymentAttemptId(UUID paymentAttemptId);
+
+    public InvoicePayment getInvoicePayment(UUID paymentAttemptId);
+
+    public void notifyOfPaymentAttempt(InvoicePayment invoicePayment);
+
+    public void notifyOfPaymentAttempt(UUID invoiceId, BigDecimal amountOutstanding, Currency currency, UUID paymentAttemptId, DateTime paymentAttemptDate);
+
+    public void notifyOfPaymentAttempt(UUID invoiceId, UUID paymentAttemptId, DateTime paymentAttemptDate);
+
 }
diff --git a/api/src/main/java/com/ning/billing/invoice/api/InvoiceUserApi.java b/api/src/main/java/com/ning/billing/invoice/api/InvoiceUserApi.java
index 2c8d02e..363e483 100644
--- a/api/src/main/java/com/ning/billing/invoice/api/InvoiceUserApi.java
+++ b/api/src/main/java/com/ning/billing/invoice/api/InvoiceUserApi.java
@@ -19,19 +19,24 @@ package com.ning.billing.invoice.api;
 import org.joda.time.DateTime;
 
 import java.math.BigDecimal;
+import java.util.Collection;
 import java.util.List;
 import java.util.UUID;
-import com.ning.billing.catalog.api.Currency;
 
 public interface InvoiceUserApi {
     public List<UUID> getInvoicesForPayment(DateTime targetDate, int numberOfDays);
 
     public List<Invoice> getInvoicesByAccount(UUID accountId);
 
+    public List<Invoice> getInvoicesByAccount(UUID accountId, DateTime fromDate);
+
+    public BigDecimal getAccountBalance(UUID accountId);
+
+    public List<InvoiceItem> getInvoiceItemsByAccount(UUID accountId);
+
     public Invoice getInvoice(UUID invoiceId);
 
-    public void paymentAttemptFailed(UUID invoiceId, UUID paymentId, DateTime paymentAttemptDate);
+    public void notifyOfPaymentAttempt(InvoicePayment invoicePayment);
 
-    public void paymentAttemptSuccessful(UUID invoiceId, BigDecimal amount, Currency currency,
-                                         UUID paymentId, DateTime paymentDate);
+    public Collection<Invoice> getUnpaidInvoicesByAccountId(UUID accountId, DateTime upToDate);
 }
diff --git a/api/src/main/java/com/ning/billing/invoice/api/test/InvoiceTestApi.java b/api/src/main/java/com/ning/billing/invoice/api/test/InvoiceTestApi.java
new file mode 100644
index 0000000..9edcd8c
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/invoice/api/test/InvoiceTestApi.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.invoice.api.test;
+
+import com.ning.billing.invoice.api.Invoice;
+
+public interface InvoiceTestApi {
+    public void create(Invoice invoice);
+}
diff --git a/api/src/main/java/com/ning/billing/payment/api/CreditCardPaymentMethodInfo.java b/api/src/main/java/com/ning/billing/payment/api/CreditCardPaymentMethodInfo.java
new file mode 100644
index 0000000..a9e25dc
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/payment/api/CreditCardPaymentMethodInfo.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * 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;
+
+
+public final class CreditCardPaymentMethodInfo extends PaymentMethodInfo {
+    public static final class Builder extends BuilderBase<CreditCardPaymentMethodInfo, Builder> {
+        private String cardHolderName;
+        private String cardType;
+        private String expirationDate;
+        private String maskNumber;
+        private String cardAddress1;
+        private String cardAddress2;
+        private String cardCity;
+        private String cardState;
+        private String cardPostalCode;
+        private String cardCountry;
+
+        public Builder() {
+            super(Builder.class);
+        }
+
+        public Builder(CreditCardPaymentMethodInfo src) {
+            super(Builder.class, src);
+            this.cardHolderName = src.cardHolderName;
+            this.cardType = src.cardType;
+            this.expirationDate = src.expirationDate;
+            this.cardAddress1 = src.cardAddress1;
+            this.cardAddress2 = src.cardAddress2;
+            this.cardCity = src.cardCity;
+            this.cardState = src.cardState;
+            this.cardPostalCode = src.cardPostalCode;
+            this.cardCountry = src.cardCountry;
+            this.maskNumber = src.maskNumber;
+        }
+
+        public Builder setCardHolderName(String cardHolderName) {
+            this.cardHolderName = cardHolderName;
+            return this;
+        }
+
+        public Builder setCardType(String cardType) {
+            this.cardType = cardType;
+            return this;
+        }
+
+        public Builder setExpirationDateStr(String expirationDateStr) {
+            this.expirationDate = expirationDateStr;
+            return this;
+        }
+
+        public Builder setCardAddress1(String creditCardAddress1) {
+            this.cardAddress1 = creditCardAddress1;
+            return this;
+        }
+
+        public Builder setCardAddress2(String creditCardAddress2) {
+            this.cardAddress2 = creditCardAddress2;
+            return this;
+        }
+
+        public Builder setCardCity(String creditCardCity) {
+            this.cardCity = creditCardCity;
+            return this;
+        }
+
+        public Builder setCardState(String creditCardState) {
+            this.cardState = creditCardState;
+            return this;
+        }
+
+        public Builder setCardPostalCode(String creditCardPostalCode) {
+            this.cardPostalCode = creditCardPostalCode;
+            return this;
+        }
+
+        public Builder setCardCountry(String creditCardCountry) {
+            this.cardCountry = creditCardCountry;
+            return this;
+        }
+
+        public Builder setMaskNumber(String maskNumber) {
+            this.maskNumber = maskNumber;
+            return this;
+        }
+
+        public CreditCardPaymentMethodInfo build() {
+            return new CreditCardPaymentMethodInfo(id,
+                                                   accountId,
+                                                   defaultMethod,
+                                                   cardHolderName,
+                                                   cardType,
+                                                   expirationDate,
+                                                   maskNumber,
+                                                   cardAddress1,
+                                                   cardAddress2,
+                                                   cardCity,
+                                                   cardState,
+                                                   cardPostalCode,
+                                                   cardCountry);
+        }
+    }
+
+    private final String cardHolderName;
+    private final String cardType;
+    private final String expirationDate;
+    private final String maskNumber;
+    private final String cardAddress1;
+    private final String cardAddress2;
+    private final String cardCity;
+    private final String cardState;
+    private final String cardPostalCode;
+    private final String cardCountry;
+
+    public CreditCardPaymentMethodInfo(String id,
+                                   String accountId,
+                                   Boolean defaultMethod,
+                                   String cardHolderName,
+                                   String cardType,
+                                   String expirationDate,
+                                   String maskNumber,
+                                   String cardAddress1,
+                                   String cardAddress2,
+                                   String cardCity,
+                                   String cardState,
+                                   String cardPostalCode,
+                                   String cardCountry) {
+
+      super(id, accountId, defaultMethod, "CreditCard");
+      this.cardHolderName = cardHolderName;
+      this.cardType = cardType;
+      this.expirationDate = expirationDate;
+      this.maskNumber = maskNumber;
+      this.cardAddress1 = cardAddress1;
+      this.cardAddress2 = cardAddress2;
+      this.cardCity = cardCity;
+      this.cardState = cardState;
+      this.cardPostalCode = cardPostalCode;
+      this.cardCountry = cardCountry;
+    }
+
+    public String getCardHolderName() {
+      return cardHolderName;
+    }
+
+    public String getCardType() {
+      return cardType;
+    }
+
+    public String getCardAddress1() {
+        return cardAddress1;
+    }
+
+    public String getCardAddress2() {
+        return cardAddress2;
+    }
+
+    public String getCardCity() {
+        return cardCity;
+    }
+
+    public String getCardState() {
+        return cardState;
+    }
+
+    public String getCardPostalCode() {
+        return cardPostalCode;
+    }
+
+    public String getCardCountry() {
+        return cardCountry;
+    }
+
+    public String getExpirationDate() {
+      return expirationDate;
+    }
+
+    public String getMaskNumber() {
+      return maskNumber;
+    }
+}
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
new file mode 100644
index 0000000..25ce8f8
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/payment/api/Either.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.payment.api;
+
+import org.codehaus.jackson.annotate.JsonValue;
+
+public abstract class Either<T, V> {
+    public static <T, V> Either<T, V> left(T value) {
+        return new Left<T, V>(value);
+    }
+    public static <T, V> Either<T, V> right(V value) {
+        return new Right<T, V>(value);
+    }
+
+    private Either() {
+    }
+
+    public boolean isLeft() {
+        return false;
+    }
+    public boolean isRight() {
+        return false;
+    }
+    public T getLeft() {
+        throw new UnsupportedOperationException();
+    }
+    public V getRight() {
+        throw new UnsupportedOperationException();
+    }
+
+    public static class Left<T, V> extends Either<T, V> {
+        private final T value;
+
+        public Left(T value) {
+            this.value = value;
+        }
+        @Override
+        public boolean isLeft() {
+            return true;
+        }
+        @Override
+        @JsonValue
+        public T getLeft() {
+            return value;
+        }
+    }
+
+    public static class Right<T, V> extends Either<T, V> {
+        private final V value;
+
+        public Right(V value) {
+            this.value = value;
+        }
+        @Override
+        public boolean isRight() {
+            return true;
+        }
+
+        @Override
+        @JsonValue
+        public V getRight() {
+            return value;
+        }
+    }
+}
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
new file mode 100644
index 0000000..58e9898
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/payment/api/PaymentApi.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.payment.api;
+
+import java.util.List;
+
+import javax.annotation.Nullable;
+
+import com.ning.billing.account.api.Account;
+
+public interface PaymentApi {
+
+    Either<PaymentError, Void> updatePaymentGateway(String accountKey);
+
+    Either<PaymentError, PaymentMethodInfo> getPaymentMethod(@Nullable String accountKey, String paymentMethodId);
+
+    Either<PaymentError, List<PaymentMethodInfo>> getPaymentMethods(String accountKey);
+
+    Either<PaymentError, String> addPaymentMethod(@Nullable String accountKey, PaymentMethodInfo paymentMethod);
+
+    Either<PaymentError, PaymentMethodInfo> updatePaymentMethod(String accountKey, PaymentMethodInfo paymentMethodInfo);
+
+    Either<PaymentError, Void> deletePaymentMethod(String accountKey, String paymentMethodId);
+
+    List<Either<PaymentError, PaymentInfo>> createPayment(String accountKey, List<String> invoiceIds);
+    List<Either<PaymentError, PaymentInfo>> createPayment(Account account, List<String> invoiceIds);
+
+    List<Either<PaymentError, PaymentInfo>> createRefund(Account account, List<String> invoiceIds); //TODO
+
+    Either<PaymentError, PaymentProviderAccount> getPaymentProviderAccount(String accountKey);
+
+    Either<PaymentError, String> createPaymentProviderAccount(Account account);
+
+    Either<PaymentError, Void> updatePaymentProviderAccountContact(String accountKey);
+
+    PaymentAttempt getPaymentAttemptForPaymentId(String id);
+
+}
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
new file mode 100644
index 0000000..b5df043
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/payment/api/PaymentAttempt.java
@@ -0,0 +1,309 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * 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.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 nextRetryDate;
+    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 nextRetryDate,
+                          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;
+        this.nextRetryDate = nextRetryDate;
+        this.createdDate = createdDate == null ? new DateTime(DateTimeZone.UTC) : createdDate;
+        this.updatedDate = updatedDate == null ? new DateTime(DateTimeZone.UTC) : updatedDate;
+    }
+
+    public PaymentAttempt(UUID paymentAttemptId,
+                          UUID invoiceId,
+                          UUID accountId,
+                          BigDecimal amount,
+                          Currency currency,
+                          DateTime invoiceDate,
+                          DateTime paymentAttemptDate,
+                          String paymentId,
+                          Integer retryCount,
+                          DateTime nextRetryDate) {
+        this(paymentAttemptId,
+             invoiceId,
+             accountId,
+             amount,
+             currency,
+             invoiceDate,
+             paymentAttemptDate,
+             paymentId,
+             retryCount,
+             nextRetryDate,
+             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, null);
+    }
+
+    public PaymentAttempt(UUID paymentAttemptId, UUID invoiceId, UUID accountId, DateTime invoiceDate, DateTime paymentAttemptDate) {
+        this(paymentAttemptId, invoiceId, accountId, null, null, invoiceDate, paymentAttemptDate, null, null, null);
+    }
+
+    public PaymentAttempt(UUID paymentAttemptId, Invoice invoice) {
+        this(paymentAttemptId, invoice.getId(), invoice.getAccountId(), invoice.getBalance(), invoice.getCurrency(), invoice.getInvoiceDate(), null, 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;
+    }
+
+    public DateTime getNextRetryDate() {
+        return nextRetryDate;
+    }
+
+    @Override
+    public String toString() {
+        return "PaymentAttempt [paymentAttemptId=" + paymentAttemptId + ", invoiceId=" + invoiceId + ", accountId=" + accountId + ", amount=" + amount + ", currency=" + currency + ", paymentId=" + paymentId + ", invoiceDate=" + invoiceDate + ", paymentAttemptDate=" + paymentAttemptDate + ", retryCount=" + retryCount + ", nextRetryDate=" + nextRetryDate + ", 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 nextRetryDate;
+        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.nextRetryDate = src.nextRetryDate;
+            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;
+        }
+
+        public Builder setPaymentAttemptDate(DateTime paymentAttemptDate) {
+            this.paymentAttemptDate = paymentAttemptDate;
+            return this;
+        }
+
+        public Builder setPaymentId(String paymentId) {
+            this.paymentId = paymentId;
+            return this;
+        }
+
+        public Builder setRetryCount(Integer retryCount) {
+            this.retryCount = retryCount;
+            return this;
+        }
+
+        public Builder setNextRetryDate(DateTime nextRetryDate) {
+            this.nextRetryDate = nextRetryDate;
+            return this;
+        }
+
+        public PaymentAttempt build() {
+            return new PaymentAttempt(paymentAttemptId,
+                                      invoiceId,
+                                      accountId,
+                                      amount,
+                                      currency,
+                                      invoiceDate,
+                                      paymentAttemptDate,
+                                      paymentId,
+                                      retryCount,
+                                      nextRetryDate,
+                                      createdDate,
+                                      updatedDate);
+        }
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(paymentAttemptId,
+                                invoiceId,
+                                accountId,
+                                amount,
+                                currency,
+                                invoiceDate,
+                                paymentAttemptDate,
+                                paymentId,
+                                retryCount,
+                                nextRetryDate,
+                                createdDate,
+                                updatedDate);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (getClass() == obj.getClass()) {
+            PaymentAttempt other = (PaymentAttempt)obj;
+            if (obj == other) {
+                return true;
+            }
+            else {
+                return Objects.equal(paymentAttemptId, other.paymentAttemptId) &&
+                       Objects.equal(invoiceId, other.invoiceId) &&
+                       Objects.equal(accountId, other.accountId) &&
+                       Objects.equal(amount, other.amount) &&
+                       Objects.equal(currency, other.currency) &&
+                       Objects.equal(invoiceDate, other.invoiceDate) &&
+                       Objects.equal(paymentAttemptDate, other.paymentAttemptDate) &&
+                       Objects.equal(retryCount, other.retryCount) &&
+                       Objects.equal(nextRetryDate, other.nextRetryDate) &&
+                       Objects.equal(paymentId, other.paymentId);
+            }
+        }
+        return false;
+    }
+
+}
diff --git a/api/src/main/java/com/ning/billing/payment/api/PaymentError.java b/api/src/main/java/com/ning/billing/payment/api/PaymentError.java
new file mode 100644
index 0000000..f1474c8
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/payment/api/PaymentError.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * 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 org.codehaus.jackson.annotate.JsonTypeInfo;
+import org.codehaus.jackson.annotate.JsonTypeInfo.Id;
+
+import com.ning.billing.util.bus.BusEvent;
+
+@JsonTypeInfo(use = Id.NAME, property = "error")
+public class PaymentError implements BusEvent {
+    private final String type;
+    private final String message;
+
+    public PaymentError(PaymentError src) {
+        this.type = src.type;
+        this.message = src.message;
+    }
+
+    public PaymentError(String type, String message) {
+        this.type = type;
+        this.message = message;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public String getMessage() {
+        return message;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((message == null) ? 0 : message.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;
+        PaymentError other = (PaymentError) obj;
+        if (message == null) {
+            if (other.message != null)
+                return false;
+        }
+        else if (!message.equals(other.message))
+            return false;
+        if (type == null) {
+            if (other.type != null)
+                return false;
+        }
+        else if (!type.equals(other.type))
+            return false;
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        return "PaymentError [type=" + type + ", message=" + message + "]";
+    }
+}
diff --git a/api/src/main/java/com/ning/billing/payment/api/PaymentInfo.java b/api/src/main/java/com/ning/billing/payment/api/PaymentInfo.java
new file mode 100644
index 0000000..943c5f7
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/payment/api/PaymentInfo.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.payment.api;
+
+import java.math.BigDecimal;
+
+import org.codehaus.jackson.annotate.JsonCreator;
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+
+import com.google.common.base.Objects;
+import com.ning.billing.util.bus.BusEvent;
+
+public class PaymentInfo implements BusEvent {
+    private final String paymentId;
+    private final BigDecimal amount;
+    private final BigDecimal refundAmount;
+    private final String paymentNumber;
+    private final String bankIdentificationNumber;
+    private final String status;
+    private final String type;
+    private final String referenceId;
+    private final String paymentMethodId;
+    private final String paymentMethod;
+    private final String cardType;
+    private final String cardCoutry;
+    private final DateTime effectiveDate;
+    private final DateTime createdDate;
+    private final DateTime updatedDate;
+
+    @JsonCreator
+    public PaymentInfo(@JsonProperty("paymentId") String paymentId,
+                       @JsonProperty("amount") BigDecimal amount,
+                       @JsonProperty("refundAmount") BigDecimal refundAmount,
+                       @JsonProperty("bankIdentificationNumber") String bankIdentificationNumber,
+                       @JsonProperty("paymentNumber") String paymentNumber,
+                       @JsonProperty("status") String status,
+                       @JsonProperty("type") String type,
+                       @JsonProperty("referenceId") String referenceId,
+                       @JsonProperty("paymentMethodId") String paymentMethodId,
+                       @JsonProperty("paymentMethod") String paymentMethod,
+                       @JsonProperty("cardType") String cardType,
+                       @JsonProperty("cardCountry") String cardCountry,
+                       @JsonProperty("effectiveDate") DateTime effectiveDate,
+                       @JsonProperty("createdDate") DateTime createdDate,
+                       @JsonProperty("updatedDate") DateTime updatedDate) {
+        this.paymentId = paymentId;
+        this.amount = amount;
+        this.refundAmount = refundAmount;
+        this.bankIdentificationNumber = bankIdentificationNumber;
+        this.paymentNumber = paymentNumber;
+        this.status = status;
+        this.type = type;
+        this.referenceId = referenceId;
+        this.paymentMethodId = paymentMethodId;
+        this.paymentMethod = paymentMethod;
+        this.cardType = cardType;
+        this.cardCoutry = cardCountry;
+        this.effectiveDate = effectiveDate;
+        this.createdDate = createdDate == null ? new DateTime(DateTimeZone.UTC) : createdDate;
+        this.updatedDate = updatedDate == null ? new DateTime(DateTimeZone.UTC) : updatedDate;
+    }
+
+    public PaymentInfo(PaymentInfo src) {
+        this(src.paymentId,
+             src.amount,
+             src.refundAmount,
+             src.bankIdentificationNumber,
+             src.paymentNumber,
+             src.status,
+             src.type,
+             src.referenceId,
+             src.paymentMethodId,
+             src.paymentMethod,
+             src.cardType,
+             src.cardCoutry,
+             src.effectiveDate,
+             src.createdDate,
+             src.updatedDate);
+    }
+
+    public Builder cloner() {
+        return new Builder(this);
+    }
+
+    public String getPaymentId() {
+        return paymentId;
+    }
+
+    public BigDecimal getAmount() {
+        return amount;
+    }
+
+    public String getBankIdentificationNumber() {
+        return bankIdentificationNumber;
+    }
+
+    public DateTime getCreatedDate() {
+        return createdDate;
+    }
+
+    public DateTime getEffectiveDate() {
+        return effectiveDate;
+    }
+
+    public String getPaymentNumber() {
+        return paymentNumber;
+    }
+
+    public String getPaymentMethod() {
+        return paymentMethod;
+    }
+
+    public String getCardType() {
+        return cardType;
+    }
+
+    public String getCardCountry() {
+        return cardCoutry;
+    }
+
+    public String getReferenceId() {
+        return referenceId;
+    }
+
+    public String getPaymentMethodId() {
+        return paymentMethodId;
+    }
+
+    public BigDecimal getRefundAmount() {
+        return refundAmount;
+    }
+
+    public String getStatus() {
+        return status;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public DateTime getUpdatedDate() {
+        return updatedDate;
+    }
+
+    public static class Builder {
+        private String paymentId;
+        private BigDecimal amount;
+        private BigDecimal refundAmount;
+        private String paymentNumber;
+        private String bankIdentificationNumber;
+        private String type;
+        private String status;
+        private String referenceId;
+        private String paymentMethodId;
+        private String paymentMethod;
+        private String cardType;
+        private String cardCountry;
+        private DateTime effectiveDate;
+        private DateTime createdDate;
+        private DateTime updatedDate;
+
+        public Builder() {
+        }
+
+        public Builder(PaymentInfo src) {
+            this.paymentId = src.paymentId;
+            this.amount = src.amount;
+            this.refundAmount = src.refundAmount;
+            this.paymentNumber = src.paymentNumber;
+            this.bankIdentificationNumber = src.bankIdentificationNumber;
+            this.type = src.type;
+            this.status = src.status;
+            this.effectiveDate = src.effectiveDate;
+            this.referenceId = src.referenceId;
+            this.paymentMethodId = src.paymentMethodId;
+            this.paymentMethod = src.paymentMethod;
+            this.cardType = src.cardType;
+            this.cardCountry = src.cardCoutry;
+            this.createdDate = src.createdDate;
+            this.updatedDate = src.updatedDate;
+        }
+
+        public Builder setPaymentId(String paymentId) {
+            this.paymentId = paymentId;
+            return this;
+        }
+
+        public Builder setAmount(BigDecimal amount) {
+            this.amount = amount;
+            return this;
+        }
+
+        public Builder setBankIdentificationNumber(String bankIdentificationNumber) {
+            this.bankIdentificationNumber = bankIdentificationNumber;
+            return this;
+        }
+
+        public Builder setCreatedDate(DateTime createdDate) {
+            this.createdDate = createdDate;
+            return this;
+        }
+
+        public Builder setEffectiveDate(DateTime effectiveDate) {
+            this.effectiveDate = effectiveDate;
+            return this;
+        }
+
+        public Builder setPaymentNumber(String paymentNumber) {
+            this.paymentNumber = paymentNumber;
+            return this;
+        }
+
+        public Builder setReferenceId(String referenceId) {
+            this.referenceId = referenceId;
+            return this;
+        }
+
+        public Builder setRefundAmount(BigDecimal refundAmount) {
+            this.refundAmount = refundAmount;
+            return this;
+        }
+
+        public Builder setStatus(String status) {
+            this.status = status;
+            return this;
+        }
+
+        public Builder setType(String type) {
+            this.type = type;
+            return this;
+        }
+
+        public Builder setPaymentMethodId(String paymentMethodId) {
+            this.paymentMethodId = paymentMethodId;
+            return this;
+        }
+
+        public Builder setPaymentMethod(String paymentMethod) {
+            this.paymentMethod = paymentMethod;
+            return this;
+        }
+
+        public Builder setCardType(String cardType) {
+            this.cardType = cardType;
+            return this;
+        }
+
+        public Builder setCardCountry(String cardCountry) {
+            this.cardCountry = cardCountry;
+            return this;
+        }
+
+        public Builder setUpdatedDate(DateTime updatedDate) {
+            this.updatedDate = updatedDate;
+            return this;
+        }
+
+        public PaymentInfo build() {
+            return new PaymentInfo(paymentId,
+                                   amount,
+                                   refundAmount,
+                                   bankIdentificationNumber,
+                                   paymentNumber,
+                                   status,
+                                   type,
+                                   referenceId,
+                                   paymentMethodId,
+                                   paymentMethod,
+                                   cardType,
+                                   cardCountry,
+                                   effectiveDate,
+                                   createdDate,
+                                   updatedDate);
+        }
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(paymentId,
+                                amount,
+                                refundAmount,
+                                bankIdentificationNumber,
+                                paymentNumber,
+                                status,
+                                type,
+                                referenceId,
+                                paymentMethodId,
+                                paymentMethod,
+                                cardType,
+                                cardCoutry,
+                                effectiveDate,
+                                createdDate,
+                                updatedDate);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (getClass() == obj.getClass()) {
+            PaymentInfo other = (PaymentInfo)obj;
+            if (obj == other) {
+                return true;
+            }
+            else {
+                return Objects.equal(amount, other.amount) &&
+                       Objects.equal(bankIdentificationNumber, other.bankIdentificationNumber) &&
+                       Objects.equal(paymentId, other.paymentId) &&
+                       Objects.equal(paymentNumber, other.paymentNumber) &&
+                       Objects.equal(referenceId, other.referenceId) &&
+                       Objects.equal(refundAmount, other.refundAmount) &&
+                       Objects.equal(status, other.status) &&
+                       Objects.equal(type, other.type) &&
+                       Objects.equal(paymentMethodId, other.paymentMethodId) &&
+                       Objects.equal(paymentMethod, other.paymentMethod) &&
+                       Objects.equal(cardType, other.cardType) &&
+                       Objects.equal(cardCoutry, other.cardCoutry) &&
+                       Objects.equal(effectiveDate, other.effectiveDate) &&
+                       Objects.equal(createdDate, other.createdDate) &&
+                       Objects.equal(updatedDate, other.updatedDate);
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public String toString() {
+        return "PaymentInfo [paymentId=" + paymentId + ", amount=" + amount + ", refundAmount=" + refundAmount + ", paymentNumber=" + paymentNumber + ", bankIdentificationNumber=" + bankIdentificationNumber + ", status=" + status + ", type=" + type + ", referenceId=" + referenceId + ", paymentMethodId=" + paymentMethodId + ", paymentMethod=" + paymentMethod + ", cardType=" + cardType + ", cardCountry=" + cardCoutry + ", effectiveDate=" + effectiveDate + ", createdDate=" + createdDate + ", updatedDate=" + updatedDate + "]";
+    }
+
+}
diff --git a/api/src/main/java/com/ning/billing/payment/api/PaymentMethodInfo.java b/api/src/main/java/com/ning/billing/payment/api/PaymentMethodInfo.java
new file mode 100644
index 0000000..78f73d3
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/payment/api/PaymentMethodInfo.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.payment.api;
+
+import com.google.common.base.Objects;
+
+public class PaymentMethodInfo {
+    private final String id;
+    private final String accountId;
+    private final Boolean defaultMethod;
+    private final String type;
+
+    public PaymentMethodInfo(String id,
+                             String accountId,
+                             Boolean defaultMethod,
+                             String type) {
+        this.id = id;
+        this.accountId = accountId;
+        this.defaultMethod = defaultMethod;
+        this.type = type;
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public String getAccountId() {
+        return accountId;
+    }
+
+    public Boolean getDefaultMethod() {
+        return defaultMethod;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(id,
+                                accountId,
+                                defaultMethod,
+                                type);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (getClass() == obj.getClass()) {
+            PaymentMethodInfo other = (PaymentMethodInfo)obj;
+            if (obj == other) {
+                return true;
+            }
+            else {
+                return Objects.equal(id, other.id) &&
+                       Objects.equal(accountId, other.accountId) &&
+                       Objects.equal(defaultMethod, other.defaultMethod) &&
+                       Objects.equal(type, other.type);
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public String toString() {
+        return "PaymentMethodInfo [id=" + id + ", accountId=" + accountId + ", defaultMethod=" + defaultMethod + ", type=" + type + "]";
+    }
+
+    protected abstract static class BuilderBase<T extends PaymentMethodInfo, V extends BuilderBase<T, V>> {
+        protected final Class<V> builderClazz;
+        protected String id;
+        protected String accountId;
+        protected Boolean defaultMethod;
+
+        protected BuilderBase(Class<V> builderClazz) {
+            this.builderClazz = builderClazz;
+        }
+
+        protected BuilderBase(Class<V> builderClazz, T src) {
+            this(builderClazz);
+            this.id = src.id;
+            this.accountId = src.accountId;
+            this.defaultMethod = src.defaultMethod;
+        }
+
+        public V setId(String id) {
+            this.id = id;
+            return builderClazz.cast(this);
+        }
+
+        public V setAccountId(String accountId) {
+            this.accountId = accountId;
+            return builderClazz.cast(this);
+        }
+
+        public V setDefaultMethod(Boolean defaultMethod) {
+            this.defaultMethod = defaultMethod;
+            return builderClazz.cast(this);
+        }
+    }
+
+    public static class Builder extends BuilderBase<PaymentMethodInfo, Builder> {
+        private String type;
+
+        public Builder() {
+            super(Builder.class);
+        }
+
+        public Builder(PaymentMethodInfo src) {
+            super(Builder.class, src);
+            this.type = src.type;
+        }
+
+        public Builder setType(String type) {
+            this.type = type;
+            return this;
+        }
+
+        public PaymentMethodInfo build() {
+            return new PaymentMethodInfo(id, accountId, defaultMethod, type);
+        }
+    }
+}
diff --git a/api/src/main/java/com/ning/billing/payment/api/PaymentProviderAccount.java b/api/src/main/java/com/ning/billing/payment/api/PaymentProviderAccount.java
new file mode 100644
index 0000000..47cad39
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/payment/api/PaymentProviderAccount.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * 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.google.common.base.Objects;
+
+public class PaymentProviderAccount {
+    private final String id;
+    private final String accountKey;
+    private final String accountName;
+    private final String phoneNumber;
+    private final String defaultPaymentMethodId;
+
+    public PaymentProviderAccount(String id,
+                                  String accountKey,
+                                  String accountName,
+                                  String phoneNumber,
+                                  String defaultPaymentMethodId) {
+        this.id = id;
+        this.accountKey = accountKey;
+        this.accountName = accountName;
+        this.phoneNumber = phoneNumber;
+        this.defaultPaymentMethodId = defaultPaymentMethodId;
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public String getAccountKey() {
+        return accountKey;
+    }
+
+    public String getAccountName() {
+        return accountName;
+    }
+
+    public String getPhoneNumber() {
+        return phoneNumber;
+    }
+
+    public String getDefaultPaymentMethodId() {
+        return defaultPaymentMethodId;
+    }
+
+    public static class Builder {
+        private String id;
+        private String accountKey;
+        private String accountName;
+        private String phoneNumber;
+        private String defaultPaymentMethodId;
+
+        public Builder copyFrom(PaymentProviderAccount src) {
+            this.id = src.getId();
+            this.accountKey = src.getAccountKey();
+            this.accountName = src.getAccountName();
+            this.phoneNumber = src.getPhoneNumber();
+            this.defaultPaymentMethodId = src.getDefaultPaymentMethodId();
+            return this;
+        }
+
+        public Builder setId(String id) {
+            this.id = id;
+            return this;
+        }
+
+        public Builder setAccountKey(String accountKey) {
+            this.accountKey = accountKey;
+            return this;
+        }
+
+        public Builder setAccountName(String accountName) {
+            this.accountName = accountName;
+            return this;
+        }
+
+        public Builder setPhoneNumber(String phoneNumber) {
+            this.phoneNumber = phoneNumber;
+            return this;
+        }
+
+        public Builder setDefaultPaymentMethod(String defaultPaymentMethod) {
+            this.defaultPaymentMethodId = defaultPaymentMethod;
+            return this;
+        }
+
+        public PaymentProviderAccount build() {
+            return new PaymentProviderAccount(id, accountKey, accountName, phoneNumber, defaultPaymentMethodId);
+        }
+
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(id,
+                                accountKey,
+                                accountName,
+                                phoneNumber,
+                                defaultPaymentMethodId);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (getClass() == obj.getClass()) {
+            PaymentProviderAccount other = (PaymentProviderAccount)obj;
+            if (obj == other) {
+                return true;
+            }
+            else {
+                return Objects.equal(id, other.id) &&
+                       Objects.equal(accountKey, other.accountKey) &&
+                       Objects.equal(accountName, other.accountName) &&
+                       Objects.equal(phoneNumber, other.phoneNumber) &&
+                       Objects.equal(defaultPaymentMethodId, other.defaultPaymentMethodId);
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public String toString() {
+        return "PaymentProviderAccount [id=" + id + ", accountKey=" + accountKey + ", accountName=" + accountName + ", phoneNumber=" + phoneNumber + ", defaultPaymentMethodId=" + defaultPaymentMethodId + "]";
+    }
+
+}
diff --git a/api/src/main/java/com/ning/billing/payment/api/PaymentProviderContactData.java b/api/src/main/java/com/ning/billing/payment/api/PaymentProviderContactData.java
new file mode 100644
index 0000000..7be9908
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/payment/api/PaymentProviderContactData.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.api;
+
+import org.apache.commons.lang.StringUtils;
+
+import com.google.common.base.Objects;
+import com.ning.billing.catalog.api.Currency;
+
+public class PaymentProviderContactData {
+    private final String firstName;
+    private final String lastName;
+    private final String email;
+    private final String phoneNumber;
+    private final String externalKey;
+    private final String locale;
+    private final Currency currency;
+
+    public PaymentProviderContactData(String firstName,
+                                      String lastName,
+                                      String email,
+                                      String phoneNumber,
+                                      String externalKey,
+                                      String locale,
+                                      Currency currency) {
+        this.firstName = StringUtils.substring(firstName, 0, 100);
+        this.lastName  = StringUtils.substring(lastName, 0, 100);
+        this.email     = StringUtils.substring(email, 0, 80);
+        this.phoneNumber = phoneNumber;
+        this.externalKey = externalKey;
+        this.locale = locale;
+        this.currency = currency;
+    }
+
+    public String getFirstName() {
+        return firstName;
+    }
+
+    public String getLastName() {
+        return lastName;
+    }
+
+    public String getEmail() {
+        return email;
+    }
+
+    public String getPhoneNumber() {
+        return phoneNumber;
+    }
+
+    public String getExternalKey() {
+        return externalKey;
+    }
+
+    public String getLocale() {
+        return locale;
+    }
+
+    public Currency getCurrency() {
+        return currency;
+    }
+
+    public static class Builder {
+        private String firstName;
+        private String lastName;
+        private String email;
+        private String phoneNumber;
+        private String externalKey;
+        private String locale;
+        private Currency currency;
+
+        public Builder setExternalKey(String externalKey) {
+            this.externalKey = externalKey;
+            return this;
+        }
+
+        public Builder setFirstName(String firstName) {
+            this.firstName = firstName;
+            return this;
+        }
+
+        public Builder setLastName(String lastName) {
+            this.lastName = lastName;
+            return this;
+        }
+
+        public Builder setEmail(String email) {
+            this.email = email;
+            return this;
+        }
+
+        public Builder setPhoneNumber(String phoneNumber) {
+            this.phoneNumber = phoneNumber;
+            return this;
+        }
+
+        public Builder setLocale(String locale) {
+            this.locale = locale;
+            return this;
+        }
+
+        public Builder setCurrency(Currency currency) {
+            this.currency = currency;
+            return this;
+        }
+
+        public PaymentProviderContactData build() {
+            return new PaymentProviderContactData(firstName, lastName, email, phoneNumber, externalKey, locale, currency);
+        }
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(firstName,
+                                lastName,
+                                email,
+                                phoneNumber,
+                                externalKey,
+                                locale,
+                                currency);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (getClass() == obj.getClass()) {
+            PaymentProviderContactData other = (PaymentProviderContactData)obj;
+            if (obj == other) {
+                return true;
+            }
+            else {
+                return Objects.equal(firstName, other.firstName) &&
+                       Objects.equal(lastName, other.lastName) &&
+                       Objects.equal(email, other.email) &&
+                       Objects.equal(phoneNumber, other.phoneNumber) &&
+                       Objects.equal(externalKey, other.externalKey) &&
+                       Objects.equal(locale, other.locale) &&
+                       Objects.equal(currency, other.currency);
+            }
+        }
+        return false;
+    }
+
+}
diff --git a/api/src/main/java/com/ning/billing/payment/api/PaymentService.java b/api/src/main/java/com/ning/billing/payment/api/PaymentService.java
new file mode 100644
index 0000000..988a00a
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/payment/api/PaymentService.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.payment.api;
+
+import com.ning.billing.lifecycle.KillbillService;
+
+public interface PaymentService extends KillbillService {
+    @Override
+    String getName();
+
+    PaymentApi getPaymentApi();
+}
diff --git a/api/src/main/java/com/ning/billing/payment/api/PaypalPaymentMethodInfo.java b/api/src/main/java/com/ning/billing/payment/api/PaypalPaymentMethodInfo.java
new file mode 100644
index 0000000..3b2ebed
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/payment/api/PaypalPaymentMethodInfo.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * 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 org.codehaus.jackson.annotate.JsonCreator;
+import org.codehaus.jackson.annotate.JsonProperty;
+
+import com.google.common.base.Strings;
+
+
+public final class PaypalPaymentMethodInfo extends PaymentMethodInfo {
+    public static final String TYPE = "PayPal";
+
+    public static final class Builder extends BuilderBase<PaypalPaymentMethodInfo, Builder> {
+        private String baid;
+        private String email;
+
+        public Builder() {
+            super(Builder.class);
+        }
+
+        public Builder(PaypalPaymentMethodInfo src) {
+            super(Builder.class, src);
+            this.baid = src.baid;
+            this.email = src.email;
+        }
+
+        public Builder setBaid(String baid) {
+            this.baid = baid;
+            return this;
+        }
+
+        public Builder setEmail(String email) {
+            this.email = email;
+            return this;
+        }
+
+        public PaypalPaymentMethodInfo build() {
+            return new PaypalPaymentMethodInfo(id, accountId, defaultMethod, baid, email);
+        }
+    }
+
+    private final String baid;
+    private final String email;
+
+    @JsonCreator
+    public PaypalPaymentMethodInfo(@JsonProperty("id") String id,
+                                   @JsonProperty("accountId") String accountId,
+                                   @JsonProperty("defaultMethod") Boolean defaultMethod,
+                                   @JsonProperty("baid") String baid,
+                                   @JsonProperty("email") String email) {
+        super(id, accountId, defaultMethod, TYPE);
+
+        if (Strings.isNullOrEmpty(baid) || Strings.isNullOrEmpty(email)) {
+            throw new IllegalArgumentException("baid and email should be present");
+        }
+
+        this.baid = baid;
+        this.email = email;
+    }
+
+    public String getBaid() {
+        return baid;
+    }
+
+    public String getEmail() {
+        return email;
+    }
+
+    @Override
+    public String toString() {
+        return "PaypalPaymentMethodInfo [baid=" + baid + ", email=" + email + "]";
+    }
+
+}
diff --git a/api/src/main/java/com/ning/billing/util/api/TagDefinitionApiException.java b/api/src/main/java/com/ning/billing/util/api/TagDefinitionApiException.java
new file mode 100644
index 0000000..81750cb
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/util/api/TagDefinitionApiException.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.api;
+
+import com.ning.billing.BillingExceptionBase;
+import com.ning.billing.ErrorCode;
+
+public class TagDefinitionApiException extends BillingExceptionBase {
+    public TagDefinitionApiException(Throwable cause, int code, final String msg) {
+        super(cause, code, msg);
+    }
+
+    public TagDefinitionApiException(Throwable cause, ErrorCode code, final Object... args) {
+        super(cause, code, args);
+    }
+
+    public TagDefinitionApiException(ErrorCode code, final Object... args) {
+        super(code, args);
+    }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/ning/billing/util/api/TagDefinitionUserApi.java b/api/src/main/java/com/ning/billing/util/api/TagDefinitionUserApi.java
new file mode 100644
index 0000000..0bd985c
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/util/api/TagDefinitionUserApi.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.util.api;
+
+import java.util.List;
+import java.util.UUID;
+import com.ning.billing.util.tag.TagDefinition;
+
+public interface TagDefinitionUserApi {
+    /***
+     *
+     * @return
+     */
+    public List<TagDefinition> getTagDefinitions();
+
+    /***
+     *
+     * @param name Identifies the definition.
+     * @param description Describes the use of the definition.
+     * @param createdBy The name of person who created the definition.
+     * @return
+     * @throws TagDefinitionApiException
+     */
+    public TagDefinition create(String name, String description, String createdBy) throws TagDefinitionApiException;
+
+    /***
+     *
+     * @param definitionName Identifies the definition.
+     * @throws TagDefinitionApiException
+     */
+    public void deleteAllTagsForDefinition(String definitionName) throws TagDefinitionApiException;
+
+    /***
+     *
+     * @param definitionName Identifies the definition.
+     * @throws TagDefinitionApiException
+     */
+    public void deleteTagDefinition(String definitionName) throws TagDefinitionApiException;
+}
diff --git a/api/src/main/java/com/ning/billing/util/bus/BusService.java b/api/src/main/java/com/ning/billing/util/bus/BusService.java
new file mode 100644
index 0000000..cc2dd10
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/util/bus/BusService.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.bus;
+
+import com.ning.billing.lifecycle.KillbillService;
+
+public interface BusService extends KillbillService {
+
+    public Bus getBus();
+}
diff --git a/api/src/main/java/com/ning/billing/util/tag/Tag.java b/api/src/main/java/com/ning/billing/util/tag/Tag.java
index 9fb15ed..5e9008b 100644
--- a/api/src/main/java/com/ning/billing/util/tag/Tag.java
+++ b/api/src/main/java/com/ning/billing/util/tag/Tag.java
@@ -21,15 +21,9 @@ import org.joda.time.DateTime;
 import com.ning.billing.util.entity.Entity;
 
 public interface Tag extends Entity {
-    UUID getTagDescriptionId();
-
-    String getName();
-
-    boolean getProcessPayment();
-
-    boolean getGenerateInvoice();
+    String getTagDefinitionName();
 
     String getAddedBy();
 
-    DateTime getDateAdded();
+    DateTime getAddedDate();
 }
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 f274294..5e2f425 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
@@ -22,10 +22,10 @@ import org.joda.time.DateTime;
 public interface Taggable {
     public List<Tag> getTagList();
     public boolean hasTag(String tagName);
-    public void addTag(TagDescription description, String addedBy, DateTime dateAdded);
+    public void addTag(TagDefinition definition, String addedBy, DateTime dateAdded);
     public void addTags(List<Tag> tags);
     public void clearTags();
-    public void removeTag(TagDescription description);
+    public void removeTag(TagDefinition definition);
     public boolean generateInvoice();
     public boolean processPayment();
 }

beatrix/pom.xml 48(+45 -3)

diff --git a/beatrix/pom.xml b/beatrix/pom.xml
index 32ceef9..64a7b38 100644
--- a/beatrix/pom.xml
+++ b/beatrix/pom.xml
@@ -13,7 +13,7 @@
     <parent>
         <groupId>com.ning.billing</groupId>
         <artifactId>killbill</artifactId>
-        <version>0.1.2-SNAPSHOT</version>
+        <version>0.1.6-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-beatrix</artifactId>
@@ -28,7 +28,15 @@
             <groupId>com.ning.billing</groupId>
             <artifactId>killbill-entitlement</artifactId>
         </dependency>
-       <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>
@@ -43,7 +51,7 @@
         <dependency>
             <groupId>com.google.inject</groupId>
             <artifactId>guice</artifactId>
-            <version>3.0</version>
+            <scope>provided</scope>
         </dependency>
         <dependency>
             <groupId>org.skife.config</groupId>
@@ -54,6 +62,12 @@
             <artifactId>joda-time</artifactId>
         </dependency>
         <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-payment</artifactId>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
             <groupId>com.ning.jdbi</groupId>
             <artifactId>jdbi-metrics</artifactId>
             <scope>test</scope>
@@ -78,11 +92,39 @@
             <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.mysql</groupId>
+            <artifactId>management</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.mysql</groupId>
+            <artifactId>management-dbfiles</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+            <scope>runtime</scope>
+        </dependency>
     </dependencies>
     <build>
         <plugins>
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <configuration>
+                    <groups>fast,slow</groups>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-jar-plugin</artifactId>
                 <executions>
                     <execution>
diff --git a/beatrix/src/main/java/com/ning/billing/beatrix/glue/BeatrixModule.java b/beatrix/src/main/java/com/ning/billing/beatrix/glue/BeatrixModule.java
index 7a672c1..02da6c9 100644
--- a/beatrix/src/main/java/com/ning/billing/beatrix/glue/BeatrixModule.java
+++ b/beatrix/src/main/java/com/ning/billing/beatrix/glue/BeatrixModule.java
@@ -17,13 +17,14 @@
 package com.ning.billing.beatrix.glue;
 
 import com.google.inject.AbstractModule;
+import com.ning.billing.beatrix.lifecycle.DefaultLifecycle;
 import com.ning.billing.beatrix.lifecycle.Lifecycle;
 
 public class BeatrixModule extends AbstractModule {
 
     @Override
     protected void configure() {
-        bind(Lifecycle.class).asEagerSingleton();
+        bind(Lifecycle.class).to(DefaultLifecycle.class).asEagerSingleton();
     }
 
 }
diff --git a/beatrix/src/main/java/com/ning/billing/beatrix/lifecycle/DefaultLifecycle.java b/beatrix/src/main/java/com/ning/billing/beatrix/lifecycle/DefaultLifecycle.java
new file mode 100644
index 0000000..8a07355
--- /dev/null
+++ b/beatrix/src/main/java/com/ning/billing/beatrix/lifecycle/DefaultLifecycle.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * 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.lifecycle;
+
+import com.google.common.base.Supplier;
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Multimaps;
+import com.google.common.collect.SetMultimap;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.ning.billing.lifecycle.KillbillService;
+import com.ning.billing.lifecycle.LifecycleHandlerType;
+import com.ning.billing.lifecycle.LifecycleHandlerType.LifecycleLevel;
+import com.ning.billing.lifecycle.LifecycleHandlerType.LifecycleLevel.Sequence;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.lang.reflect.Method;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+
+public class DefaultLifecycle implements Lifecycle {
+
+    private final static Logger log = LoggerFactory.getLogger(DefaultLifecycle.class);
+    private final SetMultimap<LifecycleLevel, LifecycleHandler<? extends KillbillService>> handlersByLevel;
+
+    private final ServiceFinder serviceFinder;
+
+    protected final Injector injector;
+
+    @Inject
+    public DefaultLifecycle(Injector injector) {
+
+        this.serviceFinder = new ServiceFinder(DefaultLifecycle.class.getClassLoader());
+        this.handlersByLevel = Multimaps.newSetMultimap(new ConcurrentHashMap<LifecycleLevel, Collection<LifecycleHandler<? extends KillbillService>>>(),
+
+                new Supplier<Set<LifecycleHandler<? extends KillbillService>>>() {
+            @Override
+            public Set<LifecycleHandler<? extends KillbillService>> get() {
+                return new CopyOnWriteArraySet<LifecycleHandler<? extends KillbillService>>();
+            }
+        });
+        this.injector = injector;
+
+        init();
+    }
+
+
+    @Override
+    public void fireStartupSequencePriorEventRegistration() {
+        fireSequence(Sequence.STARTUP_PRE_EVENT_REGISTRATION);
+    }
+
+    @Override
+    public void fireStartupSequencePostEventRegistration() {
+        fireSequence(Sequence.STARTUP_POST_EVENT_REGISTRATION);
+    }
+
+    @Override
+    public void fireShutdownSequencePriorEventUnRegistration() {
+        fireSequence(Sequence.SHUTDOWN_PRE_EVENT_UNREGISTRATION);
+    }
+
+    @Override
+    public void fireShutdownSequencePostEventUnRegistration() {
+        fireSequence(Sequence.SHUTDOWN_POST_EVENT_UNREGISTRATION);
+    }
+
+    protected Set<? extends KillbillService> findServices() {
+
+        Set<KillbillService> result = new HashSet<KillbillService>();
+        Set<Class<? extends KillbillService>> services =  serviceFinder.getServices();
+        for (Class<? extends KillbillService> cur : services) {
+            log.debug("Found service {}", cur.getName());
+            try {
+                KillbillService instance = injector.getInstance(cur);
+                log.debug("got instance {}", instance.getName());
+                result.add(instance);
+            } catch (Exception e) {
+                logWarn("Failed to inject " + cur.getName(), e);
+            }
+
+        }
+        return result;
+    }
+
+    private void init() {
+        Set<? extends KillbillService> services = findServices();
+        Iterator<? extends KillbillService> it = services.iterator();
+        while (it.hasNext()) {
+            handlersByLevel.putAll(findAllHandlers(it.next()));
+        }
+    }
+
+    private void fireSequence(Sequence seq) {
+        List<LifecycleLevel> levels = LifecycleLevel.getLevelsForSequence(seq);
+        for (LifecycleLevel cur : levels) {
+            doFireStage(cur);
+        }
+    }
+
+    private void doFireStage(LifecycleLevel level) {
+        log.info("Killbill lifecycle firing stage {}", level);
+        Set<LifecycleHandler<? extends KillbillService>> handlers = handlersByLevel.get(level);
+        for (LifecycleHandler<? extends KillbillService> cur : handlers) {
+
+            try {
+                Method method = cur.getMethod();
+                KillbillService target = cur.getTarget();
+                log.info("Killbill lifecycle calling handler {} for service {}", cur.getMethod().getName(), target.getName());
+                method.invoke(target);
+            } catch (Exception e) {
+                logWarn("Killbill lifecycle failed to invoke lifecycle handler", e);
+            }
+        }
+
+    }
+
+
+    // Used to disable valid injection failure from unit tests
+    protected void logWarn(String msg, Exception e) {
+        log.warn(msg, e);
+    }
+
+    private Multimap<LifecycleLevel, LifecycleHandler<? extends KillbillService>> findAllHandlers(KillbillService service) {
+        Multimap<LifecycleLevel, LifecycleHandler<? extends KillbillService>> methodsInService = HashMultimap.create();
+        Class<? extends KillbillService> clazz = service.getClass();
+        for (Method method : clazz.getMethods()) {
+            LifecycleHandlerType annotation = method.getAnnotation(LifecycleHandlerType.class);
+            if (annotation != null) {
+                LifecycleLevel level = annotation.value();
+                LifecycleHandler<? extends KillbillService> handler = new  LifecycleHandler<KillbillService>(service, method);
+                methodsInService.put(level, handler);
+            }
+        }
+        return methodsInService;
+    }
+
+    private final class LifecycleHandler<T> {
+        private final T target;
+        private final Method method;
+
+        public LifecycleHandler(T target, Method method) {
+            this.target = target;
+            this.method = method;
+        }
+
+        public T getTarget() {
+            return target;
+        }
+
+        public Method getMethod() {
+            return method;
+        }
+    }
+}
diff --git a/beatrix/src/main/java/com/ning/billing/beatrix/lifecycle/Lifecycle.java b/beatrix/src/main/java/com/ning/billing/beatrix/lifecycle/Lifecycle.java
index c0edf2c..8192a65 100644
--- a/beatrix/src/main/java/com/ning/billing/beatrix/lifecycle/Lifecycle.java
+++ b/beatrix/src/main/java/com/ning/billing/beatrix/lifecycle/Lifecycle.java
@@ -16,156 +16,14 @@
 
 package com.ning.billing.beatrix.lifecycle;
 
-import com.google.common.base.Supplier;
-import com.google.common.collect.HashMultimap;
-import com.google.common.collect.Multimap;
-import com.google.common.collect.Multimaps;
-import com.google.common.collect.SetMultimap;
-import com.google.inject.Inject;
-import com.google.inject.Injector;
-import com.ning.billing.lifecycle.KillbillService;
-import com.ning.billing.lifecycle.LifecycleHandlerType;
-import com.ning.billing.lifecycle.LifecycleHandlerType.LifecycleLevel;
-import com.ning.billing.lifecycle.LifecycleHandlerType.LifecycleLevel.Sequence;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
-import java.lang.reflect.Method;
-import java.util.*;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.CopyOnWriteArraySet;
+public interface Lifecycle {
 
+    public void fireStartupSequencePriorEventRegistration();
 
-public class Lifecycle {
+    public void fireStartupSequencePostEventRegistration();
 
-    private final static Logger log = LoggerFactory.getLogger(Lifecycle.class);
-    private final SetMultimap<LifecycleLevel, LifecycleHandler<? extends KillbillService>> handlersByLevel;
+    public void fireShutdownSequencePriorEventUnRegistration();
 
-    private final ServiceFinder serviceFinder;
-
-    private final Injector injector;
-
-    @Inject
-    public Lifecycle(Injector injector) {
-
-        this.serviceFinder = new ServiceFinder(Lifecycle.class.getClassLoader());
-        this.handlersByLevel = Multimaps.newSetMultimap(new ConcurrentHashMap<LifecycleLevel, Collection<LifecycleHandler<? extends KillbillService>>>(),
-
-                new Supplier<Set<LifecycleHandler<? extends KillbillService>>>() {
-            @Override
-            public Set<LifecycleHandler<? extends KillbillService>> get() {
-                return new CopyOnWriteArraySet<LifecycleHandler<? extends KillbillService>>();
-            }
-        });
-        this.injector = injector;
-
-        init();
-    }
-
-    public void init() {
-        Set<? extends KillbillService> services = findServices();
-        Iterator<? extends KillbillService> it = services.iterator();
-        while (it.hasNext()) {
-            handlersByLevel.putAll(findAllHandlers(it.next()));
-        }
-    }
-
-
-    public void fireStartupSequencePriorEventRegistration() {
-        fireSequence(Sequence.STARTUP_PRE_EVENT_REGISTRATION);
-    }
-
-    public void fireStartupSequencePostEventRegistration() {
-        fireSequence(Sequence.STARTUP_POST_EVENT_REGISTRATION);
-    }
-
-    public void fireShutdownSequencePriorEventUnRegistration() {
-        fireSequence(Sequence.SHUTDOWN_PRE_EVENT_UNREGISTRATION);
-    }
-
-    public void fireShutdownSequencePostEventUnRegistration() {
-        fireSequence(Sequence.SHUTDOWN_POST_EVENT_UNREGISTRATION);
-    }
-
-    private void fireSequence(Sequence seq) {
-        List<LifecycleLevel> levels = LifecycleLevel.getLevelsForSequence(seq);
-        for (LifecycleLevel cur : levels) {
-            doFireStage(cur);
-        }
-    }
-
-    private void doFireStage(LifecycleLevel level) {
-        log.info("Killbill lifecycle firing stage {}", level);
-        Set<LifecycleHandler<? extends KillbillService>> handlers = handlersByLevel.get(level);
-        for (LifecycleHandler<? extends KillbillService> cur : handlers) {
-
-            try {
-                Method method = cur.getMethod();
-                KillbillService target = cur.getTarget();
-                log.info("Killbill lifecycle calling handler {} for service {}", cur.getMethod().getName(), target.getName());
-                method.invoke(target);
-            } catch (Exception e) {
-                logWarn("Killbill lifecycle failed to invoke lifecycle handler", e);
-            }
-        }
-
-    }
-
-
-    private Set<? extends KillbillService> findServices() {
-
-        Set<KillbillService> result = new HashSet<KillbillService>();
-        Set<Class<? extends KillbillService>> services =  serviceFinder.getServices();
-        for (Class<? extends KillbillService> cur : services) {
-            log.debug("Found service {}", cur.getName());
-            try {
-                KillbillService instance = injector.getInstance(cur);
-                log.debug("got instance {}", instance.getName());
-                result.add(instance);
-            } catch (Exception e) {
-                logWarn("Failed to inject " + cur.getName(), e);
-            }
-
-        }
-        return result;
-    }
-
-
-    // Used to disable valid injection failure from unit tests
-    protected void logWarn(String msg, Exception e) {
-        log.warn(msg, e);
-    }
-
-    public Multimap<LifecycleLevel, LifecycleHandler<? extends KillbillService>> findAllHandlers(KillbillService service) {
-        Multimap<LifecycleLevel, LifecycleHandler<? extends KillbillService>> methodsInService = HashMultimap.create();
-        Class<? extends KillbillService> clazz = service.getClass();
-        for (Method method : clazz.getMethods()) {
-            LifecycleHandlerType annotation = method.getAnnotation(LifecycleHandlerType.class);
-            if (annotation != null) {
-                LifecycleLevel level = annotation.value();
-                LifecycleHandler<? extends KillbillService> handler = new  LifecycleHandler<KillbillService>(service, method);
-                methodsInService.put(level, handler);
-            }
-        }
-        return methodsInService;
-    }
-
-
-    private final class LifecycleHandler<T> {
-        private final T target;
-        private final Method method;
-
-        public LifecycleHandler(T target, Method method) {
-            this.target = target;
-            this.method = method;
-        }
-
-        public T getTarget() {
-            return target;
-        }
-
-        public Method getMethod() {
-            return method;
-        }
-    }
+    public void fireShutdownSequencePostEventUnRegistration();
 }
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 a4a4ce2..83115de 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
@@ -16,7 +16,6 @@
 
 package com.ning.billing.beatrix.lifecycle;
 
-
 import com.ning.billing.lifecycle.KillbillService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -25,7 +24,13 @@ import java.io.File;
 import java.io.IOException;
 import java.net.MalformedURLException;
 import java.net.URL;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
 import java.util.jar.JarFile;
 
 public class ServiceFinder {
@@ -81,8 +86,6 @@ public class ServiceFinder {
 	    }
 
 	    for (int h = 0; h < classPaths.length; h++) {
-
-
 	        Enumeration<?> files = null;
 	        JarFile module = null;
 	        File classPath = new File( (URL.class).isInstance(classPaths[h]) ?
@@ -93,7 +96,7 @@ public class ServiceFinder {
 
 	            List<String> dirListing = new ArrayList<String>();
 	            recursivelyListDir(dirListing, classPath, new StringBuffer() );
-	            files = Collections.enumeration( dirListing );
+	            files = Collections.enumeration(dirListing);
 	        } else if (classPath.getName().endsWith(".jar")) {
 
 	            log.debug("JAR : " + classPath);
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/MockModule.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/MockModule.java
new file mode 100644
index 0000000..0cb1b01
--- /dev/null
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/MockModule.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;
+
+import static org.testng.Assert.assertNotNull;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.Set;
+
+import org.skife.config.ConfigurationObjectFactory;
+import org.skife.jdbi.v2.IDBI;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.inject.AbstractModule;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.ning.billing.account.glue.AccountModule;
+import com.ning.billing.beatrix.lifecycle.DefaultLifecycle;
+import com.ning.billing.beatrix.lifecycle.Lifecycle;
+import com.ning.billing.catalog.api.CatalogService;
+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.api.EntitlementService;
+import com.ning.billing.entitlement.glue.EntitlementModule;
+import com.ning.billing.invoice.api.InvoiceService;
+import com.ning.billing.invoice.glue.InvoiceModule;
+import com.ning.billing.lifecycle.KillbillService;
+import com.ning.billing.payment.api.PaymentService;
+import com.ning.billing.payment.provider.MockPaymentProviderPluginModule;
+import com.ning.billing.payment.setup.PaymentConfig;
+import com.ning.billing.payment.setup.PaymentModule;
+import com.ning.billing.util.clock.Clock;
+import com.ning.billing.util.clock.ClockMock;
+import com.ning.billing.util.bus.BusService;
+import com.ning.billing.util.glue.BusModule;
+import com.ning.billing.util.glue.GlobalLockerModule;
+import com.ning.billing.util.glue.NotificationQueueModule;
+
+
+public class MockModule extends AbstractModule {
+
+
+    public static final String PLUGIN_NAME = "yoyo";
+
+    @Override
+    protected void configure() {
+
+        loadSystemPropertiesFromClasspath("/resource.properties");
+
+        bind(Clock.class).to(ClockMock.class).asEagerSingleton();
+        bind(ClockMock.class).asEagerSingleton();
+        bind(Lifecycle.class).to(SubsetDefaultLifecycle.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 GlobalLockerModule());
+        install(new BusModule());
+        install(new NotificationQueueModule());
+        install(new AccountModule());
+        install(new CatalogModule());
+        install(new EntitlementModule());
+        install(new InvoiceModule());
+        install(new PaymentMockModule());
+    }
+
+    private static final class PaymentMockModule extends PaymentModule {
+        @Override
+        protected void installPaymentProviderPlugins(PaymentConfig config) {
+            install(new MockPaymentProviderPluginModule(PLUGIN_NAME));
+        }
+    }
+
+    private static void loadSystemPropertiesFromClasspath(final String resource) {
+        final URL url = TestBasic.class.getResource(resource);
+        assertNotNull(url);
+        try {
+            System.getProperties().load( url.openStream() );
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private final static class SubsetDefaultLifecycle extends DefaultLifecycle {
+
+        @Inject
+        public SubsetDefaultLifecycle(Injector injector) {
+            super(injector);
+        }
+
+        @Override
+        protected Set<? extends KillbillService> findServices() {
+            ImmutableSet<? extends KillbillService> services = new ImmutableSet.Builder<KillbillService>()
+                            .add(injector.getInstance(BusService.class))
+                            .add(injector.getInstance(CatalogService.class))
+                            .add(injector.getInstance(EntitlementService.class))
+                            .add(injector.getInstance(InvoiceService.class))
+                            .add(injector.getInstance(PaymentService.class))
+                            .build();
+            return services;
+        }
+    }
+}
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestBasic.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestBasic.java
new file mode 100644
index 0000000..d04d768
--- /dev/null
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestBasic.java
@@ -0,0 +1,555 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * 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.io.IOException;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.UUID;
+
+import com.ning.billing.account.api.AccountApiException;
+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 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.Days;
+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.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.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.Currency;
+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.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;
+
+@Guice(modules = {MockModule.class})
+public class TestBasic {
+
+    private static final Logger log = LoggerFactory.getLogger(TestBasic.class);
+    private static long AT_LEAST_ONE_MONTH_MS =  31L * 24L * 3600L * 1000L;
+
+    @Inject IDBI dbi;
+
+    @Inject
+    private ClockMock clock;
+
+    @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(TestBasic.class.getResourceAsStream("/com/ning/billing/account/ddl.sql"));
+        final String entitlementDdl = IOUtils.toString(TestBasic.class.getResourceAsStream("/com/ning/billing/entitlement/ddl.sql"));
+        final String invoiceDdl = IOUtils.toString(TestBasic.class.getResourceAsStream("/com/ning/billing/invoice/ddl.sql"));
+        final String paymentDdl = IOUtils.toString(TestBasic.class.getResourceAsStream("/com/ning/billing/payment/ddl.sql"));
+        final String utilDdl = IOUtils.toString(TestBasic.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(alwaysRun = true)
+    public void setup() throws Exception{
+
+        setupMySQL();
+
+        /**
+         * 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(alwaysRun = true)
+    public void tearDown() throws Exception {
+        lifecycle.fireShutdownSequencePriorEventUnRegistration();
+        busService.getBus().unregister(busHandler);
+        lifecycle.fireShutdownSequencePostEventUnRegistration();
+    }
+
+
+    @BeforeMethod(alwaysRun = true)
+    public void setupTest() {
+
+        log.warn("\n");
+        log.warn("RESET TEST FRAMEWORK\n\n");
+        busHandler.reset();
+        clock.resetDeltaFromReality();
+        cleanupData();
+    }
+
+    @AfterMethod(alwaysRun = true)
+    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) {
+        SubscriptionData subscription = (SubscriptionData) entitlementUserApi.getSubscriptionFromId(subscriptionId);
+
+        List<InvoiceItem> invoiceItems = invoiceUserApi.getInvoiceItemsByAccount(accountId);
+        boolean wasFound = false;
+
+        Iterator<InvoiceItem> invoiceItemIterator = invoiceItems.iterator();
+        while (invoiceItemIterator.hasNext()) {
+            InvoiceItem item = invoiceItemIterator.next();
+            if (item.getStartDate().compareTo(removeMillis(startDate)) == 0) {
+                if (item.getEndDate().compareTo(removeMillis(endDate)) == 0) {
+                    if (item.getAmount().compareTo(amount) == 0) {
+                        wasFound = true;
+                        break;
+                    }
+                }
+            }
+        }
+
+        assertTrue(wasFound);
+
+        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(removeMillis(chargeThroughDate)) == 0);
+    }
+
+    private DateTime removeMillis(DateTime input) {
+        return input.toMutableDateTime().millisOfSecond().set(0).toDateTime();
+    }
+
+    @Test(groups = "fast", enabled = false)
+    public void testBasePlanCompleteWithBillingDayInPast() throws Exception {
+        DateTime startDate = new DateTime(2012, 2, 1, 0, 3, 42, 0);
+        testBasePlanComplete(startDate, 31, false);
+    }
+
+    @Test(groups = "fast", enabled = false)
+    public void testBasePlanCompleteWithBillingDayPresent() throws Exception {
+        DateTime startDate = new DateTime(2012, 2, 1, 0, 3, 42, 0);
+        testBasePlanComplete(startDate, 1, false);
+    }
+
+    @Test(groups = "fast", enabled = false)
+    public void testBasePlanCompleteWithBillingDayAlignedWithTrial() throws Exception {
+        DateTime startDate = new DateTime(2012, 2, 1, 0, 3, 42, 0);
+        testBasePlanComplete(startDate, 2, false);
+    }
+
+    @Test(groups = "fast", enabled = false)
+    public void testBasePlanCompleteWithBillingDayInFuture() throws Exception {
+        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 = "stress", enabled = false)
+    public void stressTest() throws Exception {
+        final int maxIterations = 7;
+        int curIteration = maxIterations;
+        for (curIteration = 0; curIteration < maxIterations; curIteration++) {
+            log.info("################################  ITERATION " + curIteration + "  #########################");
+            Thread.sleep(1000);
+            setupTest();
+            testBasePlanCompleteWithBillingDayPresent();
+            Thread.sleep(1000);
+            setupTest();
+            testBasePlanCompleteWithBillingDayInPast();
+            Thread.sleep(1000);
+            setupTest();
+            testBasePlanCompleteWithBillingDayAlignedWithTrial();
+            Thread.sleep(1000);
+            setupTest();
+            testBasePlanCompleteWithBillingDayInFuture();
+        }
+    }
+
+    private void testBasePlanComplete(DateTime initialCreationDate, int billingDay,
+                                      boolean proRationExpected) throws Exception {
+        long DELAY = 5000 * 10;
+
+        Account account = accountUserApi.createAccount(getAccountData(billingDay), null, null);
+        UUID accountId = account.getId();
+        assertNotNull(account);
+
+        // set clock to the initial start date
+        clock.setDeltaFromReality(initialCreationDate.getMillis() - DateTime.now().getMillis());
+        SubscriptionBundle bundle = entitlementUserApi.createBundleForAccount(account.getId(), "whatever");
+
+        String productName = "Shotgun";
+        BillingPeriod term = BillingPeriod.MONTHLY;
+        String planSetName = PriceListSet.DEFAULT_PRICELIST_NAME;
+
+        //
+        // CREATE SUBSCRIPTION AND EXPECT BOTH EVENTS: NextEvent.CREATE NextEvent.INVOICE
+        //
+        busHandler.pushExpectedEvent(NextEvent.CREATE);
+        busHandler.pushExpectedEvent(NextEvent.INVOICE);
+        SubscriptionData subscription = (SubscriptionData) entitlementUserApi.createSubscription(bundle.getId(),
+                new PlanPhaseSpecifier(productName, ProductCategory.BASE, term, planSetName, null), null);
+        assertNotNull(subscription);
+
+        assertTrue(busHandler.isCompleted(DELAY));
+        log.info("testSimple passed first busHandler checkpoint.");
+
+        //
+        // VERIFY CTD HAS BEEN SET
+        //
+        DateTime startDate = subscription.getCurrentPhaseStart();
+        DateTime endDate = startDate.plusDays(30);
+        BigDecimal price = subscription.getCurrentPhase().getFixedPrice().getPrice(Currency.USD);
+        verifyTestResult(accountId, subscription.getId(), startDate, endDate, price, endDate);
+
+        //
+        // CHANGE PLAN IMMEDIATELY AND EXPECT BOTH EVENTS: NextEvent.CHANGE NextEvent.INVOICE
+        //
+        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());
+
+        assertTrue(busHandler.isCompleted(DELAY));
+        log.info("testSimple passed second busHandler checkpoint.");
+
+        //
+        // VERIFY AGAIN CTD HAS BEEN SET
+        //
+        startDate = subscription.getCurrentPhaseStart();
+        endDate = startDate.plusMonths(1);
+        price = subscription.getCurrentPhase().getFixedPrice().getPrice(Currency.USD);
+        verifyTestResult(accountId, subscription.getId(), startDate, endDate, price, endDate);
+
+        //
+        // MOVE TIME TO AFTER TRIAL AND EXPECT BOTH EVENTS :  NextEvent.PHASE NextEvent.INVOICE
+        //
+        busHandler.pushExpectedEvent(NextEvent.PHASE);
+        busHandler.pushExpectedEvent(NextEvent.INVOICE);
+        busHandler.pushExpectedEvent(NextEvent.PAYMENT);
+
+        if (proRationExpected) {
+            busHandler.pushExpectedEvent(NextEvent.INVOICE);
+            busHandler.pushExpectedEvent(NextEvent.PAYMENT);
+        }
+
+        clock.setDeltaFromReality(AT_LEAST_ONE_MONTH_MS);
+
+        assertTrue(busHandler.isCompleted(DELAY));
+
+        //
+        // CHANGE PLAN EOT AND EXPECT NOTHING
+        //
+        newTerm = BillingPeriod.MONTHLY;
+        newPlanSetName = PriceListSet.DEFAULT_PRICELIST_NAME;
+        newProductName = "Pistol";
+        subscription = (SubscriptionData) entitlementUserApi.getSubscriptionFromId(subscription.getId());
+        subscription.changePlan(newProductName, newTerm, newPlanSetName, clock.getUTCNow());
+        log.info("testSimple has passed third busHandler checkpoint (no events)");
+
+        //
+        // MOVE TIME AFTER CTD AND EXPECT BOTH EVENTS : NextEvent.CHANGE NextEvent.INVOICE
+        //
+        busHandler.pushExpectedEvent(NextEvent.CHANGE);
+        busHandler.pushExpectedEvent(NextEvent.INVOICE);
+        busHandler.pushExpectedEvent(NextEvent.PAYMENT);
+        //clock.addDeltaFromReality(ctd.getMillis() - clock.getUTCNow().getMillis());
+        clock.addDeltaFromReality(AT_LEAST_ONE_MONTH_MS + 1000);
+
+        //waitForDebug();
+
+        assertTrue(busHandler.isCompleted(DELAY));
+        log.info("testSimple passed fourth busHandler checkpoint.");
+
+        //
+        // MOVE TIME AFTER NEXT BILL CYCLE DAY AND EXPECT EVENT : NextEvent.INVOICE
+        //
+        int maxCycles = 3;
+        startDate = endDate;
+        endDate = startDate.plusMonths(1);
+        do {
+            busHandler.pushExpectedEvent(NextEvent.INVOICE);
+            busHandler.pushExpectedEvent(NextEvent.PAYMENT);
+            clock.addDeltaFromReality(AT_LEAST_ONE_MONTH_MS + 1000);
+            assertTrue(busHandler.isCompleted(DELAY));
+            verifyTestResult(accountId, subscription.getId(), startDate, endDate, price, endDate);
+        } while (maxCycles-- > 0);
+
+        //
+        // FINALLY CANCEL SUBSCRIPTION EOT
+        //
+        subscription = (SubscriptionData) entitlementUserApi.getSubscriptionFromId(subscription.getId());
+        subscription.cancel(clock.getUTCNow(), false);
+
+        // MOVE AFTER CANCEL DATE AND EXPECT EVENT : NextEvent.CANCEL
+        busHandler.pushExpectedEvent(NextEvent.CANCEL);
+        Interval it = new Interval(clock.getUTCNow(), endDate);
+        clock.addDeltaFromReality(it.toDurationMillis());
+        assertTrue(busHandler.isCompleted(DELAY));
+
+        //
+        // CHECK AGAIN THERE IS NO MORE INVOICES GENERATED
+        //
+        busHandler.reset();
+        clock.addDeltaFromReality(AT_LEAST_ONE_MONTH_MS + 1000);
+        assertTrue(busHandler.isCompleted(DELAY));
+
+        subscription = (SubscriptionData) entitlementUserApi.getSubscriptionFromId(subscription.getId());
+        DateTime lastCtd = subscription.getChargedThroughDate();
+        assertNotNull(lastCtd);
+        log.info("Checking CTD: " + lastCtd.toString() + "; clock is " + clock.getUTCNow().toString());
+        assertTrue(lastCtd.isBefore(clock.getUTCNow()));
+
+        // The invoice system is still working to verify there is nothing to do
+        Thread.sleep(DELAY);
+        log.info("TEST PASSED !");
+    }
+
+    @Test(enabled=false)
+    public void testHappyPath() throws AccountApiException, EntitlementUserApiException {
+        long DELAY = 5000 * 10;
+
+        Account account = accountUserApi.createAccount(getAccountData(3), null, null);
+        assertNotNull(account);
+
+        SubscriptionBundle bundle = entitlementUserApi.createBundleForAccount(account.getId(), "whatever");
+
+        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);
+        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());
+
+        assertTrue(busHandler.isCompleted(DELAY));
+
+        busHandler.pushExpectedEvent(NextEvent.PHASE);
+        busHandler.pushExpectedEvent(NextEvent.INVOICE);
+        clock.setDeltaFromReality(AT_LEAST_ONE_MONTH_MS);
+        assertTrue(busHandler.isCompleted(DELAY));
+
+    }
+
+    protected AccountData getAccountData(final int billingDay) {
+
+        final String someRandomKey = RandomStringUtils.randomAlphanumeric(10);
+        AccountData accountData = 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;
+            }
+
+            @Override
+            public String getCountry() {
+                return null;
+            }
+        };
+        return accountData;
+    }
+}
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestBusHandler.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestBusHandler.java
new file mode 100644
index 0000000..57a0d6e
--- /dev/null
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestBusHandler.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * 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 java.util.Iterator;
+import java.util.List;
+import java.util.Stack;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+
+import com.google.common.base.Joiner;
+import com.google.common.eventbus.Subscribe;
+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;
+
+public class TestBusHandler {
+
+    protected static final Logger log = LoggerFactory.getLogger(TestBusHandler.class);
+
+    private final List<NextEvent> nextExpectedEvent;
+
+    private volatile boolean completed;
+
+    public TestBusHandler() {
+        nextExpectedEvent = new Stack<NextEvent>();
+        this.completed = false;
+    }
+
+    public enum NextEvent {
+        MIGRATE_ENTITLEMENT,
+        CREATE,
+        CHANGE,
+        CANCEL,
+        UNCANCEL,
+        PAUSE,
+        RESUME,
+        PHASE,
+        INVOICE,
+        PAYMENT
+    }
+
+    @Subscribe
+    public void handleEntitlementEvents(SubscriptionTransition event) {
+        log.info(String.format("TestBusHandler Got subscription event %s", event.toString()));
+        switch (event.getTransitionType()) {
+        case MIGRATE_ENTITLEMENT:
+            assertEqualsNicely(NextEvent.MIGRATE_ENTITLEMENT);
+            notifyIfStackEmpty();
+            break;
+        case CREATE:
+            assertEqualsNicely(NextEvent.CREATE);
+            notifyIfStackEmpty();
+
+            break;
+        case CANCEL:
+            assertEqualsNicely(NextEvent.CANCEL);
+            notifyIfStackEmpty();
+
+            break;
+        case CHANGE:
+            assertEqualsNicely(NextEvent.CHANGE);
+            notifyIfStackEmpty();
+
+            break;
+        case PAUSE:
+            assertEqualsNicely(NextEvent.PAUSE);
+            notifyIfStackEmpty();
+
+            break;
+        case RESUME:
+            assertEqualsNicely(NextEvent.RESUME);
+            notifyIfStackEmpty();
+
+            break;
+        case UNCANCEL:
+            assertEqualsNicely(NextEvent.UNCANCEL);
+            notifyIfStackEmpty();
+            break;
+        case PHASE:
+            assertEqualsNicely(NextEvent.PHASE);
+            notifyIfStackEmpty();
+            break;
+        default:
+            throw new RuntimeException("Unexpected event type " + event.getRequestedTransitionTime());
+        }
+    }
+
+    @Subscribe
+    public void handleInvoiceEvents(InvoiceCreationNotification event) {
+        log.info(String.format("TestBusHandler Got Invoice event %s", event.toString()));
+        assertEqualsNicely(NextEvent.INVOICE);
+        notifyIfStackEmpty();
+
+    }
+
+    @Subscribe
+    public void handlePaymentEvents(PaymentInfo event) {
+        log.info(String.format("TestBusHandler Got PaymentInfo event %s", event.toString()));
+        assertEqualsNicely(NextEvent.PAYMENT);
+        notifyIfStackEmpty();
+    }
+
+    @Subscribe
+    public void handlePaymentErrorEvents(PaymentError event) {
+        log.info(String.format("TestBusHandler Got PaymentError event %s", event.toString()));
+        Assert.fail("Unexpected payment failure");
+    }
+
+    public void reset() {
+        nextExpectedEvent.clear();
+        completed = true;
+    }
+
+    public void pushExpectedEvent(NextEvent next) {
+        synchronized (this) {
+            nextExpectedEvent.add(next);
+            completed = false;
+        }
+    }
+
+    public boolean isCompleted(long timeout) {
+        synchronized (this) {
+            if (completed) {
+                return completed;
+            }
+            try {
+                wait(timeout);
+            } catch (Exception ignore) {
+            }
+        }
+        if (!completed) {
+            Joiner joiner = Joiner.on(" ");
+            log.error("TestBusHandler did not complete in " + timeout + " ms, remaining events are " + joiner.join(nextExpectedEvent));
+        }
+        return completed;
+    }
+
+    private void notifyIfStackEmpty() {
+        log.debug("TestBusHandler notifyIfStackEmpty ENTER");
+        synchronized (this) {
+            if (nextExpectedEvent.isEmpty()) {
+                log.debug("notifyIfStackEmpty EMPTY");
+                completed = true;
+                notify();
+            }
+        }
+        log.debug("TestBusHandler notifyIfStackEmpty EXIT");
+    }
+
+    private void assertEqualsNicely(NextEvent received) {
+
+        boolean foundIt = false;
+        Iterator<NextEvent> it = nextExpectedEvent.iterator();
+        while (it.hasNext()) {
+            NextEvent ev = it.next();
+            if (ev == received) {
+                it.remove();
+                foundIt = true;
+                break;
+            }
+        }
+        if (!foundIt) {
+            Joiner joiner = Joiner.on(" ");
+            log.error("TestBusHandler Received event " + received + "; expected " + joiner.join(nextExpectedEvent));
+            Assert.fail();
+        }
+    }
+}
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/lifecycle/TestLifecycle.java b/beatrix/src/test/java/com/ning/billing/beatrix/lifecycle/TestLifecycle.java
index 69e6a0d..f60d61f 100644
--- a/beatrix/src/test/java/com/ning/billing/beatrix/lifecycle/TestLifecycle.java
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/lifecycle/TestLifecycle.java
@@ -16,7 +16,11 @@
 
 package com.ning.billing.beatrix.lifecycle;
 
-import com.google.inject.*;
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.google.inject.Stage;
 import com.ning.billing.lifecycle.KillbillService;
 import com.ning.billing.lifecycle.LifecycleHandlerType;
 import com.ning.billing.lifecycle.LifecycleHandlerType.LifecycleLevel;
@@ -34,7 +38,7 @@ public class TestLifecycle {
     private Service1 s1;
     private Service2 s2;
 
-    private Lifecycle lifecycle;
+    private DefaultLifecycle lifecycle;
 
     public static class ServiceBase {
 
@@ -117,12 +121,12 @@ public class TestLifecycle {
 
 
 
-    @BeforeClass
+    @BeforeClass(alwaysRun=true)
     public void setup() {
         final Injector g = Guice.createInjector(Stage.DEVELOPMENT, new TestLifecycleModule());
         s1 = g.getInstance(Service1.class);
         s2 = g.getInstance(Service2.class);
-        lifecycle = g.getInstance(Lifecycle.class);
+        lifecycle = g.getInstance(DefaultLifecycle.class);
     }
 
     @Test(enabled=true, groups={"fast"})
@@ -148,7 +152,7 @@ public class TestLifecycle {
         Assert.assertEquals(s1.getCount() + s2.getCount(), 1);
     }
 
-    public static class LifecycleNoWarn extends Lifecycle {
+    public static class LifecycleNoWarn extends DefaultLifecycle {
 
         @Inject
         public LifecycleNoWarn(Injector injector) {
@@ -163,7 +167,7 @@ public class TestLifecycle {
 
         @Override
         protected void configure() {
-            bind(Lifecycle.class).to(LifecycleNoWarn.class).asEagerSingleton();
+            bind(DefaultLifecycle.class).to(LifecycleNoWarn.class).asEagerSingleton();
             bind(Service1.class).asEagerSingleton();
             bind(Service2.class).asEagerSingleton();
         }
diff --git a/beatrix/src/test/resources/Catalog-Entitlement-Testplan.txt b/beatrix/src/test/resources/Catalog-Entitlement-Testplan.txt
new file mode 100644
index 0000000..6d5867f
--- /dev/null
+++ b/beatrix/src/test/resources/Catalog-Entitlement-Testplan.txt
@@ -0,0 +1,112 @@
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+
+
+NOTES
+=====
+
+Events: Create, change, cancel, migrate 
+Validate: BillingEvents, SubscriptionTransition 
+Rules: 
+    Cancellation
+        Action Policy: When to cancel (Immediate/End-of-term)
+    Creation
+        Alignment: How to align phases in a bundle
+    Change plan behavior
+        Action Policy: When to change plan (Immediate/End-of-term)
+        Alignment: How to align phases 
+        Pricelist: Which pricelist to pick when moving between plans
+    Billing alignment
+        Subscription BCD, Bundle BCD, Account BCD 
+Phases - timing
+Prices - multi-currency, fixed vs recurring prices
+Pricelists - particularly pricelist change rules
+Catalog changes new subscriptions / existing subscriptions
+Price change
+    
+TESTS
+=====
+
+BASEPLAN TESTS
+    * Create a single phase recurring plan
+        - check for creation event (timing?)
+        - check for no termination event
+        - check pricing (different currencies)
+        - check BDC (subscription, account, timezone)
+    * Create a single phase fixed length plan 
+        - check for creation event (timing - different request dates)
+        - check for termination event (timing - different lengths?)
+        - check price (fixed vs recurring)
+    * Create a two phase event use a fixed price and a recurring price
+        - check for phase change (timing)
+        - check prices change
+    * Create a multi-phase plan 
+        - check for phase events
+        - check price changes
+    * Create multiple base plans in a single bundle - should fail
+    
+    * Change base plan once
+        - check plan change policy (immediate, eot)
+        - check alignments of new plan with old 
+        - check move between pricelists
+        - check that phases progress successfully after change
+        - check obsolete events are removed
+    * Change base plan multiple times
+        - check that alignment occurs correctly 
+        - check phases progress correctly
+        - check obsolete events are removed 
+        
+    * Cancellation of a single phase plan
+        - check creation and timing of termination event
+    * Cancellation of a multi-phase plan
+        - check creation of termination event 
+        - check removal of events beyond termination event
+    * Change a cancelled base plan - should fail
+    
+    * Migration to a single phase plan
+        - check migration event occurs when it should
+    * Migration to a multi-phase plan
+        - check migration event occurs when it should
+        - check migration into different phase
+        - check alignment of phases can be correctly controlled
+    * Migration to a fixed duration plan
+        - check migration event occurs when it should
+        - check termination event occurs when it should
+        
+
+         
+STANDALONE TEST
+    * Create multiple plans in a single bundle
+        - check plans can be created
+        - check cannot add a base plan
+        - check BCD at subscription bundle level
+            
+
+PRICE CHANGE TEST
+    * Price change on a single phase base plan
+        - check new subscriptions get price after effective date
+        - check changed subscriptions get price after effective date
+        - check existing subscriptions ONLY get it after ESED
+        - check that if ESED is missing existing subs are grandfathered for ever
+    * Price change on a multi-phase subscription
+        - check price change applies correctly to correct phases
+    * Multiple price changes
+        - check multiple price changes with overlapping dates
+
+
+ADD-ON TESTS
+    * Add-on creation alignment
+    * Add-on cancel with base plan
+    
+    
\ No newline at end of file
diff --git a/beatrix/src/test/resources/catalogSample.xml b/beatrix/src/test/resources/catalogSample.xml
new file mode 100644
index 0000000..c18816f
--- /dev/null
+++ b/beatrix/src/test/resources/catalogSample.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>
+			<available>
+				<addonProduct>Telescopic-Scope</addonProduct>
+				<addonProduct>Laser-Scope</addonProduct>
+			</available>
+		</product>
+		<product name="Shotgun">
+			<category>BASE</category>
+		</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>Pistol</toProduct>
+				<policy>END_OF_TERM</policy>
+			</changePolicyCase>
+			<changePolicyCase> 
+				<toPriceList>rescue</toPriceList>
+				<policy>END_OF_TERM</policy>
+			</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>
+				<policy>END_OF_TERM</policy>
+			</changePolicyCase>
+			<changePolicyCase> 
+				<toProduct>Assault-Rifle</toProduct>
+				<policy>IMMEDIATE</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>
+				<alignment>START_OF_SUBSCRIPTION</alignment>
+			</changeAlignmentCase>
+			<changeAlignmentCase>
+				<toPriceList>rescue</toPriceList>
+				<alignment>CHANGE_OF_PLAN</alignment>
+			</changeAlignmentCase>
+			<changeAlignmentCase>
+				<fromPriceList>rescue</fromPriceList>
+				<toPriceList>rescue</toPriceList>
+				<alignment>CHANGE_OF_PRICELIST</alignment>
+			</changeAlignmentCase>
+		</changeAlignment>
+		<cancelPolicy>
+			<cancelPolicyCase>
+				<policy>END_OF_TERM</policy>
+			</cancelPolicyCase>
+			<cancelPolicyCase>
+				<phaseType>TRIAL</phaseType>
+				<policy>IMMEDIATE</policy>
+			</cancelPolicyCase>
+		</cancelPolicy>
+		<createAlignment>
+			<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-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="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>
+			</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>
+                                    <!-- no price implies $0 -->
+                                </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>
+			<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>
+			<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/beatrix/src/test/resources/log4j.xml b/beatrix/src/test/resources/log4j.xml
index 75abc76..ac530a1 100644
--- a/beatrix/src/test/resources/log4j.xml
+++ b/beatrix/src/test/resources/log4j.xml
@@ -29,6 +29,10 @@
         <level value="info"/>
     </logger>
 
+    <logger name="com.ning.billing.util.notificationq">
+        <level value="info"/>
+    </logger>
+
     <root>
         <priority value="info"/>
         <appender-ref ref="stdout"/>
diff --git a/beatrix/src/test/resources/resource.properties b/beatrix/src/test/resources/resource.properties
new file mode 100644
index 0000000..d63334b
--- /dev/null
+++ b/beatrix/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
+
+

catalog/pom.xml 2(+1 -1)

diff --git a/catalog/pom.xml b/catalog/pom.xml
index 1b5b8d6..5d19c5b 100644
--- a/catalog/pom.xml
+++ b/catalog/pom.xml
@@ -13,7 +13,7 @@
     <parent>
         <groupId>com.ning.billing</groupId>
         <artifactId>killbill</artifactId>
-        <version>0.1.2-SNAPSHOT</version>
+        <version>0.1.6-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-catalog</artifactId>
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 7f88f07..b824617 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/DefaultCatalogService.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/DefaultCatalogService.java
@@ -28,6 +28,7 @@ import com.ning.billing.lifecycle.LifecycleHandlerType;
 import com.ning.billing.lifecycle.LifecycleHandlerType.LifecycleLevel;
 
 public class DefaultCatalogService implements KillbillService, Provider<Catalog>, CatalogService {
+    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DefaultCatalogService.class);
 
     private static final String CATALOG_SERVICE_NAME = "catalog-service";
 
@@ -54,7 +55,7 @@ public class DefaultCatalogService implements KillbillService, Provider<Catalog>
             	System.out.println("Really really::" + config.getCatalogURI());
             	String url = config.getCatalogURI();
             	catalog = loader.load(url);
-            	
+
                 //catalog = XMLLoader.getObjectFromProperty(config.getCatalogURI(), Catalog.class);
                 isInitialized = true;
             } catch (Exception e) {
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 be3ea55..0e821ef 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/DefaultDuration.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/DefaultDuration.java
@@ -20,6 +20,7 @@ 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;
+import org.joda.time.DateTime;
 
 import javax.xml.bind.annotation.XmlAccessType;
 import javax.xml.bind.annotation.XmlAccessorType;
@@ -49,7 +50,25 @@ public class DefaultDuration extends ValidatingConfig<StandaloneCatalog> impleme
         return number;
     }
 
-	@Override
+    @Override
+    public DateTime addToDateTime(DateTime dateTime) {
+        if (number == null) {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 ValidationErrors validate(StandaloneCatalog catalog, ValidationErrors errors) {
 		//TODO MDW - Validation TimeUnit UNLIMITED iff number == -1
 		return errors;
diff --git a/catalog/src/main/java/com/ning/billing/catalog/DefaultInternationalPrice.java b/catalog/src/main/java/com/ning/billing/catalog/DefaultInternationalPrice.java
index 6990d3c..d0cf2c4 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/DefaultInternationalPrice.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/DefaultInternationalPrice.java
@@ -42,7 +42,7 @@ public class DefaultInternationalPrice extends ValidatingConfig<StandaloneCatalo
 
 
 	/* (non-Javadoc)
-	 * @see com.ning.billing.catalog.IInternationalPrice#getPrices()
+	 * @see com.ning.billing.catalog.InternationalPrice#getPrices()
 	 */
 	@Override
 	public Price[] getPrices() {
@@ -119,4 +119,18 @@ public class DefaultInternationalPrice extends ValidatingConfig<StandaloneCatalo
 		return zeroPrice;
 	}
 
+	@Override
+	public boolean isZero() {
+		for(DefaultPrice price :prices) {
+			try {
+				if( price.getValue().compareTo(BigDecimal.ZERO) != 0) {
+					return false;
+				}
+			} catch (CurrencyValueNull e) {
+				//Ignore if the currency is null we treat it as 0
+			}
+		}
+		return true;
+	}
+
 }
diff --git a/catalog/src/main/java/com/ning/billing/catalog/DefaultPlan.java b/catalog/src/main/java/com/ning/billing/catalog/DefaultPlan.java
index 4972e26..a249289 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/DefaultPlan.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/DefaultPlan.java
@@ -29,9 +29,12 @@ import javax.xml.bind.annotation.XmlElementWrapper;
 import javax.xml.bind.annotation.XmlID;
 import javax.xml.bind.annotation.XmlIDREF;
 
+import org.joda.time.DateTime;
+
 import com.ning.billing.ErrorCode;
 import com.ning.billing.catalog.api.BillingPeriod;
 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.PlanPhase;
 import com.ning.billing.catalog.api.Product;
@@ -228,6 +231,18 @@ public class DefaultPlan extends ValidatingConfig<StandaloneCatalog> implements 
 		this.plansAllowedInBundle = plansAllowedInBundle;
 		return this;
 	}
+	@Override
+	public DateTime dateOfFirstRecurringNonZeroCharge(DateTime subscriptionStartDate) {
+		DateTime result = subscriptionStartDate.toDateTime();
+		for (PlanPhase phase : getAllPhases()) {
+			if(phase.getRecurringPrice() == null || phase.getRecurringPrice().isZero()) {
+				result = phase.getDuration().addToDateTime(result);
+			} else {
+				break;
+			}
+		}
+		return result;
+	}
 	
 	
 }
diff --git a/catalog/src/main/java/com/ning/billing/catalog/DefaultPlanPhase.java b/catalog/src/main/java/com/ning/billing/catalog/DefaultPlanPhase.java
index 4afe278..6d00162 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/DefaultPlanPhase.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/DefaultPlanPhase.java
@@ -17,7 +17,13 @@
 package com.ning.billing.catalog;
 
 import com.ning.billing.ErrorCode;
-import com.ning.billing.catalog.api.*;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.CatalogApiException;
+import com.ning.billing.catalog.api.Duration;
+import com.ning.billing.catalog.api.InternationalPrice;
+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.util.config.ValidatingConfig;
 import com.ning.billing.util.config.ValidationError;
 import com.ning.billing.util.config.ValidationErrors;
@@ -38,7 +44,7 @@ public class DefaultPlanPhase extends ValidatingConfig<StandaloneCatalog> implem
     private DefaultDuration duration;
     
     @XmlElement(required=true)
-    private BillingPeriod billingPeriod = BillingPeriod.NO_BILLING_PERIOD;
+    private BillingPeriod billingPeriod;
 
 	@XmlElement(required=false)
 	private DefaultInternationalPrice recurringPrice;
@@ -127,30 +133,31 @@ public class DefaultPlanPhase extends ValidatingConfig<StandaloneCatalog> implem
 	public ValidationErrors validate(StandaloneCatalog catalog, ValidationErrors errors) {
 		//Validation: check for nulls
 		if(billingPeriod == null) {
-			errors.add(new ValidationError(String.format("Phase %s of plan %s has a reccurring price but no billing period", type.toString(), plan.getName()), 
+			errors.add(new ValidationError(String.format("Phase %s of plan %s has a recurring price but no billing period", type.toString(), plan.getName()),
 					catalog.getCatalogURI(), DefaultPlanPhase.class, type.toString()));
 		}
-		
+	
 		//Validation: if there is a recurring price there must be a billing period
-		if(recurringPrice != null && (billingPeriod == null || billingPeriod ==BillingPeriod.NO_BILLING_PERIOD)) {
-			errors.add(new ValidationError(String.format("Phase %s of plan %s has a reccurring price but no billing period", type.toString(), plan.getName()), 
+		if((recurringPrice != null) && (billingPeriod == null || billingPeriod == BillingPeriod.NO_BILLING_PERIOD)) {
+			errors.add(new ValidationError(String.format("Phase %s of plan %s has a recurring price but no billing period", type.toString(), plan.getName()),
 					catalog.getCatalogURI(), DefaultPlanPhase.class, type.toString()));
 		}
-		//Validation: if there is no reccuring price there should be no billing period
-		if(recurringPrice == null && billingPeriod != BillingPeriod.NO_BILLING_PERIOD) {
-			errors.add(new ValidationError(String.format("Phase %s of plan %s has no reccurring price but does have a billing period. The billing period should be set to '%s'", 
+
+		//Validation: if there is no recurring price there should be no billing period
+		if((recurringPrice == null) && billingPeriod != BillingPeriod.NO_BILLING_PERIOD) {
+			errors.add(new ValidationError(String.format("Phase %s of plan %s has no recurring price but does have a billing period. The billing period should be set to '%s'",
 					type.toString(), plan.getName(), BillingPeriod.NO_BILLING_PERIOD), 
 					catalog.getCatalogURI(), DefaultPlanPhase.class, type.toString()));
 		}
 		
-		//Validation: there must be at least one of reccuringPrice or fixedPrice
-		if(recurringPrice == null && fixedPrice == null) {
-			errors.add(new ValidationError(String.format("Phase %s of plan %s has neither a reccurring price or a fixed price.", 
+		//Validation: there must be at least one of recurringPrice or fixedPrice
+		if((recurringPrice == null) && fixedPrice == null) {
+			errors.add(new ValidationError(String.format("Phase %s of plan %s has neither a recurring price or a fixed price.",
 					type.toString(), plan.getName()), 
 					catalog.getCatalogURI(), DefaultPlanPhase.class, type.toString()));
 		}
-		return errors;
-
+		//TODO : if there BP is set to NO_BILLING_PERIOD there must be a recurring price
+        return errors;
 	}
 	
 	@Override
@@ -164,7 +171,7 @@ public class DefaultPlanPhase extends ValidatingConfig<StandaloneCatalog> implem
 		return this;
 	}
 
-	protected DefaultPlanPhase setReccuringPrice(DefaultInternationalPrice price) {
+	protected DefaultPlanPhase setRecurringPrice(DefaultInternationalPrice price) {
 		this.recurringPrice = price;
 		return this;
 	}
diff --git a/catalog/src/main/java/com/ning/billing/catalog/DefaultPrice.java b/catalog/src/main/java/com/ning/billing/catalog/DefaultPrice.java
index d9d26a6..9b5f00b 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/DefaultPrice.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/DefaultPrice.java
@@ -35,6 +35,16 @@ public class DefaultPrice extends ValidatingConfig<StandaloneCatalog> implements
 	@XmlElement(required=true,nillable=true)
 	private BigDecimal value;
 
+    public DefaultPrice() {
+        // for serialization support
+    }
+
+    public DefaultPrice(final BigDecimal value, final Currency currency) {
+        // for sanity support
+        this.value = value;
+        this.currency = currency;
+    }
+
 	/* (non-Javadoc)
 	 * @see com.ning.billing.catalog.IPrice#getCurrency()
 	 */
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 48de5d4..aba447d 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/DefaultPriceList.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/DefaultPriceList.java
@@ -23,7 +23,13 @@ import com.ning.billing.util.config.ValidatingConfig;
 import com.ning.billing.util.config.ValidationError;
 import com.ning.billing.util.config.ValidationErrors;
 
-import javax.xml.bind.annotation.*;
+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.XmlElementWrapper;
+import javax.xml.bind.annotation.XmlID;
+import javax.xml.bind.annotation.XmlIDREF;
 
 @XmlAccessorType(XmlAccessType.NONE)
 public class DefaultPriceList extends ValidatingConfig<StandaloneCatalog> implements PriceList  {
@@ -36,8 +42,7 @@ public class DefaultPriceList extends ValidatingConfig<StandaloneCatalog> implem
 	private Boolean retired = false;
 	
 	@XmlElementWrapper(name="plans", required=true)
-	@XmlElement(name="plan", required=true)
-	@XmlIDREF
+	@XmlIDREF @XmlElement(name="plan", required=true)
     private DefaultPlan[] plans;
 	
 	public DefaultPriceList(){}
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 bf8cfd4..ff3868c 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/DefaultProduct.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/DefaultProduct.java
@@ -21,15 +21,20 @@ import com.ning.billing.catalog.api.ProductCategory;
 import com.ning.billing.util.config.ValidatingConfig;
 import com.ning.billing.util.config.ValidationErrors;
 
-import javax.xml.bind.annotation.*;
-
+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.XmlElementWrapper;
+import javax.xml.bind.annotation.XmlID;
+import javax.xml.bind.annotation.XmlIDREF;
 import java.net.URI;
 
 @XmlAccessorType(XmlAccessType.NONE)
 public class DefaultProduct extends ValidatingConfig<StandaloneCatalog> implements Product {
 	private static final DefaultProduct[] EMPTY_PRODUCT_LIST = new DefaultProduct[0];
 	
-	@XmlAttribute (required=true)
+	@XmlAttribute(required=true)
 	@XmlID
     private String name;
 
@@ -42,7 +47,7 @@ public class DefaultProduct extends ValidatingConfig<StandaloneCatalog> implemen
 	@XmlElementWrapper(name="included", required=false)
 	@XmlIDREF @XmlElement(name="addonProduct", required=true)
     private DefaultProduct[] included = EMPTY_PRODUCT_LIST;
-	
+
 	@XmlElementWrapper(name="available", required=false)
 	@XmlIDREF @XmlElement(name="addonProduct", required=true)
     private DefaultProduct[] available = EMPTY_PRODUCT_LIST;
diff --git a/catalog/src/main/java/com/ning/billing/catalog/rules/CaseChange.java b/catalog/src/main/java/com/ning/billing/catalog/rules/CaseChange.java
index f101ef1..963dc2f 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/rules/CaseChange.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/rules/CaseChange.java
@@ -19,7 +19,12 @@ package com.ning.billing.catalog.rules;
 import com.ning.billing.catalog.DefaultPriceList;
 import com.ning.billing.catalog.DefaultProduct;
 import com.ning.billing.catalog.StandaloneCatalog;
-import com.ning.billing.catalog.api.*;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.CatalogApiException;
+import com.ning.billing.catalog.api.PhaseType;
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.catalog.api.PlanSpecifier;
+import com.ning.billing.catalog.api.ProductCategory;
 import com.ning.billing.util.config.ValidatingConfig;
 import com.ning.billing.util.config.ValidationErrors;
 
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 f652347..ef9b125 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/VersionedCatalog.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/VersionedCatalog.java
@@ -66,24 +66,16 @@ public class VersionedCatalog extends ValidatingConfig<StandaloneCatalog> implem
 
 	public VersionedCatalog(Clock clock) {
 		this.clock = clock;
-		StandaloneCatalog baseline = new StandaloneCatalog(new Date(0)); // init with an empty catalog may need to 
-													 // populate some empty pieces here to make validation work
-		try {
-			add(baseline);
-		} catch (CatalogApiException e) {
-			// This should never happen
-			log.error("This error should never happpen", e);
-		} 
 	}
 
 	//
 	// Private methods
 	//
-	private StandaloneCatalog versionForDate(DateTime date) {
+	private StandaloneCatalog versionForDate(DateTime date) throws CatalogApiException {
 		return versions.get(indexOfVersionForDate(date.toDate()));
 	}
 
-	private List<StandaloneCatalog> versionsBeforeDate(Date date) {
+	private List<StandaloneCatalog> versionsBeforeDate(Date date) throws CatalogApiException {
 		List<StandaloneCatalog> result = new ArrayList<StandaloneCatalog>();
 		int index = indexOfVersionForDate(date);
 		for(int i = 0; i <= index; i++) {
@@ -92,14 +84,14 @@ public class VersionedCatalog extends ValidatingConfig<StandaloneCatalog> implem
 		return result;
 	}
 
-	private int indexOfVersionForDate(Date date) {
-		for(int i = 1; i < versions.size(); i++) {
+	private int indexOfVersionForDate(Date date) throws CatalogApiException {
+		for(int i = versions.size() - 1; i >= 0; i--) {
 			StandaloneCatalog c = versions.get(i);
-			if(c.getEffectiveDate().getTime() > date.getTime()) {
-				return i - 1;
+			if(c.getEffectiveDate().getTime() < date.getTime()) {
+				return i;
 			}
 		}
-		return versions.size() - 1;
+		throw new CatalogApiException(ErrorCode.CAT_NO_CATALOG_FOR_GIVEN_DATE, date.toString());
 	}
 	
 	private class PlanRequestWrapper {
@@ -205,17 +197,17 @@ public class VersionedCatalog extends ValidatingConfig<StandaloneCatalog> implem
 	}
 
 	@Override
-	public DefaultProduct[] getProducts(DateTime requestedDate) {
+	public DefaultProduct[] getProducts(DateTime requestedDate) throws CatalogApiException {
 		return versionForDate(requestedDate).getCurrentProducts();
 	}
 
 	@Override
-	public Currency[] getSupportedCurrencies(DateTime requestedDate) {
+	public Currency[] getSupportedCurrencies(DateTime requestedDate) throws CatalogApiException {
 		return versionForDate(requestedDate).getCurrentSupportedCurrencies();
 	}
 
 	@Override
-	public DefaultPlan[] getPlans(DateTime requestedDate) {
+	public DefaultPlan[] getPlans(DateTime requestedDate) throws CatalogApiException {
 		return versionForDate(requestedDate).getCurrentPlans();
 	}
 
@@ -350,22 +342,22 @@ public class VersionedCatalog extends ValidatingConfig<StandaloneCatalog> implem
 	// Static catalog API
 	//
 	@Override
-	public Date getEffectiveDate() {
+	public Date getEffectiveDate() throws CatalogApiException {
 		return versionForDate(clock.getUTCNow()).getEffectiveDate();
 	}
 
 	@Override
-	public Currency[] getCurrentSupportedCurrencies() {
+	public Currency[] getCurrentSupportedCurrencies() throws CatalogApiException {
 		return versionForDate(clock.getUTCNow()).getCurrentSupportedCurrencies();
 	}
 
 	@Override
-	public Product[] getCurrentProducts() {
+	public Product[] getCurrentProducts() throws CatalogApiException {
 		return versionForDate(clock.getUTCNow()).getCurrentProducts() ;
 	}
 
 	@Override
-	public Plan[] getCurrentPlans() {
+	public Plan[] getCurrentPlans() throws CatalogApiException {
 		return versionForDate(clock.getUTCNow()).getCurrentPlans();
 	}
 
diff --git a/catalog/src/test/java/com/ning/billing/catalog/io/TestVersionedCatalogLoader.java b/catalog/src/test/java/com/ning/billing/catalog/io/TestVersionedCatalogLoader.java
index 9708b99..0d5ac66 100644
--- a/catalog/src/test/java/com/ning/billing/catalog/io/TestVersionedCatalogLoader.java
+++ b/catalog/src/test/java/com/ning/billing/catalog/io/TestVersionedCatalogLoader.java
@@ -121,9 +121,8 @@ public class TestVersionedCatalogLoader {
 	@Test(enabled=true)
 	public void testLoad() throws MalformedURLException, IOException, SAXException, InvalidConfigException, JAXBException, TransformerException, URISyntaxException, ServiceException {
 		VersionedCatalog c = loader.load(Resources.getResource("versionedCatalog").toString());
-		assertEquals(4, c.size());
+		assertEquals(3, c.size());
 		Iterator<StandaloneCatalog> it = c.iterator();
-		it.next(); //discard the baseline
 		DateTime dt = new DateTime("2011-01-01T00:00:00+00:00");
 		assertEquals(dt.toDate(),it.next().getEffectiveDate());
 		dt = new DateTime("2011-02-02T00:00:00+00:00");
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 765d4bd..5fc51b8 100644
--- a/catalog/src/test/java/com/ning/billing/catalog/MockCatalog.java
+++ b/catalog/src/test/java/com/ning/billing/catalog/MockCatalog.java
@@ -19,7 +19,11 @@ package com.ning.billing.catalog;
 import com.ning.billing.catalog.api.BillingPeriod;
 import com.ning.billing.catalog.api.PhaseType;
 import com.ning.billing.catalog.api.ProductCategory;
-import com.ning.billing.catalog.rules.*;
+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;
 
 import java.util.Date;
 
@@ -60,7 +64,7 @@ public class MockCatalog extends StandaloneCatalog {
 		DefaultProduct[] products = getCurrentProducts();
 		DefaultPlan[] plans = new DefaultPlan[products.length];
 		for(int i = 0; i < products.length; i++) {
-			DefaultPlanPhase phase = new DefaultPlanPhase().setPhaseType(PhaseType.EVERGREEN).setBillingPeriod(BillingPeriod.MONTHLY).setReccuringPrice(new DefaultInternationalPrice());
+			DefaultPlanPhase phase = new DefaultPlanPhase().setPhaseType(PhaseType.EVERGREEN).setBillingPeriod(BillingPeriod.MONTHLY).setRecurringPrice(new DefaultInternationalPrice());
 			plans[i] = new MockPlan().setName(products[i].getName().toLowerCase() + "-plan").setProduct(products[i]).setFinalPhase(phase);
 		}
 		setPlans(plans);
diff --git a/catalog/src/test/java/com/ning/billing/catalog/MockInternationalPrice.java b/catalog/src/test/java/com/ning/billing/catalog/MockInternationalPrice.java
index 773c58b..cc3a679 100644
--- a/catalog/src/test/java/com/ning/billing/catalog/MockInternationalPrice.java
+++ b/catalog/src/test/java/com/ning/billing/catalog/MockInternationalPrice.java
@@ -19,17 +19,16 @@ package com.ning.billing.catalog;
 import com.ning.billing.catalog.api.Currency;
 
 import java.math.BigDecimal;
-import java.util.Date;
 
 public class MockInternationalPrice extends DefaultInternationalPrice {
-	
-	MockInternationalPrice() {
+
+	public MockInternationalPrice() {
 		setPrices(new DefaultPrice[] {
-			new DefaultPrice().setCurrency(Currency.USD).setValue(new BigDecimal(1))	
+			new DefaultPrice().setCurrency(Currency.USD).setValue(new BigDecimal(1))
 		});
 	}
-	
-	MockInternationalPrice(DefaultPrice... price) {
+
+	public MockInternationalPrice(DefaultPrice... price) {
 		setPrices(price);
 	}
 
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 b9e9afe..53c73fe 100644
--- a/catalog/src/test/java/com/ning/billing/catalog/MockPlan.java
+++ b/catalog/src/test/java/com/ning/billing/catalog/MockPlan.java
@@ -34,6 +34,14 @@ public class MockPlan extends DefaultPlan {
 		setPlansAllowedInBundle(1);
 	}
 
+    public MockPlan(String planName) {
+		setName(planName);
+		setProduct(new MockProduct());
+		setFinalPhase(new MockPlanPhase(this));
+		setInitialPhases(null);
+		setPlansAllowedInBundle(1);
+	}
+
 	public MockPlan(MockPlanPhase mockPlanPhase) {
 		setName("test-plan");
 		setProduct(new MockProduct());
diff --git a/catalog/src/test/java/com/ning/billing/catalog/MockPlanPhase.java b/catalog/src/test/java/com/ning/billing/catalog/MockPlanPhase.java
index fb244eb..d4ae5ff 100644
--- a/catalog/src/test/java/com/ning/billing/catalog/MockPlanPhase.java
+++ b/catalog/src/test/java/com/ning/billing/catalog/MockPlanPhase.java
@@ -18,8 +18,11 @@ package com.ning.billing.catalog;
 
 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.TimeUnit;
 
+import javax.annotation.Nullable;
+
 public class MockPlanPhase extends DefaultPlanPhase {
 
     public MockPlanPhase(
@@ -31,16 +34,34 @@ public class MockPlanPhase extends DefaultPlanPhase {
 		setBillingPeriod(billingPeriod);
 		setPhaseType(type);
 		setDuration(duration);
-		setReccuringPrice(recurringPrice);
+		setRecurringPrice(recurringPrice);
 		setFixedPrice(fixedPrice);
 	}
     
     public MockPlanPhase() {
-		setBillingPeriod(BillingPeriod.MONTHLY);
-		setPhaseType(PhaseType.EVERGREEN);
+        this(new MockInternationalPrice(), null);
+	}
+
+    public MockPlanPhase(@Nullable MockInternationalPrice recurringPrice,
+                         @Nullable MockInternationalPrice fixedPrice) {
+        this(recurringPrice, fixedPrice, BillingPeriod.MONTHLY);
+	}
+
+    public MockPlanPhase(@Nullable MockInternationalPrice recurringPrice,
+                         @Nullable MockInternationalPrice fixedPrice,
+                         BillingPeriod billingPeriod) {
+		this(recurringPrice, fixedPrice, billingPeriod, PhaseType.EVERGREEN);
+	}
+
+    public MockPlanPhase(@Nullable MockInternationalPrice recurringPrice,
+                         @Nullable MockInternationalPrice fixedPrice,
+                         BillingPeriod billingPeriod,
+                         PhaseType phaseType) {
+		setBillingPeriod(billingPeriod);
+		setPhaseType(phaseType);
 		setDuration(new DefaultDuration().setNumber(-1).setUnit(TimeUnit.UNLIMITED));
-		setReccuringPrice(new MockInternationalPrice());
-		setFixedPrice(null);
+		setRecurringPrice(recurringPrice);
+		setFixedPrice(fixedPrice);
 		setPlan(new MockPlan(this));
 	}
 
@@ -48,10 +69,17 @@ public class MockPlanPhase extends DefaultPlanPhase {
 		setBillingPeriod(BillingPeriod.MONTHLY);
 		setPhaseType(PhaseType.EVERGREEN);
 		setDuration(new DefaultDuration().setNumber(-1).setUnit(TimeUnit.UNLIMITED));
-		setReccuringPrice(new MockInternationalPrice());
+		setRecurringPrice(new MockInternationalPrice());
 		setFixedPrice(null);
 		setPlan(mockPlan);
 	}
 
-	
+    public MockPlanPhase(Plan plan, PhaseType phaseType) {
+		setBillingPeriod(BillingPeriod.MONTHLY);
+		setPhaseType(phaseType);
+		setDuration(new DefaultDuration().setNumber(-1).setUnit(TimeUnit.UNLIMITED));
+		setRecurringPrice(new MockInternationalPrice());
+		setFixedPrice(null);
+		setPlan(plan);
+	}
 }
diff --git a/catalog/src/test/java/com/ning/billing/catalog/rules/TestCaseChange.java b/catalog/src/test/java/com/ning/billing/catalog/rules/TestCaseChange.java
index f684503..ef1eb17 100644
--- a/catalog/src/test/java/com/ning/billing/catalog/rules/TestCaseChange.java
+++ b/catalog/src/test/java/com/ning/billing/catalog/rules/TestCaseChange.java
@@ -21,9 +21,14 @@ import com.ning.billing.catalog.DefaultPriceList;
 import com.ning.billing.catalog.DefaultProduct;
 import com.ning.billing.catalog.MockCatalog;
 import com.ning.billing.catalog.StandaloneCatalog;
-import com.ning.billing.catalog.api.*;
-import com.ning.billing.catalog.rules.TestCase.CaseResult;
 
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.CatalogApiException;
+import com.ning.billing.catalog.api.PhaseType;
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.catalog.api.PlanSpecifier;
+import com.ning.billing.catalog.api.PriceListSet;
+import com.ning.billing.catalog.api.ProductCategory;
 import org.testng.Assert;
 import org.testng.annotations.Test;
 
@@ -39,10 +44,10 @@ public class TestCaseChange {
 		private Result result;
 
 		public CaseChangeResult(DefaultProduct from, DefaultProduct to, 
-				ProductCategory fromProductCategory, ProductCategory toProductCategory, 
-				BillingPeriod fromBP, BillingPeriod toBP, 
+				ProductCategory fromProductCategory, ProductCategory toProductCategory,
+				BillingPeriod fromBP, BillingPeriod toBP,
 				DefaultPriceList fromPriceList, DefaultPriceList toPriceList,
-				PhaseType fromType, 
+				PhaseType fromType,
 				Result result) {
 			setFromProduct(from);
 			setToProduct(to);
@@ -63,7 +68,7 @@ public class TestCaseChange {
 		}
 	}
 	@Test(enabled=true)
-	public void testBasic() throws CatalogApiException{
+	public void testBasic() throws CatalogApiException {
 		MockCatalog cat = new MockCatalog();
 
 		DefaultProduct product1 = cat.getCurrentProducts()[0];
@@ -1043,8 +1048,8 @@ public class TestCaseChange {
 				String fromPriceListName, String toPriceListName,
 				PhaseType phaseType, StandaloneCatalog cat){
 	        try{
-	        	cr.getResult(new PlanPhaseSpecifier(fromProductName, fromProductCategory, fromBp, fromPriceListName, phaseType), 
-						new PlanSpecifier(toProductName, toProductCategory, toBp, toPriceListName),cat);	
+	        	cr.getResult(new PlanPhaseSpecifier(fromProductName, fromProductCategory, fromBp, fromPriceListName, phaseType),
+						new PlanSpecifier(toProductName, toProductCategory, toBp, toPriceListName),cat);
 	        	Assert.fail("Expecting an exception");
 	        } catch (CatalogApiException e) {
 	        	Assert.assertEquals(e.getCode(), ErrorCode.CAT_PRICE_LIST_NOT_FOUND.getCode());
diff --git a/catalog/src/test/java/com/ning/billing/catalog/rules/TestCasePhase.java b/catalog/src/test/java/com/ning/billing/catalog/rules/TestCasePhase.java
index 02f7ab5..1b28da7 100644
--- a/catalog/src/test/java/com/ning/billing/catalog/rules/TestCasePhase.java
+++ b/catalog/src/test/java/com/ning/billing/catalog/rules/TestCasePhase.java
@@ -21,7 +21,11 @@ import com.ning.billing.catalog.DefaultPriceList;
 import com.ning.billing.catalog.DefaultProduct;
 import com.ning.billing.catalog.MockCatalog;
 import com.ning.billing.catalog.StandaloneCatalog;
-import com.ning.billing.catalog.api.*;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.CatalogApiException;
+import com.ning.billing.catalog.api.PhaseType;
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.catalog.api.ProductCategory;
 import org.testng.Assert;
 import org.testng.annotations.Test;
 
@@ -195,7 +199,7 @@ public class TestCasePhase {
 	}
 	
 	@Test(enabled=true)
-	public void testOrder() throws CatalogApiException{
+	public void testOrder() throws CatalogApiException {
 		MockCatalog cat = new MockCatalog();
 
 		DefaultProduct product = cat.getCurrentProducts()[0];
diff --git a/catalog/src/test/java/com/ning/billing/catalog/rules/TestPlanRules.java b/catalog/src/test/java/com/ning/billing/catalog/rules/TestPlanRules.java
index 7ac9cc0..e9617ea 100644
--- a/catalog/src/test/java/com/ning/billing/catalog/rules/TestPlanRules.java
+++ b/catalog/src/test/java/com/ning/billing/catalog/rules/TestPlanRules.java
@@ -19,8 +19,17 @@ package com.ning.billing.catalog.rules;
 import com.ning.billing.catalog.DefaultPriceList;
 import com.ning.billing.catalog.DefaultProduct;
 import com.ning.billing.catalog.MockCatalog;
-import com.ning.billing.catalog.api.*;
 
+import com.ning.billing.catalog.api.ActionPolicy;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.CatalogApiException;
+import com.ning.billing.catalog.api.IllegalPlanChange;
+import com.ning.billing.catalog.api.PhaseType;
+import com.ning.billing.catalog.api.PlanAlignmentChange;
+import com.ning.billing.catalog.api.PlanChangeResult;
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.catalog.api.PlanSpecifier;
+import com.ning.billing.catalog.api.PriceListSet;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.testng.Assert;
diff --git a/catalog/src/test/java/com/ning/billing/catalog/TestPlan.java b/catalog/src/test/java/com/ning/billing/catalog/TestPlan.java
index 00dd1b6..e58f71c 100644
--- a/catalog/src/test/java/com/ning/billing/catalog/TestPlan.java
+++ b/catalog/src/test/java/com/ning/billing/catalog/TestPlan.java
@@ -18,17 +18,21 @@ package com.ning.billing.catalog;
 
 import java.util.Date;
 
+import org.joda.time.DateTime;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.testng.Assert;
 import org.testng.annotations.Test;
 
 import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.catalog.api.Duration;
+import com.ning.billing.catalog.api.InternationalPrice;
+import com.ning.billing.catalog.api.PlanPhase;
 import com.ning.billing.util.config.ValidationErrors;
 
 public class TestPlan {
 	private static final Logger log = LoggerFactory.getLogger(TestPlan.class);
-	@Test
+	@Test(groups={"fast"}, enabled = true)
 	public void testDateValidation() {
 
 		StandaloneCatalog c = new MockCatalog();
@@ -40,4 +44,80 @@ public class TestPlan {
 		errors.log(log);
 
 	}
+	
+	private static class MyDuration extends DefaultDuration {
+		final int days;
+		
+		public MyDuration(int days) {
+			this.days = days;
+		}
+		
+		@Override
+		public DateTime addToDateTime(DateTime dateTime) {
+			return dateTime.plusDays(days);
+		}
+	}
+	
+	private static class MyPlanPhase extends MockPlanPhase {
+		Duration duration;
+		boolean recurringPriceIsZero;
+		
+		MyPlanPhase(int duration, boolean recurringPriceIsZero) {
+			this.duration= new MyDuration( duration );
+			this.recurringPriceIsZero = recurringPriceIsZero;
+		}
+		@Override
+		public Duration getDuration(){
+			return duration;
+		}
+		
+		@Override
+		public InternationalPrice getRecurringPrice() {
+			return new MockInternationalPrice() {
+				@Override
+				public boolean isZero() {
+					return recurringPriceIsZero;
+				}
+			};
+		}
+	}
+	
+	@Test(groups={"fast"}, enabled = true)
+	public void testDataCalc() {
+		DefaultPlan p0 =  new MockPlan() {
+			public PlanPhase[] getAllPhases() {
+				return new PlanPhase[]{
+						new MyPlanPhase(10, true),
+						new MyPlanPhase(10, false),
+				};
+			}
+		};
+		
+		DefaultPlan p1 =  new MockPlan() {
+			public PlanPhase[] getAllPhases() {
+				return new PlanPhase[]{
+						new MyPlanPhase(10, true),
+						new MyPlanPhase(10, true),
+						new MyPlanPhase(10, true),
+						new MyPlanPhase(10, true),
+						new MyPlanPhase(10, false),
+						new MyPlanPhase(10, true),
+				};
+			}
+		};
+		
+		DefaultPlan p2 =  new MockPlan() {
+			public PlanPhase[] getAllPhases() {
+				return new PlanPhase[]{
+						new MyPlanPhase(10, false),
+						new MyPlanPhase(10, true),
+				};
+			}
+		};
+		DateTime requestedDate = new DateTime();
+		Assert.assertEquals(p0.dateOfFirstRecurringNonZeroCharge(requestedDate), requestedDate.plusDays(10));
+		Assert.assertEquals(p1.dateOfFirstRecurringNonZeroCharge(requestedDate), requestedDate.plusDays(40));
+		Assert.assertEquals(p2.dateOfFirstRecurringNonZeroCharge(requestedDate), requestedDate.plusDays(0));
+
+	}
 }
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 d778c4b..9b07bfe 100644
--- a/catalog/src/test/java/com/ning/billing/catalog/TestPlanPhase.java
+++ b/catalog/src/test/java/com/ning/billing/catalog/TestPlanPhase.java
@@ -32,17 +32,17 @@ public class TestPlanPhase {
 	public void testValidation() {
 		log.info("Testing Plan Phase Validation");
 		
-		DefaultPlanPhase pp = new MockPlanPhase().setBillCycleDuration(BillingPeriod.MONTHLY).setReccuringPrice(null).setFixedPrice(new DefaultInternationalPrice());
+		DefaultPlanPhase pp = new MockPlanPhase().setBillCycleDuration(BillingPeriod.MONTHLY).setRecurringPrice(null).setFixedPrice(new DefaultInternationalPrice());
 		ValidationErrors errors = pp.validate(new MockCatalog(), new ValidationErrors());
 		errors.log(log);
 		Assert.assertEquals(errors.size(), 1);
 
-		pp = new MockPlanPhase().setBillCycleDuration(BillingPeriod.NO_BILLING_PERIOD).setReccuringPrice(new MockInternationalPrice());
+		pp = new MockPlanPhase().setBillCycleDuration(BillingPeriod.NO_BILLING_PERIOD).setRecurringPrice(new MockInternationalPrice());
 		errors = pp.validate(new MockCatalog(), new ValidationErrors());
 		errors.log(log);
 		Assert.assertEquals(errors.size(), 1);
 
-		pp = new MockPlanPhase().setReccuringPrice(null).setFixedPrice(null).setBillCycleDuration(BillingPeriod.NO_BILLING_PERIOD);
+		pp = new MockPlanPhase().setRecurringPrice(null).setFixedPrice(null).setBillCycleDuration(BillingPeriod.NO_BILLING_PERIOD);
 		errors = pp.validate(new MockCatalog(), new ValidationErrors());
 		errors.log(log);
 		Assert.assertEquals(errors.size(), 1);
diff --git a/catalog/src/test/java/com/ning/billing/catalog/TestVersionedCatalog.java b/catalog/src/test/java/com/ning/billing/catalog/TestVersionedCatalog.java
index ddd623e..fbc99df 100644
--- a/catalog/src/test/java/com/ning/billing/catalog/TestVersionedCatalog.java
+++ b/catalog/src/test/java/com/ning/billing/catalog/TestVersionedCatalog.java
@@ -35,6 +35,7 @@ import org.testng.annotations.Test;
 import org.xml.sax.SAXException;
 
 import com.google.common.io.Resources;
+import com.ning.billing.ErrorCode;
 import com.ning.billing.catalog.api.CatalogApiException;
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.catalog.api.InvalidConfigException;
@@ -53,14 +54,14 @@ public class TestVersionedCatalog {
 		vc = loader.load(Resources.getResource("versionedCatalog").toString());
 	}
 
-	@Test(enabled=true)
+	@Test(groups={"fast"},enabled=true)
 	public void testAddCatalog() throws MalformedURLException, IOException, SAXException, InvalidConfigException, JAXBException, TransformerException, URISyntaxException, ServiceException, CatalogApiException {
 		vc.add(new StandaloneCatalog(new Date()));
-		assertEquals(5, vc.size());
+		assertEquals(4, vc.size());
 	}
 	
 		
-	@Test(enabled=true)
+	@Test(groups={"fast"},enabled=true)
 	public void testFindPlanWithDates() throws Exception {
 		DateTime dt0= new DateTime("2010-01-01T00:00:00+00:00");
 		DateTime dt1 = new DateTime("2011-01-01T00:01:00+00:00");
@@ -97,6 +98,18 @@ public class TestVersionedCatalog {
 		Assert.assertEquals(exSubPlan214.getAllPhases()[1].getRecurringPrice().getPrice(Currency.USD), new BigDecimal("2.0"));
 		Assert.assertEquals(exSubPlan3.getAllPhases()[1].getRecurringPrice().getPrice(Currency.USD), new BigDecimal("2.0"));
 
-		
+	}
+	
+	@Test(groups={"fast"},enabled=true)
+	public void testErrorOnDateTooEarly() {
+		DateTime dt0= new DateTime("1977-01-01T00:00:00+00:00");
+		try {
+			vc.findPlan("foo", dt0);
+			Assert.fail("Date is too early an exception should have been thrown");
+		} catch (CatalogApiException e) {
+			e.printStackTrace();
+			Assert.assertEquals(e.getCode(), ErrorCode.CAT_NO_CATALOG_FOR_GIVEN_DATE.getCode());
+
+		}
 	}
 }

entitlement/pom.xml 13(+12 -1)

diff --git a/entitlement/pom.xml b/entitlement/pom.xml
index f0d0aae..f8a781f 100644
--- a/entitlement/pom.xml
+++ b/entitlement/pom.xml
@@ -13,7 +13,7 @@
     <parent>
         <groupId>com.ning.billing</groupId>
         <artifactId>killbill</artifactId>
-        <version>0.1.2-SNAPSHOT</version>
+        <version>0.1.6-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-entitlement</artifactId>
@@ -48,6 +48,17 @@
         <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-account</artifactId>
             <scope>test</scope>
         </dependency>
         <dependency>
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/billing/DefaultEntitlementBillingApi.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/billing/DefaultEntitlementBillingApi.java
index fd130a9..ee9ec81 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/billing/DefaultEntitlementBillingApi.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/billing/DefaultEntitlementBillingApi.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2010-2011 Ning, Inc.
+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
@@ -16,16 +16,6 @@
 
 package com.ning.billing.entitlement.api.billing;
 
-import java.util.ArrayList;
-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;
@@ -42,18 +32,32 @@ import com.ning.billing.entitlement.api.user.Subscription;
 import com.ning.billing.entitlement.api.user.SubscriptionBundle;
 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.entitlement.api.user.SubscriptionTransition;
 import com.ning.billing.entitlement.engine.dao.EntitlementDao;
+import com.ning.billing.entitlement.engine.dao.SubscriptionSqlDao;
+import org.joda.time.DateTime;
+import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.UUID;
+
 
 public class DefaultEntitlementBillingApi implements EntitlementBillingApi {
-	private Logger log = LoggerFactory.getLogger(DefaultEntitlementBillingApi.class);
-	
+	private static final Logger log = LoggerFactory.getLogger(DefaultEntitlementBillingApi.class);
+
     private final EntitlementDao dao;
     private final AccountUserApi accountApi;
     private final CatalogService catalogService;
 
     @Inject
-    public DefaultEntitlementBillingApi(EntitlementDao dao, AccountUserApi accountApi, CatalogService catalogService) {
+    public DefaultEntitlementBillingApi(final EntitlementDao dao, final AccountUserApi accountApi, final CatalogService catalogService) {
         super();
         this.dao = dao;
         this.accountApi = accountApi;
@@ -62,48 +66,60 @@ public class DefaultEntitlementBillingApi implements EntitlementBillingApi {
 
     @Override
     public SortedSet<BillingEvent> getBillingEventsForAccount(
-            UUID accountId) {
-        
+            final UUID accountId) {
+
         List<SubscriptionBundle> bundles = dao.getSubscriptionBundleForAccount(accountId);
         List<Subscription> subscriptions = new ArrayList<Subscription>();
-        for (SubscriptionBundle bundle: bundles) {
+        for (final SubscriptionBundle bundle: bundles) {
             subscriptions.addAll(dao.getSubscriptions(bundle.getId()));
         }
 
-        SortedSet<BillingEvent> result = new TreeSet<BillingEvent>();        
-        for (Subscription subscription: subscriptions) {
-        	for (SubscriptionTransition transition : subscription.getAllTransitions()) {
+        SortedSet<BillingEvent> result = new TreeSet<BillingEvent>();
+        for (final Subscription subscription: subscriptions) {
+        	for (final SubscriptionTransition transition : subscription.getAllTransitions()) {
         		try {
-        			result.add(new DefaultBillingEvent(transition, subscription, calculateBCD(transition, accountId)));
+                    BillingEvent event = new DefaultBillingEvent(transition, subscription, calculateBCD(transition, accountId));
+        			result.add(event);
         		} catch (CatalogApiException e) {
-        			log.error("Failing to identify catalog components while creating BillingEvent from transition: " + 
+        			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);
         		}
         	}
         }
         return result;
     }
-    
-    private int calculateBCD(SubscriptionTransition transition, UUID accountId) throws CatalogApiException {
+
+    @Override
+    public UUID getAccountIdFromSubscriptionId(final UUID subscriptionId) {
+        return dao.getAccountIdFromSubscriptionId(subscriptionId);
+    }
+
+    private int calculateBCD(final SubscriptionTransition transition, final UUID accountId) throws CatalogApiException {
     	Catalog catalog = catalogService.getFullCatalog();
-    	Plan plan = transition.getNextPlan();
+    	Plan plan =  (transition.getTransitionType() != SubscriptionTransitionType.CANCEL) ?
+    	        transition.getNextPlan() : transition.getPreviousPlan();
     	Product product = plan.getProduct();
-    	PlanPhase phase = transition.getNextPhase();
-    	
+    	PlanPhase phase = (transition.getTransitionType() != SubscriptionTransitionType.CANCEL) ?
+    	        transition.getNextPhase() : transition.getPreviousPhase();
+
     	BillingAlignment alignment = catalog.billingAlignment(
-    			new PlanPhaseSpecifier(product.getName(), 
-    					product.getCategory(), 
-    					phase.getBillingPeriod(), 
-    					transition.getNextPriceList(), 
-    					phase.getPhaseType()), 
+    			new PlanPhaseSpecifier(product.getName(),
+    					product.getCategory(),
+    					phase.getBillingPeriod(),
+    					transition.getNextPriceList(),
+    					phase.getPhaseType()),
     					transition.getRequestedTransitionTime());
     	int result = 0;
-    	Account account = accountApi.getAccountById(accountId);
+
+        Account account = accountApi.getAccountById(accountId);
+
     	switch (alignment) {
-    		case ACCOUNT : 
+    		case ACCOUNT :
     			result = account.getBillCycleDay();
     		break;
-    		case BUNDLE : 
+    		case BUNDLE :
     			SubscriptionBundle bundle = dao.getSubscriptionBundleFromId(transition.getBundleId());
     			//TODO result = bundle.getStartDate().toDateTime(account.getTimeZone()).getDayOfMonth();
     			result = bundle.getStartDate().getDayOfMonth();
@@ -118,20 +134,35 @@ public class DefaultEntitlementBillingApi implements EntitlementBillingApi {
     		throw new CatalogApiException(ErrorCode.CAT_INVALID_BILLING_ALIGNMENT, alignment.toString());
     	}
     	return result;
-    		
+
     }
-    
 
     @Override
-    public void setChargedThroughDate(UUID subscriptionId, DateTime ctd) {
+    public void setChargedThroughDate(final UUID subscriptionId, final DateTime ctd) {
         SubscriptionData subscription = (SubscriptionData) dao.getSubscriptionFromId(subscriptionId);
-        if (subscription == null) {
-            new EntitlementBillingApiException(String.format("Unknown subscription %s", subscriptionId));
-        }
 
         SubscriptionBuilder builder = new SubscriptionBuilder(subscription)
             .setChargedThroughDate(ctd)
             .setPaidThroughDate(subscription.getPaidThroughDate());
+
         dao.updateSubscription(new SubscriptionData(builder));
     }
+
+    @Override
+    public void setChargedThroughDateFromTransaction(final Transmogrifier transactionalDao, final UUID subscriptionId, final DateTime ctd) {
+        SubscriptionSqlDao subscriptionSqlDao = transactionalDao.become(SubscriptionSqlDao.class);
+        SubscriptionData subscription = (SubscriptionData) subscriptionSqlDao.getSubscriptionFromId(subscriptionId.toString());
+
+        if (subscription == null) {
+            log.warn("Subscription not found when setting CTD.");
+        } else {
+            Date paidThroughDate = (subscription.getPaidThroughDate() == null) ? null : subscription.getPaidThroughDate().toDate();
+
+            DateTime chargedThroughDate = subscription.getChargedThroughDate();
+            if (chargedThroughDate == null || chargedThroughDate.isBefore(ctd)) {
+                subscriptionSqlDao.updateSubscription(subscriptionId.toString(), subscription.getActiveVersion(),
+                                                      ctd.toDate(), paidThroughDate);
+            }
+        }
+    }
 }
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 deedb6d..115139a 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
@@ -96,7 +96,6 @@ public class DefaultEntitlementUserApi implements EntitlementUserApi {
 
     @Override
     public Subscription createSubscription(UUID bundleId, PlanPhaseSpecifier spec, DateTime requestedDate) throws EntitlementUserApiException {
-
         try {
             String realPriceList = (spec.getPriceListName() == null) ? PriceListSet.DEFAULT_PRICELIST_NAME : spec.getPriceListName();
             DateTime now = clock.getUTCNow();
@@ -109,7 +108,6 @@ public class DefaultEntitlementUserApi implements EntitlementUserApi {
 
             Plan plan = catalogService.getFullCatalog().findPlan(spec.getProductName(), spec.getBillingPeriod(), realPriceList, requestedDate);
 
-
             PlanPhase phase = plan.getAllPhases()[0];
             if (phase == null) {
                 throw new EntitlementError(String.format("No initial PlanPhase for Product %s, term %s and set %s does not exist in the catalog",
@@ -156,6 +154,7 @@ public class DefaultEntitlementUserApi implements EntitlementUserApi {
         }
     }
 
+
     private void checkAddonCreationRights(SubscriptionData baseSubscription, Plan targetAddOnPlan)
         throws EntitlementUserApiException, CatalogApiException {
 
@@ -173,4 +172,21 @@ public class DefaultEntitlementUserApi implements EntitlementUserApi {
                     targetAddOnPlan.getName(), baseSubscription.getCurrentPlan().getProduct().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;
+	}
 }
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionApiService.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionApiService.java
index 1b95f6b..540f35e 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionApiService.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionApiService.java
@@ -99,10 +99,7 @@ public class SubscriptionApiService {
 
             DateTime now = clock.getUTCNow();
             requestedDate = (requestedDate != null) ? DefaultClock.truncateMs(requestedDate) : now;
-            // STEPH needs to check if requestedDate is before last 'erasable event'?
-            if (requestedDate != null && requestedDate.isAfter(now)) {
-                throw new EntitlementUserApiException(ErrorCode.ENT_INVALID_REQUESTED_DATE, requestedDate.toString());
-            }
+            validateRequestedDateOnChangeOrCancel(subscription, now, requestedDate);
 
             Plan currentPlan = subscription.getCurrentPlan();
             PlanPhaseSpecifier planPhase = new PlanPhaseSpecifier(currentPlan.getProduct().getName(),
@@ -159,6 +156,7 @@ public class SubscriptionApiService {
         subscription.rebuildTransitions(dao.getEventsForSubscription(subscription.getId()), catalogService.getFullCatalog());
     }
 
+
     public void changePlan(SubscriptionData subscription, String productName, BillingPeriod term,
             String priceList, DateTime requestedDate)
         throws EntitlementUserApiException {
@@ -168,10 +166,7 @@ public class SubscriptionApiService {
 
             DateTime now = clock.getUTCNow();
             requestedDate = (requestedDate != null) ? DefaultClock.truncateMs(requestedDate) : now;
-            // STEPH needs to check if requestedDate is before last 'erasable event'?
-            if (requestedDate != null && requestedDate.isAfter(now)) {
-                throw new EntitlementUserApiException(ErrorCode.ENT_INVALID_REQUESTED_DATE, requestedDate.toString());
-            }
+            validateRequestedDateOnChangeOrCancel(subscription, now, requestedDate);
 
             String currentPriceList = subscription.getCurrentPriceList();
 
@@ -206,7 +201,7 @@ public class SubscriptionApiService {
             PriceList newPriceList = planChangeResult.getNewPriceList();
 
             Plan newPlan = catalogService.getFullCatalog().findPlan(productName, term, newPriceList.getName(), requestedDate);
-            DateTime effectiveDate = subscription.getPlanChangeEffectiveDate(policy, now);
+            DateTime effectiveDate = subscription.getPlanChangeEffectiveDate(policy, requestedDate);
 
             TimedPhase currentTimedPhase = planAligner.getCurrentTimedPhaseOnChange(subscription, newPlan, newPriceList.getName(), requestedDate, effectiveDate);
 
@@ -236,4 +231,18 @@ public class SubscriptionApiService {
             throw new EntitlementUserApiException(e);
         }
     }
+
+    private void validateRequestedDateOnChangeOrCancel(SubscriptionData subscription, DateTime now, DateTime requestedDate)
+        throws EntitlementUserApiException {
+
+        if (requestedDate.isAfter(now) ) {
+            throw new EntitlementUserApiException(ErrorCode.ENT_INVALID_REQUESTED_FUTURE_DATE, requestedDate.toString());
+        }
+
+        SubscriptionTransition previousTransition = subscription.getPreviousTransition();
+        if (previousTransition.getEffectiveTransitionTime().isAfter(requestedDate)) {
+            throw new EntitlementUserApiException(ErrorCode.ENT_INVALID_REQUESTED_DATE,
+                    requestedDate.toString(), previousTransition.getEffectiveTransitionTime());
+        }
+    }
 }
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 0cee49d..20e4663 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
@@ -31,7 +31,12 @@ import org.joda.time.DateTime;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.UUID;
 
 public class SubscriptionData implements Subscription {
 
@@ -100,29 +105,29 @@ public class SubscriptionData implements Subscription {
 
     @Override
     public SubscriptionState getState() {
-        return (transitions == null) ? null : getLatestTranstion().getNextState();
+        return (getPreviousTransition() == null) ? null : getPreviousTransition().getNextState();
     }
 
     @Override
     public PlanPhase getCurrentPhase() {
-        return (transitions == null) ? null : getLatestTranstion().getNextPhase();
+        return (getPreviousTransition() == null) ? null : getPreviousTransition().getNextPhase();
     }
 
 
     @Override
     public Plan getCurrentPlan() {
-        return (transitions == null) ? null : getLatestTranstion().getNextPlan();
+        return (getPreviousTransition() == null) ? null : getPreviousTransition().getNextPlan();
     }
 
     @Override
     public String getCurrentPriceList() {
-        return (transitions == null) ? null : getLatestTranstion().getNextPriceList();
+        return (getPreviousTransition() == null) ? null : getPreviousTransition().getNextPriceList();
     }
 
 
     @Override
     public DateTime getEndDate() {
-        SubscriptionTransition latestTransition = getLatestTranstion();
+        SubscriptionTransition latestTransition = getPreviousTransition();
         if (latestTransition.getNextState() == SubscriptionState.CANCELLED) {
             return latestTransition.getEffectiveTransitionTime();
         }
@@ -184,6 +189,7 @@ public class SubscriptionData implements Subscription {
         return result;
     }
 
+    @Override
     public SubscriptionTransition getPendingTransition() {
         if (transitions == null) {
             return null;
@@ -196,7 +202,7 @@ public class SubscriptionData implements Subscription {
         return null;
     }
 
-    public SubscriptionTransition getLatestTranstion() {
+    public SubscriptionTransition getPreviousTransition() {
 
         if (transitions == null) {
             return null;
@@ -236,10 +242,12 @@ public class SubscriptionData implements Subscription {
         return bundleStartDate;
     }
 
+    @Override
     public DateTime getChargedThroughDate() {
         return chargedThroughDate;
     }
 
+    @Override
     public DateTime getPaidThroughDate() {
         return paidThroughDate;
     }
@@ -342,7 +350,7 @@ public class SubscriptionData implements Subscription {
         transitions = new LinkedList<SubscriptionTransitionData>();
         Plan previousPlan = null;
         PlanPhase previousPhase = null;
-        
+
         for (final EntitlementEvent cur : events) {
 
             if (!cur.isActive() || cur.getActiveVersion() < activeVersion) {
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 cb37037..c7294b4 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
@@ -17,16 +17,18 @@
 package com.ning.billing.entitlement.engine.core;
 
 
+
 import java.util.Iterator;
 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.catalog.api.CatalogService;
+
 import com.ning.billing.catalog.api.Plan;
 import com.ning.billing.catalog.api.ProductCategory;
 import com.ning.billing.config.EntitlementConfig;
@@ -37,8 +39,6 @@ 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.test.DefaultEntitlementTestApi;
-import com.ning.billing.entitlement.api.test.EntitlementTestApi;
 import com.ning.billing.entitlement.api.user.DefaultEntitlementUserApi;
 import com.ning.billing.entitlement.api.user.EntitlementUserApi;
 import com.ning.billing.entitlement.api.user.Subscription;
@@ -57,12 +57,12 @@ 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.eventbus.EventBus;
-import com.ning.billing.util.eventbus.EventBus.EventBusException;
+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.notificationq.NotificationQueue;
 import com.ning.billing.util.notificationq.NotificationQueueService;
-import com.ning.billing.util.notificationq.NotificationQueueService.NotficationQueueAlreadyExists;
+import com.ning.billing.util.notificationq.NotificationQueueService.NotificationQueueAlreadyExists;
 import com.ning.billing.util.notificationq.NotificationQueueService.NotificationQueueHandler;
 
 public class Engine implements EventListener, EntitlementService {
@@ -70,10 +70,6 @@ public class Engine implements EventListener, EntitlementService {
     public static final String NOTIFICATION_QUEUE_NAME = "subscription-events";
     public static final String ENTITLEMENT_SERVICE_NAME = "entitlement-service";
 
-    private final long MAX_NOTIFICATION_THREAD_WAIT_MS = 10000; // 10 secs
-    private final long NOTIFICATION_THREAD_WAIT_INCREMENT_MS = 1000; // 1 sec
-    private final long NANO_TO_MS = (1000 * 1000);
-
     private final static Logger log = LoggerFactory.getLogger(Engine.class);
 
     private final Clock clock;
@@ -81,29 +77,26 @@ public class Engine implements EventListener, EntitlementService {
     private final PlanAligner planAligner;
     private final EntitlementUserApi userApi;
     private final EntitlementBillingApi billingApi;
-    private final EntitlementTestApi testApi;
     private final EntitlementMigrationApi migrationApi;
     private final AddonUtils addonUtils;
-    private final EventBus eventBus;
+    private final Bus eventBus;
+
     private final EntitlementConfig config;
     private final NotificationQueueService notificationQueueService;
 
-    private boolean startedNotificationThread;
-    private boolean stoppedNotificationThread;
     private NotificationQueue subscritionEventQueue;
 
     @Inject
     public Engine(Clock clock, EntitlementDao dao, PlanAligner planAligner,
             EntitlementConfig config, DefaultEntitlementUserApi userApi,
-            DefaultEntitlementBillingApi billingApi, DefaultEntitlementTestApi testApi,
-            DefaultEntitlementMigrationApi migrationApi, AddonUtils addonUtils, EventBus eventBus,
+            DefaultEntitlementBillingApi billingApi,
+            DefaultEntitlementMigrationApi migrationApi, AddonUtils addonUtils, Bus eventBus,
             NotificationQueueService notificationQueueService) {
         super();
         this.clock = clock;
         this.dao = dao;
         this.planAligner = planAligner;
         this.userApi = userApi;
-        this.testApi = testApi;
         this.billingApi = billingApi;
         this.migrationApi = migrationApi;
         this.addonUtils = addonUtils;
@@ -121,13 +114,11 @@ public class Engine implements EventListener, EntitlementService {
     public void initialize() {
 
         try {
-            this.stoppedNotificationThread = false;
-            this.startedNotificationThread = false;
             subscritionEventQueue = notificationQueueService.createNotificationQueue(ENTITLEMENT_SERVICE_NAME,
                     NOTIFICATION_QUEUE_NAME,
                     new NotificationQueueHandler() {
                 @Override
-                public void handleReadyNotification(String notificationKey) {
+                public void handleReadyNotification(String notificationKey, DateTime eventDateTime) {
                     EntitlementEvent event = dao.getEventById(UUID.fromString(notificationKey));
                     if (event == null) {
                         log.warn("Failed to extract event for notification key {}", notificationKey);
@@ -135,21 +126,6 @@ public class Engine implements EventListener, EntitlementService {
                         processEventReady(event);
                     }
                 }
-
-                @Override
-                public void completedQueueStop() {
-                    synchronized (this) {
-                        stoppedNotificationThread = true;
-                        this.notifyAll();
-                    }
-                }
-                @Override
-                public void completedQueueStart() {
-                    synchronized (this) {
-                        startedNotificationThread = true;
-                        this.notifyAll();
-                    }
-                }
             },
             new NotificationConfig() {
                 @Override
@@ -169,7 +145,7 @@ public class Engine implements EventListener, EntitlementService {
                     return config.getDaoMaxReadyEvents();
                 }
             });
-        } catch (NotficationQueueAlreadyExists e) {
+        } catch (NotificationQueueAlreadyExists e) {
             throw new RuntimeException(e);
         }
     }
@@ -177,16 +153,13 @@ public class Engine implements EventListener, EntitlementService {
     @LifecycleHandlerType(LifecycleLevel.START_SERVICE)
     public void start() {
         subscritionEventQueue.startQueue();
-        waitForNotificationStartCompletion();
     }
 
     @LifecycleHandlerType(LifecycleLevel.STOP_SERVICE)
     public void stop() {
         if (subscritionEventQueue != null) {
             subscritionEventQueue.stopQueue();
-            waitForNotificationStopCompletion();
-        }
-        startedNotificationThread = false;
+         }
     }
 
     @Override
@@ -201,11 +174,6 @@ public class Engine implements EventListener, EntitlementService {
 
 
     @Override
-    public EntitlementTestApi getTestApi() {
-        return testApi;
-    }
-
-    @Override
     public EntitlementMigrationApi getMigrationApi() {
         return migrationApi;
     }
@@ -237,42 +205,6 @@ public class Engine implements EventListener, EntitlementService {
         }
     }
 
-    private void waitForNotificationStartCompletion() {
-        waitForNotificationEventCompletion(true);
-    }
-
-    private void waitForNotificationStopCompletion() {
-        waitForNotificationEventCompletion(false);
-    }
-
-    private void waitForNotificationEventCompletion(boolean startEvent) {
-
-        long ini = System.nanoTime();
-        synchronized(this) {
-            do {
-                if ((startEvent ? startedNotificationThread : stoppedNotificationThread)) {
-                    break;
-                }
-                try {
-                    this.wait(NOTIFICATION_THREAD_WAIT_INCREMENT_MS);
-                } catch (InterruptedException e ) {
-                    Thread.currentThread().interrupt();
-                    throw new EntitlementError(e);
-                }
-            } while (!(startEvent ? startedNotificationThread : stoppedNotificationThread) &&
-                    (System.nanoTime() - ini) / NANO_TO_MS < MAX_NOTIFICATION_THREAD_WAIT_MS);
-
-            if (!(startEvent ? startedNotificationThread : stoppedNotificationThread)) {
-                log.error("Could not {} notification thread in {} msec !!!",
-                        (startEvent ? "start" : "stop"),
-                        MAX_NOTIFICATION_THREAD_WAIT_MS);
-                throw new EntitlementError("Failed to start service!!");
-            }
-            log.info("Notification thread has been {} in {} ms",
-                    (startEvent ? "started" : "stopped"),
-                    (System.nanoTime() - ini) / NANO_TO_MS);
-        }
-    }
 
     private void onPhaseEvent(SubscriptionData subscription) {
         try {
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 ea62b84..c9ddf90 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
@@ -16,7 +16,9 @@
 
 package com.ning.billing.entitlement.engine.dao;
 
+import com.ning.billing.entitlement.api.billing.EntitlementBillingApiException;
 import com.ning.billing.entitlement.api.migration.AccountMigrationData;
+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.SubscriptionBundleData;
@@ -40,6 +42,8 @@ public interface EntitlementDao {
 
     public Subscription getSubscriptionFromId(UUID subscriptionId);
 
+    // Account retrieval
+    public UUID getAccountIdFromSubscriptionId(UUID subscriptionId);
 
     // Subscription retrieval
     public Subscription getBaseSubscription(UUID bundleId);
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EntitlementSqlDao.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EntitlementSqlDao.java
index df3ee92..062a0d5 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EntitlementSqlDao.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EntitlementSqlDao.java
@@ -21,9 +21,18 @@ import java.util.Collections;
 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.Transaction;
+import org.skife.jdbi.v2.TransactionStatus;
+import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
+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.ErrorCode;
 import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.entitlement.api.billing.EntitlementBillingApiException;
 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;
@@ -44,14 +53,7 @@ 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;
-
-import org.joda.time.DateTime;
-import org.skife.jdbi.v2.DBI;
-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 sun.jkernel.Bundle;
 
 
 public class EntitlementSqlDao implements EntitlementDao {
@@ -66,7 +68,8 @@ public class EntitlementSqlDao implements EntitlementDao {
     private final NotificationQueueService notificationQueueService;
 
     @Inject
-    public EntitlementSqlDao(DBI dbi, Clock clock, SubscriptionFactory factory, NotificationQueueService notificationQueueService) {
+    public EntitlementSqlDao(final IDBI dbi, final Clock clock, final SubscriptionFactory factory,
+                             final NotificationQueueService notificationQueueService) {
         this.clock = clock;
         this.factory = factory;
         this.subscriptionsDao = dbi.onDemand(SubscriptionSqlDao.class);
@@ -76,18 +79,18 @@ public class EntitlementSqlDao implements EntitlementDao {
     }
 
     @Override
-    public SubscriptionBundle getSubscriptionBundleFromKey(String bundleKey) {
+    public SubscriptionBundle getSubscriptionBundleFromKey(final String bundleKey) {
         return bundlesDao.getBundleFromKey(bundleKey);
     }
 
     @Override
     public List<SubscriptionBundle> getSubscriptionBundleForAccount(
-            UUID accountId) {
+            final UUID accountId) {
         return bundlesDao.getBundleFromAccount(accountId.toString());
     }
 
     @Override
-    public SubscriptionBundle getSubscriptionBundleFromId(UUID bundleId) {
+    public SubscriptionBundle getSubscriptionBundleFromId(final UUID bundleId) {
         return bundlesDao.getBundleFromId(bundleId.toString());
     }
 
@@ -103,11 +106,34 @@ public class EntitlementSqlDao implements EntitlementDao {
     }
 
     @Override
-    public Subscription getSubscriptionFromId(UUID subscriptionId) {
+    public Subscription getSubscriptionFromId(final UUID subscriptionId) {
         return buildSubscription(subscriptionsDao.getSubscriptionFromId(subscriptionId.toString()));
     }
 
     @Override
+    public UUID getAccountIdFromSubscriptionId(final UUID subscriptionId) {
+        Subscription subscription = subscriptionsDao.getSubscriptionFromId(subscriptionId.toString());
+        if (subscription == null) {
+            log.error(String.format(ErrorCode.ENT_INVALID_SUBSCRIPTION_ID.getFormat(), subscriptionId.toString()));
+            return null;
+        }
+
+        UUID bundleId = subscription.getBundleId();
+        if (bundleId == null) {
+            log.error(String.format(ErrorCode.ENT_GET_NO_BUNDLE_FOR_SUBSCRIPTION.getFormat(), subscriptionId.toString()));
+            return null;
+        }
+
+        SubscriptionBundle bundle = bundlesDao.getBundleFromId(bundleId.toString());
+        if (bundle == null) {
+            log.error(String.format(ErrorCode.ENT_GET_INVALID_BUNDLE_ID.getFormat(), bundleId.toString()));
+            return null;
+        }
+
+        return bundle.getAccountId();
+    }
+
+    @Override
     public Subscription getBaseSubscription(final UUID bundleId) {
 
         List<Subscription> subscriptions = subscriptionsDao.getSubscriptionsFromBundleId(bundleId.toString());
@@ -194,9 +220,9 @@ public class EntitlementSqlDao implements EntitlementDao {
 
                 dao.insertSubscription(subscription);
                 // STEPH batch as well
-                EventSqlDao eventsDaoFromSameTranscation = dao.become(EventSqlDao.class);
+                EventSqlDao eventsDaoFromSameTransaction = dao.become(EventSqlDao.class);
                 for (final EntitlementEvent cur : initialEvents) {
-                    eventsDaoFromSameTranscation.insertEvent(cur);
+                    eventsDaoFromSameTransaction.insertEvent(cur);
                     recordFutureNotificationFromTransaction(dao,
                             cur.getEffectiveDate(),
                             new NotificationKey() {
@@ -418,9 +444,9 @@ public class EntitlementSqlDao implements EntitlementDao {
 
     private void recordFutureNotificationFromTransaction(final Transmogrifier transactionalDao, final DateTime effectiveDate, final NotificationKey notificationKey) {
         try {
-            NotificationQueue subscritionEventQueue = notificationQueueService.getNotificationQueue(Engine.ENTITLEMENT_SERVICE_NAME,
+            NotificationQueue subscriptionEventQueue = notificationQueueService.getNotificationQueue(Engine.ENTITLEMENT_SERVICE_NAME,
                 Engine.NOTIFICATION_QUEUE_NAME);
-            subscritionEventQueue.recordFutureNotificationFromTransaction(transactionalDao, effectiveDate, notificationKey);
+            subscriptionEventQueue.recordFutureNotificationFromTransaction(transactionalDao, effectiveDate, notificationKey);
         } catch (NoSuchNotificationQueue e) {
             throw new RuntimeException(e);
         }
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/glue/EntitlementModule.java b/entitlement/src/main/java/com/ning/billing/entitlement/glue/EntitlementModule.java
index 0135ebf..704b765 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/glue/EntitlementModule.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/glue/EntitlementModule.java
@@ -16,6 +16,8 @@
 
 package com.ning.billing.entitlement.glue;
 
+import org.skife.config.ConfigurationObjectFactory;
+
 import com.google.inject.AbstractModule;
 import com.ning.billing.config.EntitlementConfig;
 import com.ning.billing.entitlement.alignment.MigrationPlanAligner;
@@ -25,8 +27,6 @@ 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.test.DefaultEntitlementTestApi;
-import com.ning.billing.entitlement.api.test.EntitlementTestApi;
 import com.ning.billing.entitlement.api.user.DefaultEntitlementUserApi;
 import com.ning.billing.entitlement.api.user.EntitlementUserApi;
 import com.ning.billing.entitlement.api.user.SubscriptionApiService;
@@ -34,24 +34,18 @@ import com.ning.billing.entitlement.engine.addon.AddonUtils;
 import com.ning.billing.entitlement.engine.core.Engine;
 import com.ning.billing.entitlement.engine.dao.EntitlementDao;
 import com.ning.billing.entitlement.engine.dao.EntitlementSqlDao;
-import com.ning.billing.util.clock.Clock;
-import com.ning.billing.util.clock.DefaultClock;
-import org.skife.config.ConfigurationObjectFactory;
 
 
 
 public class EntitlementModule extends AbstractModule {
 
 
-    protected void installClock() {
-        bind(Clock.class).to(DefaultClock.class).asEagerSingleton();
-    }
-
     protected void installConfig() {
         final EntitlementConfig config = new ConfigurationObjectFactory(System.getProperties()).build(EntitlementConfig.class);
         bind(EntitlementConfig.class).toInstance(config);
     }
 
+
     protected void installEntitlementDao() {
         bind(EntitlementDao.class).to(EntitlementSqlDao.class).asEagerSingleton();
     }
@@ -63,7 +57,6 @@ public class EntitlementModule extends AbstractModule {
         bind(PlanAligner.class).asEagerSingleton();
         bind(AddonUtils.class).asEagerSingleton();
         bind(MigrationPlanAligner.class).asEagerSingleton();
-        bind(EntitlementTestApi.class).to(DefaultEntitlementTestApi.class).asEagerSingleton();
         bind(EntitlementUserApi.class).to(DefaultEntitlementUserApi.class).asEagerSingleton();
         bind(EntitlementBillingApi.class).to(DefaultEntitlementBillingApi.class).asEagerSingleton();
         bind(EntitlementMigrationApi.class).to(DefaultEntitlementMigrationApi.class).asEagerSingleton();
@@ -72,7 +65,6 @@ public class EntitlementModule extends AbstractModule {
     @Override
     protected void configure() {
         installConfig();
-        installClock();
         installEntitlementDao();
         installEntitlementCore();
     }
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 55ad7f4..dfdc746 100644
--- a/entitlement/src/main/resources/com/ning/billing/entitlement/ddl.sql
+++ b/entitlement/src/main/resources/com/ning/billing/entitlement/ddl.sql
@@ -1,5 +1,5 @@
-DROP TABLE IF EXISTS events;
-CREATE TABLE 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,
     event_type varchar(9) NOT NULL,
diff --git a/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/EventSqlDao.sql.stg b/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/EventSqlDao.sql.stg
index 704e2c7..10f565d 100644
--- a/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/EventSqlDao.sql.stg
+++ b/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/EventSqlDao.sql.stg
@@ -15,14 +15,14 @@ getEventById(event_id) ::= <<
       , plist_name
       , current_version
       , is_active  
-  from events
+  from entitlement_events
   where
       event_id = :event_id
   ;
 >>
 
 insertEvent() ::= <<
-    insert into events (
+    insert into entitlement_events (
       event_id
       , event_type
       , user_type
@@ -54,14 +54,14 @@ insertEvent() ::= <<
 >>
 
 removeEvents(subscription_id) ::= <<
-    delete from events
+    delete from entitlement_events
       where
     subscription_id = :subscription_id
     ;
 >>
 
 unactiveEvent(event_id, now) ::= <<
-    update events
+    update entitlement_events
     set
       is_active = 0
       , updated_dt = :now
@@ -71,7 +71,7 @@ unactiveEvent(event_id, now) ::= <<
 >>
 
 reactiveEvent(event_id, now) ::= <<
-    update events
+    update entitlement_events
     set
       is_active = 1
       , updated_dt = :now
@@ -95,7 +95,7 @@ getFutureActiveEventForSubscription(subscription_id, now) ::= <<
       , plist_name
       , current_version
       , is_active
-    from events
+    from entitlement_events
     where
       subscription_id = :subscription_id
       and is_active = 1
@@ -123,7 +123,7 @@ getEventsForSubscription(subscription_id) ::= <<
       , plist_name
       , current_version
       , is_active
-    from events
+    from entitlement_events
     where
       subscription_id = :subscription_id
     order by
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/ApiTestListener.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/ApiTestListener.java
index 6bee471..39f6c48 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/ApiTestListener.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/ApiTestListener.java
@@ -19,7 +19,7 @@ package com.ning.billing.entitlement.api;
 import com.google.common.base.Joiner;
 import com.google.common.eventbus.Subscribe;
 import com.ning.billing.entitlement.api.user.SubscriptionTransition;
-import com.ning.billing.util.eventbus.EventBus;
+import com.ning.billing.util.bus.Bus;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -45,7 +45,7 @@ public class ApiTestListener {
         PHASE
     }
 
-    public ApiTestListener(EventBus eventBus) {
+    public ApiTestListener(Bus eventBus) {
         this.nextExpectedEvent = new Stack<NextEvent>();
         this.completed = false;
     }
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/BrainDeadAccount.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/BrainDeadAccount.java
index cc08699..73b7369 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/BrainDeadAccount.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/BrainDeadAccount.java
@@ -16,17 +16,17 @@
 
 package com.ning.billing.entitlement.api.billing;
 
-import java.math.BigDecimal;
 import java.util.List;
 import java.util.UUID;
 
 import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
 
 import com.ning.billing.account.api.Account;
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.util.customfield.CustomField;
 import com.ning.billing.util.tag.Tag;
-import com.ning.billing.util.tag.TagDescription;
+import com.ning.billing.util.tag.TagDefinition;
 
 public class BrainDeadAccount implements Account {
 
@@ -69,7 +69,52 @@ public class BrainDeadAccount implements Account {
 		throw new UnsupportedOperationException();
 	}
 
-	@Override
+    @Override
+    public DateTimeZone getTimeZone() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getLocale() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getAddress1() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getAddress2() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getCompanyName() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getCity() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getStateOrProvince() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getPostalCode() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getCountry() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
 	public String getFieldValue(String fieldName) {
 		throw new UnsupportedOperationException();
 	}
@@ -109,13 +154,12 @@ public class BrainDeadAccount implements Account {
 		throw new UnsupportedOperationException();
 	}
 
-	@Override
-	public void addTag(TagDescription description, String addedBy,
-			DateTime dateAdded) {
-		throw new UnsupportedOperationException();
-	}
+    @Override
+    public void addTag(final TagDefinition definition, final String addedBy, final DateTime dateAdded) {
+        throw new UnsupportedOperationException();
+    }
 
-	@Override
+    @Override
 	public void addTags(List<Tag> tags) {
 		throw new UnsupportedOperationException();
 	}
@@ -126,7 +170,7 @@ public class BrainDeadAccount implements Account {
 	}
 
 	@Override
-	public void removeTag(TagDescription description) {
+	public void removeTag(TagDefinition definition) {
 		throw new UnsupportedOperationException();
 	}
 
@@ -139,16 +183,20 @@ public class BrainDeadAccount implements Account {
 	public boolean processPayment() {
 		throw new UnsupportedOperationException();
 	}
-
-	@Override
-	public BigDecimal getBalance() {
-		throw new UnsupportedOperationException();
-	}
-
 	@Override
 	public void addFields(List<CustomField> fields) {
 		throw new UnsupportedOperationException();
-		
+
 	}
 
+    @Override
+    public DateTime getCreatedDate() {
+        return new DateTime(DateTimeZone.UTC);
+    }
+
+    @Override
+    public DateTime getUpdatedDate() {
+        return new DateTime(DateTimeZone.UTC);
+    }
+
 }
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/BrainDeadAccountUserApi.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/BrainDeadAccountUserApi.java
index 94f0ea7..4602fe6 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/BrainDeadAccountUserApi.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/BrainDeadAccountUserApi.java
@@ -23,6 +23,7 @@ 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.MigrationAccountData;
 import com.ning.billing.util.customfield.CustomField;
 import com.ning.billing.util.tag.Tag;
 
@@ -60,4 +61,23 @@ public class BrainDeadAccountUserApi implements AccountUserApi {
 		throw new UnsupportedOperationException();
 	}
 
+	@Override
+	public void deleteAccountByKey(String externalKey)
+			throws AccountApiException {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public Account migrateAccount(MigrationAccountData data,
+			List<CustomField> fields, List<Tag> tags)
+			throws AccountApiException {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public void updateAccount(String key, AccountData accountData)
+			throws AccountApiException {
+		throw new UnsupportedOperationException();
+	}
+
 }
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/BrainDeadMockEntitlementDao.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/BrainDeadMockEntitlementDao.java
index 94fd234..4d57dac 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/BrainDeadMockEntitlementDao.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/BrainDeadMockEntitlementDao.java
@@ -16,11 +16,10 @@
 
 package com.ning.billing.entitlement.api.billing;
 
-import java.util.Collection;
 import java.util.List;
 import java.util.UUID;
-
 import com.ning.billing.entitlement.api.migration.AccountMigrationData;
+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.SubscriptionBundleData;
@@ -32,110 +31,112 @@ class BrainDeadMockEntitlementDao implements EntitlementDao {
 
 	@Override
 	public List<SubscriptionBundle> getSubscriptionBundleForAccount(
-			UUID accountId) {
+			final UUID accountId) {
 		throw new UnsupportedOperationException();
 	}
 
 	@Override
-	public SubscriptionBundle getSubscriptionBundleFromKey(String bundleKey) {
+	public SubscriptionBundle getSubscriptionBundleFromKey(final String bundleKey) {
 		throw new UnsupportedOperationException();
 	}
 
 	@Override
-	public SubscriptionBundle getSubscriptionBundleFromId(UUID bundleId) {
+	public SubscriptionBundle getSubscriptionBundleFromId(final UUID bundleId) {
 		throw new UnsupportedOperationException();
 	}
 
 	@Override
 	public SubscriptionBundle createSubscriptionBundle(
-			SubscriptionBundleData bundle) {
+			final SubscriptionBundleData bundle) {
 		throw new UnsupportedOperationException();
 	}
 
 	@Override
-	public Subscription getSubscriptionFromId(UUID subscriptionId) {
+	public Subscription getSubscriptionFromId(final UUID subscriptionId) {
 		throw new UnsupportedOperationException();
 
 	}
 
-	@Override
-	public Subscription getBaseSubscription(UUID bundleId) {
-		throw new UnsupportedOperationException();
+    @Override
+    public UUID getAccountIdFromSubscriptionId(final UUID subscriptionId) {
+        throw new UnsupportedOperationException();
+    }
 
+    @Override
+	public Subscription getBaseSubscription(final UUID bundleId) {
+		throw new UnsupportedOperationException();
 	}
 
 	@Override
-	public List<Subscription> getSubscriptions(UUID bundleId) {
+	public List<Subscription> getSubscriptions(final UUID bundleId) {
 		throw new UnsupportedOperationException();
-
 	}
 
 	@Override
-	public List<Subscription> getSubscriptionsForKey(String bundleKey) {
+	public List<Subscription> getSubscriptionsForKey(final String bundleKey) {
 		throw new UnsupportedOperationException();
 	}
 
 	@Override
-	public void updateSubscription(SubscriptionData subscription) {
+	public void updateSubscription(final SubscriptionData subscription) {
 		throw new UnsupportedOperationException();
 	}
 
 	@Override
-	public void createNextPhaseEvent(UUID subscriptionId,
-			EntitlementEvent nextPhase) {
+	public void createNextPhaseEvent(final UUID subscriptionId,
+			final EntitlementEvent nextPhase) {
 		throw new UnsupportedOperationException();
 	}
 
 	@Override
 	public List<EntitlementEvent> getEventsForSubscription(
-			UUID subscriptionId) {
+			final UUID subscriptionId) {
 		throw new UnsupportedOperationException();
 	}
 
 	@Override
 	public List<EntitlementEvent> getPendingEventsForSubscription(
-			UUID subscriptionId) {
+			final UUID subscriptionId) {
 		throw new UnsupportedOperationException();
 	}
 
-
 	@Override
-	public void createSubscription(SubscriptionData subscription,
-			List<EntitlementEvent> initialEvents) {
+	public void createSubscription(final SubscriptionData subscription,
+			final List<EntitlementEvent> initialEvents) {
 		throw new UnsupportedOperationException();
 	}
 
 	@Override
-	public void cancelSubscription(UUID subscriptionId,
-			EntitlementEvent cancelEvent) {
+	public void cancelSubscription(final UUID subscriptionId,
+			final EntitlementEvent cancelEvent) {
 		throw new UnsupportedOperationException();
-
 	}
 
 	@Override
-	public void uncancelSubscription(UUID subscriptionId,
-			List<EntitlementEvent> uncancelEvents) {
+	public void uncancelSubscription(final UUID subscriptionId,
+			final List<EntitlementEvent> uncancelEvents) {
 		throw new UnsupportedOperationException();
 	}
 
 	@Override
-	public void changePlan(UUID subscriptionId,
-			List<EntitlementEvent> changeEvents) {
+	public void changePlan(final UUID subscriptionId,
+			final List<EntitlementEvent> changeEvents) {
 		throw new UnsupportedOperationException();
 	}
 
 	@Override
-	public void migrate(UUID acountId, AccountMigrationData data) {
+	public void migrate(final UUID acountId, final AccountMigrationData data) {
 		throw new UnsupportedOperationException();
 	}
 
 	@Override
-	public void undoMigration(UUID accountId) {
+	public void undoMigration(final UUID accountId) {
         throw new UnsupportedOperationException();
 	}
 
+
     @Override
-    public EntitlementEvent getEventById(UUID eventId) {
+    public EntitlementEvent getEventById(final UUID eventId) {
         throw new UnsupportedOperationException();
     }
 }
\ No newline at end of file
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/BrainDeadSubscription.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/BrainDeadSubscription.java
new file mode 100644
index 0000000..98cc376
--- /dev/null
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/BrainDeadSubscription.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * 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.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.entitlement.api.user.EntitlementUserApiException;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.api.user.SubscriptionTransition;
+
+public class BrainDeadSubscription implements Subscription {
+
+	@Override
+	public void cancel(DateTime requestedDate, boolean eot)
+			throws EntitlementUserApiException {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public void uncancel() throws EntitlementUserApiException {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public void changePlan(String productName, BillingPeriod term,
+			String planSet, DateTime requestedDate)
+			throws EntitlementUserApiException {
+		throw new UnsupportedOperationException();
+
+
+	}
+
+	@Override
+	public void pause() throws EntitlementUserApiException {
+		throw new UnsupportedOperationException();
+
+
+	}
+
+	@Override
+	public void resume() throws EntitlementUserApiException {
+		throw new UnsupportedOperationException();
+
+	}
+
+	@Override
+	public UUID getId() {
+		throw new UnsupportedOperationException();
+
+	}
+
+	@Override
+	public UUID getBundleId() {
+		throw new UnsupportedOperationException();
+
+	}
+
+	@Override
+	public SubscriptionState getState() {
+		throw new UnsupportedOperationException();
+
+	}
+
+	@Override
+	public DateTime getStartDate() {
+		throw new UnsupportedOperationException();
+
+	}
+
+	@Override
+	public DateTime getEndDate() {
+		throw new UnsupportedOperationException();
+
+	}
+
+	@Override
+	public Plan getCurrentPlan() {
+		throw new UnsupportedOperationException();
+
+	}
+
+	@Override
+	public String getCurrentPriceList() {
+		throw new UnsupportedOperationException();
+
+	}
+
+	@Override
+	public PlanPhase getCurrentPhase() {
+		throw new UnsupportedOperationException();
+
+	}
+
+	@Override
+	public DateTime getChargedThroughDate() {
+		throw new UnsupportedOperationException();
+
+	}
+
+	@Override
+	public DateTime getPaidThroughDate() {
+		throw new UnsupportedOperationException();
+
+	}
+
+	@Override
+	public List<SubscriptionTransition> getActiveTransitions() {
+		throw new UnsupportedOperationException();
+
+	}
+
+	@Override
+	public List<SubscriptionTransition> getAllTransitions() {
+		throw new UnsupportedOperationException();
+
+	}
+
+	@Override
+	public SubscriptionTransition getPendingTransition() {
+		throw new UnsupportedOperationException();
+
+	}
+
+    @Override
+    public SubscriptionTransition getPreviousTransition() {
+        return null;
+    }
+
+}
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/TestDefaultBillingEvent.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/TestDefaultBillingEvent.java
new file mode 100644
index 0000000..100d184
--- /dev/null
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/TestDefaultBillingEvent.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * 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.math.BigDecimal;
+import java.util.Iterator;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.UUID;
+
+import javax.annotation.Nullable;
+
+import org.joda.time.DateTime;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.ning.billing.catalog.DefaultPrice;
+import com.ning.billing.catalog.MockInternationalPrice;
+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.InternationalPrice;
+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.user.Subscription;
+import com.ning.billing.entitlement.api.user.SubscriptionTransition.SubscriptionTransitionType;
+
+public class TestDefaultBillingEvent {
+	public static final UUID ID_ZERO = new UUID(0L,0L);
+	public static final UUID ID_ONE = new UUID(0L,1L);
+	public static final UUID ID_TWO = new UUID(0L,2L);
+
+	@Test(groups={"fast"})
+	public void testEventOrderingSubscription() {
+	
+		BillingEvent event0 = createEvent(subscription(ID_ZERO), new DateTime("2012-01-31T00:02:04.000Z"), SubscriptionTransitionType.CREATE);
+		BillingEvent event1 = createEvent(subscription(ID_ONE), new DateTime("2012-01-31T00:02:04.000Z"), SubscriptionTransitionType.CREATE);
+		BillingEvent event2 = createEvent(subscription(ID_TWO), new DateTime("2012-01-31T00:02:04.000Z"), SubscriptionTransitionType.CREATE);
+		
+		SortedSet<BillingEvent> set = new TreeSet<BillingEvent>();
+		set.add(event2);
+		set.add(event1);
+		set.add(event0);
+		
+		Iterator<BillingEvent> it = set.iterator();
+		
+		Assert.assertEquals(event0, it.next());
+		Assert.assertEquals(event1, it.next());
+		Assert.assertEquals(event2, it.next());
+	}
+	
+	@Test(groups={"fast"})
+	public void testEventOrderingDate() {
+	
+		BillingEvent event0 = createEvent(subscription(ID_ZERO), new DateTime("2012-01-01T00:02:04.000Z"), SubscriptionTransitionType.CREATE);
+		BillingEvent event1 = createEvent(subscription(ID_ZERO), new DateTime("2012-02-01T00:02:04.000Z"), SubscriptionTransitionType.CREATE);
+		BillingEvent event2 = createEvent(subscription(ID_ZERO), new DateTime("2012-03-01T00:02:04.000Z"), SubscriptionTransitionType.CREATE);
+		
+		SortedSet<BillingEvent> set = new TreeSet<BillingEvent>();
+		set.add(event2);
+		set.add(event1);
+		set.add(event0);
+		
+		Iterator<BillingEvent> it = set.iterator();
+		
+		Assert.assertEquals(event0, it.next());
+		Assert.assertEquals(event1, it.next());
+		Assert.assertEquals(event2, it.next());
+	}
+	
+	@Test(groups={"fast"})
+	public void testEventOrderingType() {
+	
+		BillingEvent event0 = createEvent(subscription(ID_ZERO), new DateTime("2012-01-01T00:02:04.000Z"), SubscriptionTransitionType.CREATE);
+		BillingEvent event1 = createEvent(subscription(ID_ZERO), new DateTime("2012-01-01T00:02:04.000Z"), SubscriptionTransitionType.CHANGE);
+		BillingEvent event2 = createEvent(subscription(ID_ZERO), new DateTime("2012-01-01T00:02:04.000Z"), SubscriptionTransitionType.CANCEL);
+		
+		SortedSet<BillingEvent> set = new TreeSet<BillingEvent>();
+		set.add(event2);
+		set.add(event1);
+		set.add(event0);
+		
+		Iterator<BillingEvent> it = set.iterator();
+		
+		Assert.assertEquals(event0, it.next());
+		Assert.assertEquals(event1, it.next());
+		Assert.assertEquals(event2, it.next());
+	}
+	
+	@Test(groups={"fast"})
+	public void testEventOrderingMix() {
+	
+		BillingEvent event0 = createEvent(subscription(ID_ZERO), new DateTime("2012-01-01T00:02:04.000Z"), SubscriptionTransitionType.CREATE);
+		BillingEvent event1 = createEvent(subscription(ID_ZERO), new DateTime("2012-01-02T00:02:04.000Z"), SubscriptionTransitionType.CHANGE);
+		BillingEvent event2 = createEvent(subscription(ID_ONE), new DateTime("2012-01-01T00:02:04.000Z"), SubscriptionTransitionType.CANCEL);
+		
+		SortedSet<BillingEvent> set = new TreeSet<BillingEvent>();
+		set.add(event2);
+		set.add(event1);
+		set.add(event0);
+		
+		Iterator<BillingEvent> it = set.iterator();
+		
+		Assert.assertEquals(event0, it.next());
+		Assert.assertEquals(event1, it.next());
+		Assert.assertEquals(event2, it.next());
+	}
+
+	
+	private BillingEvent createEvent(Subscription sub, DateTime effectiveDate, SubscriptionTransitionType type) {
+		InternationalPrice zeroPrice = new MockInternationalPrice(new DefaultPrice(BigDecimal.ZERO, Currency.USD));
+		int billCycleDay = 1;
+
+		Plan shotgun = new MockPlan();
+		PlanPhase shotgunMonthly = createMockMonthlyPlanPhase(null, BigDecimal.ZERO, PhaseType.TRIAL);
+		
+		return new DefaultBillingEvent(sub , effectiveDate,
+				shotgun, shotgunMonthly,
+				zeroPrice, null, BillingPeriod.NO_BILLING_PERIOD, billCycleDay,
+				BillingModeType.IN_ADVANCE, "Test Event 1", type);
+	}
+
+	private MockPlanPhase createMockMonthlyPlanPhase(@Nullable final BigDecimal recurringRate,
+			final BigDecimal fixedRate, PhaseType phaseType) {
+		return new MockPlanPhase(new MockInternationalPrice(new DefaultPrice(recurringRate, Currency.USD)),
+				new MockInternationalPrice(new DefaultPrice(fixedRate, Currency.USD)),
+				BillingPeriod.MONTHLY, phaseType);
+	}
+	
+	private Subscription subscription(final UUID id) {
+		return new BrainDeadSubscription() {
+			public UUID getId() {
+				return id;
+			}
+		};
+	}
+
+}
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/TestDefaultEntitlementBillingApi.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/TestDefaultEntitlementBillingApi.java
index 08b05d6..91ddc91 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/TestDefaultEntitlementBillingApi.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/TestDefaultEntitlementBillingApi.java
@@ -28,7 +28,6 @@ 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;
@@ -43,6 +42,7 @@ import com.ning.billing.catalog.api.PlanPhase;
 import com.ning.billing.catalog.api.PriceListSet;
 import com.ning.billing.catalog.glue.CatalogModule;
 import com.ning.billing.entitlement.api.TestApiBase;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
 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;
@@ -56,7 +56,9 @@ import com.ning.billing.entitlement.events.EntitlementEvent.EventType;
 import com.ning.billing.entitlement.events.user.ApiEventType;
 import com.ning.billing.lifecycle.KillbillService.ServiceException;
 import com.ning.billing.util.clock.Clock;
-import com.ning.billing.util.clock.DefaultClock;
+import com.ning.billing.util.glue.ClockModule;
+
+import static org.testng.Assert.assertTrue;
 
 public class TestDefaultEntitlementBillingApi {
 	private static final UUID zeroId = new UUID(0L,0L);
@@ -76,11 +78,7 @@ public class TestDefaultEntitlementBillingApi {
 	@BeforeClass(groups={"setup"})
 	public void setup() throws ServiceException {
 		TestApiBase.loadSystemPropertiesFromClasspath("/entitlement.properties");
-        final Injector g = Guice.createInjector(Stage.PRODUCTION, new CatalogModule(), new AbstractModule() {
-			protected void configure() {
-				 bind(Clock.class).to(DefaultClock.class).asEagerSingleton();
-			}  	
-        });
+        final Injector g = Guice.createInjector(Stage.PRODUCTION, new CatalogModule(), new ClockModule());
 
         
         catalogService = g.getInstance(CatalogService.class);
@@ -89,7 +87,7 @@ public class TestDefaultEntitlementBillingApi {
         ((DefaultCatalogService)catalogService).loadCatalog();
 	}
 	
-	@BeforeMethod
+	@BeforeMethod(alwaysRun=true)
 	public void setupEveryTime() {
 		bundles = new ArrayList<SubscriptionBundle>();
 		final SubscriptionBundle bundle = new SubscriptionBundleData( zeroId,"TestKey", oneId,  new DateTime().minusDays(4));
@@ -97,13 +95,11 @@ public class TestDefaultEntitlementBillingApi {
 		
 		
 		transitions = new ArrayList<SubscriptionTransition>();
-		
-		
 		subscriptions = new ArrayList<Subscription>();
 		
 		SubscriptionBuilder builder = new SubscriptionBuilder();
 		subscriptionStartDate = new DateTime().minusDays(3);
-		builder.setStartDate(subscriptionStartDate);
+		builder.setStartDate(subscriptionStartDate).setId(oneId);
 		subscription = new SubscriptionData(builder) {
 		    public List<SubscriptionTransition> getAllTransitions() {
 		    	return transitions;
@@ -127,33 +123,42 @@ public class TestDefaultEntitlementBillingApi {
 				return subscription;
 
 			}
-			
-			@Override
+
+            @Override
+            public UUID getAccountIdFromSubscriptionId(final UUID subscriptionId) {
+                throw new UnsupportedOperationException();
+            }
+
+            @Override
 			public SubscriptionBundle getSubscriptionBundleFromId(UUID bundleId) {
 				return bundle;
 			}
-
-
 		};
 
+        assertTrue(true);
 	}
 	
-	@Test
+    @Test(enabled=true, groups="fast")
 	public void testBillingEventsEmpty() {
 		EntitlementDao dao = new BrainDeadMockEntitlementDao() {
 			public List<SubscriptionBundle> getSubscriptionBundleForAccount(
 					UUID accountId) {
 				return new ArrayList<SubscriptionBundle>();
 			}
-			
-		};
+
+            @Override
+            public UUID getAccountIdFromSubscriptionId(final UUID subscriptionId) {
+                throw new UnsupportedOperationException();
+            }
+
+        };
 		AccountUserApi accountApi = new BrainDeadAccountUserApi() ;
 		DefaultEntitlementBillingApi api = new DefaultEntitlementBillingApi(dao,accountApi,catalogService);
 		SortedSet<BillingEvent> events = api.getBillingEventsForAccount(new UUID(0L,0L));
 		Assert.assertEquals(events.size(), 0);
 	}
 	
-	@Test
+    @Test(enabled=true, groups="fast")
 	public void testBillingEventsNoBillingPeriod() throws CatalogApiException {
 		DateTime now = clock.getUTCNow();
 		DateTime then = now.minusDays(1);
@@ -178,7 +183,7 @@ public class TestDefaultEntitlementBillingApi {
 		checkFirstEvent(events, nextPlan, 32, oneId, now, nextPhase, ApiEventType.CREATE.toString());
 	}
 
-	@Test
+    @Test(enabled=true, groups="fast")
 	public void testBillingEventsAnual() throws CatalogApiException {
 		DateTime now = clock.getUTCNow();
 		DateTime then = now.minusDays(1);
@@ -203,11 +208,11 @@ public class TestDefaultEntitlementBillingApi {
 		checkFirstEvent(events, nextPlan, subscription.getStartDate().getDayOfMonth(), oneId, now, nextPhase, ApiEventType.CREATE.toString());
 	}
 	
-	@Test
+    @Test(enabled=true, groups="fast")
 	public void testBillingEventsMonthly() throws CatalogApiException {
 		DateTime now = clock.getUTCNow();
 		DateTime then = now.minusDays(1);
-		Plan nextPlan = catalogService.getFullCatalog().findPlan("shotgun-annual", now);
+		Plan nextPlan = catalogService.getFullCatalog().findPlan("shotgun-monthly", now);
 		PlanPhase nextPhase = nextPlan.getAllPhases()[1];
 		String nextPriceList = PriceListSet.DEFAULT_PRICELIST_NAME;
 		SubscriptionTransition t = new SubscriptionTransitionData(
@@ -228,11 +233,11 @@ public class TestDefaultEntitlementBillingApi {
 		checkFirstEvent(events, nextPlan, 32, oneId, now, nextPhase, ApiEventType.CREATE.toString());
 	}
 	
-	@Test
+    @Test(enabled=true, groups="fast")
 	public void testBillingEventsAddOn() throws CatalogApiException {
 		DateTime now = clock.getUTCNow();
 		DateTime then = now.minusDays(1);
-		Plan nextPlan = catalogService.getFullCatalog().findPlan("shotgun-annual", now);
+		Plan nextPlan = catalogService.getFullCatalog().findPlan("laser-scope-monthly", now);
 		PlanPhase nextPhase = nextPlan.getAllPhases()[0];
 		String nextPriceList = PriceListSet.DEFAULT_PRICELIST_NAME;
 		SubscriptionTransition t = new SubscriptionTransitionData(
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 271de3c..7ef459d 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
@@ -28,28 +28,22 @@ import java.util.UUID;
 
 import org.joda.time.DateTime;
 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.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.TestApiBase;
 import com.ning.billing.entitlement.api.ApiTestListener.NextEvent;
-import com.ning.billing.entitlement.api.migration.EntitlementMigrationApiException;
+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;
 import com.ning.billing.entitlement.api.migration.EntitlementMigrationApi.EntitlementSubscriptionMigration;
 import com.ning.billing.entitlement.api.migration.EntitlementMigrationApi.EntitlementSubscriptionMigrationCase;
 import com.ning.billing.entitlement.api.user.Subscription;
-import com.ning.billing.entitlement.api.user.SubscriptionBundle;
 import com.ning.billing.entitlement.api.user.Subscription.SubscriptionState;
-import com.ning.billing.entitlement.glue.MockEngineModuleSql;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
 
 public abstract class TestMigration extends TestApiBase {
 
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 30df1f9..da06cb5 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
@@ -16,12 +16,18 @@
 
 package com.ning.billing.entitlement.api;
 
+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.io.IOException;
-import java.lang.reflect.Method;
 import java.net.URL;
 import java.util.List;
 import java.util.UUID;
+
 import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.testng.Assert;
@@ -29,6 +35,7 @@ import org.testng.annotations.AfterClass;
 import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.BeforeMethod;
+
 import com.google.inject.Injector;
 import com.ning.billing.account.api.AccountData;
 import com.ning.billing.catalog.DefaultCatalogService;
@@ -43,7 +50,6 @@ 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.ApiTestListener.NextEvent;
-import com.ning.billing.entitlement.api.EntitlementService;
 import com.ning.billing.entitlement.api.billing.EntitlementBillingApi;
 import com.ning.billing.entitlement.api.migration.EntitlementMigrationApi;
 import com.ning.billing.entitlement.api.user.EntitlementUserApi;
@@ -61,13 +67,8 @@ import com.ning.billing.entitlement.events.user.ApiEventType;
 import com.ning.billing.lifecycle.KillbillService.ServiceException;
 import com.ning.billing.util.clock.Clock;
 import com.ning.billing.util.clock.ClockMock;
-import com.ning.billing.util.eventbus.DefaultEventBusService;
-import com.ning.billing.util.eventbus.EventBusService;
-
-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 com.ning.billing.util.bus.DefaultBusService;
+import com.ning.billing.util.bus.BusService;
 
 
 public abstract class TestApiBase {
@@ -86,7 +87,7 @@ public abstract class TestApiBase {
     protected EntitlementConfig config;
     protected EntitlementDao dao;
     protected ClockMock clock;
-    protected EventBusService busService;
+    protected BusService busService;
 
     protected AccountData accountData;
     protected Catalog catalog;
@@ -108,8 +109,8 @@ public abstract class TestApiBase {
     @AfterClass(groups={"setup"})
     public void tearDown() {
         try {
-            busService.getEventBus().register(testListener);
-            ((DefaultEventBusService) busService).stopBus();
+            busService.getBus().register(testListener);
+            ((DefaultBusService) busService).stopBus();
         } catch (Exception e) {
             log.warn("Failed to tearDown test properly ", e);
         }
@@ -124,14 +125,13 @@ public abstract class TestApiBase {
 
         entitlementService = g.getInstance(EntitlementService.class);
         catalogService = g.getInstance(CatalogService.class);
-        busService = g.getInstance(EventBusService.class);
+        busService = g.getInstance(BusService.class);
         config = g.getInstance(EntitlementConfig.class);
         dao = g.getInstance(EntitlementDao.class);
         clock = (ClockMock) g.getInstance(Clock.class);
         try {
-
             ((DefaultCatalogService) catalogService).loadCatalog();
-            ((DefaultEventBusService) busService).startBus();
+            ((DefaultBusService) busService).startBus();
             ((Engine) entitlementService).initialize();
             init();
         } catch (EntitlementUserApiException e) {
@@ -151,7 +151,7 @@ public abstract class TestApiBase {
         assertNotNull(catalog);
 
 
-        testListener = new ApiTestListener(busService.getEventBus());
+        testListener = new ApiTestListener(busService.getBus());
         entitlementApi = entitlementService.getUserApi();
         billingApi = entitlementService.getBillingApi();
         migrationApi = entitlementService.getMigrationApi();
@@ -169,7 +169,7 @@ public abstract class TestApiBase {
         clock.resetDeltaFromReality();
         ((MockEntitlementDao) dao).reset();
         try {
-            busService.getEventBus().register(testListener);
+            busService.getBus().register(testListener);
             UUID accountId = UUID.randomUUID();
             bundle = entitlementApi.createBundleForAccount(accountId, "myDefaultBundle");
         } catch (Exception e) {
@@ -244,6 +244,11 @@ public abstract class TestApiBase {
             public int getNumber() {
                 return days;
             }
+
+            @Override
+            public DateTime addToDateTime(DateTime dateTime) {
+                return null;
+            }
         };
         return result;
     }
@@ -258,6 +263,11 @@ public abstract class TestApiBase {
             public int getNumber() {
                 return months;
             }
+
+            @Override
+            public DateTime addToDateTime(DateTime dateTime) {
+                return null;  //To change body of implemented methods use File | Settings | File Templates.
+            }
         };
         return result;
     }
@@ -273,6 +283,11 @@ public abstract class TestApiBase {
             public int getNumber() {
                 return years;
             }
+
+            @Override
+            public DateTime addToDateTime(DateTime dateTime) {
+                return null;  //To change body of implemented methods use File | Settings | File Templates.
+            }
         };
         return result;
     }
@@ -293,26 +308,75 @@ public abstract class TestApiBase {
             public String getEmail() {
                 return "accountName@yahoo.com";
             }
+
             @Override
             public String getPhone() {
                 return "4152876341";
             }
+
             @Override
             public String getExternalKey() {
                 return "k123456";
             }
+
             @Override
             public int getBillCycleDay() {
                 return 1;
             }
+
             @Override
             public Currency getCurrency() {
                 return Currency.USD;
             }
+
             @Override
             public String getPaymentProviderName() {
                 return "Paypal";
             }
+            @Override
+            public DateTimeZone getTimeZone() {
+                return DateTimeZone.forID("Europe/Paris");
+            }
+
+            @Override
+            public String getLocale() {
+                throw new UnsupportedOperationException();
+            }
+
+            @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;  
+            }
         };
         return accountData;
     }
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 3326bc8..1e725bf 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
@@ -22,6 +22,7 @@ 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.testng.Assert;
@@ -80,7 +81,7 @@ public abstract class TestUserApiCancel extends TestApiBase {
     }
 
 
-    protected void testCancelSubscriptionEOTWithChargeThroughDate() {
+    protected void testCancelSubscriptionEOTWithChargeThroughDate() throws EntitlementBillingApiException {
         log.info("Starting testCancelSubscriptionEOTWithChargeThroughDate");
 
         try {
@@ -176,7 +177,7 @@ public abstract class TestUserApiCancel extends TestApiBase {
     // Similar test to testCancelSubscriptionEOTWithChargeThroughDate except we uncancel and check things
     // are as they used to be and we can move forward without hitting cancellation
     //
-    protected void testUncancel() {
+    protected void testUncancel() throws EntitlementBillingApiException {
 
         log.info("Starting testUncancel");
 
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 630d925..dbcc680 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
@@ -19,6 +19,7 @@ package com.ning.billing.entitlement.api.user;
 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;
 
@@ -38,7 +39,7 @@ public class TestUserApiCancelMemory extends TestUserApiCancel {
 
     @Override
     @Test(enabled=true, groups={"fast"})
-    public void testCancelSubscriptionEOTWithChargeThroughDate() {
+    public void testCancelSubscriptionEOTWithChargeThroughDate() throws EntitlementBillingApiException {
         super.testCancelSubscriptionEOTWithChargeThroughDate();
     }
 
@@ -50,7 +51,7 @@ public class TestUserApiCancelMemory extends TestUserApiCancel {
 
     @Override
     @Test(enabled=true, groups={"fast"})
-    public void testUncancel() {
+    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 87491c7..840f357 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
@@ -19,6 +19,7 @@ package com.ning.billing.entitlement.api.user;
 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;
 
@@ -33,7 +34,7 @@ public class TestUserApiCancelSql extends TestUserApiCancel {
     }
 
     @Test(enabled= false, groups={"stress"})
-    public void stressTest() {
+    public void stressTest() throws EntitlementBillingApiException {
         for (int i = 0; i < MAX_STRESS_ITERATIONS; i++) {
             cleanupTest();
             setupTest();
@@ -55,7 +56,7 @@ public class TestUserApiCancelSql extends TestUserApiCancel {
 
     @Override
     @Test(enabled=true, groups={"sql"})
-    public void testCancelSubscriptionEOTWithChargeThroughDate() {
+    public void testCancelSubscriptionEOTWithChargeThroughDate() throws EntitlementBillingApiException {
         super.testCancelSubscriptionEOTWithChargeThroughDate();
     }
 
@@ -67,7 +68,7 @@ public class TestUserApiCancelSql extends TestUserApiCancel {
 
     @Override
     @Test(enabled=true, groups={"sql"})
-    public void testUncancel() {
+    public void testUncancel() throws EntitlementBillingApiException {
         super.testUncancel();
     }
 
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 2c17255..4505ef0 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
@@ -37,6 +37,7 @@ 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;
 import com.ning.billing.util.clock.DefaultClock;
@@ -100,12 +101,12 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
     }
 
 
-    protected void testChangePlanBundleAlignEOTWithChargeThroughDate() {
+    protected void testChangePlanBundleAlignEOTWithChargeThroughDate() throws EntitlementBillingApiException {
         testChangePlanBundleAlignEOTWithChargeThroughDate("Shotgun", BillingPeriod.ANNUAL, "gunclubDiscount", "Pistol", BillingPeriod.ANNUAL, "gunclubDiscount");
     }
 
     private void testChangePlanBundleAlignEOTWithChargeThroughDate(String fromProd, BillingPeriod fromTerm, String fromPlanSet,
-            String toProd, BillingPeriod toTerm, String toPlanSet) {
+            String toProd, BillingPeriod toTerm, String toPlanSet) throws EntitlementBillingApiException {
 
         log.info("Starting testChangeSubscriptionEOTWithChargeThroughDate");
         try {
@@ -216,12 +217,12 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
     }
 
 
-    protected void testChangePlanChangePlanAlignEOTWithChargeThroughDate() {
+    protected void testChangePlanChangePlanAlignEOTWithChargeThroughDate() throws EntitlementBillingApiException {
         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) {
+            String toProd, BillingPeriod toTerm, String toPlanSet) throws EntitlementBillingApiException {
 
         log.info("Starting testChangePlanBundleAlignEOTWithChargeThroughDate");
 
@@ -297,7 +298,7 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
         }
     }
 
-    protected void testMultipleChangeLastIMM() {
+    protected void testMultipleChangeLastIMM() throws EntitlementBillingApiException {
 
         try {
             SubscriptionData subscription = createSubscription("Assault-Rifle", BillingPeriod.MONTHLY, "gunclubDiscount");
@@ -344,7 +345,7 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
         }
     }
 
-    protected void testMultipleChangeLastEOT() {
+    protected void testMultipleChangeLastEOT() throws EntitlementBillingApiException {
 
         try {
 
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 c303c62..03b9d91 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
@@ -19,6 +19,7 @@ package com.ning.billing.entitlement.api.user;
 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;
 
@@ -38,7 +39,7 @@ public class TestUserApiChangePlanMemory extends TestUserApiChangePlan {
 
     @Override
     @Test(enabled=true, groups={"fast"})
-    public void testChangePlanBundleAlignEOTWithChargeThroughDate() {
+    public void testChangePlanBundleAlignEOTWithChargeThroughDate() throws EntitlementBillingApiException {
         super.testChangePlanBundleAlignEOTWithChargeThroughDate();
     }
 
@@ -50,20 +51,20 @@ public class TestUserApiChangePlanMemory extends TestUserApiChangePlan {
 
     @Override
     @Test(enabled=true, groups={"fast"})
-    public void testMultipleChangeLastIMM() {
+    public void testMultipleChangeLastIMM() throws EntitlementBillingApiException {
         super.testMultipleChangeLastIMM();
     }
 
     @Override
     @Test(enabled=true, groups={"fast"})
-    public void testMultipleChangeLastEOT() {
+    public void testMultipleChangeLastEOT() throws EntitlementBillingApiException {
         super.testMultipleChangeLastEOT();
     }
 
     // Set to false until we implement rescue example.
     @Override
     @Test(enabled=false, groups={"fast"})
-    public void testChangePlanChangePlanAlignEOTWithChargeThroughDate() {
+    public void testChangePlanChangePlanAlignEOTWithChargeThroughDate() throws EntitlementBillingApiException {
         super.testChangePlanChangePlanAlignEOTWithChargeThroughDate();
     }
 
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 81cbbc8..ad0235f 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
@@ -19,6 +19,7 @@ package com.ning.billing.entitlement.api.user;
 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;
 
@@ -32,7 +33,7 @@ public class TestUserApiChangePlanSql extends TestUserApiChangePlan {
     }
 
     @Test(enabled= true, groups={"stress"})
-    public void stressTest() {
+    public void stressTest() throws EntitlementBillingApiException {
         for (int i = 0; i < MAX_STRESS_ITERATIONS; i++) {
             cleanupTest();
             setupTest();
@@ -66,7 +67,7 @@ public class TestUserApiChangePlanSql extends TestUserApiChangePlan {
 
     @Override
     @Test(enabled=true, groups={"sql"})
-    public void testChangePlanBundleAlignEOTWithChargeThroughDate() {
+    public void testChangePlanBundleAlignEOTWithChargeThroughDate() throws EntitlementBillingApiException {
         super.testChangePlanBundleAlignEOTWithChargeThroughDate();
     }
 
@@ -78,20 +79,20 @@ public class TestUserApiChangePlanSql extends TestUserApiChangePlan {
 
     @Override
     @Test(enabled=true, groups={"sql"})
-    public void testMultipleChangeLastIMM() {
+    public void testMultipleChangeLastIMM() throws EntitlementBillingApiException {
         super.testMultipleChangeLastIMM();
     }
 
     @Override
     @Test(enabled=true, groups={"sql"})
-    public void testMultipleChangeLastEOT() {
+    public void testMultipleChangeLastEOT() throws EntitlementBillingApiException {
         super.testMultipleChangeLastEOT();
     }
 
     // rescue not implemented yet
     @Override
     @Test(enabled=false, groups={"sql"})
-    public void testChangePlanChangePlanAlignEOTWithChargeThroughDate() {
+    public void testChangePlanChangePlanAlignEOTWithChargeThroughDate() throws EntitlementBillingApiException {
         super.testChangePlanChangePlanAlignEOTWithChargeThroughDate();
     }
 
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 4a8b4b1..7fceab9 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
@@ -29,6 +29,7 @@ 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;
@@ -64,7 +65,7 @@ public class TestUserApiDemos extends TestApiBase {
      *  8. Cancel EOT
      */
     @Test(enabled=true, groups="demos")
-    public void testDemo1() {
+    public void testDemo1() throws EntitlementBillingApiException {
 
         try {
             System.out.println("DEMO 1 START");
@@ -189,7 +190,7 @@ public class TestUserApiDemos extends TestApiBase {
     }
 
     @Test(enabled= true, groups={"stress"})
-    public void stressTest() {
+    public void stressTest() throws EntitlementBillingApiException {
         for (int i = 0; i < 100; i++) {
             cleanupTest();
             setupTest();
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 84d1031..f69b36a 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
@@ -26,6 +26,7 @@ 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;
@@ -42,7 +43,7 @@ public class TestUserApiScenarios extends TestApiBase {
     }
 
     @Test(enabled=true)
-    public void testChangeIMMCancelUncancelChangeEOT() {
+    public void testChangeIMMCancelUncancelChangeEOT() throws EntitlementBillingApiException {
 
         log.info("Starting testChangeIMMCancelUncancelChangeEOT");
 
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 86e458f..6395470 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
@@ -16,20 +16,30 @@
 
 package com.ning.billing.entitlement.engine.dao;
 
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedList;
+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 org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import com.google.inject.Inject;
 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.billing.EntitlementBillingApiException;
 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.user.Subscription;
 import com.ning.billing.entitlement.api.user.SubscriptionBundle;
-import com.ning.billing.entitlement.api.user.SubscriptionData;
 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.engine.core.Engine;
 import com.ning.billing.entitlement.events.EntitlementEvent;
@@ -38,20 +48,10 @@ import com.ning.billing.entitlement.events.user.ApiEvent;
 import com.ning.billing.entitlement.events.user.ApiEventType;
 import com.ning.billing.util.clock.Clock;
 import com.ning.billing.util.notificationq.NotificationKey;
-import com.ning.billing.util.notificationq.NotificationLifecycle;
 import com.ning.billing.util.notificationq.NotificationQueue;
 import com.ning.billing.util.notificationq.NotificationQueueService;
 import com.ning.billing.util.notificationq.NotificationQueueService.NoSuchNotificationQueue;
 
-import org.joda.time.DateTime;
-import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.*;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-
 public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlementDao {
 
     protected final static Logger log = LoggerFactory.getLogger(EntitlementDao.class);
@@ -65,7 +65,9 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
     private final NotificationQueueService notificationQueueService;
 
     @Inject
-    public MockEntitlementDaoMemory(Clock clock, EntitlementConfig config, SubscriptionFactory factory, NotificationQueueService notificationQueueService) {
+    public MockEntitlementDaoMemory(final Clock clock, final EntitlementConfig config,
+                                    final SubscriptionFactory factory,
+                                    final NotificationQueueService notificationQueueService) {
         super();
         this.clock = clock;
         this.config = config;
@@ -84,9 +86,9 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
     }
 
     @Override
-    public List<SubscriptionBundle> getSubscriptionBundleForAccount(UUID accountId) {
+    public List<SubscriptionBundle> getSubscriptionBundleForAccount(final UUID accountId) {
         List<SubscriptionBundle> results = new ArrayList<SubscriptionBundle>();
-        for (SubscriptionBundle cur : bundles) {
+        for (final SubscriptionBundle cur : bundles) {
             if (cur.getAccountId().equals(accountId)) {
                 results.add(cur);
             }
@@ -95,8 +97,8 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
     }
 
     @Override
-    public SubscriptionBundle getSubscriptionBundleFromId(UUID bundleId) {
-        for (SubscriptionBundle cur : bundles) {
+    public SubscriptionBundle getSubscriptionBundleFromId(final UUID bundleId) {
+        for (final SubscriptionBundle cur : bundles) {
             if (cur.getId().equals(bundleId)) {
                 return cur;
             }
@@ -105,8 +107,8 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
     }
 
     @Override
-    public SubscriptionBundle getSubscriptionBundleFromKey(String bundleKey) {
-        for (SubscriptionBundle cur : bundles) {
+    public SubscriptionBundle getSubscriptionBundleFromKey(final String bundleKey) {
+        for (final SubscriptionBundle cur : bundles) {
             if (cur.getKey().equals(bundleKey)) {
                 return cur;
             }
@@ -116,14 +118,14 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
 
 
     @Override
-    public SubscriptionBundle createSubscriptionBundle(SubscriptionBundleData bundle) {
+    public SubscriptionBundle createSubscriptionBundle(final SubscriptionBundleData bundle) {
         bundles.add(bundle);
         return getSubscriptionBundleFromId(bundle.getId());
     }
 
     @Override
-    public Subscription getSubscriptionFromId(UUID subscriptionId) {
-        for (Subscription cur : subscriptions) {
+    public Subscription getSubscriptionFromId(final UUID subscriptionId) {
+        for (final Subscription cur : subscriptions) {
             if (cur.getId().equals(subscriptionId)) {
                 return buildSubscription((SubscriptionData) cur);
             }
@@ -132,9 +134,14 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
     }
 
     @Override
-    public List<Subscription> getSubscriptionsForKey(String bundleKey) {
+    public UUID getAccountIdFromSubscriptionId(final UUID subscriptionId) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public List<Subscription> getSubscriptionsForKey(final String bundleKey) {
 
-        for (SubscriptionBundle cur : bundles) {
+        for (final SubscriptionBundle cur : bundles) {
             if (cur.getKey().equals(bundleKey)) {
                 return getSubscriptions(cur.getId());
             }
@@ -144,11 +151,11 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
 
 
     @Override
-    public void createSubscription(SubscriptionData subscription, List<EntitlementEvent> initalEvents) {
+    public void createSubscription(final SubscriptionData subscription, final List<EntitlementEvent> initialEvents) {
 
         synchronized(events) {
-            events.addAll(initalEvents);
-            for (final EntitlementEvent cur : initalEvents) {
+            events.addAll(initialEvents);
+            for (final EntitlementEvent cur : initialEvents) {
                 recordFutureNotificationFromTransaction(null, cur.getEffectiveDate(), new NotificationKey() {
                     @Override
                     public String toString() {
@@ -162,10 +169,10 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
     }
 
     @Override
-    public List<Subscription> getSubscriptions(UUID bundleId) {
+    public List<Subscription> getSubscriptions(final UUID bundleId) {
 
         List<Subscription> results = new ArrayList<Subscription>();
-        for (Subscription cur : subscriptions) {
+        for (final Subscription cur : subscriptions) {
             if (cur.getBundleId().equals(bundleId)) {
                 results.add(buildSubscription((SubscriptionData) cur));
             }
@@ -174,10 +181,10 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
     }
 
     @Override
-    public List<EntitlementEvent> getEventsForSubscription(UUID subscriptionId) {
+    public List<EntitlementEvent> getEventsForSubscription(final UUID subscriptionId) {
         synchronized(events) {
             List<EntitlementEvent> results = new LinkedList<EntitlementEvent>();
-            for (EntitlementEvent cur : events) {
+            for (final EntitlementEvent cur : events) {
                 if (cur.getSubscriptionId().equals(subscriptionId)) {
                     results.add(cur);
                 }
@@ -187,10 +194,10 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
     }
 
     @Override
-    public List<EntitlementEvent> getPendingEventsForSubscription(UUID subscriptionId) {
+    public List<EntitlementEvent> getPendingEventsForSubscription(final UUID subscriptionId) {
         synchronized(events) {
             List<EntitlementEvent> results = new LinkedList<EntitlementEvent>();
-            for (EntitlementEvent cur : events) {
+            for (final EntitlementEvent cur : events) {
                 if (cur.isActive() &&
                         cur.getEffectiveDate().isAfter(clock.getUTCNow()) &&
                             cur.getSubscriptionId().equals(subscriptionId)) {
@@ -203,8 +210,8 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
 
 
     @Override
-    public Subscription getBaseSubscription(UUID bundleId) {
-        for (Subscription cur : subscriptions) {
+    public Subscription getBaseSubscription(final UUID bundleId) {
+        for (final Subscription cur : subscriptions) {
             if (cur.getBundleId().equals(bundleId) &&
                     cur.getCurrentPlan().getProduct().getCategory() == ProductCategory.BASE) {
                 return buildSubscription((SubscriptionData) cur);
@@ -214,19 +221,19 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
     }
 
     @Override
-    public void createNextPhaseEvent(UUID subscriptionId, EntitlementEvent nextPhase) {
+    public void createNextPhaseEvent(final UUID subscriptionId, final EntitlementEvent nextPhase) {
         cancelNextPhaseEvent(subscriptionId);
         insertEvent(nextPhase);
     }
 
 
 
-    private Subscription buildSubscription(SubscriptionData in) {
+    private Subscription buildSubscription(final SubscriptionData in) {
         return factory.createSubscription(new SubscriptionBuilder(in), getEventsForSubscription(in.getId()));
     }
 
     @Override
-    public void updateSubscription(SubscriptionData subscription) {
+    public void updateSubscription(final SubscriptionData subscription) {
 
         boolean found = false;
         Iterator<Subscription> it = subscriptions.iterator();
@@ -244,7 +251,7 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
     }
 
     @Override
-    public void cancelSubscription(UUID subscriptionId, EntitlementEvent cancelEvent) {
+    public void cancelSubscription(final UUID subscriptionId, final EntitlementEvent cancelEvent) {
         synchronized (cancelEvent) {
             cancelNextPhaseEvent(subscriptionId);
             insertEvent(cancelEvent);
@@ -252,7 +259,7 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
     }
 
     @Override
-    public void changePlan(UUID subscriptionId, List<EntitlementEvent> changeEvents) {
+    public void changePlan(final UUID subscriptionId, final List<EntitlementEvent> changeEvents) {
         synchronized(events) {
             cancelNextChangeEvent(subscriptionId);
             cancelNextPhaseEvent(subscriptionId);
@@ -280,7 +287,7 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
         }
     }
 
-    private void cancelNextPhaseEvent(UUID subscriptionId) {
+    private void cancelNextPhaseEvent(final UUID subscriptionId) {
 
         Subscription curSubscription = getSubscriptionFromId(subscriptionId);
         if (curSubscription.getCurrentPhase() == null ||
@@ -307,7 +314,7 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
     }
 
 
-    private void cancelNextChangeEvent(UUID subscriptionId) {
+    private void cancelNextChangeEvent(final UUID subscriptionId) {
 
         synchronized(events) {
 
@@ -328,7 +335,7 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
     }
 
     @Override
-    public void uncancelSubscription(UUID subscriptionId, List<EntitlementEvent> uncancelEvents) {
+    public void uncancelSubscription(final UUID subscriptionId, final List<EntitlementEvent> uncancelEvents) {
 
         synchronized (events) {
             boolean foundCancel = false;
@@ -346,7 +353,7 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
                 }
             }
             if (foundCancel) {
-                for (EntitlementEvent cur : uncancelEvents) {
+                for (final EntitlementEvent cur : uncancelEvents) {
                     insertEvent(cur);
                 }
             }
@@ -360,9 +367,9 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
 
             undoMigration(accountId);
 
-            for (BundleMigrationData curBundle : accountData.getData()) {
+            for (final BundleMigrationData curBundle : accountData.getData()) {
                 SubscriptionBundleData bundleData = curBundle.getData();
-                for (SubscriptionMigrationData curSubscription : curBundle.getSubscriptions()) {
+                for (final SubscriptionMigrationData curSubscription : curBundle.getSubscriptions()) {
                     SubscriptionData subData = curSubscription.getData();
                     for (final EntitlementEvent curEvent : curSubscription.getInitialEvents()) {
                         events.add(curEvent);
@@ -382,15 +389,15 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
     }
 
     @Override
-    public void undoMigration(UUID accountId) {
+    public void undoMigration(final UUID accountId) {
         synchronized(events) {
 
             List<SubscriptionBundle> allBundles = getSubscriptionBundleForAccount(accountId);
-            for (SubscriptionBundle bundle : allBundles) {
+            for (final SubscriptionBundle bundle : allBundles) {
                 List<Subscription> allSubscriptions = getSubscriptions(bundle.getId());
-                for (Subscription subscription : allSubscriptions) {
+                for (final Subscription subscription : allSubscriptions) {
                     List<EntitlementEvent> allEvents = getEventsForSubscription(subscription.getId());
-                    for (EntitlementEvent event : allEvents) {
+                    for (final EntitlementEvent event : allEvents) {
                         events.remove(event);
                     }
                     subscriptions.remove(subscription);
@@ -402,9 +409,9 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
     }
 
     @Override
-    public EntitlementEvent getEventById(UUID eventId) {
+    public EntitlementEvent getEventById(final UUID eventId) {
         synchronized(events) {
-            for (EntitlementEvent cur : events) {
+            for (final EntitlementEvent cur : events) {
                 if (cur.getId().equals(eventId)) {
                     return cur;
                 }
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 503fd1a..c5881f9 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,25 +16,24 @@
 
 package com.ning.billing.entitlement.engine.dao;
 
-import com.google.inject.Inject;
-import com.ning.billing.config.EntitlementConfig;
-import com.ning.billing.entitlement.api.user.SubscriptionFactory;
-import com.ning.billing.util.clock.Clock;
-import com.ning.billing.util.notificationq.NotificationQueueService;
-
-import org.skife.jdbi.v2.DBI;
+import org.skife.jdbi.v2.IDBI;
 import org.skife.jdbi.v2.Transaction;
 import org.skife.jdbi.v2.TransactionStatus;
 import org.skife.jdbi.v2.sqlobject.SqlUpdate;
 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.util.clock.Clock;
+import com.ning.billing.util.notificationq.NotificationQueueService;
+
 public class MockEntitlementDaoSql extends EntitlementSqlDao implements MockEntitlementDao {
 
     private final ResetSqlDao resetDao;
 
     @Inject
-    public MockEntitlementDaoSql(DBI dbi, Clock clock, SubscriptionFactory factory, NotificationQueueService notificationQueueService) {
+    public MockEntitlementDaoSql(IDBI dbi, Clock clock, SubscriptionFactory factory, NotificationQueueService notificationQueueService) {
         super(dbi, clock, factory, notificationQueueService);
         this.resetDao = dbi.onDemand(ResetSqlDao.class);
     }
@@ -59,7 +58,7 @@ public class MockEntitlementDaoSql extends EntitlementSqlDao implements MockEnti
 
     public static interface ResetSqlDao extends Transactional<ResetSqlDao>, CloseMe {
 
-        @SqlUpdate("truncate table events")
+        @SqlUpdate("truncate table entitlement_events")
         public void resetEvents();
 
         @SqlUpdate("truncate table subscriptions")
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 1555bdb..d46fe83 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
@@ -16,26 +16,19 @@
 
 package com.ning.billing.entitlement.glue;
 
-import com.ning.billing.account.glue.AccountModuleMock;
+import com.ning.billing.account.glue.AccountModuleWithMocks;
 import com.ning.billing.catalog.glue.CatalogModule;
-import com.ning.billing.util.clock.Clock;
-import com.ning.billing.util.clock.ClockMock;
-import com.ning.billing.util.glue.EventBusModule;
-import com.ning.billing.util.glue.NotificationQueueModule;
+import com.ning.billing.util.clock.MockClockModule;
+import com.ning.billing.util.glue.BusModule;
 
 public class MockEngineModule extends EntitlementModule {
 
     @Override
-    protected void installClock() {
-        bind(Clock.class).to(ClockMock.class).asEagerSingleton();
-    }
-
-    @Override
     protected void configure() {
         super.configure();
-        install(new EventBusModule());
+        install(new BusModule());
         install(new CatalogModule());
-        install(new AccountModuleMock());
+        install(new AccountModuleWithMocks());
+        install(new MockClockModule());
     }
-
 }
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 dbe2938..e9e6134 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
@@ -25,7 +25,7 @@ import com.ning.billing.util.clock.ClockMock;
 import com.ning.billing.util.glue.NotificationQueueModule;
 
 import org.skife.config.ConfigurationObjectFactory;
-import org.skife.jdbi.v2.DBI;
+import org.skife.jdbi.v2.IDBI;
 
 public class MockEngineModuleSql extends MockEngineModule {
 
@@ -35,13 +35,8 @@ public class MockEngineModuleSql extends MockEngineModule {
         bind(EntitlementDao.class).to(MockEntitlementDaoSql.class).asEagerSingleton();
     }
 
-    @Override
-    protected void installClock() {
-        bind(Clock.class).to(ClockMock.class).asEagerSingleton();
-    }
-
     protected void installDBI() {
-        bind(DBI.class).toProvider(DBIProvider.class).asEagerSingleton();
+        bind(IDBI.class).toProvider(DBIProvider.class).asEagerSingleton();
         final DbiConfig config = new ConfigurationObjectFactory(System.getProperties()).build(DbiConfig.class);
         bind(DbiConfig.class).toInstance(config);
     }

invoice/pom.xml 20(+15 -5)

diff --git a/invoice/pom.xml b/invoice/pom.xml
index 8c939ca..81f9eb0 100644
--- a/invoice/pom.xml
+++ b/invoice/pom.xml
@@ -13,7 +13,7 @@
     <parent>
         <groupId>com.ning.billing</groupId>
         <artifactId>killbill</artifactId>
-        <version>0.1.2-SNAPSHOT</version>
+        <version>0.1.6-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-invoice</artifactId>
@@ -21,6 +21,11 @@
     <packaging>jar</packaging>
     <dependencies>
         <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
             <groupId>com.ning.billing</groupId>
             <artifactId>killbill-api</artifactId>
         </dependency>
@@ -48,7 +53,6 @@
             <type>test-jar</type>
             <scope>test</scope>
         </dependency>
-        
         <dependency>
             <groupId>org.testng</groupId>
             <artifactId>testng</artifactId>
@@ -67,18 +71,15 @@
         <dependency>
             <groupId>commons-io</groupId>
             <artifactId>commons-io</artifactId>
-            <version>2.0</version>
         </dependency>
         <dependency>
             <groupId>commons-io</groupId>
             <artifactId>commons-io</artifactId>
-            <version>2.0</version>
             <scope>test</scope>
         </dependency>
         <dependency>
             <groupId>org.jdbi</groupId>
             <artifactId>jdbi</artifactId>
-            <version>2.27</version>
         </dependency>
         <dependency>
             <groupId>org.antlr</groupId>
@@ -94,6 +95,15 @@
             <artifactId>guice</artifactId>
             <scope>provided</scope>
         </dependency>
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.jayway.awaitility</groupId>
+            <artifactId>awaitility</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
     <build>
     </build>
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 b7bc657..6f033db 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/api/DefaultInvoiceService.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/api/DefaultInvoiceService.java
@@ -17,19 +17,31 @@
 package com.ning.billing.invoice.api;
 
 import com.google.inject.Inject;
+import com.ning.billing.invoice.InvoiceListener;
+import com.ning.billing.invoice.notification.NextBillingDateNotifier;
 import com.ning.billing.lifecycle.LifecycleHandlerType;
+import com.ning.billing.lifecycle.LifecycleHandlerType.LifecycleLevel;
+import com.ning.billing.util.bus.Bus;
 
 public class DefaultInvoiceService implements InvoiceService {
-    private static final String INVOICE_SERVICE_NAME = "invoice-service";
+
+    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(InvoiceUserApi userApi, InvoicePaymentApi paymentApi) {
+    public DefaultInvoiceService(InvoiceListener invoiceListener, Bus eventBus, InvoiceUserApi userApi, InvoicePaymentApi paymentApi, NextBillingDateNotifier dateNotifier) {
+        this.invoiceListener = invoiceListener;
+        this.eventBus = eventBus;
         this.userApi = userApi;
         this.paymentApi = paymentApi;
+        this.dateNotifier = dateNotifier;
     }
 
+
     @Override
     public String getName() {
         return INVOICE_SERVICE_NAME;
@@ -47,5 +59,34 @@ public class DefaultInvoiceService implements InvoiceService {
 
     @LifecycleHandlerType(LifecycleHandlerType.LifecycleLevel.INIT_SERVICE)
     public void initialize() {
+        dateNotifier.initialize();
+    }
+
+    @LifecycleHandlerType(LifecycleLevel.START_SERVICE)
+    public void start() {
+        dateNotifier.start();
+    }
+
+    @LifecycleHandlerType(LifecycleHandlerType.LifecycleLevel.REGISTER_EVENTS)
+    public void registerForNotifications() {
+        try {
+            eventBus.register(invoiceListener);
+        } catch (Bus.EventBusException e) {
+            throw new RuntimeException("Unable to register to the EventBus!", e);
+        }
+    }
+
+    @LifecycleHandlerType(LifecycleHandlerType.LifecycleLevel.UNREGISTER_EVENTS)
+    public void unregisterForNotifications() {
+        try {
+            eventBus.unregister(invoiceListener);
+        } catch (Bus.EventBusException e) {
+            throw new RuntimeException("Unable to unregister to the EventBus!", e);
+        }
+    }
+
+    @LifecycleHandlerType(LifecycleLevel.STOP_SERVICE)
+    public void stop() {
+        dateNotifier.stop();
     }
 }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/api/invoice/DefaultInvoicePaymentApi.java b/invoice/src/main/java/com/ning/billing/invoice/api/invoice/DefaultInvoicePaymentApi.java
index fac63ba..4d99525 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/api/invoice/DefaultInvoicePaymentApi.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/api/invoice/DefaultInvoicePaymentApi.java
@@ -1,4 +1,5 @@
 /*
+
  * Copyright 2010-2011 Ning, Inc.
  *
  * Ning licenses this file to you under the Apache License, version 2.0
@@ -19,39 +20,66 @@ package com.ning.billing.invoice.api.invoice;
 import java.math.BigDecimal;
 import java.util.List;
 import java.util.UUID;
+
 import org.joda.time.DateTime;
-import org.skife.jdbi.v2.IDBI;
+
 import com.google.inject.Inject;
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoicePayment;
 import com.ning.billing.invoice.api.InvoicePaymentApi;
 import com.ning.billing.invoice.dao.InvoiceDao;
+import com.ning.billing.invoice.model.DefaultInvoicePayment;
 
 public class DefaultInvoicePaymentApi implements InvoicePaymentApi {
     private final InvoiceDao dao;
 
     @Inject
-    public DefaultInvoicePaymentApi(InvoiceDao dao) {
+    public DefaultInvoicePaymentApi(final InvoiceDao dao) {
         this.dao = dao;
+     }
+
+    @Override
+    public void notifyOfPaymentAttempt(InvoicePayment invoicePayment) {
+        dao.notifyOfPaymentAttempt(invoicePayment);
+    }
+
+//    @Override
+//    public void paymentFailed(UUID invoiceId, UUID paymentId, DateTime paymentAttemptDate) {
+//        dao.notifyFailedPayment(invoiceId.toString(), paymentId.toString(), paymentAttemptDate.toDate());
+//    }
+
+    @Override
+    public List<Invoice> getInvoicesByAccount(final UUID accountId) {
+        return dao.getInvoicesByAccount(accountId);
+    }
+
+    @Override
+    public Invoice getInvoice(final UUID invoiceId) {
+        return dao.getById(invoiceId);
     }
 
     @Override
-    public void paymentSuccessful(UUID invoiceId, BigDecimal amount, Currency currency, UUID paymentId, DateTime paymentAttemptDate) {
-        dao.notifySuccessfulPayment(invoiceId.toString(), amount, currency.toString(), paymentId.toString(), paymentAttemptDate.toDate());
+    public Invoice getInvoiceForPaymentAttemptId(UUID paymentAttemptId) {
+        UUID invoiceIdStr = dao.getInvoiceIdByPaymentAttemptId(paymentAttemptId);
+        return invoiceIdStr == null ? null : dao.getById(invoiceIdStr);
     }
 
     @Override
-    public void paymentFailed(UUID invoiceId, UUID paymentId, DateTime paymentAttemptDate) {
-        dao.notifyFailedPayment(invoiceId.toString(), paymentId.toString(), paymentAttemptDate.toDate());
+    public InvoicePayment getInvoicePayment(UUID paymentAttemptId) {
+        return dao.getInvoicePayment(paymentAttemptId);
     }
 
     @Override
-    public List<Invoice> getInvoicesByAccount(UUID accountId) {
-        return dao.getInvoicesByAccount(accountId.toString());
+    public void notifyOfPaymentAttempt(UUID invoiceId, BigDecimal amount, Currency currency, UUID paymentAttemptId, DateTime paymentAttemptDate) {
+        InvoicePayment invoicePayment = new DefaultInvoicePayment(paymentAttemptId, invoiceId, paymentAttemptDate, amount, currency, null, null);
+        dao.notifyOfPaymentAttempt(invoicePayment);
     }
 
     @Override
-    public Invoice getInvoice(UUID invoiceId) {
-        return dao.getById(invoiceId.toString());
+    public void notifyOfPaymentAttempt(UUID invoiceId, UUID paymentAttemptId, DateTime paymentAttemptDate) {
+        InvoicePayment invoicePayment = new DefaultInvoicePayment(paymentAttemptId, invoiceId, paymentAttemptDate);
+        dao.notifyOfPaymentAttempt(invoicePayment);
     }
-}
+    
+}
\ No newline at end of file
diff --git a/invoice/src/main/java/com/ning/billing/invoice/api/test/DefaultInvoiceTestApi.java b/invoice/src/main/java/com/ning/billing/invoice/api/test/DefaultInvoiceTestApi.java
new file mode 100644
index 0000000..bbec57f
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/api/test/DefaultInvoiceTestApi.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.invoice.api.test;
+
+import com.google.inject.Inject;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.dao.InvoiceDao;
+
+public class DefaultInvoiceTestApi implements InvoiceTestApi {
+    private final InvoiceDao invoiceDao;
+
+    @Inject
+    public DefaultInvoiceTestApi(InvoiceDao invoiceDao) {
+        this.invoiceDao = invoiceDao;
+    }
+
+    @Override
+    public void create(Invoice invoice) {
+        invoiceDao.create(invoice);
+    }
+}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/api/user/DefaultInvoiceCreationNotification.java b/invoice/src/main/java/com/ning/billing/invoice/api/user/DefaultInvoiceCreationNotification.java
index 094e6f1..5c6785c 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/api/user/DefaultInvoiceCreationNotification.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/api/user/DefaultInvoiceCreationNotification.java
@@ -18,7 +18,9 @@ package com.ning.billing.invoice.api.user;
 
 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.InvoiceCreationNotification;
 
@@ -61,4 +63,10 @@ public class DefaultInvoiceCreationNotification implements InvoiceCreationNotifi
     public DateTime getInvoiceCreationDate() {
         return invoiceCreationDate;
     }
+
+    @Override
+    public String toString() {
+        return "DefaultInvoiceCreationNotification [invoiceId=" + invoiceId + ", accountId=" + accountId + ", amountOwed=" + amountOwed + ", currency=" + currency + ", invoiceCreationDate=" + invoiceCreationDate + "]";
+    }
+
 }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/api/user/DefaultInvoiceUserApi.java b/invoice/src/main/java/com/ning/billing/invoice/api/user/DefaultInvoiceUserApi.java
index 22f4990..da7f565 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/api/user/DefaultInvoiceUserApi.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/api/user/DefaultInvoiceUserApi.java
@@ -16,50 +16,66 @@
 
 package com.ning.billing.invoice.api.user;
 
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.UUID;
+
+import com.ning.billing.invoice.InvoiceListener;
+import com.ning.billing.invoice.api.InvoicePayment;
+import org.joda.time.DateTime;
 import com.google.inject.Inject;
-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 com.ning.billing.invoice.api.InvoiceUserApi;
-import com.ning.billing.invoice.dao.DefaultInvoiceDao;
 import com.ning.billing.invoice.dao.InvoiceDao;
-import com.ning.billing.util.eventbus.EventBus;
-import org.joda.time.DateTime;
-import org.skife.jdbi.v2.IDBI;
-
-import java.math.BigDecimal;
-import java.util.List;
-import java.util.UUID;
 
 public class DefaultInvoiceUserApi implements InvoiceUserApi {
     private final InvoiceDao dao;
 
     @Inject
-    public DefaultInvoiceUserApi(InvoiceDao dao) {
+    public DefaultInvoiceUserApi(final InvoiceDao dao) {
         this.dao = dao;
     }
 
     @Override
-    public List<UUID> getInvoicesForPayment(DateTime targetDate, int numberOfDays) {
-        return dao.getInvoicesForPayment(targetDate.toDate(), numberOfDays);
+    public List<UUID> getInvoicesForPayment(final DateTime targetDate, final int numberOfDays) {
+        return dao.getInvoicesForPayment(targetDate, numberOfDays);
     }
 
     @Override
-    public List<Invoice> getInvoicesByAccount(UUID accountId) {
-        return dao.getInvoicesByAccount(accountId.toString());
+    public List<Invoice> getInvoicesByAccount(final UUID accountId) {
+        return dao.getInvoicesByAccount(accountId);
     }
 
     @Override
-    public Invoice getInvoice(UUID invoiceId) {
-        return dao.getById(invoiceId.toString());
+    public List<Invoice> getInvoicesByAccount(final UUID accountId, final DateTime fromDate) {
+        return dao.getInvoicesByAccount(accountId, fromDate);
+    }
+
+    @Override
+    public void notifyOfPaymentAttempt(InvoicePayment invoicePayment) {
+        dao.notifyOfPaymentAttempt(invoicePayment);
+    }
+
+    @Override
+	public BigDecimal getAccountBalance(UUID accountId) {
+		BigDecimal result = dao.getAccountBalance(accountId);
+		return result == null ? BigDecimal.ZERO : result;
+	}
+
+    @Override
+    public List<InvoiceItem> getInvoiceItemsByAccount(final UUID accountId) {
+        return dao.getInvoiceItemsByAccount(accountId);
     }
 
     @Override
-    public void paymentAttemptFailed(UUID invoiceId, UUID paymentId, DateTime paymentAttemptDate) {
-        dao.notifyFailedPayment(invoiceId.toString(), paymentId.toString(), paymentAttemptDate.toDate());
+    public Invoice getInvoice(final UUID invoiceId) {
+        return dao.getById(invoiceId);
     }
 
     @Override
-    public void paymentAttemptSuccessful(UUID invoiceId, BigDecimal amount, Currency currency, UUID paymentId, DateTime paymentDate) {
-        dao.notifySuccessfulPayment(invoiceId.toString(), amount, currency.toString(), paymentId.toString(), paymentDate.toDate());
+    public List<Invoice> getUnpaidInvoicesByAccountId(final UUID accountId, final DateTime upToDate) {
+        return dao.getUnpaidInvoicesByAccountId(accountId, upToDate);
     }
 }
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 20f33c7..3b27fa6 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
@@ -16,54 +16,113 @@
 
 package com.ning.billing.invoice.dao;
 
-import sun.reflect.generics.reflectiveObjects.NotImplementedException;
+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.google.inject.Inject;
-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.user.DefaultInvoiceCreationNotification;
-import com.ning.billing.util.eventbus.EventBus;
+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 java.math.BigDecimal;
-import java.util.Date;
-import java.util.List;
-import java.util.UUID;
+import com.google.inject.Inject;
+import com.ning.billing.entitlement.api.billing.EntitlementBillingApi;
+import com.ning.billing.invoice.api.DefaultInvoiceService;
+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.DefaultNextBillingDateNotifier;
+import com.ning.billing.invoice.notification.NextBillingDatePoster;
+import com.ning.billing.util.bus.Bus;
+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 DefaultInvoiceDao implements InvoiceDao {
-    private final InvoiceSqlDao invoiceDao;
-
-    private final EventBus eventBus;
     private final static Logger log = LoggerFactory.getLogger(DefaultInvoiceDao.class);
 
+    private final InvoiceSqlDao invoiceSqlDao;
+    private final RecurringInvoiceItemSqlDao recurringInvoiceItemSqlDao;
+    private final FixedPriceInvoiceItemSqlDao fixedPriceInvoiceItemSqlDao;
+    private final InvoicePaymentSqlDao invoicePaymentSqlDao;
+    private final EntitlementBillingApi entitlementBillingApi;
+
+    private final Bus eventBus;
+
+	private NextBillingDatePoster nextBillingDatePoster;
+
     @Inject
-    public DefaultInvoiceDao(final IDBI dbi, final EventBus eventBus) {
-        this.invoiceDao = dbi.onDemand(InvoiceSqlDao.class);
+    public DefaultInvoiceDao(final IDBI dbi, final Bus eventBus,
+                             final EntitlementBillingApi entitlementBillingApi,
+                             NextBillingDatePoster nextBillingDatePoster) {
+        this.invoiceSqlDao = dbi.onDemand(InvoiceSqlDao.class);
+        this.recurringInvoiceItemSqlDao = dbi.onDemand(RecurringInvoiceItemSqlDao.class);
+        this.fixedPriceInvoiceItemSqlDao = dbi.onDemand(FixedPriceInvoiceItemSqlDao.class);
+        this.invoicePaymentSqlDao = dbi.onDemand(InvoicePaymentSqlDao.class);
         this.eventBus = eventBus;
+        this.entitlementBillingApi = entitlementBillingApi;
+        this.nextBillingDatePoster = nextBillingDatePoster;
+    }
+
+    @Override
+    public List<Invoice> getInvoicesByAccount(final UUID accountId) {
+        return invoiceSqlDao.inTransaction(new Transaction<List<Invoice>, InvoiceSqlDao>() {
+            @Override
+            public List<Invoice> inTransaction(final InvoiceSqlDao invoiceDao, final TransactionStatus status) throws Exception {
+                List<Invoice> invoices = invoiceDao.getInvoicesByAccount(accountId.toString());
+
+                getInvoiceItemsWithinTransaction(invoices, invoiceDao);
+                getInvoicePaymentsWithinTransaction(invoices, invoiceDao);
+
+                return invoices;
+            }
+        });
     }
 
     @Override
-    public List<Invoice> getInvoicesByAccount(final String accountId) {
-        return invoiceDao.getInvoicesByAccount(accountId);
+    public List<Invoice> getInvoicesByAccount(final UUID accountId, final DateTime fromDate) {
+        return invoiceSqlDao.inTransaction(new Transaction<List<Invoice>, InvoiceSqlDao>() {
+            @Override
+            public List<Invoice> inTransaction(final InvoiceSqlDao invoiceDao, final TransactionStatus status) throws Exception {
+                List<Invoice> invoices = invoiceDao.getInvoicesByAccountAfterDate(accountId.toString(), fromDate.toDate());
+
+                getInvoiceItemsWithinTransaction(invoices, invoiceDao);
+                getInvoicePaymentsWithinTransaction(invoices, invoiceDao);
+
+                return invoices;
+            }
+        });
+    }
+
+    @Override
+    public List<InvoiceItem> getInvoiceItemsByAccount(final UUID accountId) {
+        List<InvoiceItem> results = new ArrayList<InvoiceItem>();
+        results.addAll(recurringInvoiceItemSqlDao.getInvoiceItemsByAccount(accountId.toString()));
+        results.addAll(fixedPriceInvoiceItemSqlDao.getInvoiceItemsByAccount(accountId.toString()));
+        return results;
     }
 
     @Override
     public List<Invoice> get() {
-        return invoiceDao.inTransaction(new Transaction<List<Invoice>, InvoiceSqlDao>() {
+        return invoiceSqlDao.inTransaction(new Transaction<List<Invoice>, InvoiceSqlDao>() {
              @Override
              public List<Invoice> inTransaction(final InvoiceSqlDao invoiceDao, final TransactionStatus status) throws Exception {
                  List<Invoice> invoices = invoiceDao.get();
 
-                 InvoiceItemSqlDao invoiceItemDao = invoiceDao.become(InvoiceItemSqlDao.class);
-                 for (Invoice invoice : invoices) {
-                     List<InvoiceItem> invoiceItems = invoiceItemDao.getInvoiceItemsByInvoice(invoice.getId().toString());
-                     invoice.add(invoiceItems);
-                 }
+                 getInvoiceItemsWithinTransaction(invoices, invoiceDao);
+                 getInvoicePaymentsWithinTransaction(invoices, invoiceDao);
 
                  return invoices;
              }
@@ -71,16 +130,15 @@ public class DefaultInvoiceDao implements InvoiceDao {
     }
 
     @Override
-    public Invoice getById(final String invoiceId) {
-        return invoiceDao.inTransaction(new Transaction<Invoice, InvoiceSqlDao>() {
+    public Invoice getById(final UUID invoiceId) {
+        return invoiceSqlDao.inTransaction(new Transaction<Invoice, InvoiceSqlDao>() {
              @Override
              public Invoice inTransaction(final InvoiceSqlDao invoiceDao, final TransactionStatus status) throws Exception {
-                 Invoice invoice = invoiceDao.getById(invoiceId);
+                 Invoice invoice = invoiceDao.getById(invoiceId.toString());
 
                  if (invoice != null) {
-                     InvoiceItemSqlDao invoiceItemDao = invoiceDao.become(InvoiceItemSqlDao.class);
-                     List<InvoiceItem> invoiceItems = invoiceItemDao.getInvoiceItemsByInvoice(invoiceId);
-                     invoice.add(invoiceItems);
+                     getInvoiceItemsWithinTransaction(invoice, invoiceDao);
+                     getInvoicePaymentsWithinTransaction(invoice, invoiceDao);
                  }
 
                  return invoice;
@@ -90,65 +148,172 @@ public class DefaultInvoiceDao implements InvoiceDao {
 
     @Override
     public void create(final Invoice invoice) {
-         invoiceDao.inTransaction(new Transaction<Void, InvoiceSqlDao>() {
-             @Override
-             public Void inTransaction(final InvoiceSqlDao invoiceDao, final TransactionStatus status) throws Exception {
+        invoiceSqlDao.inTransaction(new Transaction<Void, InvoiceSqlDao>() {
+            @Override
+            public Void inTransaction(final InvoiceSqlDao invoiceDao, final TransactionStatus status) throws Exception {
+
+                // STEPH this seems useless
                 Invoice currentInvoice = invoiceDao.getById(invoice.getId().toString());
 
                 if (currentInvoice == null) {
                     invoiceDao.create(invoice);
 
-                    List<InvoiceItem> invoiceItems = invoice.getItems();
-                    InvoiceItemSqlDao invoiceItemDao = invoiceDao.become(InvoiceItemSqlDao.class);
-                    invoiceItemDao.create(invoiceItems);
+                    List<InvoiceItem> recurringInvoiceItems = invoice.getInvoiceItems(RecurringInvoiceItem.class);
+                    RecurringInvoiceItemSqlDao recurringInvoiceItemDao = invoiceDao.become(RecurringInvoiceItemSqlDao.class);
+                    recurringInvoiceItemDao.batchCreateFromTransaction(recurringInvoiceItems);
+
+                    notifyOfFutureBillingEvents(invoiceSqlDao, recurringInvoiceItems);
+
+                    List<InvoiceItem> fixedPriceInvoiceItems = invoice.getInvoiceItems(FixedPriceInvoiceItem.class);
+                    FixedPriceInvoiceItemSqlDao fixedPriceInvoiceItemDao = invoiceDao.become(FixedPriceInvoiceItemSqlDao.class);
+                    fixedPriceInvoiceItemDao.batchCreateFromTransaction(fixedPriceInvoiceItems);
+
+                    setChargedThroughDates(invoiceSqlDao, fixedPriceInvoiceItems, recurringInvoiceItems);
+
+                    // 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.batchCreateFromTransaction(invoicePayments);
 
                     InvoiceCreationNotification event;
                     event = new DefaultInvoiceCreationNotification(invoice.getId(), invoice.getAccountId(),
-                                                                  invoice.getAmountOutstanding(), invoice.getCurrency(),
+                                                                  invoice.getBalance(), invoice.getCurrency(),
                                                                   invoice.getInvoiceDate());
-                    eventBus.post(event);
+                    eventBus.postFromTransaction(event, invoiceDao);
                 }
 
                 return null;
-             }
-         });
+            }
+        });
     }
 
     @Override
-    public List<Invoice> getInvoicesBySubscription(final String subscriptionId) {
-        return invoiceDao.inTransaction(new Transaction<List<Invoice>, InvoiceSqlDao>() {
-             @Override
-             public List<Invoice> inTransaction(InvoiceSqlDao invoiceDao, TransactionStatus status) throws Exception {
-                 List<Invoice> invoices = invoiceDao.getInvoicesBySubscription(subscriptionId);
+    public List<Invoice> getInvoicesBySubscription(final UUID subscriptionId) {
+        return invoiceSqlDao.inTransaction(new Transaction<List<Invoice>, InvoiceSqlDao>() {
+            @Override
+            public List<Invoice> inTransaction(final InvoiceSqlDao invoiceDao, final TransactionStatus status) throws Exception {
+                List<Invoice> invoices = invoiceDao.getInvoicesBySubscription(subscriptionId.toString());
 
-                 InvoiceItemSqlDao invoiceItemDao = invoiceDao.become(InvoiceItemSqlDao.class);
-                 for (Invoice invoice : invoices) {
-                     List<InvoiceItem> invoiceItems = invoiceItemDao.getInvoiceItemsByInvoice(invoice.getId().toString());
-                     invoice.add(invoiceItems);
-                 }
+                getInvoiceItemsWithinTransaction(invoices, invoiceDao);
+                getInvoicePaymentsWithinTransaction(invoices, invoiceDao);
 
-                 return invoices;
-             }
+                return invoices;
+            }
         });
     }
 
     @Override
-    public List<UUID> getInvoicesForPayment(Date targetDate, int numberOfDays) {
-        return invoiceDao.getInvoicesForPayment(targetDate, numberOfDays);
+    public List<UUID> getInvoicesForPayment(final DateTime targetDate, final int numberOfDays) {
+        return invoiceSqlDao.getInvoicesForPayment(targetDate.toDate(), numberOfDays);
+    }
+
+    @Override
+    public BigDecimal getAccountBalance(final UUID accountId) {
+        return invoiceSqlDao.getAccountBalance(accountId.toString());
+    }
+
+    @Override
+    public void notifyOfPaymentAttempt(InvoicePayment invoicePayment) {
+        invoicePaymentSqlDao.notifyOfPaymentAttempt(invoicePayment);
+    }
+
+    @Override
+    public List<Invoice> getUnpaidInvoicesByAccountId(final UUID accountId, final DateTime upToDate) {
+        return invoiceSqlDao.inTransaction(new Transaction<List<Invoice>, InvoiceSqlDao>() {
+            @Override
+            public List<Invoice> inTransaction(final InvoiceSqlDao invoiceDao, final TransactionStatus status) throws Exception {
+                List<Invoice> invoices = invoiceSqlDao.getUnpaidInvoicesByAccountId(accountId.toString(), upToDate.toDate());
+
+                getInvoiceItemsWithinTransaction(invoices, invoiceDao);
+                getInvoicePaymentsWithinTransaction(invoices, invoiceDao);
+
+                return invoices;
+            }
+        });
     }
 
     @Override
-    public void notifySuccessfulPayment(String invoiceId, BigDecimal paymentAmount, String currency, String paymentId, Date paymentDate) {
-        invoiceDao.notifySuccessfulPayment(invoiceId, paymentAmount, currency, paymentId, paymentDate);
+    public UUID getInvoiceIdByPaymentAttemptId(UUID paymentAttemptId) {
+        return invoiceSqlDao.getInvoiceIdByPaymentAttemptId(paymentAttemptId.toString());
     }
 
     @Override
-    public void notifyFailedPayment(String invoiceId, String paymentId, Date paymentAttemptDate) {
-        invoiceDao.notifyFailedPayment(invoiceId, paymentId, paymentAttemptDate);
+    public InvoicePayment getInvoicePayment(UUID paymentAttemptId) {
+        return invoicePaymentSqlDao.getInvoicePayment(paymentAttemptId);
     }
 
     @Override
     public void test() {
-        invoiceDao.test();
+        invoiceSqlDao.test();
+    }
+
+    private void getInvoiceItemsWithinTransaction(final List<Invoice> invoices, final InvoiceSqlDao invoiceDao) {
+        for (final Invoice invoice : invoices) {
+            getInvoiceItemsWithinTransaction(invoice, invoiceDao);
+        }
+    }
+
+    private void getInvoiceItemsWithinTransaction(final Invoice invoice, final InvoiceSqlDao invoiceDao) {
+        RecurringInvoiceItemSqlDao recurringInvoiceItemDao = invoiceDao.become(RecurringInvoiceItemSqlDao.class);
+        List<InvoiceItem> recurringInvoiceItems = recurringInvoiceItemDao.getInvoiceItemsByInvoice(invoice.getId().toString());
+        invoice.addInvoiceItems(recurringInvoiceItems);
+
+        FixedPriceInvoiceItemSqlDao fixedPriceInvoiceItemDao = invoiceDao.become(FixedPriceInvoiceItemSqlDao.class);
+        List<InvoiceItem> fixedPriceInvoiceItems = fixedPriceInvoiceItemDao.getInvoiceItemsByInvoice(invoice.getId().toString());
+        invoice.addInvoiceItems(fixedPriceInvoiceItems);
+    }
+
+    private void getInvoicePaymentsWithinTransaction(final List<Invoice> invoices, final InvoiceSqlDao invoiceDao) {
+        for (Invoice invoice : invoices) {
+            getInvoicePaymentsWithinTransaction(invoice, invoiceDao);
+        }
+    }
+
+    private void getInvoicePaymentsWithinTransaction(final Invoice invoice, final InvoiceSqlDao invoiceDao) {
+        InvoicePaymentSqlDao invoicePaymentSqlDao = invoiceDao.become(InvoicePaymentSqlDao.class);
+        String invoiceId = invoice.getId().toString();
+        List<InvoicePayment> invoicePayments = invoicePaymentSqlDao.getPaymentsForInvoice(invoiceId);
+        invoice.addPayments(invoicePayments);
+    }
+
+    private void notifyOfFutureBillingEvents(final InvoiceSqlDao dao, final List<InvoiceItem> invoiceItems) {
+        for (final InvoiceItem item : invoiceItems) {
+            if (item instanceof RecurringInvoiceItem) {
+                RecurringInvoiceItem recurringInvoiceItem = (RecurringInvoiceItem) item;
+                if ((recurringInvoiceItem.getEndDate() != null) &&
+                        (recurringInvoiceItem.getAmount() == null ||
+                                recurringInvoiceItem.getAmount().compareTo(BigDecimal.ZERO) >= 0)) {
+                	nextBillingDatePoster.insertNextBillingNotification(dao, item.getSubscriptionId(), recurringInvoiceItem.getEndDate());
+                }
+            }
+        }
+    }
+    
+    private void setChargedThroughDates(final InvoiceSqlDao dao, final Collection<InvoiceItem> fixedPriceItems,
+                                        final Collection<InvoiceItem> recurringItems) {
+        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);
+        }
+    }
+
+    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
new file mode 100644
index 0000000..330784e
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/FixedPriceInvoiceItemSqlDao.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.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.entity.EntityDao;
+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.BinderFactory;
+import org.skife.jdbi.v2.sqlobject.BindingAnnotation;
+import org.skife.jdbi.v2.sqlobject.SqlBatch;
+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.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;
+
+@ExternalizedSqlViaStringTemplate3()
+@RegisterMapper(FixedPriceInvoiceItemSqlDao.FixedPriceInvoiceItemMapper.class)
+public interface FixedPriceInvoiceItemSqlDao extends EntityDao<InvoiceItem> {
+    @SqlQuery
+    List<InvoiceItem> getInvoiceItemsByInvoice(@Bind("invoiceId") final String invoiceId);
+
+    @SqlQuery
+    List<InvoiceItem> getInvoiceItemsByAccount(@Bind("accountId") final String accountId);
+
+    @SqlQuery
+    List<InvoiceItem> getInvoiceItemsBySubscription(@Bind("subscriptionId") final String subscriptionId);
+
+    @Override
+    @SqlUpdate
+    void create(@FixedPriceInvoiceItemBinder final InvoiceItem invoiceItem);
+
+    @Override
+    @SqlUpdate
+    void update(@FixedPriceInvoiceItemBinder final InvoiceItem invoiceItem);
+
+    @SqlBatch
+    void create(@FixedPriceInvoiceItemBinder final List<InvoiceItem> items);
+
+    @SqlBatch(transactional=false)
+    void batchCreateFromTransaction(@FixedPriceInvoiceItemBinder final List<InvoiceItem> items);
+
+    @BindingAnnotation(FixedPriceInvoiceItemBinder.FixedPriceInvoiceItemBinderFactory.class)
+    @Retention(RetentionPolicy.RUNTIME)
+    @Target({ElementType.PARAMETER})
+    public @interface FixedPriceInvoiceItemBinder {
+        public static class FixedPriceInvoiceItemBinderFactory implements BinderFactory {
+            public Binder build(Annotation annotation) {
+                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("subscriptionId", item.getSubscriptionId().toString());
+                        q.bind("planName", item.getPlanName());
+                        q.bind("phaseName", item.getPhaseName());
+                        q.bind("startDate", item.getStartDate().toDate());
+                        q.bind("endDate", item.getEndDate().toDate());
+                        q.bind("amount", item.getAmount());
+                        q.bind("currency", item.getCurrency().toString());
+                    }
+                };
+            }
+        }
+    }
+
+    public static class FixedPriceInvoiceItemMapper implements ResultSetMapper<InvoiceItem> {
+        @Override
+        public FixedPriceInvoiceItem 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 subscriptionId = 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"));
+
+            return new FixedPriceInvoiceItem(id, invoiceId, 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/InvoiceDao.java b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceDao.java
index e9306b2..7a7c280 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceDao.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceDao.java
@@ -16,35 +16,42 @@
 
 package com.ning.billing.invoice.dao;
 
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.api.InvoicePayment;
+import org.joda.time.DateTime;
+
 import java.math.BigDecimal;
-import java.util.Date;
 import java.util.List;
 import java.util.UUID;
-import com.ning.billing.invoice.api.Invoice;
 
 public interface InvoiceDao {
     void create(Invoice invoice);
 
-    Invoice getById(final String id);
+    Invoice getById(final UUID id);
 
     List<Invoice> get();
 
-    List<Invoice> getInvoicesByAccount(final String accountId);
+    List<Invoice> getInvoicesByAccount(final UUID accountId);
+
+    List<Invoice> getInvoicesByAccount(final UUID accountId, final DateTime fromDate);
 
-    List<Invoice> getInvoicesBySubscription(final String subscriptionId);
+    List<InvoiceItem> getInvoiceItemsByAccount(final UUID accountId);
 
-    List<UUID> getInvoicesForPayment(final Date targetDate,
+    List<Invoice> getInvoicesBySubscription(final UUID subscriptionId);
+
+    List<UUID> getInvoicesForPayment(final DateTime targetDate,
                                      final int numberOfDays);
 
-    void notifySuccessfulPayment(final String invoiceId,
-                                 final BigDecimal paymentAmount,
-                                 final String currency,
-                                 final String paymentId,
-                                 final Date paymentDate);
+    UUID getInvoiceIdByPaymentAttemptId(final UUID paymentAttemptId);
+
+    InvoicePayment getInvoicePayment(final UUID paymentAttemptId);
+
+    void notifyOfPaymentAttempt(final InvoicePayment invoicePayment);
+
+    BigDecimal getAccountBalance(final UUID accountId);
 
-    void notifyFailedPayment(final String invoiceId,
-                             final String paymentId,
-                             final Date paymentAttemptDate);
+    List<Invoice> getUnpaidInvoicesByAccountId(final UUID accountId, final DateTime upToDate);
 
     void test();
 }
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
new file mode 100644
index 0000000..7179ec1
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoicePaymentSqlDao.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * 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.dao;
+
+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.sql.Timestamp;
+import java.util.List;
+import java.util.UUID;
+
+import com.ning.billing.catalog.api.Currency;
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.skife.jdbi.v2.SQLStatement;
+import org.skife.jdbi.v2.StatementContext;
+import org.skife.jdbi.v2.sqlobject.*;
+import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
+import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
+import org.skife.jdbi.v2.tweak.ResultSetMapper;
+
+import com.ning.billing.invoice.api.InvoicePayment;
+
+@ExternalizedSqlViaStringTemplate3
+@RegisterMapper(InvoicePaymentSqlDao.InvoicePaymentMapper.class)
+public interface InvoicePaymentSqlDao {
+    @SqlQuery
+    public InvoicePayment getByPaymentAttemptId(@Bind("paymentAttempt") final String paymentAttemptId);
+
+    @SqlQuery
+    public List<InvoicePayment> get();
+
+    @SqlUpdate
+    public void create(@InvoicePaymentBinder  InvoicePayment invoicePayment);
+
+    @SqlBatch(transactional=false)
+    void batchCreateFromTransaction(@InvoicePaymentBinder List<InvoicePayment> items);
+
+    @SqlUpdate
+    public void update(@InvoicePaymentBinder  InvoicePayment invoicePayment);
+
+    @SqlQuery
+    public List<InvoicePayment> getPaymentsForInvoice(@Bind("invoiceId") String invoiceId);
+
+    @SqlQuery
+    InvoicePayment getInvoicePayment(@Bind("paymentAttemptId") UUID paymentAttemptId);
+
+    @SqlUpdate
+    void notifyOfPaymentAttempt(@InvoicePaymentBinder InvoicePayment invoicePayment);
+
+    public static class InvoicePaymentMapper implements ResultSetMapper<InvoicePayment> {
+        private DateTime getDate(ResultSet rs, String fieldName) throws SQLException {
+            final Timestamp resultStamp = rs.getTimestamp(fieldName);
+            return rs.wasNull() ? null : new DateTime(resultStamp).toDateTime(DateTimeZone.UTC);
+        }
+
+        @Override
+        public InvoicePayment map(int index, ResultSet result, StatementContext context) throws SQLException {
+            final UUID paymentAttemptId = UUID.fromString(result.getString("payment_attempt_id"));
+            final UUID invoiceId = UUID.fromString(result.getString("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 DateTime createdDate = getDate(result, "created_date");
+            final DateTime updatedDate = getDate(result, "updated_date");
+
+            return new InvoicePayment() {
+                private final  DateTime now = new DateTime();
+
+                @Override
+                public UUID getPaymentAttemptId() {
+                    return paymentAttemptId;
+                }
+                @Override
+                public UUID getInvoiceId() {
+                    return invoiceId;
+                }
+                @Override
+                public DateTime getPaymentAttemptDate() {
+                    return paymentAttemptDate;
+                }
+                @Override
+                public BigDecimal getAmount() {
+                    return amount;
+                }
+                @Override
+                public Currency getCurrency() {
+                    return currency;
+                }
+                @Override
+                public DateTime getCreatedDate() {
+                    return createdDate ;
+                }
+                @Override
+                public DateTime getUpdatedDate() {
+                    return updatedDate;
+                }
+            };
+        }
+    }
+
+    @BindingAnnotation(InvoicePaymentBinder.InvoicePaymentBinderFactory.class)
+    @Retention(RetentionPolicy.RUNTIME)
+    @Target({ElementType.PARAMETER})
+    public @interface InvoicePaymentBinder {
+        public static class InvoicePaymentBinderFactory implements BinderFactory {
+            @Override
+            public Binder build(Annotation annotation) {
+                return new Binder<InvoicePaymentBinder, InvoicePayment>() {
+                    @Override
+                    public void bind(SQLStatement q, InvoicePaymentBinder bind, InvoicePayment payment) {
+                        q.bind("invoiceId", payment.getInvoiceId().toString());
+                        q.bind("paymentAttemptId", payment.getPaymentAttemptId().toString());
+                        q.bind("paymentAttemptDate", payment.getPaymentAttemptDate().toDate());
+                        q.bind("amount", payment.getAmount());
+                        Currency currency = payment.getCurrency();
+                        q.bind("currency", (currency == null) ? null : currency.toString());
+                        DateTime createdDate = payment.getCreatedDate();
+                        q.bind("createdDate", (createdDate == null) ? new DateTime().toDate() : createdDate.toDate());
+                        DateTime updatedDate = payment.getUpdatedDate();
+                        q.bind("updatedDate", (updatedDate == null) ? new DateTime().toDate() : updatedDate.toDate());
+                    }
+                };
+            }
+        }
+    }
+}
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 9ede145..05be556 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
@@ -18,7 +18,6 @@ package com.ning.billing.invoice.dao;
 
 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.model.DefaultInvoice;
 import com.ning.billing.util.UuidMapper;
 import com.ning.billing.util.entity.EntityDao;
@@ -38,18 +37,20 @@ 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.lang.annotation.*;
+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.sql.Timestamp;
-import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
 import java.util.UUID;
 
 @ExternalizedSqlViaStringTemplate3()
-@RegisterMapper({UuidMapper.class, InvoiceSqlDao.InvoiceMapper.class})
+@RegisterMapper(InvoiceSqlDao.InvoiceMapper.class)
 public interface InvoiceSqlDao extends EntityDao<Invoice>, Transactional<InvoiceSqlDao>, Transmogrifier, CloseMe {
     @Override
     @SqlUpdate
@@ -63,38 +64,44 @@ public interface InvoiceSqlDao extends EntityDao<Invoice>, Transactional<Invoice
     List<Invoice> getInvoicesByAccount(@Bind("accountId") final String accountId);
 
     @SqlQuery
+    List<Invoice> getInvoicesByAccountAfterDate(@Bind("accountId") final String accountId,
+                                                @Bind("fromDate") final Date fromDate);
+
+    @SqlQuery
     List<Invoice> getInvoicesBySubscription(@Bind("subscriptionId") final String subscriptionId);
 
     @SqlQuery
-    List<UUID> getInvoicesForPayment(@Bind("targetDate") final Date targetDate,
-                                     @Bind("numberOfDays") final int numberOfDays);
+    @RegisterMapper(UuidMapper.class)
+    UUID getInvoiceIdByPaymentAttemptId(@Bind("paymentAttemptId") final String paymentAttemptId);
 
-    @SqlUpdate
-    void notifySuccessfulPayment(@Bind("invoiceId") final String invoiceId,
-                                 @Bind("amount") final BigDecimal paymentAmount,
-                                 @Bind("currency") final String currency,
-                                 @Bind("paymentId") final String paymentId,
-                                 @Bind("paymentDate") final Date paymentDate);
+    @SqlQuery
+    @RegisterMapper(UuidMapper.class)
+    List<UUID> getInvoicesForPayment(@Bind("targetDate") final Date targetDate,
+                                    @Bind("numberOfDays") final int numberOfDays);
 
-    @SqlUpdate
-    void notifyFailedPayment(@Bind("invoiceId") final String invoiceId,
-                             @Bind("paymentId") final String paymentId,
-                             @Bind("paymentAttemptDate") final Date paymentAttemptDate);
+    @SqlQuery
+    @RegisterMapper(BalanceMapper.class)
+    BigDecimal getAccountBalance(@Bind("accountId") final String accountId);
 
+    @SqlQuery
+    List<Invoice> getUnpaidInvoicesByAccountId(@Bind("accountId") final String accountId,
+                                               @Bind("upToDate") final Date upToDate);
     @BindingAnnotation(InvoiceBinder.InvoiceBinderFactory.class)
     @Retention(RetentionPolicy.RUNTIME)
     @Target({ElementType.PARAMETER})
     public @interface InvoiceBinder {
         public static class InvoiceBinderFactory implements BinderFactory {
-            public Binder build(Annotation annotation) {
+            @Override
+            public Binder<InvoiceBinder, Invoice> build(Annotation annotation) {
                 return new Binder<InvoiceBinder, Invoice>() {
-                    public void bind(SQLStatement q, InvoiceBinder bind, Invoice invoice) {
+                    @Override
+                    public void bind(@SuppressWarnings("rawtypes") SQLStatement q, InvoiceBinder bind, Invoice invoice) {
                         q.bind("id", invoice.getId().toString());
                         q.bind("accountId", invoice.getAccountId().toString());
                         q.bind("invoiceDate", invoice.getInvoiceDate().toDate());
                         q.bind("targetDate", invoice.getTargetDate().toDate());
                         q.bind("amountPaid", invoice.getAmountPaid());
-                        q.bind("amountOutstanding", invoice.getAmountOutstanding());
+                        q.bind("amountOutstanding", invoice.getBalance());
                         DateTime last_payment_date = invoice.getLastPaymentAttempt();
                         q.bind("lastPaymentAttempt", last_payment_date == null ? null : last_payment_date.toDate());
                         q.bind("currency", invoice.getCurrency().toString());
@@ -111,16 +118,30 @@ public interface InvoiceSqlDao extends EntityDao<Invoice>, Transactional<Invoice
             UUID accountId = UUID.fromString(result.getString("account_id"));
             DateTime invoiceDate = new DateTime(result.getTimestamp("invoice_date"));
             DateTime targetDate = new DateTime(result.getTimestamp("target_date"));
+            Currency currency = Currency.valueOf(result.getString("currency"));
+
+            return new DefaultInvoice(id, accountId, invoiceDate, targetDate, currency);
+        }
+    }
+
+    public static class BalanceMapper implements ResultSetMapper<BigDecimal> {
+        @Override
+        public BigDecimal map(final int index, final ResultSet result, final StatementContext context) throws SQLException {
+            BigDecimal amountInvoiced = result.getBigDecimal("amount_invoiced");
             BigDecimal amountPaid = result.getBigDecimal("amount_paid");
+
+            if (amountInvoiced == null) {
+                amountInvoiced = BigDecimal.ZERO;
+            }
+
             if (amountPaid == null) {
                 amountPaid = BigDecimal.ZERO;
             }
-            Timestamp lastPaymentAttemptTimeStamp = result.getTimestamp("last_payment_attempt");
-            DateTime lastPaymentAttempt = lastPaymentAttemptTimeStamp == null ? null : new DateTime(lastPaymentAttemptTimeStamp);
-            Currency currency = Currency.valueOf(result.getString("currency"));
 
-            return new DefaultInvoice(id, accountId, invoiceDate, targetDate, currency, lastPaymentAttempt, amountPaid, new ArrayList<InvoiceItem>());
+            return amountInvoiced.subtract(amountPaid);
         }
     }
+
+
 }
 
diff --git a/invoice/src/main/java/com/ning/billing/invoice/glue/InvoiceModule.java b/invoice/src/main/java/com/ning/billing/invoice/glue/InvoiceModule.java
index 54990c5..1dfac5b 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/glue/InvoiceModule.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/glue/InvoiceModule.java
@@ -16,16 +16,31 @@
 
 package com.ning.billing.invoice.glue;
 
+import org.skife.config.ConfigurationObjectFactory;
+
 import com.google.inject.AbstractModule;
+import com.ning.billing.config.InvoiceConfig;
+import com.ning.billing.invoice.InvoiceListener;
+import com.ning.billing.invoice.api.DefaultInvoiceService;
 import com.ning.billing.invoice.api.InvoicePaymentApi;
+import com.ning.billing.invoice.api.InvoiceService;
+import com.ning.billing.invoice.api.InvoiceUserApi;
 import com.ning.billing.invoice.api.invoice.DefaultInvoicePaymentApi;
 import com.ning.billing.invoice.api.user.DefaultInvoiceUserApi;
-import com.ning.billing.invoice.api.InvoiceUserApi;
 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.invoice.notification.DefaultNextBillingDateNotifier;
+import com.ning.billing.invoice.notification.DefaultNextBillingDatePoster;
+import com.ning.billing.invoice.notification.NextBillingDateNotifier;
+import com.ning.billing.invoice.notification.NextBillingDatePoster;
+import com.ning.billing.util.glue.ClockModule;
+import com.ning.billing.util.glue.GlobalLockerModule;
+
 
 public class InvoiceModule extends AbstractModule {
-    private void installInvoiceDao() {
+    protected void installInvoiceDao() {
         bind(InvoiceDao.class).to(DefaultInvoiceDao.class).asEagerSingleton();
     }
 
@@ -37,8 +52,36 @@ public class InvoiceModule extends AbstractModule {
         bind(InvoicePaymentApi.class).to(DefaultInvoicePaymentApi.class).asEagerSingleton();
     }
 
+    protected void installClock() {
+    	install(new ClockModule());
+    }
+
+    protected void installConfig() {
+        final InvoiceConfig config = new ConfigurationObjectFactory(System.getProperties()).build(InvoiceConfig.class);
+        bind(InvoiceConfig.class).toInstance(config);
+    }
+
+    protected void installInvoiceService() {
+        bind(InvoiceService.class).to(DefaultInvoiceService.class).asEagerSingleton();
+    }
+
+    protected void installNotifier() {
+        bind(NextBillingDateNotifier.class).to(DefaultNextBillingDateNotifier.class).asEagerSingleton();
+        bind(NextBillingDatePoster.class).to(DefaultNextBillingDatePoster.class).asEagerSingleton();
+    }
+
+    protected void installInvoiceListener() {
+        install(new GlobalLockerModule());
+        bind(InvoiceListener.class).asEagerSingleton();
+    }
+
     @Override
     protected void configure() {
+        installInvoiceService();
+        installNotifier();
+        installInvoiceListener();
+        bind(InvoiceGenerator.class).to(DefaultInvoiceGenerator.class).asEagerSingleton();
+        installConfig();
         installInvoiceDao();
         installInvoiceUserApi();
         installInvoicePaymentApi();
diff --git a/invoice/src/main/java/com/ning/billing/invoice/InvoiceDispatcher.java b/invoice/src/main/java/com/ning/billing/invoice/InvoiceDispatcher.java
new file mode 100644
index 0000000..0a23d01
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/InvoiceDispatcher.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.invoice;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.SortedSet;
+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.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.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceApiException;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.dao.InvoiceDao;
+import com.ning.billing.invoice.model.BillingEventSet;
+import com.ning.billing.invoice.model.InvoiceGenerator;
+import com.ning.billing.invoice.model.InvoiceItemList;
+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;
+
+public class InvoiceDispatcher {
+	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 AccountUserApi accountUserApi;
+    private final InvoiceDao invoiceDao;
+    private final GlobalLocker locker;
+
+    private final static boolean VERBOSE_OUTPUT = false;
+    @Inject
+    public InvoiceDispatcher(final InvoiceGenerator generator, final AccountUserApi accountUserApi,
+                           final EntitlementBillingApi entitlementBillingApi,
+                           final InvoiceDao invoiceDao,
+                           final GlobalLocker locker) {
+        this.generator = generator;
+        this.entitlementBillingApi = entitlementBillingApi;
+        this.accountUserApi = accountUserApi;
+        this.invoiceDao = invoiceDao;
+        this.locker = locker;
+    }
+
+
+    public void processSubscription(final SubscriptionTransition transition) throws InvoiceApiException {
+        UUID subscriptionId = transition.getSubscriptionId();
+        DateTime targetDate = transition.getEffectiveTransitionTime();
+        log.info("Got subscription transition from InvoiceListener. id: " + subscriptionId.toString() + "; targetDate: " + targetDate.toString());
+        log.info("Transition type: " + transition.getTransitionType().toString());
+        processSubscription(subscriptionId, targetDate);
+    }
+
+    public void processSubscription(final UUID subscriptionId, final DateTime targetDate) 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) {
+            log.error("Failed handling entitlement change.",
+                    new InvoiceApiException(ErrorCode.INVOICE_NO_ACCOUNT_ID_FOR_SUBSCRIPTION_ID, subscriptionId.toString()));
+            return;
+        }
+
+        GlobalLock lock = null;
+        try {
+            lock = locker.lockWithNumberOfTries(LockerService.INVOICE, accountId.toString(), NB_LOCK_TRY);
+
+            processAccountWithLock(accountId, targetDate);
+
+        } catch (LockFailedException e) {
+            // Not good!
+            log.error(String.format("Failed to process invoice for account %s, subscription %s, targetDate %s",
+                    accountId.toString(), subscriptionId.toString(), targetDate), e);
+        } finally {
+            if (lock != null) {
+                lock.release();
+            }
+        }
+    }
+
+    private void processAccountWithLock(final UUID accountId, final DateTime targetDate) throws InvoiceApiException {
+
+        Account account = accountUserApi.getAccountById(accountId);
+        if (account == null) {
+            log.error("Failed handling entitlement change.",
+                    new InvoiceApiException(ErrorCode.INVOICE_ACCOUNT_ID_INVALID, accountId.toString()));
+            return;
+        }
+
+        SortedSet<BillingEvent> events = entitlementBillingApi.getBillingEventsForAccount(accountId);
+        BillingEventSet billingEvents = new BillingEventSet(events);
+
+        Currency targetCurrency = account.getCurrency();
+
+        List<InvoiceItem> items = invoiceDao.getInvoiceItemsByAccount(accountId);
+        InvoiceItemList invoiceItemList = new InvoiceItemList(items);
+        Invoice invoice = generator.generateInvoice(accountId, billingEvents, invoiceItemList, targetDate, targetCurrency);
+
+        if (invoice == null) {
+            log.info("Generated null invoice.");
+            outputDebugData(events, invoiceItemList);
+        } 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, invoiceItemList);
+
+            if (invoice.getNumberOfItems() > 0) {
+                invoiceDao.create(invoice);
+            }
+        }
+    }
+
+    private void outputDebugData(Collection<BillingEvent> events, Collection<InvoiceItem> invoiceItemList) {
+        if (VERBOSE_OUTPUT) {
+            log.info("Events");
+            for (BillingEvent event : events) {
+                log.info(event.toString());
+            }
+
+            log.info("Existing items");
+            for (InvoiceItem item : invoiceItemList) {
+                log.info(item.toString());
+            }
+        }
+    }
+}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/InvoiceListener.java b/invoice/src/main/java/com/ning/billing/invoice/InvoiceListener.java
new file mode 100644
index 0000000..d31e98b
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/InvoiceListener.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * 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 java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.slf4j.Logger;
+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.invoice.api.InvoiceApiException;
+
+public class InvoiceListener {
+    private final static Logger log = LoggerFactory.getLogger(InvoiceListener.class);
+	private final InvoiceDispatcher dispatcher;
+
+    @Inject
+    public InvoiceListener(InvoiceDispatcher dispatcher) {
+        this.dispatcher = dispatcher;
+    }
+
+    @Subscribe
+    public void handleSubscriptionTransition(final SubscriptionTransition transition) {
+        try {
+        	dispatcher.processSubscription(transition);
+        } catch (InvoiceApiException e) {
+            log.error(e.getMessage());
+        }
+    }
+
+    public void handleNextBillingDateEvent(final UUID subscriptionId, final DateTime eventDateTime) {
+        try {
+        	dispatcher.processSubscription(subscriptionId, eventDateTime);
+        } catch (InvoiceApiException e) {
+            log.error(e.getMessage());
+        }
+    }
+}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/BillingMode.java b/invoice/src/main/java/com/ning/billing/invoice/model/BillingMode.java
index 71c4765..4920b88 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/BillingMode.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/BillingMode.java
@@ -19,14 +19,9 @@ package com.ning.billing.invoice.model;
 import com.ning.billing.catalog.api.BillingPeriod;
 import org.joda.time.DateTime;
 
-import java.math.BigDecimal;
+import java.util.List;
 
 public interface BillingMode {
-    BigDecimal calculateNumberOfBillingCycles(DateTime startDate, DateTime endDate, DateTime targetDate, int billingCycleDay, BillingPeriod billingPeriod) throws InvalidDateSequenceException;
-
-    BigDecimal calculateNumberOfBillingCycles(DateTime startDate, DateTime targetDate, int billingCycleDay, BillingPeriod billingPeriod) throws InvalidDateSequenceException;
-
-    DateTime calculateEffectiveEndDate(DateTime startDate, DateTime targetDate, int billingCycleDay, BillingPeriod billingPeriod);
-
-    DateTime calculateEffectiveEndDate(DateTime startDate, DateTime endDate, DateTime targetDate, int billingCycleDay, BillingPeriod billingPeriod);
+    List<RecurringInvoiceItemData> calculateInvoiceItemData(DateTime startDate, DateTime endDate, DateTime targetDate, int billingCycleDay, BillingPeriod billingPeriod) throws InvalidDateSequenceException;
+    List<RecurringInvoiceItemData> calculateInvoiceItemData(DateTime startDate, DateTime targetDate, int billingCycleDay, BillingPeriod billingPeriod) throws InvalidDateSequenceException;
 }
\ No newline at end of file
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/BillingModeBase.java b/invoice/src/main/java/com/ning/billing/invoice/model/BillingModeBase.java
index 949f711..55c646c 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/BillingModeBase.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/BillingModeBase.java
@@ -21,64 +21,62 @@ import org.joda.time.DateTime;
 
 import java.math.BigDecimal;
 
-public abstract class BillingModeBase implements BillingMode {
-    @Override
-    public BigDecimal calculateNumberOfBillingCycles(final DateTime startDate, final DateTime endDate, final DateTime targetDate, final int billingCycleDay, final BillingPeriod billingPeriod) throws InvalidDateSequenceException {
-        if (endDate.isBefore(startDate)) {throw new InvalidDateSequenceException();}
-        if (targetDate.isBefore(startDate)) {throw new InvalidDateSequenceException();}
-
-        BigDecimal precedingProRation = calculateProRationBeforeFirstBillingPeriod(startDate, billingCycleDay, billingPeriod);
-
-        DateTime firstBillCycleDate = calculateBillingCycleDateOnOrAfter(startDate, billingCycleDay);
-        DateTime endBillCycleDate;
-        BigDecimal trailingProRation;
-        BigDecimal numberOfBillingPeriods;
-
-        DateTime effectiveEndDate = calculateEffectiveEndDate(firstBillCycleDate, targetDate, endDate, billingPeriod);
-        endBillCycleDate = calculateLastBillingCycleDateBefore(effectiveEndDate, firstBillCycleDate, billingCycleDay, billingPeriod);
-        numberOfBillingPeriods = calculateNumberOfWholeBillingPeriods(firstBillCycleDate, endBillCycleDate, billingPeriod);
-
-        trailingProRation = calculateProRationAfterLastBillingCycleDate(effectiveEndDate, endBillCycleDate, billingPeriod);
-
-        return precedingProRation.add(numberOfBillingPeriods).add(trailingProRation);
-    }
-
-    @Override
-    public BigDecimal calculateNumberOfBillingCycles(final DateTime startDate, final DateTime targetDate, final int billingCycleDay, final BillingPeriod billingPeriod) throws InvalidDateSequenceException {
-        if (targetDate.isBefore(startDate)) {throw new InvalidDateSequenceException();}
-
-        BigDecimal precedingProRation = calculateProRationBeforeFirstBillingPeriod(startDate, billingCycleDay, billingPeriod);
-
-        DateTime firstBillCycleDate = calculateBillingCycleDateOnOrAfter(startDate, billingCycleDay);
-        DateTime endBillCycleDate = calculateBillingCycleDateAfter(targetDate, firstBillCycleDate, billingCycleDay, billingPeriod);
-        BigDecimal numberOfBillingPeriods = calculateNumberOfWholeBillingPeriods(firstBillCycleDate, endBillCycleDate, billingPeriod);
-
-        return precedingProRation.add(numberOfBillingPeriods);
-    }
-
-    DateTime buildDate(final int year, final int month, final int day) {
-        return new DateTime(year, month, day, 0, 0, 0, 0);
-    }
-
-    boolean isNotBetween(DateTime targetDate, DateTime startDate, DateTime endDate) {
-        return (targetDate.isBefore(startDate) || !targetDate.isBefore(endDate));
-    }
-
-    public abstract DateTime calculateEffectiveEndDate(final DateTime startDate, final DateTime targetDate, final int billingCycleDay, final BillingPeriod billingPeriod);
-
-    public abstract DateTime calculateEffectiveEndDate(final DateTime startDate, final DateTime endDate, final DateTime targetDate, final int billingCycleDay, final BillingPeriod billingPeriod);
-
-    protected abstract BigDecimal calculateNumberOfWholeBillingPeriods(final DateTime startDate, final DateTime endDate, final BillingPeriod billingPeriod);
-
-    protected abstract DateTime calculateBillingCycleDateOnOrAfter(final DateTime date, final int billingCycleDay);
-
-    protected abstract DateTime calculateBillingCycleDateAfter(final DateTime date, final DateTime billingCycleDate, final int billingCycleDay, final BillingPeriod billingPeriod);
-
-    protected abstract DateTime calculateLastBillingCycleDateBefore(final DateTime date, final DateTime previousBillCycleDate, final int billingCycleDay, final BillingPeriod billingPeriod);
-
-    protected abstract BigDecimal calculateProRationBeforeFirstBillingPeriod(final DateTime startDate, final int billingCycleDay, final BillingPeriod billingPeriod);
-
-    protected abstract BigDecimal calculateProRationAfterLastBillingCycleDate(final DateTime endDate, final DateTime previousBillThroughDate, final BillingPeriod billingPeriod);
-
-    protected abstract DateTime calculateEffectiveEndDate(final DateTime billCycleDate, final DateTime targetDate, final DateTime endDate, final BillingPeriod billingPeriod);
+public abstract class BillingModeBase {
+//    public BigDecimal calculateNumberOfBillingCycles(final DateTime startDate, final DateTime endDate, final DateTime targetDate, final int billingCycleDay, final BillingPeriod billingPeriod) throws InvalidDateSequenceException {
+//        if (endDate.isBefore(startDate)) {throw new InvalidDateSequenceException();}
+//        if (targetDate.isBefore(startDate)) {throw new InvalidDateSequenceException();}
+//
+//        if (billingPeriod == BillingPeriod.NO_BILLING_PERIOD) {
+//            return BigDecimal.ZERO;
+//        }
+//
+//        BigDecimal precedingProRation = calculateProRationBeforeFirstBillingPeriod(startDate, billingCycleDay, billingPeriod);
+//
+//        DateTime firstBillCycleDate = calculateBillingCycleDateOnOrAfter(startDate, billingCycleDay);
+//        DateTime endBillCycleDate;
+//        BigDecimal trailingProRation;
+//        BigDecimal numberOfBillingPeriods;
+//
+//        DateTime effectiveEndDate = calculateEffectiveEndDate(firstBillCycleDate, targetDate, endDate, billingPeriod);
+//        endBillCycleDate = calculateLastBillingCycleDateBefore(effectiveEndDate, firstBillCycleDate, billingCycleDay, billingPeriod);
+//        numberOfBillingPeriods = calculateNumberOfWholeBillingPeriods(firstBillCycleDate, endBillCycleDate, billingPeriod);
+//
+//        trailingProRation = calculateProRationAfterLastBillingCycleDate(effectiveEndDate, endBillCycleDate, billingPeriod);
+//
+//        return precedingProRation.add(numberOfBillingPeriods).add(trailingProRation);
+//    }
+//
+//    public BigDecimal calculateNumberOfBillingCycles(final DateTime startDate, final DateTime targetDate, final int billingCycleDay, final BillingPeriod billingPeriod) throws InvalidDateSequenceException {
+//        if (targetDate.isBefore(startDate)) {throw new InvalidDateSequenceException();}
+//
+//        BigDecimal precedingProRation = calculateProRationBeforeFirstBillingPeriod(startDate, billingCycleDay, billingPeriod);
+//
+//        DateTime firstBillCycleDate = calculateBillingCycleDateOnOrAfter(startDate, billingCycleDay);
+//        DateTime endBillCycleDate = calculateBillingCycleDateAfter(targetDate, firstBillCycleDate, billingCycleDay, billingPeriod);
+//        BigDecimal numberOfBillingPeriods = calculateNumberOfWholeBillingPeriods(firstBillCycleDate, endBillCycleDate, billingPeriod);
+//
+//        return precedingProRation.add(numberOfBillingPeriods);
+//    }
+//
+//    boolean isNotBetween(DateTime targetDate, DateTime startDate, DateTime endDate) {
+//        return (targetDate.isBefore(startDate) || !targetDate.isBefore(endDate));
+//    }
+//
+//    public abstract DateTime calculateEffectiveEndDate(final DateTime startDate, final DateTime targetDate, final int billingCycleDay, final BillingPeriod billingPeriod);
+//
+//    public abstract DateTime calculateEffectiveEndDate(final DateTime startDate, final DateTime endDate, final DateTime targetDate, final int billingCycleDay, final BillingPeriod billingPeriod);
+//
+//    protected abstract BigDecimal calculateNumberOfWholeBillingPeriods(final DateTime startDate, final DateTime endDate, final BillingPeriod billingPeriod);
+//
+//    protected abstract DateTime calculateBillingCycleDateOnOrAfter(final DateTime date, final int billingCycleDay);
+//
+//    protected abstract DateTime calculateBillingCycleDateAfter(final DateTime date, final DateTime billingCycleDate, final int billingCycleDay, final BillingPeriod billingPeriod);
+//
+//    protected abstract DateTime calculateLastBillingCycleDateBefore(final DateTime date, final DateTime previousBillCycleDate, final int billingCycleDay, final BillingPeriod billingPeriod);
+//
+//    protected abstract BigDecimal calculateProRationBeforeFirstBillingPeriod(final DateTime startDate, final int billingCycleDay, final BillingPeriod billingPeriod);
+//
+//    protected abstract BigDecimal calculateProRationAfterLastBillingCycleDate(final DateTime endDate, final DateTime previousBillThroughDate, final BillingPeriod billingPeriod);
+//
+//    protected abstract DateTime calculateEffectiveEndDate(final DateTime billCycleDate, final DateTime targetDate, final DateTime endDate, final BillingPeriod billingPeriod);
 }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/DateRange.java b/invoice/src/main/java/com/ning/billing/invoice/model/DateRange.java
index 9f21ca6..b1e2fa9 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/DateRange.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/DateRange.java
@@ -33,7 +33,11 @@ public class DateRange {
      * @return whether the DateRange contains (inclusively) the DateTime in question
      */
     public boolean contains(DateTime date) {
-        return (!date.isBefore(startDate)) && (!date.isAfter(endDate));
+        if (endDate == null) {
+            return date.compareTo(startDate) >= 0;
+        }
+
+        return !date.isBefore(startDate) && !date.isAfter(endDate);
     }
 
     public boolean overlaps(DateRange range) {
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 97f82e0..6d419de 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
@@ -19,6 +19,8 @@ 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.InvoiceItem;
+import com.ning.billing.invoice.api.InvoicePayment;
+import com.ning.billing.util.clock.Clock;
 import com.ning.billing.util.clock.DefaultClock;
 import org.joda.time.DateTime;
 
@@ -27,56 +29,84 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.UUID;
 
+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.util.clock.DefaultClock;
+
 public class DefaultInvoice implements Invoice {
-    private final InvoiceItemList items = new InvoiceItemList();
+    private final InvoiceItemList invoiceItems = new InvoiceItemList();
+    private final List<InvoicePayment> payments = new ArrayList<InvoicePayment>();
     private final UUID id;
-    private UUID accountId;
+    private final UUID accountId;
     private final DateTime invoiceDate;
     private final DateTime targetDate;
-    private Currency currency;
-    private BigDecimal amountPaid;
-    private DateTime lastPaymentAttempt;
-
-    public DefaultInvoice(UUID accountId, DateTime targetDate, Currency currency) {
-        this(UUID.randomUUID(), accountId, new DefaultClock().getUTCNow(), targetDate, currency, null, BigDecimal.ZERO, new ArrayList<InvoiceItem>());
-    }
+    private final Currency currency;
 
-    public DefaultInvoice(UUID invoiceId, UUID accountId, DateTime invoiceDate, DateTime targetDate,
-                          Currency currency, DateTime lastPaymentAttempt, BigDecimal amountPaid) {
-        this(invoiceId, accountId, invoiceDate, targetDate, currency, lastPaymentAttempt, amountPaid, new ArrayList<InvoiceItem>());
+    public DefaultInvoice(UUID accountId, DateTime targetDate, Currency currency, Clock clock) {
+        this(UUID.randomUUID(), accountId, clock.getUTCNow(), targetDate, currency);
     }
 
     public DefaultInvoice(UUID invoiceId, UUID accountId, DateTime invoiceDate, DateTime targetDate,
-                          Currency currency, DateTime lastPaymentAttempt, BigDecimal amountPaid,
-                          List<InvoiceItem> invoiceItems) {
+                          Currency currency) {
         this.id = invoiceId;
         this.accountId = accountId;
         this.invoiceDate = invoiceDate;
         this.targetDate = targetDate;
         this.currency = currency;
-        this.lastPaymentAttempt= lastPaymentAttempt;
-        this.amountPaid = amountPaid;
-        this.items.addAll(invoiceItems);
     }
 
     @Override
-    public boolean add(InvoiceItem item) {
-        return items.add(item);
+    public boolean addInvoiceItem(final InvoiceItem item) {
+        return invoiceItems.add(item);
     }
 
     @Override
-    public boolean add(List<InvoiceItem> items) {
-        return this.items.addAll(items);
+    public boolean addInvoiceItems(final List<InvoiceItem> items) {
+        return this.invoiceItems.addAll(items);
     }
 
     @Override
-    public List<InvoiceItem> getItems() {
-        return items;
+    public List<InvoiceItem> getInvoiceItems() {
+        return invoiceItems;
+    }
+
+    @Override
+    public List<InvoiceItem> getInvoiceItems(Class clazz) {
+        List<InvoiceItem> results = new ArrayList<InvoiceItem>();
+        for (InvoiceItem item : invoiceItems) {
+            if (item.getClass() == clazz) {
+                results.add(item);
+            }
+        }
+        return results;
     }
 
     @Override
     public int getNumberOfItems() {
-        return items.size();
+        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
@@ -106,21 +136,40 @@ public class DefaultInvoice implements Invoice {
 
     @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() {
-        return items.getTotalAmount();
+        return invoiceItems.getTotalAmount();
     }
 
     @Override
-    public BigDecimal getAmountOutstanding() {
+    public BigDecimal getBalance() {
         return getTotalAmount().subtract(getAmountPaid());
     }
 
@@ -130,11 +179,18 @@ public class DefaultInvoice implements Invoice {
             return false;
         }
 
+        DateTime lastPaymentAttempt = getLastPaymentAttempt();
         if (lastPaymentAttempt == null) {
             return true;
         }
 
         return lastPaymentAttempt.plusDays(numberOfDays).isBefore(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() + "]";
+    }
+
 }
 
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 3b9311e..039f305 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,187 +16,251 @@
 
 package com.ning.billing.invoice.model;
 
-
 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 org.joda.time.DateTime;
-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.CatalogApiException;
+import com.ning.billing.catalog.api.Duration;
+import com.ning.billing.catalog.api.InternationalPrice;
+import com.ning.billing.entitlement.api.billing.BillingModeType;
+import com.ning.billing.invoice.api.InvoiceApiException;
+import org.joda.time.DateTime;
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.entitlement.api.billing.BillingEvent;
-import com.ning.billing.entitlement.api.billing.BillingModeType;
-import com.ning.billing.invoice.api.BillingEventSet;
 import com.ning.billing.invoice.api.Invoice;
 import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.util.clock.Clock;
+
+import javax.annotation.Nullable;
 
 public class DefaultInvoiceGenerator implements InvoiceGenerator {
-    private static final Logger log = LoggerFactory.getLogger(DefaultInvoiceGenerator.class); 
-    @Override
-    public Invoice generateInvoice(final UUID accountId, final BillingEventSet events, final InvoiceItemList existingItems, final DateTime targetDate, final Currency targetCurrency) {
-        if (events == null) {return new DefaultInvoice(accountId, targetDate, targetCurrency);}
-        if (events.size() == 0) {return new DefaultInvoice(accountId, targetDate, targetCurrency);}
+    private static final int ROUNDING_MODE = InvoicingConfiguration.getRoundingMode();
+    private static final int NUMBER_OF_DECIMALS = InvoicingConfiguration.getNumberOfDecimals();
+    //private static final Logger log = LoggerFactory.getLogger(DefaultInvoiceGenerator.class);
 
-        DefaultInvoice invoice = new DefaultInvoice(accountId, targetDate, targetCurrency);
-        InvoiceItemList currentItems = generateInvoiceItems(events, invoice.getId(), targetDate, targetCurrency);
-        InvoiceItemList itemsToPost = reconcileInvoiceItems(invoice.getId(), currentItems, existingItems);
-        invoice.add(itemsToPost);
+    private final Clock clock;
 
-        return invoice;
+    @Inject
+    public DefaultInvoiceGenerator(Clock clock) {
+        this.clock = clock;
     }
 
-    private InvoiceItemList reconcileInvoiceItems(final UUID invoiceId, final InvoiceItemList currentInvoiceItems, final InvoiceItemList existingInvoiceItems) {
-        InvoiceItemList currentItems = new InvoiceItemList();
-        for (InvoiceItem item : currentInvoiceItems) {
-            currentItems.add(new DefaultInvoiceItem(item, invoiceId));
+    @Override
+    public Invoice generateInvoice(final UUID accountId, final BillingEventSet events,
+                                   @Nullable final List<InvoiceItem> items, final DateTime targetDate,
+                                   final Currency targetCurrency) throws InvoiceApiException {
+        if ((events == null) || (events.size() == 0)) {
+            return null;
         }
 
-        InvoiceItemList existingItems = (InvoiceItemList) existingInvoiceItems.clone();
+        Collections.sort(events);
+
+        List<InvoiceItem> existingItems = new ArrayList<InvoiceItem>();
+        if (items != null) {
+            existingItems = new ArrayList<InvoiceItem>(items);
+            Collections.sort(existingItems);
+        }
 
-        Collections.sort(currentItems);
-        Collections.sort(existingItems);
+        DefaultInvoice invoice = new DefaultInvoice(accountId, targetDate, targetCurrency, clock);
+        UUID invoiceId = invoice.getId();
+        List<InvoiceItem> proposedItems = generateInvoiceItems(invoiceId, events, targetDate, targetCurrency);
 
-        List<InvoiceItem> existingItemsToRemove = new ArrayList<InvoiceItem>();
+        if (existingItems != null) {
+            removeCancellingInvoiceItems(existingItems);
+            removeDuplicatedInvoiceItems(proposedItems, existingItems);
 
-        for (InvoiceItem currentItem : currentItems) {
-            // see if there are any existing items that are covered by the current item
             for (InvoiceItem existingItem : existingItems) {
-                if (currentItem.duplicates(existingItem)) {
-                    currentItem.subtract(existingItem);
-                    existingItemsToRemove.add(existingItem);
+                if (existingItem instanceof RecurringInvoiceItem) {
+                    RecurringInvoiceItem recurringItem = (RecurringInvoiceItem) existingItem;
+                    proposedItems.add(recurringItem.asCredit());
                 }
             }
         }
 
-        existingItems.removeAll(existingItemsToRemove);
+        if (proposedItems == null || proposedItems.size()  == 0) {
+            return null;
+        } else {
+            invoice.addInvoiceItems(proposedItems);
+            return invoice;
+        }
+    }
 
-        // remove cancelling pairs of invoice items
-        existingItems.removeCancellingPairs();
+   /*
+    * removes all matching items from both submitted collections
+    */
+    private void removeDuplicatedInvoiceItems(final List<InvoiceItem> proposedItems,
+                                              final List<InvoiceItem> existingInvoiceItems) {
+        Iterator<InvoiceItem> proposedItemIterator = proposedItems.iterator();
+        while (proposedItemIterator.hasNext()) {
+            InvoiceItem proposedItem = proposedItemIterator.next();
+
+            Iterator<InvoiceItem> existingItemIterator = existingInvoiceItems.iterator();
+            while (existingItemIterator.hasNext()) {
+                InvoiceItem existingItem = existingItemIterator.next();
+                if (existingItem.equals(proposedItem)) {
+                    existingItemIterator.remove();
+                    proposedItemIterator.remove();
+                }
+            }
+        }
+    }
 
-        // remove zero-dollar invoice items
-        currentItems.removeZeroDollarItems();
+    private void removeCancellingInvoiceItems(final List<InvoiceItem> items) {
+        List<UUID> itemsToRemove = new ArrayList<UUID>();
 
-        // add existing items that aren't covered by current items as credit items
-        for (InvoiceItem existingItem : existingItems) {
-            currentItems.add(existingItem.asCredit(invoiceId));
+        for (InvoiceItem item1 : items) {
+            if (item1 instanceof RecurringInvoiceItem) {
+                RecurringInvoiceItem recurringInvoiceItem = (RecurringInvoiceItem) item1;
+                if (recurringInvoiceItem.reversesItem()) {
+                    itemsToRemove.add(recurringInvoiceItem.getId());
+                    itemsToRemove.add(recurringInvoiceItem.getReversedItemId());
+                }
+            }
         }
 
-        return currentItems;
+        Iterator<InvoiceItem> iterator = items.iterator();
+        while (iterator.hasNext()) {
+            InvoiceItem item = iterator.next();
+            if (itemsToRemove.contains(item.getId())) {
+                iterator.remove();
+            }
+        }
     }
 
-    private InvoiceItemList generateInvoiceItems(BillingEventSet events, UUID invoiceId, DateTime targetDate, Currency targetCurrency) {
-        InvoiceItemList items = new InvoiceItemList();
+    private List<InvoiceItem> generateInvoiceItems(final UUID invoiceId, final BillingEventSet events,
+                                                   final DateTime targetDate, final Currency currency) throws InvoiceApiException {
+        List<InvoiceItem> items = new ArrayList<InvoiceItem>();
 
-        // sort events; this relies on the sort order being by subscription id then start date
-        Collections.sort(events);
-
-        // for each event, process it either as a terminated event (if there's a subsequent event)
-        // ...or as a non-terminated event (if no subsequent event exists)
-        for (int i = 0; i < (events.size() - 1); i++) {
+        for (int i = 0; i < events.size(); i++) {
             BillingEvent thisEvent = events.get(i);
-            BillingEvent nextEvent = events.get(i + 1);
-
-            if (thisEvent.getSubscription().getId() == nextEvent.getSubscription().getId()) {
-                processEvents(invoiceId, thisEvent, nextEvent, items, targetDate, targetCurrency);
-            } else {
-                processEvent(invoiceId, thisEvent, items, targetDate, targetCurrency);
+            BillingEvent nextEvent = events.isLast(thisEvent) ? null : events.get(i + 1);
+            if (nextEvent != null) {
+                nextEvent = (thisEvent.getSubscription().getId() == nextEvent.getSubscription().getId()) ? nextEvent : null;
             }
-        }
 
-        // process the last item in the event set
-        if (events.size() > 0) {
-            processEvent(invoiceId, events.getLast(), items, targetDate, targetCurrency);
+            items.addAll(processEvents(invoiceId, thisEvent, nextEvent, targetDate, currency));
         }
 
         return items;
     }
 
-    private void processEvent(UUID invoiceId, BillingEvent event, List<InvoiceItem> items, DateTime targetDate, Currency targetCurrency) {
-    	try {
-    		//TODO: Jeff getPrice() -> getRecurringPrice()
-    		BigDecimal rate = event.getRecurringPrice(targetCurrency);
-    		BigDecimal invoiceItemAmount = calculateInvoiceItemAmount(event, targetDate, rate);
-    		BillingMode billingMode = getBillingMode(event.getBillingMode());
-    		DateTime billThroughDate = billingMode.calculateEffectiveEndDate(event.getEffectiveDate(), targetDate, event.getBillCycleDay(), event.getBillingPeriod());
-
-    		addInvoiceItem(invoiceId, items, event, billThroughDate, invoiceItemAmount, rate, targetCurrency);
-    	} catch (CatalogApiException e) {
-            log.error(String.format("Encountered a catalog error processing invoice %s for billing event on date %s", 
-                    invoiceId.toString(), 
-                    ISODateTimeFormat.basicDateTime().print(event.getEffectiveDate())), e);
+    private List<InvoiceItem> processEvents(final UUID invoiceId, final BillingEvent thisEvent, final BillingEvent nextEvent,
+                                            final DateTime targetDate, final Currency currency) throws InvoiceApiException {
+        List<InvoiceItem> items = new ArrayList<InvoiceItem>();
+        InvoiceItem fixedPriceInvoiceItem = generateFixedPriceItem(invoiceId, thisEvent, targetDate, currency);
+        if (fixedPriceInvoiceItem != null) {
+            items.add(fixedPriceInvoiceItem);
         }
-    }
 
-    private void processEvents(UUID invoiceId, BillingEvent firstEvent, BillingEvent secondEvent, List<InvoiceItem> items, DateTime targetDate, Currency targetCurrency) {
-    	//TODO: Jeff getPrice() -> getRecurringPrice()
-    	try {
-    		BigDecimal rate = firstEvent.getRecurringPrice(targetCurrency);
-    		BigDecimal invoiceItemAmount = calculateInvoiceItemAmount(firstEvent, secondEvent, targetDate, rate);
-    		BillingMode billingMode = getBillingMode(firstEvent.getBillingMode());
-    		DateTime billThroughDate = billingMode.calculateEffectiveEndDate(firstEvent.getEffectiveDate(), secondEvent.getEffectiveDate(), targetDate, firstEvent.getBillCycleDay(), firstEvent.getBillingPeriod());
-
-    		addInvoiceItem(invoiceId, items, firstEvent, billThroughDate, invoiceItemAmount, rate, targetCurrency);
-    	} catch (CatalogApiException e) {
-    		log.error(String.format("Encountered a catalog error processing invoice %s for billing event on date %s", 
-                    invoiceId.toString(), 
-                    ISODateTimeFormat.basicDateTime().print(firstEvent.getEffectiveDate())), e);
-        }
-    }
-
-    private void addInvoiceItem(UUID invoiceId, List<InvoiceItem> items, BillingEvent event, DateTime billThroughDate, BigDecimal amount, BigDecimal rate, Currency currency) {
-        if (!(amount.compareTo(BigDecimal.ZERO) == 0)) {
-            DefaultInvoiceItem item = new DefaultInvoiceItem(invoiceId, event.getSubscription().getId(), event.getEffectiveDate(), billThroughDate, event.getDescription(), amount, rate, currency);
-            items.add(item);
-        }
-    }
+        BillingPeriod billingPeriod = thisEvent.getBillingPeriod();
+        if (billingPeriod != BillingPeriod.NO_BILLING_PERIOD) {
+            BillingMode billingMode = instantiateBillingMode(thisEvent.getBillingMode());
+            DateTime startDate = thisEvent.getEffectiveDate();
+            if (!startDate.isAfter(targetDate)) {
+                DateTime endDate = (nextEvent == null) ? null : nextEvent.getEffectiveDate();
+                int billCycleDay = thisEvent.getBillCycleDay();
+
+                List<RecurringInvoiceItemData> itemData;
+                try {
+                    itemData = billingMode.calculateInvoiceItemData(startDate, endDate, targetDate, billCycleDay, billingPeriod);
+                } catch (InvalidDateSequenceException e) {
+                    throw new InvoiceApiException(ErrorCode.INVOICE_INVALID_DATE_SEQUENCE, startDate, endDate, targetDate);
+                }
 
-    private BigDecimal calculateInvoiceItemAmount(BillingEvent event, DateTime targetDate, BigDecimal rate){
-        BillingMode billingMode = getBillingMode(event.getBillingMode());
-        DateTime startDate = event.getEffectiveDate();
-        int billingCycleDay = event.getBillCycleDay();
-        BillingPeriod billingPeriod = event.getBillingPeriod();
-
-        try {
-            BigDecimal numberOfBillingCycles;
-            numberOfBillingCycles = billingMode.calculateNumberOfBillingCycles(startDate, targetDate, billingCycleDay, billingPeriod);
-            return numberOfBillingCycles.multiply(rate);
-        } catch (InvalidDateSequenceException e) {
-            // TODO: Jeff -- log issue
-            return BigDecimal.ZERO;
+                for (RecurringInvoiceItemData itemDatum : itemData) {
+                    InternationalPrice price = thisEvent.getRecurringPrice();
+                    if (price != null) {
+                        BigDecimal rate;
+
+                        try {
+                            rate = thisEvent.getRecurringPrice().getPrice(currency);
+                        } catch (CatalogApiException e) {
+                            throw new InvoiceApiException(e, ErrorCode.CAT_NO_PRICE_FOR_CURRENCY, currency.toString());
+                        }
+
+                        BigDecimal amount = itemDatum.getNumberOfCycles().multiply(rate).setScale(NUMBER_OF_DECIMALS, ROUNDING_MODE);
+
+                        RecurringInvoiceItem recurringItem = new RecurringInvoiceItem(invoiceId, thisEvent.getSubscription().getId(),
+                                                                                      thisEvent.getPlan().getName(),
+                                                                                      thisEvent.getPlanPhase().getName(),
+                                                                                      itemDatum.getStartDate(), itemDatum.getEndDate(),
+                                                                                      amount, rate, currency);
+                        items.add(recurringItem);
+                    }
+                }
+            }
         }
-    }
 
-    private BigDecimal calculateInvoiceItemAmount(BillingEvent firstEvent, BillingEvent secondEvent, DateTime targetDate, BigDecimal rate) {
-        BillingMode billingMode = getBillingMode(firstEvent.getBillingMode());
-        DateTime startDate = firstEvent.getEffectiveDate();
-        int billingCycleDay = firstEvent.getBillCycleDay();
-        BillingPeriod billingPeriod = firstEvent.getBillingPeriod();
-
-        DateTime endDate = secondEvent.getEffectiveDate();
-
-        try {
-            BigDecimal numberOfBillingCycles;
-            numberOfBillingCycles = billingMode.calculateNumberOfBillingCycles(startDate, endDate, targetDate, billingCycleDay, billingPeriod);
-            return numberOfBillingCycles.multiply(rate);
-        } catch (InvalidDateSequenceException e) {
-            // TODO: Jeff -- log issue
-            return BigDecimal.ZERO;
-        }
+        return items;
     }
 
-    private BillingMode getBillingMode(BillingModeType billingModeType) {
-        switch (billingModeType) {
+    private BillingMode instantiateBillingMode(BillingModeType billingMode) {
+        switch (billingMode) {
             case IN_ADVANCE:
                 return new InAdvanceBillingMode();
             default:
-                return null;
+                throw new UnsupportedOperationException();
         }
     }
+
+    private InvoiceItem generateFixedPriceItem(final UUID invoiceId, final BillingEvent thisEvent,
+                                               final DateTime targetDate, final Currency currency) throws InvoiceApiException {
+        if (thisEvent.getEffectiveDate().isAfter(targetDate)) {
+            return null;
+        } else {
+            FixedPriceInvoiceItem fixedPriceInvoiceItem = null;
+
+            if (thisEvent.getFixedPrice() != null) {
+                try {
+                    Duration duration = thisEvent.getPlanPhase().getDuration();
+                    DateTime endDate = duration.addToDateTime(thisEvent.getEffectiveDate());
+                    BigDecimal fixedPrice = thisEvent.getFixedPrice().getPrice(currency);
+                    fixedPriceInvoiceItem = new FixedPriceInvoiceItem(invoiceId, thisEvent.getSubscription().getId(),
+                                                                      thisEvent.getPlan().getName(), thisEvent.getPlanPhase().getName(),
+                                                                      thisEvent.getEffectiveDate(), endDate, fixedPrice, currency);
+                } catch (CatalogApiException e) {
+                    throw new InvoiceApiException(e, ErrorCode.CAT_NO_PRICE_FOR_CURRENCY, currency.toString());
+                }
+            }
+
+            return fixedPriceInvoiceItem;
+        }
+    }
+
+//    // assumption: startDate is in the user's time zone
+//    private DateTime calculateSegmentEndDate(final DateTime startDate, final DateTime nextEndDate,
+//                                             final int billCycleDay, final BillingPeriod billingPeriod) {
+//        int dayOfMonth = startDate.getDayOfMonth();
+//        int maxDayOfMonth = startDate.dayOfMonth().getMaximumValue();
+//
+//        DateTime nextBillingDate;
+//
+//        // if the start date is not on the bill cycle day, move it to the nearest following date that works
+//        if ((billCycleDay > maxDayOfMonth) || (dayOfMonth == billCycleDay)) {
+//            nextBillingDate = startDate.plusMonths(billingPeriod.getNumberOfMonths());
+//        } else {
+//            MutableDateTime proposedDate = startDate.toMutableDateTime();
+//
+//            if (dayOfMonth < billCycleDay) {
+//                // move the end date forward to the bill cycle date (same month)
+//                int effectiveBillCycleDay = (billCycleDay > maxDayOfMonth) ? maxDayOfMonth : billCycleDay;
+//                nextBillingDate = proposedDate.dayOfMonth().set(effectiveBillCycleDay).toDateTime();
+//            } else {
+//                // go to the next month
+//                proposedDate = proposedDate.monthOfYear().add(1);
+//                maxDayOfMonth = proposedDate.dayOfMonth().getMaximumValue();
+//                int effectiveBillCycleDay = (billCycleDay > maxDayOfMonth) ? maxDayOfMonth : billCycleDay;
+//                nextBillingDate = proposedDate.dayOfMonth().set(effectiveBillCycleDay).toDateTime();
+//            }
+//        }
+//
+//        return nextBillingDate.isAfter(nextEndDate) ? nextEndDate : nextBillingDate;
+//    }
 }
\ No newline at end of file
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
new file mode 100644
index 0000000..6760481
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoicePayment.java
@@ -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.
+ */
+
+package com.ning.billing.invoice.model;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.InvoicePayment;
+import org.joda.time.DateTime;
+
+import javax.annotation.Nullable;
+import java.math.BigDecimal;
+import java.util.UUID;
+
+public class DefaultInvoicePayment implements InvoicePayment {
+    private final UUID paymentAttemptId;
+    private final UUID invoiceId;
+    private final DateTime paymentDate;
+    private final BigDecimal amount;
+    private final Currency currency;
+    private final DateTime createdDate;
+    private final DateTime updatedDate;
+
+    public DefaultInvoicePayment(final UUID invoiceId, final DateTime paymentDate) {
+        this(UUID.randomUUID(), invoiceId, paymentDate, null, null, null, null);
+    }
+
+    public DefaultInvoicePayment(final UUID paymentAttemptId, final UUID invoiceId, final DateTime paymentDate) {
+        this(paymentAttemptId, invoiceId, paymentDate, null, null, null, null);
+    }
+
+    public DefaultInvoicePayment(final UUID invoiceId, final DateTime paymentDate,
+                                 final BigDecimal amount, final Currency currency) {
+        this(UUID.randomUUID(), invoiceId, paymentDate, amount, currency, null, null);
+    }
+
+    public DefaultInvoicePayment(final UUID paymentAttemptId, final UUID invoiceId, final DateTime paymentDate,
+                                 final BigDecimal amount, final Currency currency) {
+        this(paymentAttemptId, invoiceId, paymentDate, amount, currency, null, null);
+    }
+
+    public DefaultInvoicePayment(final UUID paymentAttemptId, final UUID invoiceId, final DateTime paymentDate,
+                                 @Nullable final BigDecimal amount, @Nullable final Currency currency,
+                                 @Nullable final DateTime createdDate, @Nullable final DateTime updatedDate) {
+        this.paymentAttemptId = paymentAttemptId;
+        this.amount = amount;
+        this.invoiceId = invoiceId;
+        this.paymentDate = paymentDate;
+        this.currency = currency;
+        this.createdDate = (createdDate == null) ? new DateTime() : createdDate;
+        this.updatedDate = (updatedDate == null) ? new DateTime() : updatedDate;
+    }
+
+    @Override
+    public UUID getPaymentAttemptId() {
+        return paymentAttemptId;
+    }
+
+    @Override
+    public UUID getInvoiceId() {
+        return invoiceId;
+    }
+
+    @Override
+    public DateTime getPaymentAttemptDate() {
+        return paymentDate;
+    }
+
+    @Override
+    public BigDecimal getAmount() {
+        return amount;
+    }
+
+    @Override
+    public Currency getCurrency() {
+        return currency;
+    }
+
+    @Override
+    public DateTime getCreatedDate() {
+        return createdDate;
+    }
+
+    @Override
+    public DateTime getUpdatedDate() {
+        return updatedDate;
+    }
+}
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
new file mode 100644
index 0000000..2265abd
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/FixedPriceInvoiceItem.java
@@ -0,0 +1,121 @@
+/*
+* Copyright 2010-2011 Ning, Inc.
+*
+* Ning licenses this file to you under the Apache License, version 2.0
+* (the "License"); you may not use this file except in compliance with the
+* License.  You may obtain a copy of the License at:
+*
+*    http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+* 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 FixedPriceInvoiceItem extends InvoiceItemBase {
+    public FixedPriceInvoiceItem(UUID invoiceId, UUID subscriptionId, String planName, String phaseName, DateTime startDate, DateTime endDate, BigDecimal amount, Currency currency) {
+        super(invoiceId, subscriptionId, planName, phaseName, startDate, endDate, amount, currency);
+    }
+
+    public FixedPriceInvoiceItem(UUID id, UUID invoiceId, UUID subscriptionId, String planName, String phaseName, DateTime startDate, DateTime endDate, BigDecimal amount, Currency currency) {
+        super(id, invoiceId, subscriptionId, planName, phaseName, startDate, endDate, amount, currency);
+    }
+
+    @Override
+    public InvoiceItem asCredit() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getDescription() {
+        return String.format("%s (fixed price) on %s", getPhaseName(), getStartDate().toString());
+    }
+
+    @Override
+    public int hashCode() {
+        int result = subscriptionId != null ? subscriptionId.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);
+        result = 31 * result + (endDate != null ? endDate.hashCode() : 0);
+        result = 31 * result + (amount != null ? amount.hashCode() : 0);
+        result = 31 * result + (currency != null ? currency.hashCode() : 0);
+        return result;
+    }
+
+    @Override
+    public int compareTo(InvoiceItem item) {
+        if (!(item instanceof FixedPriceInvoiceItem)) {
+            return 1;
+        }
+
+        FixedPriceInvoiceItem that = (FixedPriceInvoiceItem) item;
+        int compareSubscriptions = getSubscriptionId().compareTo(that.getSubscriptionId());
+
+        if (compareSubscriptions == 0) {
+            return getStartDate().compareTo(that.getStartDate());
+        } else {
+            return compareSubscriptions;
+        }
+    }
+
+    @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(", ");
+
+        return sb.toString();
+//        StringBuilder sb = new StringBuilder();
+//        sb.append("InvoiceItem = {").append("id = ").append(id.toString()).append(", ");
+//        sb.append("invoiceId = ").append(invoiceId.toString()).append(", ");
+//        sb.append("subscriptionId = ").append(subscriptionId.toString()).append(", ");
+//        sb.append("planName = ").append(planName).append(", ");
+//        sb.append("phaseName = ").append(phaseName).append(", ");
+//        sb.append("startDate = ").append(startDate.toString()).append(", ");
+//        sb.append("endDate = ").append(endDate.toString()).append(", ");
+//
+//        sb.append("amount = ");
+//        if (amount == null) {
+//            sb.append("null");
+//        } else {
+//            sb.append(amount.toString());
+//        }
+//
+//        sb.append("}");
+//        return sb.toString();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        FixedPriceInvoiceItem that = (FixedPriceInvoiceItem) o;
+
+        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;
+        if (endDate != null ? endDate.compareTo(that.endDate) != 0 : that.endDate != null) return false;
+        if (phaseName != null ? !phaseName.equals(that.phaseName) : that.phaseName != null) return false;
+        if (planName != null ? !planName.equals(that.planName) : that.planName != null) return false;
+        if (subscriptionId != null ? !subscriptionId.equals(that.subscriptionId) : that.subscriptionId != null)
+            return false;
+
+        return true;
+    }
+}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/InAdvanceBillingMode.java b/invoice/src/main/java/com/ning/billing/invoice/model/InAdvanceBillingMode.java
index 37b5820..4d3dff9 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/InAdvanceBillingMode.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/InAdvanceBillingMode.java
@@ -20,142 +20,218 @@ import com.ning.billing.catalog.api.BillingPeriod;
 import org.joda.time.DateTime;
 import org.joda.time.Days;
 import org.joda.time.Months;
+import org.joda.time.MutableDateTime;
 
 import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
 
-public class InAdvanceBillingMode extends BillingModeBase {
-    private static final int ROUNDING_METHOD = InvoicingConfiguration.getRoundingMethod();
+public class InAdvanceBillingMode implements BillingMode {
+    private static final int ROUNDING_METHOD = InvoicingConfiguration.getRoundingMode();
     private static final int NUMBER_OF_DECIMALS = InvoicingConfiguration.getNumberOfDecimals();
 
     @Override
-    public DateTime calculateEffectiveEndDate(final DateTime startDate, final DateTime targetDate, final int billingCycleDay, final BillingPeriod billingPeriod) {
-        DateTime firstBillCycleDate = calculateBillingCycleDateOnOrAfter(startDate, billingCycleDay);
-        return calculateBillingCycleDateAfter(targetDate, firstBillCycleDate, billingCycleDay, billingPeriod);
-    }
-
-    @Override
-    public DateTime calculateEffectiveEndDate(final DateTime startDate, final DateTime endDate, final DateTime targetDate, final int billingCycleDay, final BillingPeriod billingPeriod) {
-        DateTime firstBillCycleDate = calculateBillingCycleDateOnOrAfter(startDate, billingCycleDay);
-        return calculateEffectiveEndDate(firstBillCycleDate, targetDate, endDate, billingPeriod);
-    }
+    public List<RecurringInvoiceItemData> calculateInvoiceItemData(final DateTime startDate, final DateTime endDate,
+                                                                   final DateTime targetDate, final int billingCycleDay,
+                                                                   final BillingPeriod billingPeriod) throws InvalidDateSequenceException {
+        if (endDate == null) {
+            return calculateInvoiceItemData(startDate, targetDate, billingCycleDay, billingPeriod);
+        }
 
-    @Override
-    protected BigDecimal calculateNumberOfWholeBillingPeriods(final DateTime startDate, final DateTime endDate, final BillingPeriod billingPeriod) {
-        int numberOfMonths = Months.monthsBetween(startDate, endDate).getMonths();
-        BigDecimal numberOfMonthsInPeriod = new BigDecimal(billingPeriod.getNumberOfMonths());
-        return new BigDecimal(numberOfMonths).divide(numberOfMonthsInPeriod, 0, ROUNDING_METHOD);
-    }
+        if (endDate.isBefore(startDate)) {throw new InvalidDateSequenceException();}
+        if (targetDate.isBefore(startDate)) {throw new InvalidDateSequenceException();}
 
-    @Override
-    protected DateTime calculateBillingCycleDateOnOrAfter(final DateTime date, final int billingCycleDay) {
-        int lastDayOfMonth = date.dayOfMonth().getMaximumValue();
+        List<RecurringInvoiceItemData> results = new ArrayList<RecurringInvoiceItemData>();
 
-        DateTime proposedDate;
-        if (billingCycleDay > lastDayOfMonth) {
-            proposedDate = buildDate(date.getYear(), date.getMonthOfYear(), lastDayOfMonth);
-        } else {
-            proposedDate = buildDate(date.getYear(), date.getMonthOfYear(), billingCycleDay);
-        }
+        // beginning from the start date, find the first billing date
+        DateTime firstBillingCycleDate = calculateBillingCycleDateOnOrAfter(startDate, billingCycleDay);
 
-        while (proposedDate.isBefore(date)) {
-            proposedDate = proposedDate.plusMonths(1);
+        // add pro-ration item if needed
+        if (firstBillingCycleDate.isAfter(startDate)) {
+            BigDecimal leadingProRationPeriods = calculateProRationBeforeFirstBillingPeriod(startDate, firstBillingCycleDate, billingPeriod);
+            if (leadingProRationPeriods != null && leadingProRationPeriods.compareTo(BigDecimal.ZERO) > 0) {
+                results.add(new RecurringInvoiceItemData(startDate, firstBillingCycleDate, leadingProRationPeriods));
+            }
         }
 
-        return proposedDate;
-    }
-
-    protected DateTime calculateBillingCycleDateAfter(final DateTime date, final DateTime billingCycleDate, final int billingCycleDay, final BillingPeriod billingPeriod) {
-        DateTime proposedDate = billingCycleDate;
-
-        while (!proposedDate.isAfter(date)) {
-            proposedDate = proposedDate.plusMonths(billingPeriod.getNumberOfMonths());
+        // add one item per billing period
+        DateTime effectiveEndDate = calculateEffectiveEndDate(firstBillingCycleDate, targetDate, endDate, billingPeriod);
+        DateTime lastBillingCycleDate = calculateLastBillingCycleDateBefore(effectiveEndDate, firstBillingCycleDate, billingCycleDay, billingPeriod);
+        int numberOfWholeBillingPeriods =  calculateNumberOfWholeBillingPeriods(firstBillingCycleDate, lastBillingCycleDate, billingPeriod);
+        int numberOfMonthsPerBillingPeriod = billingPeriod.getNumberOfMonths();
 
-            if (proposedDate.dayOfMonth().get() != billingCycleDay) {
-                int lastDayOfMonth = proposedDate.dayOfMonth().getMaximumValue();
+        for (int i = 0; i < numberOfWholeBillingPeriods; i++) {
+            results.add(new RecurringInvoiceItemData(firstBillingCycleDate.plusMonths(i * numberOfMonthsPerBillingPeriod),
+                                                     firstBillingCycleDate.plusMonths((i + 1) * numberOfMonthsPerBillingPeriod), BigDecimal.ONE));
+        }
 
-                if (lastDayOfMonth < billingCycleDay) {
-                    proposedDate = buildDate(proposedDate.getYear(), proposedDate.getMonthOfYear(), lastDayOfMonth);
-                } else {
-                    proposedDate = buildDate(proposedDate.getYear(), proposedDate.getMonthOfYear(), billingCycleDay);
-                }
+        // check to see if a trailing pro-ration amount is needed
+        if (effectiveEndDate.isAfter(lastBillingCycleDate)) {
+            BigDecimal trailingProRationPeriods = calculateProRationAfterLastBillingCycleDate(effectiveEndDate, lastBillingCycleDate, billingPeriod);
+            if (trailingProRationPeriods.compareTo(BigDecimal.ZERO) > 0) {
+                results.add(new RecurringInvoiceItemData(lastBillingCycleDate, effectiveEndDate, trailingProRationPeriods));
             }
         }
 
-        return proposedDate;
+        return results;
     }
 
     @Override
-    protected DateTime calculateLastBillingCycleDateBefore(final DateTime date, final DateTime previousBillCycleDate, final int billingCycleDay, final BillingPeriod billingPeriod) {
-        DateTime proposedDate = previousBillCycleDate;
-        proposedDate = proposedDate.plusMonths(billingPeriod.getNumberOfMonths());
+    public List<RecurringInvoiceItemData> calculateInvoiceItemData(final DateTime startDate,
+                                                                   final DateTime targetDate, final int billingCycleDay,
+                                                                   final BillingPeriod billingPeriod) throws InvalidDateSequenceException {
+        List<RecurringInvoiceItemData> results = new ArrayList<RecurringInvoiceItemData>();
+
+        if (targetDate.isBefore(startDate)) {
+            // since the target date is before the start date of the event, this should result in no items being generated
+            throw new InvalidDateSequenceException();
+        }
 
-        if (!proposedDate.isBefore(date)) {return previousBillCycleDate;}
+        // beginning from the start date, find the first billing date
+        DateTime firstBillingCycleDate = calculateBillingCycleDateOnOrAfter(startDate, billingCycleDay);
 
-        while (proposedDate.isBefore(date)) {
-            proposedDate = proposedDate.plusMonths(billingPeriod.getNumberOfMonths());
+        // add pro-ration item if needed
+        if (firstBillingCycleDate.isAfter(startDate)) {
+            BigDecimal leadingProRationPeriods = calculateProRationBeforeFirstBillingPeriod(startDate, firstBillingCycleDate, billingPeriod);
+            if (leadingProRationPeriods != null && leadingProRationPeriods.compareTo(BigDecimal.ZERO) > 0) {
+                results.add(new RecurringInvoiceItemData(startDate, firstBillingCycleDate, leadingProRationPeriods));
+            }
         }
 
-        proposedDate = proposedDate.plusMonths(-billingPeriod.getNumberOfMonths());
+        // add one item per billing period
+        DateTime effectiveEndDate = calculateEffectiveEndDate(firstBillingCycleDate, targetDate, billingPeriod);
+        DateTime lastBillingCycleDate = calculateLastBillingCycleDateBefore(effectiveEndDate, firstBillingCycleDate, billingCycleDay, billingPeriod);
+        int numberOfWholeBillingPeriods =  calculateNumberOfWholeBillingPeriods(firstBillingCycleDate, lastBillingCycleDate, billingPeriod);
+        int numberOfMonthsPerBillingPeriod = billingPeriod.getNumberOfMonths();
 
-        if (proposedDate.dayOfMonth().get() < billingCycleDay) {
-            int lastDayOfTheMonth = proposedDate.dayOfMonth().getMaximumValue();
-            if (lastDayOfTheMonth < billingCycleDay) {
-                return buildDate(proposedDate.getYear(), proposedDate.getMonthOfYear(), lastDayOfTheMonth);
-            } else {
-                return buildDate(proposedDate.getYear(), proposedDate.getMonthOfYear(), billingCycleDay);
+        for (int i = 0; i < numberOfWholeBillingPeriods; i++) {
+            results.add(new RecurringInvoiceItemData(firstBillingCycleDate.plusMonths(i * numberOfMonthsPerBillingPeriod),
+                                                     firstBillingCycleDate.plusMonths((i + 1) * numberOfMonthsPerBillingPeriod), BigDecimal.ONE));
+        }
+
+        // check to see if a trailing pro-ration amount is needed
+        if (effectiveEndDate.isAfter(lastBillingCycleDate)) {
+            BigDecimal trailingProRationPeriods = calculateProRationAfterLastBillingCycleDate(effectiveEndDate, lastBillingCycleDate, billingPeriod);
+            if (trailingProRationPeriods.compareTo(BigDecimal.ZERO) > 0) {
+                results.add(new RecurringInvoiceItemData(lastBillingCycleDate, effectiveEndDate, trailingProRationPeriods));
             }
+        }
+
+        return results;
+    }
+
+    private DateTime calculateBillingCycleDateOnOrAfter(final DateTime date, final int billingCycleDay) {
+        int lastDayOfMonth = date.dayOfMonth().getMaximumValue();
+
+        MutableDateTime tmp = date.toMutableDateTime();
+        if (billingCycleDay > lastDayOfMonth) {
+            tmp.setDayOfMonth(lastDayOfMonth);
         } else {
-            return proposedDate;
+            tmp.setDayOfMonth(billingCycleDay);
         }
+        DateTime proposedDate = tmp.toDateTime();
+
+        while (proposedDate.isBefore(date)) {
+            // STEPH could be an annual ?
+            proposedDate = proposedDate.plusMonths(1);
+        }
+        return proposedDate;
     }
 
-    @Override
-    protected BigDecimal calculateProRationBeforeFirstBillingPeriod(final DateTime startDate, final int billingCycleDay, final BillingPeriod billingPeriod) {
-        DateTime nextBillingCycleDate = calculateBillingCycleDateOnOrAfter(startDate, billingCycleDay);
+    private BigDecimal calculateProRationBeforeFirstBillingPeriod(final DateTime startDate, DateTime nextBillingCycleDate, final BillingPeriod billingPeriod) {
         DateTime previousBillingCycleDate = nextBillingCycleDate.plusMonths(-billingPeriod.getNumberOfMonths());
 
-        BigDecimal daysInPeriod = new BigDecimal(Days.daysBetween(previousBillingCycleDate, nextBillingCycleDate).getDays());
+        int daysBetween = Days.daysBetween(previousBillingCycleDate, nextBillingCycleDate).getDays();
+        if (daysBetween <= 0) {
+            return BigDecimal.ZERO;
+        }
+
+        BigDecimal daysInPeriod = new BigDecimal(daysBetween);
         BigDecimal days = new BigDecimal(Days.daysBetween(startDate, nextBillingCycleDate).getDays());
 
         return days.divide(daysInPeriod, NUMBER_OF_DECIMALS, ROUNDING_METHOD);
     }
 
-    @Override
-    protected BigDecimal calculateProRationAfterLastBillingCycleDate(final DateTime endDate, final DateTime previousBillThroughDate, final BillingPeriod billingPeriod) {
-        // note: assumption is that previousBillThroughDate is correctly aligned with the billing cycle day
-        DateTime nextBillThroughDate = previousBillThroughDate.plusMonths(billingPeriod.getNumberOfMonths());
-        BigDecimal daysInPeriod = new BigDecimal(Days.daysBetween(previousBillThroughDate, nextBillThroughDate).getDays());
-
-        BigDecimal days = new BigDecimal(Days.daysBetween(previousBillThroughDate, endDate).getDays());
-
-        return days.divide(daysInPeriod, NUMBER_OF_DECIMALS, ROUNDING_METHOD);
+    private int calculateNumberOfWholeBillingPeriods(final DateTime startDate, final DateTime endDate, final BillingPeriod billingPeriod) {
+        int numberOfMonths = Months.monthsBetween(startDate, endDate).getMonths();
+        int numberOfMonthsInPeriod = billingPeriod.getNumberOfMonths();
+        return numberOfMonths / numberOfMonthsInPeriod;
     }
 
-    @Override
-    protected DateTime calculateEffectiveEndDate(DateTime billCycleDate, DateTime targetDate, DateTime endDate, BillingPeriod billingPeriod) {
+    private DateTime calculateEffectiveEndDate(DateTime billCycleDate, DateTime targetDate, DateTime endDate, BillingPeriod billingPeriod) {
         if (targetDate.isBefore(endDate)) {
             if (targetDate.isBefore(billCycleDate)) {
                 return billCycleDate;
             }
 
             int numberOfMonthsInPeriod = billingPeriod.getNumberOfMonths();
-            DateTime startOfPeriod = billCycleDate;
-            DateTime startOfNextPeriod = billCycleDate.plusMonths(numberOfMonthsInPeriod);
+            int numberOfPeriods = 0;
+            DateTime proposedDate = billCycleDate;
 
-            while (isNotBetween(targetDate, startOfPeriod, startOfNextPeriod)) {
-                startOfPeriod = startOfNextPeriod;
-                startOfNextPeriod = startOfPeriod.plusMonths(numberOfMonthsInPeriod);
+            while (!proposedDate.isAfter(targetDate)) {
+                proposedDate = billCycleDate.plusMonths(numberOfPeriods * numberOfMonthsInPeriod);
+                numberOfPeriods += 1;
             }
 
             // the current period includes the target date
             // check to see whether the end date truncates the period
-            if (endDate.isBefore(startOfNextPeriod)) {
+            if (endDate.isBefore(proposedDate)) {
                 return endDate;
             } else {
-                return startOfNextPeriod;
+                return proposedDate;
             }
         } else {
             return endDate;
         }
     }
+
+    private DateTime calculateEffectiveEndDate(DateTime billCycleDate, DateTime targetDate, BillingPeriod billingPeriod) {
+        if (targetDate.isBefore(billCycleDate)) {
+            return billCycleDate;
+        }
+
+        int numberOfMonthsInPeriod = billingPeriod.getNumberOfMonths();
+        int numberOfPeriods = 0;
+        DateTime proposedDate = billCycleDate;
+
+        while (!proposedDate.isAfter(targetDate)) {
+            proposedDate = billCycleDate.plusMonths(numberOfPeriods * numberOfMonthsInPeriod);
+            numberOfPeriods += 1;
+        }
+
+        return proposedDate;
+    }
+
+    private DateTime calculateLastBillingCycleDateBefore(final DateTime date, final DateTime previousBillCycleDate, final int billingCycleDay, final BillingPeriod billingPeriod) {
+        DateTime proposedDate = previousBillCycleDate;
+
+        int numberOfPeriods = 0;
+        while (!proposedDate.isAfter(date)) {
+            proposedDate = previousBillCycleDate.plusMonths(numberOfPeriods * billingPeriod.getNumberOfMonths());
+            numberOfPeriods += 1;
+        }
+
+        proposedDate = proposedDate.plusMonths(-billingPeriod.getNumberOfMonths());
+
+        if (proposedDate.dayOfMonth().get() < billingCycleDay) {
+            int lastDayOfTheMonth = proposedDate.dayOfMonth().getMaximumValue();
+            if (lastDayOfTheMonth < billingCycleDay) {
+                return new MutableDateTime(proposedDate).dayOfMonth().set(lastDayOfTheMonth).toDateTime();
+            } else {
+                return new MutableDateTime(proposedDate).dayOfMonth().set(billingCycleDay).toDateTime();
+            }
+        } else {
+            return proposedDate;
+        }
+    }
+
+    private BigDecimal calculateProRationAfterLastBillingCycleDate(final DateTime endDate, final DateTime previousBillThroughDate, final BillingPeriod billingPeriod) {
+        // note: assumption is that previousBillThroughDate is correctly aligned with the billing cycle day
+        DateTime nextBillThroughDate = previousBillThroughDate.plusMonths(billingPeriod.getNumberOfMonths());
+        BigDecimal daysInPeriod = new BigDecimal(Days.daysBetween(previousBillThroughDate, nextBillThroughDate).getDays());
+
+        BigDecimal days = new BigDecimal(Days.daysBetween(previousBillThroughDate, endDate).getDays());
+
+        return days.divide(daysInPeriod, NUMBER_OF_DECIMALS, ROUNDING_METHOD);
+    }
 }
\ No newline at end of file
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 6066a75..6bc6c9d 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
@@ -17,12 +17,15 @@
 package com.ning.billing.invoice.model;
 
 import com.ning.billing.catalog.api.Currency;
-import com.ning.billing.invoice.api.BillingEventSet;
 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;
+import java.util.List;
 import java.util.UUID;
 
 public interface InvoiceGenerator {
-    public Invoice generateInvoice(UUID accountId, BillingEventSet events, InvoiceItemList items, DateTime targetDate, Currency targetCurrency);
+    public Invoice generateInvoice(UUID accountId, BillingEventSet events, @Nullable List<InvoiceItem> items, DateTime targetDate, Currency targetCurrency) throws InvoiceApiException;
 }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceItemList.java b/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceItemList.java
index fb82f06..683bbaf 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceItemList.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceItemList.java
@@ -16,52 +16,73 @@
 
 package com.ning.billing.invoice.model;
 
-import com.ning.billing.invoice.api.InvoiceItem;
-
 import java.math.BigDecimal;
 import java.util.ArrayList;
 import java.util.List;
+import com.ning.billing.invoice.api.InvoiceItem;
 
 public class InvoiceItemList extends ArrayList<InvoiceItem> {
     private static final int NUMBER_OF_DECIMALS = InvoicingConfiguration.getNumberOfDecimals();
+    private static final int ROUNDING_METHOD = InvoicingConfiguration.getRoundingMode();
 
-    public BigDecimal getTotalAmount() {
-        // TODO: Jeff -- naive implementation, assumes all invoice items share the same currency
-        BigDecimal total = new BigDecimal("0");
-
-        for (InvoiceItem item : this) {
-            total = total.add(item.getAmount());
-        }
+    public InvoiceItemList() {
+        super();
+    }
 
-        return total.setScale(NUMBER_OF_DECIMALS);
+    public InvoiceItemList(final List<InvoiceItem> invoiceItems) {
+        super();
+        this.addAll(invoiceItems);
     }
 
-    public void removeZeroDollarItems() {
-        List<InvoiceItem> itemsToRemove = new ArrayList<InvoiceItem>();
+    public BigDecimal getTotalAmount() {
+        // naive implementation, assumes all invoice items share the same currency
+        BigDecimal total = BigDecimal.ZERO.setScale(NUMBER_OF_DECIMALS, ROUNDING_METHOD);
 
-        for (InvoiceItem item : this) {
-            if (item.getAmount().compareTo(BigDecimal.ZERO) == 0) {
-                itemsToRemove.add(item);
+        for (final InvoiceItem item : this) {
+            if (item.getAmount() != null) {
+                total = total.add(item.getAmount());
             }
         }
 
-        this.removeAll(itemsToRemove);
+        return total.setScale(NUMBER_OF_DECIMALS, ROUNDING_METHOD);
     }
 
-    public void removeCancellingPairs() {
-        List<InvoiceItem> itemsToRemove = new ArrayList<InvoiceItem>();
-
-        for (int firstItemIndex = 0; firstItemIndex < this.size(); firstItemIndex++) {
-            for (int secondItemIndex = firstItemIndex + 1; secondItemIndex < this.size(); secondItemIndex++) {
-                InvoiceItem firstItem = this.get(firstItemIndex);
-                InvoiceItem secondItem = this.get(secondItemIndex);
-                if (firstItem.cancels(secondItem)) {
-                    itemsToRemove.add(firstItem);
-                    itemsToRemove.add(secondItem);
-                }
-            }
-        }
+//    public void removeCancellingPairs() {
+//        List<InvoiceItem> itemsToRemove = new ArrayList<InvoiceItem>();
+//
+//        for (int firstItemIndex = 0; firstItemIndex < this.size(); firstItemIndex++) {
+//            for (int secondItemIndex = firstItemIndex + 1; secondItemIndex < this.size(); secondItemIndex++) {
+//                InvoiceItem firstItem = this.get(firstItemIndex);
+//                InvoiceItem secondItem = this.get(secondItemIndex);
+//                if (firstItem.cancels(secondItem)) {
+//                    itemsToRemove.add(firstItem);
+//                    itemsToRemove.add(secondItem);
+//                }
+//            }
+//        }
+//
+//        this.removeAll(itemsToRemove);
+//    }
 
-        this.removeAll(itemsToRemove);
-    }
+//   /*
+//    * removes recurring items from the list that have a recurring amount of zero, but a recurring rate that is not zero
+//    */
+//    public void cleanupDuplicatedItems() {
+//        Iterator<InvoiceItem> iterator = this.iterator();
+//        while (iterator.hasNext()) {
+//            InvoiceItem item = iterator.next();
+//
+//            if (item instanceof RecurringInvoiceItem) {
+//                RecurringInvoiceItem that = (RecurringInvoiceItem) item;
+//                boolean recurringRateNull = (that.getRate() == null);
+//                boolean recurringAmountZero = (that.getAmount() !=null) && (that.getAmount().compareTo(BigDecimal.ZERO) == 0);
+//
+//                if (recurringRateNull || recurringAmountZero) {
+//                    iterator.remove();
+//                } else if (that.getEndDate() != null && that.getStartDate().compareTo(that.getEndDate()) == 0) {
+//                    iterator.remove();
+//                }
+//            }
+//        }
+//    }
 }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/InvoicingConfiguration.java b/invoice/src/main/java/com/ning/billing/invoice/model/InvoicingConfiguration.java
index 7482465..554dc62 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/InvoicingConfiguration.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/InvoicingConfiguration.java
@@ -22,7 +22,7 @@ public class InvoicingConfiguration {
     private final static int roundingMethod = BigDecimal.ROUND_HALF_UP;
     private final static int numberOfDecimals = 4;
 
-    public static int getRoundingMethod() {
+    public static int getRoundingMode() {
         return roundingMethod;
     }
 
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
new file mode 100644
index 0000000..93ef474
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/RecurringInvoiceItem.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * 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 RecurringInvoiceItem extends InvoiceItemBase {
+    private final BigDecimal rate;
+    private final UUID reversedItemId;
+
+    public RecurringInvoiceItem(UUID invoiceId, UUID subscriptionId, String planName, String phaseName,
+                                DateTime startDate, DateTime endDate,
+                                BigDecimal amount, BigDecimal rate,
+                                Currency currency) {
+        this(UUID.randomUUID(), invoiceId, subscriptionId, planName, phaseName, startDate, endDate,
+             amount, rate, currency);
+    }
+
+    public RecurringInvoiceItem(UUID invoiceId, UUID subscriptionId, String planName, String phaseName,
+                                DateTime startDate, DateTime endDate,
+                                BigDecimal amount, BigDecimal rate,
+                                Currency currency, UUID reversedItemId) {
+        this(UUID.randomUUID(), invoiceId, subscriptionId, planName, phaseName, startDate, endDate,
+             amount, rate, currency, reversedItemId);
+    }
+
+    public RecurringInvoiceItem(UUID id, UUID invoiceId, UUID subscriptionId, String planName, String phaseName,
+                                DateTime startDate, DateTime endDate,
+                                BigDecimal amount, BigDecimal rate,
+                                Currency currency) {
+        super(id, invoiceId, subscriptionId, planName, phaseName, startDate, endDate, amount, currency);
+
+        this.rate = rate;
+        this.reversedItemId = null;
+    }
+
+    public RecurringInvoiceItem(UUID id, UUID invoiceId, UUID subscriptionId, String planName, String phaseName,
+                                DateTime startDate, DateTime endDate,
+                                BigDecimal amount, BigDecimal rate,
+                                Currency currency, UUID reversedItemId) {
+        super(id, invoiceId, subscriptionId, planName, phaseName, startDate, endDate, amount, currency);
+
+        this.rate = rate;
+        this.reversedItemId = reversedItemId;
+    }
+
+    @Override
+    public InvoiceItem asCredit() {
+        BigDecimal amountNegated = amount == null ? null : amount.negate();
+        return new RecurringInvoiceItem(invoiceId, subscriptionId, planName, phaseName, startDate, endDate,
+                                        amountNegated, rate, currency, id);
+    }
+
+    @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 RecurringInvoiceItem)) {return -1;}
+
+        RecurringInvoiceItem that = (RecurringInvoiceItem) item;
+
+        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;
+        }
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        RecurringInvoiceItem that = (RecurringInvoiceItem) o;
+
+        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.equals(that.subscriptionId)) return false;
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = invoiceId.hashCode();
+        result = 31 * result + subscriptionId.hashCode();
+        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(", ");
+
+        return sb.toString();
+
+//        StringBuilder sb = new StringBuilder();
+//        sb.append("InvoiceItem = {").append("id = ").append(id.toString()).append(", ");
+//        sb.append("invoiceId = ").append(invoiceId.toString()).append(", ");
+//        sb.append("subscriptionId = ").append(subscriptionId.toString()).append(", ");
+//        sb.append("planName = ").append(planName).append(", ");
+//        sb.append("phaseName = ").append(phaseName).append(", ");
+//        sb.append("startDate = ").append(startDate.toString()).append(", ");
+//        if (endDate != null) {
+//            sb.append("endDate = ").append(endDate.toString()).append(", ");
+//        } else {
+//            sb.append("endDate = null");
+//        }
+//        sb.append("recurringAmount = ");
+//        if (amount == null) {
+//            sb.append("null");
+//        } else {
+//            sb.append(amount.toString());
+//        }
+//        sb.append(", ");
+//
+//        sb.append("recurringRate = ");
+//        if (rate == null) {
+//            sb.append("null");
+//        } else {
+//            sb.append(rate.toString());
+//        }
+//        sb.append(", ");
+//
+//        sb.append("}");
+//        return sb.toString();
+    }
+}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/RecurringInvoiceItemData.java b/invoice/src/main/java/com/ning/billing/invoice/model/RecurringInvoiceItemData.java
new file mode 100644
index 0000000..d42c533
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/RecurringInvoiceItemData.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.invoice.model;
+
+import org.joda.time.DateTime;
+
+import java.math.BigDecimal;
+
+public class RecurringInvoiceItemData {
+    private final DateTime startDate;
+    private final DateTime endDate;
+    private final BigDecimal numberOfCycles;
+
+    public RecurringInvoiceItemData(DateTime startDate, DateTime endDate, BigDecimal numberOfCycles) {
+        this.startDate = startDate;
+        this.endDate = endDate;
+        this.numberOfCycles = numberOfCycles;
+    }
+
+    public DateTime getStartDate() {
+        return startDate;
+    }
+
+    public DateTime getEndDate() {
+        return endDate;
+    }
+
+    public BigDecimal getNumberOfCycles() {
+        return numberOfCycles;
+    }
+}
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
new file mode 100644
index 0000000..0dd5e3a
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/notification/DefaultNextBillingDateNotifier.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * 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 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.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.engine.dao.EntitlementDao;
+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;
+import com.ning.billing.util.notificationq.NotificationQueueService.NotificationQueueHandler;
+
+public class DefaultNextBillingDateNotifier implements  NextBillingDateNotifier {
+
+    private final static Logger log = LoggerFactory.getLogger(DefaultNextBillingDateNotifier.class);
+
+    public static final String NEXT_BILLING_DATE_NOTIFIER_QUEUE = "next-billing-date-queue";
+
+    private final NotificationQueueService notificationQueueService;
+	private final InvoiceConfig config;
+    private final EntitlementDao entitlementDao;
+
+    private NotificationQueue nextBillingQueue;
+	private InvoiceListener listener;
+
+    @Inject
+	public DefaultNextBillingDateNotifier(NotificationQueueService notificationQueueService, 
+			InvoiceConfig config, EntitlementDao entitlementDao, InvoiceListener listener){
+		this.notificationQueueService = notificationQueueService;
+		this.config = config;
+        this.entitlementDao = entitlementDao;
+        this.listener = listener; 
+	}
+
+    @Override
+    public void initialize() {
+		try {
+            nextBillingQueue = notificationQueueService.createNotificationQueue(DefaultInvoiceService.INVOICE_SERVICE_NAME,
+            		NEXT_BILLING_DATE_NOTIFIER_QUEUE,
+                    new NotificationQueueHandler() {
+                @Override
+                public void handleReadyNotification(String notificationKey, DateTime eventDate) {
+                	try {
+                 		UUID key = UUID.fromString(notificationKey);
+                        Subscription subscription = entitlementDao.getSubscriptionFromId(key);
+                        if (subscription == null) {
+                            log.warn("Next Billing Date Notification Queue handled spurious notification (key: " + key + ")" );
+                        } else {
+                            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.isEventProcessingOff();
+                }
+                @Override
+                public long getNotificationSleepTimeMs() {
+                    return config.getNotificationSleepTimeMs();
+                }
+                @Override
+                public int getDaoMaxReadyEvents() {
+                    return config.getDaoMaxReadyEvents();
+                }
+                @Override
+                public long getDaoClaimTimeMs() {
+                    return config.getDaoMaxReadyEvents();
+                }
+            });
+        } catch (NotificationQueueAlreadyExists e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public void start() {
+    	nextBillingQueue.startQueue();
+    }
+
+    @Override
+    public void stop() {
+        if (nextBillingQueue != null) {
+        	nextBillingQueue.stopQueue();
+        }
+    }
+
+    private void processEvent(UUID subscriptionId, DateTime eventDateTime) {
+        listener.handleNextBillingDateEvent(subscriptionId, eventDateTime);
+    }
+
+ 
+}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/notification/NextBillingDateNotifier.java b/invoice/src/main/java/com/ning/billing/invoice/notification/NextBillingDateNotifier.java
new file mode 100644
index 0000000..ea630aa
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/notification/NextBillingDateNotifier.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;
+
+
+public interface NextBillingDateNotifier {
+
+    public void initialize();
+
+    public void start();
+
+    public void stop();
+
+}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/notification/NextBillingDatePoster.java b/invoice/src/main/java/com/ning/billing/invoice/notification/NextBillingDatePoster.java
new file mode 100644
index 0000000..67598d1
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/notification/NextBillingDatePoster.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.invoice.notification;
+
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
+
+public interface NextBillingDatePoster {
+
+	void insertNextBillingNotification(Transmogrifier transactionalDao,
+			UUID subscriptionId, DateTime futureNotificationTime);
+
+}
\ No newline at end of file
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
new file mode 100644
index 0000000..90310d5
--- /dev/null
+++ b/invoice/src/main/resources/com/ning/billing/invoice/dao/FixedPriceInvoiceItemSqlDao.sql.stg
@@ -0,0 +1,63 @@
+group FixedPriceInvoiceItemSqlDao;
+
+fields(prefix) ::= <<
+  <prefix>id,
+  <prefix>invoice_id,
+  <prefix>subscription_id,
+  <prefix>plan_name,
+  <prefix>phase_name,
+  <prefix>start_date,
+  <prefix>end_date,
+  <prefix>amount,
+  <prefix>currency
+>>
+
+getById() ::= <<
+  SELECT <fields()>
+  FROM fixed_invoice_items
+  WHERE id = :id;
+>>
+
+getInvoiceItemsByInvoice() ::= <<
+  SELECT <fields()>
+  FROM fixed_invoice_items
+  WHERE invoice_id = :invoiceId;
+>>
+
+getInvoiceItemsByAccount() ::= <<
+  SELECT <fields("rii.")>
+  FROM fixed_invoice_items rii
+  INNER JOIN invoices i ON i.id = rii.invoice_id
+  WHERE i.account_id = :accountId;
+>>
+
+getInvoiceItemsBySubscription() ::= <<
+  SELECT <fields()>
+  FROM fixed_invoice_items
+  WHERE subscription_id = :subscriptionId;
+>>
+
+create() ::= <<
+  INSERT INTO fixed_invoice_items(<fields()>)
+  VALUES(:id, :invoiceId, :subscriptionId, :planName, :phaseName,
+         :startDate, :endDate, :amount, :currency);
+>>
+
+batchCreateFromTransaction() ::= <<
+  INSERT INTO fixed_invoice_items(<fields()>)
+  VALUES(:id, :invoiceId, :subscriptionId, :planName, :phaseName,
+         :startDate, :endDate, :amount, :currency);
+>>
+
+update() ::= <<
+  UPDATE fixed_invoice_items
+  SET invoice_id = :invoiceId, subscription_id = :subscriptionId, plan_name = :planName, phase_name = :phaseName,
+      start_date = :startDate, end_date = :endDate, amount = :amount, currency = :currency
+  WHERE id = :id;
+>>
+
+test() ::= <<
+  SELECT 1
+  FROM fixed_invoice_items;
+>>
+;
\ No newline at end of file
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
new file mode 100644
index 0000000..2172573
--- /dev/null
+++ b/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoicePaymentSqlDao.sql.stg
@@ -0,0 +1,61 @@
+group InvoicePayment;
+
+invoicePaymentFields(prefix) ::= <<
+  <prefix>invoice_id,
+  <prefix>payment_attempt_id,
+  <prefix>payment_attempt_date,
+  <prefix>amount,
+  <prefix>currency,
+  <prefix>created_date,
+  <prefix>updated_date
+>>
+
+create() ::= <<
+  INSERT INTO invoice_payments(<invoicePaymentFields()>)
+  VALUES(:invoiceId, :paymentAttemptId, :paymentAttemptDate, :amount, :currency, :createdDate, :updatedDate);
+>>
+
+batchCreateFromTransaction() ::= <<
+  INSERT INTO invoice_payments(<invoicePaymentFields()>)
+  VALUES(:invoiceId, :paymentAttemptId, :paymentAttemptDate, :amount, :currency, :createdDate, :updatedDate);
+>>
+
+
+update() ::= <<
+  UPDATE invoice_payments
+  SET payment_date = :paymentAttemptDate, amount = :amount, currency = :currency, created_date = :createdDate, updated_date = :updatedDate
+  WHERE invoice_id = :invoiceId, payment_attempt_id = :paymentAttemptId;
+>>
+
+getByPaymentAttemptId() ::= <<
+  SELECT <invoicePaymentFields()>
+  FROM invoice_payments
+  WHERE payment_id = :paymentAttemptId;
+>>
+
+get() ::= <<
+  SELECT <invoicePaymentFields()>
+  FROM invoice_payments;
+>>
+
+getPaymentsForInvoice() ::= <<
+  SELECT <invoicePaymentFields()>
+  FROM invoice_payments
+  WHERE invoice_id = :invoiceId;
+>>
+
+notifyOfPaymentAttempt() ::= <<
+  INSERT INTO invoice_payments(<invoicePaymentFields()>)
+  VALUES(:invoiceId, :paymentAttemptId, :paymentAttemptDate, :amount, :currency, NOW(), NOW());
+>>
+
+getInvoicePayment() ::= <<
+    SELECT <invoicePaymentFields()>
+    FROM invoice_payments
+    WHERE payment_id = :payment_id;
+>>
+
+test() ::= <<
+  SELECT 1 FROM invoice_payments;
+>>
+;
\ No newline at end of file
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 8766e18..35c45bf 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,34 +1,39 @@
 group InvoiceDao;
 
+invoiceFields(prefix) ::= <<
+    <prefix>id,
+    <prefix>account_id,
+    <prefix>invoice_date,
+    <prefix>target_date,
+    <prefix>currency
+>>
+
 get() ::= <<
-  SELECT i.id, i.account_id, i.invoice_date, i.target_date, i.currency, SUM(ii.amount) AS amount,
-         SUM(ip.amount) AS amount_paid, MAX(ip.payment_date) AS last_payment_attempt
-  FROM invoices i
-  LEFT JOIN invoice_payments ip ON ip.invoice_id = i.id
-  LEFT JOIN invoice_items ii ON ii.invoice_id = i.id
-  GROUP BY i.id, i.account_id, i.invoice_date, i.target_date, i.currency
-  ORDER BY i.invoice_date ASC;
+  SELECT <invoiceFields()>
+  FROM invoices
+  ORDER BY target_date ASC;
 >>
 
 getInvoicesByAccount() ::= <<
-  SELECT i.id, i.account_id, i.invoice_date, i.target_date, i.currency, SUM(ii.amount) AS amount,
-         SUM(ip.amount) AS amount_paid, MAX(ip.payment_date) AS last_payment_attempt
-  FROM invoices i
-  LEFT JOIN invoice_payments ip ON ip.invoice_id = i.id
-  LEFT JOIN invoice_items ii ON ii.invoice_id = i.id
-  WHERE i.account_id = :accountId
-  GROUP BY i.id, i.account_id, i.invoice_date, i.target_date, i.currency
-  ORDER BY i.invoice_date ASC;
+  SELECT <invoiceFields()>
+  FROM invoices
+  WHERE account_id = :accountId
+  ORDER BY target_date ASC;
+>>
+
+getInvoicesByAccountAfterDate() ::= <<
+  SELECT <invoiceFields()>
+  FROM invoices
+  WHERE account_id = :accountId AND target_date >= :fromDate
+  ORDER BY target_date ASC;
 >>
 
 getInvoicesBySubscription() ::= <<
-  SELECT i.id, i.account_id, i.invoice_date, i.target_date, i.currency, SUM(ii.amount) AS amount,
-         SUM(ip.amount) AS amount_paid, MAX(ip.payment_date) AS last_payment_attempt
+  SELECT <invoiceFields("i.")>
   FROM invoices i
-  LEFT JOIN invoice_items ii ON i.id = ii.invoice_id
-  LEFT JOIN invoice_payments ip ON ip.invoice_id = i.id
-  WHERE ii.subscription_id = :subscriptionId
-  GROUP BY i.id, i.account_id, i.invoice_date, i.target_date, i.currency;
+  LEFT JOIN recurring_invoice_items rii ON i.id = rii.invoice_id
+  WHERE rii.subscription_id = :subscriptionId
+  GROUP BY <invoiceFields("i.")>;
 >>
 
 getInvoicesForPayment() ::= <<
@@ -37,41 +42,56 @@ getInvoicesForPayment() ::= <<
   LEFT JOIN invoice_payment_summary ips ON ips.invoice_id = i.id
   LEFT JOIN invoice_item_summary iis ON iis.invoice_id = i.id
   WHERE ((ips.last_payment_date IS NULL) OR (DATEDIFF(:targetDate, ips.last_payment_date) >= :numberOfDays))
-        AND ((ips.total_paid IS NULL) OR (iis.total_amount >= ips.total_paid))
-        AND ((iis.total_amount IS NOT NULL) AND (iis.total_amount > 0))
-  GROUP BY i.id, i.account_id, i.invoice_date, i.target_date, i.currency;
+        AND ((ips.total_paid IS NULL) OR (iis.amount_invoiced >= ips.total_paid))
+        AND ((iis.amount_invoiced IS NOT NULL) AND (iis.amount_invoiced > 0))
+  GROUP BY <invoiceFields("i.")>;
 >>
 
 getById() ::= <<
-  SELECT i.id, i.account_id, i.invoice_date, i.target_date, i.currency, SUM(ii.amount) AS amount,
-         SUM(ip.amount) AS amount_paid, MAX(ip.payment_date) AS last_payment_attempt
+  SELECT <invoiceFields()>
+  FROM invoices
+  WHERE id = :id;
+>>
+
+getAccountBalance() ::= <<
+  SELECT SUM(iis.amount_invoiced) AS amount_invoiced,
+         SUM(ips.total_paid) AS amount_paid
   FROM invoices i
-  LEFT JOIN invoice_items ii ON i.id = ii.invoice_id
-  LEFT JOIN invoice_payments ip ON ip.invoice_id = i.id
-  WHERE i.id = :id
-  GROUP BY i.id, i.account_id, i.invoice_date, i.target_date, i.currency;
+  LEFT JOIN invoice_payment_summary ips ON i.id = ips.invoice_id
+  LEFT JOIN invoice_item_summary iis ON i.id = iis.invoice_id
+  WHERE i.account_id = :accountId
+  GROUP BY i.account_id;
 >>
 
 create() ::= <<
-  INSERT INTO invoices(id, account_id, invoice_date, target_date, currency)
+  INSERT INTO invoices(<invoiceFields()>)
   VALUES (:id, :accountId, :invoiceDate, :targetDate, :currency);
 >>
 
+getInvoiceIdByPaymentAttemptId() ::= <<
+  SELECT i.id
+    FROM invoices i, invoice_payments ip
+   WHERE ip.invoice_id = i.id
+     AND ip.payment_attempt_id = :paymentAttemptId
+>>
+
 update() ::= <<
   UPDATE invoices
   SET account_id = :accountId, invoice_date = :invoiceDate, target_date = :targetDate, currency = :currency
   WHERE id = :id;
 >>
 
-notifySuccessfulPayment() ::= <<
-  INSERT INTO invoice_payments(invoice_id, payment_id, payment_date, amount, currency)
-  VALUES(:invoiceId, :paymentId, :paymentDate, :amount, :currency);
+getUnpaidInvoicesByAccountId() ::= <<
+  SELECT i.id, i.account_id, i.invoice_date, i.target_date, i.currency
+  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
+  WHERE i.account_id = :accountId AND NOT (i.target_date > :upToDate)
+  GROUP BY i.id, i.account_id, i.invoice_date, i.target_date, i.currency
+  HAVING (SUM(iis.amount_invoiced) > SUM(ips.total_paid)) OR (SUM(ips.total_paid) IS NULL)
+  ORDER BY i.target_date ASC;
 >>
 
-notifyFailedPayment() ::= <<
-  INSERT INTO invoice_payments(invoice_id, payment_id, payment_date)
-  VALUES(:invoiceId, :paymentId, :paymentAttemptDate);
->>
 
 test() ::= <<
   SELECT 1
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
new file mode 100644
index 0000000..d50a1c5
--- /dev/null
+++ b/invoice/src/main/resources/com/ning/billing/invoice/dao/RecurringInvoiceItemSqlDao.sql.stg
@@ -0,0 +1,66 @@
+group RecurringInvoiceItemSqlDao;
+
+fields(prefix) ::= <<
+  <prefix>id,
+  <prefix>invoice_id,
+  <prefix>subscription_id,
+  <prefix>plan_name,
+  <prefix>phase_name,
+  <prefix>start_date,
+  <prefix>end_date,
+  <prefix>amount,
+  <prefix>rate,
+  <prefix>currency,
+  <prefix>reversed_item_id
+>>
+
+getById() ::= <<
+  SELECT <fields()>
+  FROM recurring_invoice_items
+  WHERE id = :id;
+>>
+
+getInvoiceItemsByInvoice() ::= <<
+  SELECT <fields()>
+  FROM recurring_invoice_items
+  WHERE invoice_id = :invoiceId;
+>>
+
+getInvoiceItemsByAccount() ::= <<
+  SELECT <fields("rii.")>
+  FROM recurring_invoice_items rii
+  INNER JOIN invoices i ON i.id = rii.invoice_id
+  WHERE i.account_id = :accountId;
+>>
+
+getInvoiceItemsBySubscription() ::= <<
+  SELECT <fields()>
+  FROM recurring_invoice_items
+  WHERE subscription_id = :subscriptionId;
+>>
+
+create() ::= <<
+  INSERT INTO recurring_invoice_items(<fields()>)
+  VALUES(:id, :invoiceId, :subscriptionId, :planName, :phaseName, :startDate, :endDate,
+         :amount, :rate, :currency, :reversedItemId);
+>>
+
+batchCreateFromTransaction() ::= <<
+  INSERT INTO recurring_invoice_items(<fields()>)
+  VALUES(:id, :invoiceId, :subscriptionId, :planName, :phaseName, :startDate, :endDate,
+         :amount, :rate, :currency, :reversedItemId);
+>>
+
+update() ::= <<
+  UPDATE recurring_invoice_items
+  SET invoice_id = :invoiceId, subscription_id = :subscriptionId, plan_name = :planName, phase_name = :phaseName,
+      start_date = :startDate, end_date = :endDate, amount = :amount, rate = :rate, currency = :currency,
+      reversed_item_id = :reversedItemId
+  WHERE id = :id;
+>>
+
+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 a3ff37d..c7712a8 100644
--- a/invoice/src/main/resources/com/ning/billing/invoice/ddl.sql
+++ b/invoice/src/main/resources/com/ning/billing/invoice/ddl.sql
@@ -1,18 +1,43 @@
 DROP TABLE IF EXISTS invoice_items;
-CREATE TABLE 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,
   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,
-  description varchar(100) NOT NULL,
-  amount numeric(10,4) NOT NULL,
-  rate numeric(10,4) NOT NULL,
+  amount numeric(10,4) NULL,
+  rate numeric(10,4) NULL,
   currency char(3) NOT NULL,
+  reversed_item_id char(36),
   PRIMARY KEY(id)
 ) ENGINE=innodb;
+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);
 
-CREATE INDEX invoice_items_subscription_id ON invoice_items(subscription_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,
+  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,
+  PRIMARY KEY(id)
+) ENGINE=innodb;
+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);
+
+DROP TABLE IF EXISTS invoice_locking;
+CREATE TABLE invoice_locking (
+  account_id char(36) NOT NULL,
+  PRIMARY KEY(account_id)
+) ENGINE = innodb;
 
 DROP TABLE IF EXISTS invoices;
 CREATE TABLE invoices (
@@ -23,28 +48,32 @@ CREATE TABLE invoices (
   currency char(3) NOT NULL,
   PRIMARY KEY(id)
 ) ENGINE=innodb;
-
 CREATE INDEX invoices_account_id ON invoices(account_id ASC);
 
 DROP TABLE IF EXISTS invoice_payments;
 CREATE TABLE invoice_payments (
   invoice_id char(36) NOT NULL,
-  payment_id char(36) NOT NULL,
-  payment_date datetime NOT NULL,
+  payment_attempt_id char(36) COLLATE utf8_bin NOT NULL,
+  payment_attempt_date datetime,
   amount numeric(10,4),
   currency char(3),
-  PRIMARY KEY(invoice_id, payment_id)
+  created_date datetime NOT NULL,
+  updated_date datetime NOT NULL,
+  PRIMARY KEY(invoice_id, payment_attempt_id)
 ) ENGINE=innodb;
-CREATE UNIQUE INDEX invoice_payments_unique ON invoice_payments(invoice_id, payment_id);
+CREATE UNIQUE INDEX invoice_payments_unique ON invoice_payments(invoice_id, payment_attempt_id);
 
 DROP VIEW IF EXISTS invoice_payment_summary;
 CREATE VIEW invoice_payment_summary AS
-SELECT invoice_id, SUM(amount) AS total_paid, MAX(payment_date) AS last_payment_date
+SELECT invoice_id,
+       CASE WHEN SUM(amount) IS NULL THEN 0 ELSE SUM(amount) END AS total_paid,
+       MAX(payment_attempt_date) AS last_payment_date
 FROM invoice_payments
 GROUP BY invoice_id;
 
 DROP VIEW IF EXISTS invoice_item_summary;
 CREATE VIEW invoice_item_summary AS
-SELECT invoice_id, SUM(amount) AS total_amount
-FROM invoice_items
-GROUP BY invoice_id;
\ No newline at end of file
+SELECT invoice_id,
+       CASE WHEN SUM(amount) IS NULL THEN 0 ELSE SUM(amount) END AS amount_invoiced
+FROM recurring_invoice_items
+GROUP BY invoice_id;
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
new file mode 100644
index 0000000..11e6ead
--- /dev/null
+++ b/invoice/src/test/java/com/ning/billing/invoice/api/MockInvoicePaymentApi.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * 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 java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.joda.time.DateTime;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.model.DefaultInvoicePayment;
+
+public class MockInvoicePaymentApi implements InvoicePaymentApi
+{
+    private final CopyOnWriteArrayList<Invoice> invoices = new CopyOnWriteArrayList<Invoice>();
+    private final CopyOnWriteArrayList<InvoicePayment> invoicePayments = new CopyOnWriteArrayList<InvoicePayment>();
+
+    public void add(Invoice invoice) {
+        invoices.add(invoice);
+    }
+
+    @Override
+    public void notifyOfPaymentAttempt(InvoicePayment invoicePayment) {
+        for (InvoicePayment existingInvoicePayment : invoicePayments) {
+            if (existingInvoicePayment.getInvoiceId().equals(invoicePayment.getInvoiceId()) && existingInvoicePayment.getPaymentAttemptId().equals(invoicePayment.getPaymentAttemptId())) {
+                invoicePayments.remove(existingInvoicePayment);
+            }
+        }
+        invoicePayments.add(invoicePayment);
+    }
+
+    @Override
+    public List<Invoice> getInvoicesByAccount(UUID accountId) {
+        ArrayList<Invoice> result = new ArrayList<Invoice>();
+
+        for (Invoice invoice : invoices) {
+            if (accountId.equals(invoice.getAccountId())) {
+                result.add(invoice);
+            }
+        }
+        return result;
+    }
+
+    @Override
+    public Invoice getInvoice(UUID invoiceId) {
+        for (Invoice invoice : invoices) {
+            if (invoiceId.equals(invoice.getId())) {
+                return invoice;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public Invoice getInvoiceForPaymentAttemptId(UUID paymentAttemptId) {
+        for (InvoicePayment invoicePayment : invoicePayments) {
+            if (invoicePayment.getPaymentAttemptId().equals(paymentAttemptId)) {
+                return getInvoice(invoicePayment.getInvoiceId());
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public InvoicePayment getInvoicePayment(UUID paymentAttemptId) {
+        for (InvoicePayment invoicePayment : invoicePayments) {
+            if (paymentAttemptId.equals(invoicePayment.getPaymentAttemptId())) {
+                return invoicePayment;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public void notifyOfPaymentAttempt(UUID invoiceId, BigDecimal amountOutstanding, Currency currency, UUID paymentAttemptId, DateTime paymentAttemptDate) {
+        InvoicePayment invoicePayment = new DefaultInvoicePayment(paymentAttemptId, invoiceId, paymentAttemptDate, amountOutstanding, currency);
+        notifyOfPaymentAttempt(invoicePayment);
+    }
+
+    @Override
+    public void notifyOfPaymentAttempt(UUID invoiceId, UUID paymentAttemptId, DateTime paymentAttemptDate) {
+        InvoicePayment invoicePayment = new DefaultInvoicePayment(paymentAttemptId, invoiceId, paymentAttemptDate);
+        notifyOfPaymentAttempt(invoicePayment);
+    }
+
+}
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 268aaf5..6957ba0 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
@@ -16,42 +16,63 @@
 
 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.invoice.tests.InvoicingTestBase;
 import org.apache.commons.io.IOUtils;
+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.invoice.glue.InvoiceModuleMock;
-import com.ning.billing.util.eventbus.DefaultEventBusService;
-import com.ning.billing.util.eventbus.EventBusService;
-
-import static org.testng.Assert.fail;
+import com.ning.billing.invoice.glue.InvoiceModuleWithEmbeddedDb;
+import com.ning.billing.util.bus.BusService;
+import com.ning.billing.util.bus.DefaultBusService;
 
-public abstract class InvoiceDaoTestBase {
+public abstract class InvoiceDaoTestBase extends InvoicingTestBase {
     protected InvoiceDao invoiceDao;
-    protected InvoiceItemSqlDao invoiceItemDao;
+    protected RecurringInvoiceItemSqlDao recurringInvoiceItemDao;
+    protected InvoicePaymentSqlDao invoicePaymentDao;
+    protected InvoiceModuleWithEmbeddedDb module;
 
-    @BeforeClass()
+    @BeforeClass(alwaysRun = true)
     protected void setup() throws IOException {
         // Health check test to make sure MySQL is setup properly
         try {
-            InvoiceModuleMock module = new InvoiceModuleMock();
-            final String ddl = IOUtils.toString(DefaultInvoiceDao.class.getResourceAsStream("/com/ning/billing/invoice/ddl.sql"));
-            module.createDb(ddl);
+            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"));
+
+            module.startDb();
+            module.initDb(invoiceDdl);
+            module.initDb(entitlementDdl);
 
             final Injector injector = Guice.createInjector(Stage.DEVELOPMENT, module);
 
             invoiceDao = injector.getInstance(InvoiceDao.class);
             invoiceDao.test();
 
-            invoiceItemDao = module.getInvoiceItemDao();
+            recurringInvoiceItemDao = module.getInvoiceItemSqlDao();
+
+            invoicePaymentDao = module.getInvoicePaymentSqlDao();
+
+            BusService busService = injector.getInstance(BusService.class);
+            ((DefaultBusService) busService).startBus();
 
-            EventBusService busService = injector.getInstance(EventBusService.class);
-            ((DefaultEventBusService) busService).startBus();
+            assertTrue(true);
         }
         catch (Throwable t) {
             fail(t.toString());
         }
     }
+
+    @AfterClass(alwaysRun = true)
+    protected void tearDown() {
+        module.stopDb();
+        assertTrue(true);
+    }
 }
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 3fe72ed..4e07a39 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,38 +16,57 @@
 
 package com.ning.billing.invoice.dao;
 
-import java.math.BigDecimal;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.UUID;
-import org.joda.time.DateTime;
-import org.joda.time.Days;
-import org.testng.annotations.Test;
+import com.ning.billing.catalog.DefaultPrice;
+import com.ning.billing.catalog.MockInternationalPrice;
+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.PhaseType;
+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;
+import com.ning.billing.invoice.api.InvoicePayment;
+import com.ning.billing.invoice.model.BillingEventSet;
 import com.ning.billing.invoice.model.DefaultInvoice;
-import com.ning.billing.invoice.model.DefaultInvoiceItem;
+import com.ning.billing.invoice.model.DefaultInvoiceGenerator;
+import com.ning.billing.invoice.model.RecurringInvoiceItem;
+import com.ning.billing.invoice.model.DefaultInvoicePayment;
+import com.ning.billing.invoice.model.InvoiceGenerator;
+import com.ning.billing.invoice.model.InvoiceItemList;
+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 java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.UUID;
+
+import static org.testng.Assert.*;
 import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertNotNull;
-import static org.testng.Assert.assertNull;
-import static org.testng.Assert.assertTrue;
 
 @Test(groups = {"invoicing", "invoicing-invoiceDao"})
 public class InvoiceDaoTests extends InvoiceDaoTestBase {
     private final int NUMBER_OF_DAY_BETWEEN_RETRIES = 8;
+    private final Clock clock = new DefaultClock();
 
     @Test
     public void testCreationAndRetrievalByAccount() {
         UUID accountId = UUID.randomUUID();
-        Invoice invoice = new DefaultInvoice(accountId, new DefaultClock().getUTCNow(), Currency.USD);
+        Invoice invoice = new DefaultInvoice(accountId, clock.getUTCNow(), Currency.USD, clock);
         DateTime invoiceDate = invoice.getInvoiceDate();
 
         invoiceDao.create(invoice);
 
-        List<Invoice> invoices = invoiceDao.getInvoicesByAccount(accountId.toString());
+        List<Invoice> invoices = invoiceDao.getInvoicesByAccount(accountId);
         assertNotNull(invoices);
         assertEquals(invoices.size(), 1);
         Invoice thisInvoice = invoices.get(0);
@@ -61,37 +80,38 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
     @Test
     public void testInvoicePayment() {
         UUID accountId = UUID.randomUUID();
-        Invoice invoice = new DefaultInvoice(accountId, new DefaultClock().getUTCNow(), Currency.USD);
+        Invoice invoice = new DefaultInvoice(accountId, clock.getUTCNow(), Currency.USD, clock);
         UUID invoiceId = invoice.getId();
         UUID subscriptionId = 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 DefaultInvoiceItem(invoiceId, subscriptionId, startDate, endDate, "test", new BigDecimal("21.00"), new BigDecimal("7.00"), Currency.USD);
-        invoice.add(invoiceItem);
+        InvoiceItem invoiceItem = new RecurringInvoiceItem(invoiceId, subscriptionId, "test plan", "test phase", startDate, endDate, new BigDecimal("21.00"), new BigDecimal("7.00"), Currency.USD);
+        invoice.addInvoiceItem(invoiceItem);
         invoiceDao.create(invoice);
 
-        Invoice savedInvoice = invoiceDao.getById(invoiceId.toString());
+        Invoice savedInvoice = invoiceDao.getById(invoiceId);
         assertNotNull(savedInvoice);
         assertEquals(savedInvoice.getTotalAmount().compareTo(new BigDecimal("21.00")), 0);
-        assertEquals(savedInvoice.getAmountOutstanding().compareTo(new BigDecimal("21.00")), 0);
+        assertEquals(savedInvoice.getBalance().compareTo(new BigDecimal("21.00")), 0);
         assertEquals(savedInvoice.getAmountPaid(), BigDecimal.ZERO);
-        assertEquals(savedInvoice.getItems().size(), 1);
+        assertEquals(savedInvoice.getInvoiceItems().size(), 1);
 
         BigDecimal paymentAmount = new BigDecimal("11.00");
-        String paymentId = UUID.randomUUID().toString();
-        invoiceDao.notifySuccessfulPayment(invoiceId.toString(), paymentAmount, Currency.USD.toString(), paymentId, new DefaultClock().getUTCNow().plusDays(12).toDate());
+        UUID paymentAttemptId = UUID.randomUUID();
+
+        invoiceDao.notifyOfPaymentAttempt(new DefaultInvoicePayment(paymentAttemptId, invoiceId, clock.getUTCNow().plusDays(12), paymentAmount, Currency.USD));
 
-        Invoice retrievedInvoice = invoiceDao.getById(invoiceId.toString());
+        Invoice retrievedInvoice = invoiceDao.getById(invoiceId);
         assertNotNull(retrievedInvoice);
-        assertEquals(retrievedInvoice.getItems().size(), 1);
+        assertEquals(retrievedInvoice.getInvoiceItems().size(), 1);
         assertEquals(retrievedInvoice.getTotalAmount().compareTo(new BigDecimal("21.00")), 0);
-        assertEquals(retrievedInvoice.getAmountOutstanding().compareTo(new BigDecimal("10.00")), 0);
+        assertEquals(retrievedInvoice.getBalance().compareTo(new BigDecimal("10.00")), 0);
         assertEquals(retrievedInvoice.getAmountPaid().compareTo(new BigDecimal("11.00")), 0);
     }
 
     @Test
     public void testRetrievalForNonExistentInvoiceId() {
-        Invoice invoice = invoiceDao.getById(UUID.randomUUID().toString());
+        Invoice invoice = invoiceDao.getById(UUID.randomUUID());
         assertNull(invoice);
     }
 
@@ -99,49 +119,50 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
     public void testAddPayment() {
         UUID accountId = UUID.randomUUID();
         DateTime targetDate = new DateTime(2011, 10, 6, 0, 0, 0, 0);
-        Invoice invoice = new DefaultInvoice(accountId, targetDate, Currency.USD);
+        Invoice invoice = new DefaultInvoice(accountId, targetDate, Currency.USD, clock);
 
-        String paymentId = UUID.randomUUID().toString();
+        UUID paymentAttemptId = UUID.randomUUID();
         DateTime paymentAttemptDate = new DateTime(2011, 6, 24, 12, 14, 36, 0);
         BigDecimal paymentAmount = new BigDecimal("14.0");
 
         invoiceDao.create(invoice);
-        invoiceDao.notifySuccessfulPayment(invoice.getId().toString(), paymentAmount, Currency.USD.toString(), paymentId, paymentAttemptDate.toDate());
+        invoiceDao.notifyOfPaymentAttempt(new DefaultInvoicePayment(paymentAttemptId, invoice.getId(), paymentAttemptDate, paymentAmount, Currency.USD));
 
-        invoice = invoiceDao.getById(invoice.getId().toString());
+        invoice = invoiceDao.getById(invoice.getId());
         assertEquals(invoice.getAmountPaid().compareTo(paymentAmount), 0);
-        assertTrue(invoice.getLastPaymentAttempt().equals(paymentAttemptDate));
+        assertEquals(invoice.getLastPaymentAttempt().compareTo(paymentAttemptDate), 0);
+        assertEquals(invoice.getNumberOfPayments(), 1);
     }
 
     @Test
     public void testAddPaymentAttempt() {
         UUID accountId = UUID.randomUUID();
         DateTime targetDate = new DateTime(2011, 10, 6, 0, 0, 0, 0);
-        Invoice invoice = new DefaultInvoice(accountId, targetDate, Currency.USD);
+        Invoice invoice = new DefaultInvoice(accountId, targetDate, Currency.USD, clock);
 
         DateTime paymentAttemptDate = new DateTime(2011, 6, 24, 12, 14, 36, 0);
 
         invoiceDao.create(invoice);
-        invoiceDao.notifyFailedPayment(invoice.getId().toString(), UUID.randomUUID().toString(), paymentAttemptDate.toDate());
+        invoiceDao.notifyOfPaymentAttempt(new DefaultInvoicePayment(invoice.getId(), paymentAttemptDate));
 
-        invoice = invoiceDao.getById(invoice.getId().toString());
-        assertTrue(invoice.getLastPaymentAttempt().equals(paymentAttemptDate));
+        invoice = invoiceDao.getById(invoice.getId());
+        assertEquals(invoice.getLastPaymentAttempt().compareTo(paymentAttemptDate), 0);
     }
 
     @Test
     public void testGetInvoicesForPaymentWithNoResults() {
         DateTime notionalDate = new DateTime();
         DateTime targetDate = new DateTime(2011, 10, 6, 0, 0, 0, 0);
-        
+
         // determine the number of existing invoices available for payment (to avoid side effects from other tests)
-        List<UUID> invoices = invoiceDao.getInvoicesForPayment(notionalDate.toDate(), NUMBER_OF_DAY_BETWEEN_RETRIES);
+        List<UUID> invoices = invoiceDao.getInvoicesForPayment(notionalDate, NUMBER_OF_DAY_BETWEEN_RETRIES);
         int existingInvoiceCount = invoices.size();
-        
+
         UUID accountId = UUID.randomUUID();
-        Invoice invoice = new DefaultInvoice(accountId, targetDate, Currency.USD);
+        Invoice invoice = new DefaultInvoice(accountId, targetDate, Currency.USD, clock);
 
         invoiceDao.create(invoice);
-        invoices = invoiceDao.getInvoicesForPayment(notionalDate.toDate(), NUMBER_OF_DAY_BETWEEN_RETRIES);
+        invoices = invoiceDao.getInvoicesForPayment(notionalDate, NUMBER_OF_DAY_BETWEEN_RETRIES);
         assertEquals(invoices.size(), existingInvoiceCount);
     }
 
@@ -153,7 +174,7 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         // create a new invoice with one item
         UUID accountId = UUID.randomUUID();
         DateTime targetDate = new DateTime(2011, 10, 6, 0, 0, 0, 0);
-        Invoice invoice = new DefaultInvoice(accountId, targetDate, Currency.USD);
+        Invoice invoice = new DefaultInvoice(accountId, targetDate, Currency.USD, clock);
 
         UUID invoiceId = invoice.getId();
         UUID subscriptionId = UUID.randomUUID();
@@ -161,71 +182,72 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         BigDecimal rate = new BigDecimal("9.0");
         BigDecimal amount = rate.multiply(new BigDecimal("3.0"));
 
-        DefaultInvoiceItem item = new DefaultInvoiceItem(invoiceId, subscriptionId, targetDate, endDate, "test", amount, rate, Currency.USD);
-        invoice.add(item);
+        RecurringInvoiceItem item = new RecurringInvoiceItem(invoiceId, subscriptionId, "test plan", "test phase", targetDate, endDate, amount, rate, Currency.USD);
+        invoice.addInvoiceItem(item);
         invoiceDao.create(invoice);
 
         // ensure that the number of invoices for payment has increased by 1
         int count;
-        invoices = invoiceDao.getInvoicesForPayment(notionalDate.toDate(), NUMBER_OF_DAY_BETWEEN_RETRIES);
+        invoices = invoiceDao.getInvoicesForPayment(notionalDate, NUMBER_OF_DAY_BETWEEN_RETRIES);
         List<Invoice> invoicesDue = getInvoicesDueForPaymentAttempt(invoiceDao.get(), notionalDate);
         count = invoicesDue.size();
         assertEquals(invoices.size(), count);
 
         // attempt a payment; ensure that the number of invoices for payment has decreased by 1
         // (no retries for NUMBER_OF_DAYS_BETWEEN_RETRIES days)
-        invoiceDao.notifyFailedPayment(invoice.getId().toString(), UUID.randomUUID().toString(), notionalDate.toDate());
-        invoices = invoiceDao.getInvoicesForPayment(notionalDate.toDate(), NUMBER_OF_DAY_BETWEEN_RETRIES);
+        invoiceDao.notifyOfPaymentAttempt(new DefaultInvoicePayment(invoice.getId(), notionalDate));
+        invoices = invoiceDao.getInvoicesForPayment(notionalDate, NUMBER_OF_DAY_BETWEEN_RETRIES);
         count = getInvoicesDueForPaymentAttempt(invoiceDao.get(), notionalDate).size();
         assertEquals(invoices.size(), count);
 
         // advance clock by NUMBER_OF_DAYS_BETWEEN_RETRIES days
         // ensure that number of invoices for payment has increased by 1 (retry)
         notionalDate = notionalDate.plusDays(NUMBER_OF_DAY_BETWEEN_RETRIES);
-        invoices = invoiceDao.getInvoicesForPayment(notionalDate.toDate(), NUMBER_OF_DAY_BETWEEN_RETRIES);
+        invoices = invoiceDao.getInvoicesForPayment(notionalDate, NUMBER_OF_DAY_BETWEEN_RETRIES);
         count = getInvoicesDueForPaymentAttempt(invoiceDao.get(), notionalDate).size();
         assertEquals(invoices.size(), count);
 
         // post successful partial payment; ensure that number of invoices for payment has decreased by 1
-        invoiceDao.notifySuccessfulPayment(invoiceId.toString(), new BigDecimal("22.0000"), Currency.USD.toString(), UUID.randomUUID().toString(), notionalDate.toDate());
-        invoices = invoiceDao.getInvoicesForPayment(notionalDate.toDate(), NUMBER_OF_DAY_BETWEEN_RETRIES);
+        invoiceDao.notifyOfPaymentAttempt(new DefaultInvoicePayment(UUID.randomUUID(), invoice.getId(), notionalDate, new BigDecimal("22.0000"), Currency.USD));
+
+        invoices = invoiceDao.getInvoicesForPayment(notionalDate, NUMBER_OF_DAY_BETWEEN_RETRIES);
         count = getInvoicesDueForPaymentAttempt(invoiceDao.get(), notionalDate).size();
         assertEquals(invoices.size(), count);
 
         // get invoice; verify amount paid is correct
-        invoice = invoiceDao.getById(invoiceId.toString());
+        invoice = invoiceDao.getById(invoiceId);
         assertEquals(invoice.getAmountPaid().compareTo(new BigDecimal("22.0")), 0);
 
         // advance clock NUMBER_OF_DAYS_BETWEEN_RETRIES days
         // ensure that number of invoices for payment has increased by 1 (retry)
         notionalDate = notionalDate.plusDays(NUMBER_OF_DAY_BETWEEN_RETRIES);
-        invoices = invoiceDao.getInvoicesForPayment(notionalDate.toDate(), NUMBER_OF_DAY_BETWEEN_RETRIES);
+        invoices = invoiceDao.getInvoicesForPayment(notionalDate, NUMBER_OF_DAY_BETWEEN_RETRIES);
         count = getInvoicesDueForPaymentAttempt(invoiceDao.get(), notionalDate).size();
         assertEquals(invoices.size(), count);
 
         // post completed payment; ensure that the number of invoices for payment has decreased by 1
-        invoiceDao.notifySuccessfulPayment(invoiceId.toString(), new BigDecimal("5.0000"), Currency.USD.toString(), UUID.randomUUID().toString(), notionalDate.toDate());
-        invoices = invoiceDao.getInvoicesForPayment(notionalDate.toDate(), NUMBER_OF_DAY_BETWEEN_RETRIES);
+        invoiceDao.notifyOfPaymentAttempt(new DefaultInvoicePayment(UUID.randomUUID(), invoice.getId(), notionalDate, new BigDecimal("5.0000"), Currency.USD));
+
+        invoices = invoiceDao.getInvoicesForPayment(notionalDate, NUMBER_OF_DAY_BETWEEN_RETRIES);
         count = getInvoicesDueForPaymentAttempt(invoiceDao.get(), notionalDate).size();
         assertEquals(invoices.size(), count);
 
         // get invoice; verify amount paid is correct
-        invoice = invoiceDao.getById(invoiceId.toString());
-        count = getInvoicesDueForPaymentAttempt(invoiceDao.get(), notionalDate).size();
+        invoice = invoiceDao.getById(invoiceId);
         assertEquals(invoice.getAmountPaid().compareTo(new BigDecimal("27.0")), 0);
 
         // advance clock by NUMBER_OF_DAYS_BETWEEN_RETRIES days
         // ensure that the number of invoices for payment hasn't changed
         notionalDate = notionalDate.plusDays(NUMBER_OF_DAY_BETWEEN_RETRIES);
-        invoices = invoiceDao.getInvoicesForPayment(notionalDate.toDate(), NUMBER_OF_DAY_BETWEEN_RETRIES);
+        invoices = invoiceDao.getInvoicesForPayment(notionalDate, NUMBER_OF_DAY_BETWEEN_RETRIES);
         count = getInvoicesDueForPaymentAttempt(invoiceDao.get(), notionalDate).size();
         assertEquals(invoices.size(), count);
     }
 
-    private List<Invoice> getInvoicesDueForPaymentAttempt(List<Invoice> invoices, DateTime date) {
+    private List<Invoice> getInvoicesDueForPaymentAttempt(final List<Invoice> invoices, final DateTime date) {
         List<Invoice> invoicesDue= new ArrayList<Invoice>();
 
-        for (Invoice invoice : invoices) {
+        for (final Invoice invoice : invoices) {
             if (invoice.isDueForPayment(date, NUMBER_OF_DAY_BETWEEN_RETRIES)) {
                 invoicesDue.add(invoice);
             }
@@ -247,7 +269,7 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
 
 
         // create invoice 1 (subscriptions 1-4)
-        Invoice invoice1 = new DefaultInvoice(accountId, targetDate, Currency.USD);
+        Invoice invoice1 = new DefaultInvoice(accountId, targetDate, Currency.USD, clock);
         invoiceDao.create(invoice1);
 
         UUID invoiceId1 = invoice1.getId();
@@ -255,20 +277,20 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         DateTime startDate = new DateTime(2011, 3, 1, 0, 0, 0, 0);
         DateTime endDate = startDate.plusMonths(1);
 
-        DefaultInvoiceItem item1 = new DefaultInvoiceItem(invoiceId1, subscriptionId1, startDate, endDate, "test A", rate1, rate1, Currency.USD);
-        invoiceItemDao.create(item1);
+        RecurringInvoiceItem item1 = new RecurringInvoiceItem(invoiceId1, subscriptionId1, "test plan", "test A", startDate, endDate, rate1, rate1, Currency.USD);
+        recurringInvoiceItemDao.create(item1);
 
-        DefaultInvoiceItem item2 = new DefaultInvoiceItem(invoiceId1, subscriptionId2, startDate, endDate, "test B", rate2, rate2, Currency.USD);
-        invoiceItemDao.create(item2);
+        RecurringInvoiceItem item2 = new RecurringInvoiceItem(invoiceId1, subscriptionId2, "test plan", "test B", startDate, endDate, rate2, rate2, Currency.USD);
+        recurringInvoiceItemDao.create(item2);
 
-        DefaultInvoiceItem item3 = new DefaultInvoiceItem(invoiceId1, subscriptionId3, startDate, endDate, "test C", rate3, rate3, Currency.USD);
-        invoiceItemDao.create(item3);
+        RecurringInvoiceItem item3 = new RecurringInvoiceItem(invoiceId1, subscriptionId3, "test plan", "test C", startDate, endDate, rate3, rate3, Currency.USD);
+        recurringInvoiceItemDao.create(item3);
 
-        DefaultInvoiceItem item4 = new DefaultInvoiceItem(invoiceId1, subscriptionId4, startDate, endDate, "test D", rate4, rate4, Currency.USD);
-        invoiceItemDao.create(item4);
+        RecurringInvoiceItem item4 = new RecurringInvoiceItem(invoiceId1, subscriptionId4, "test plan", "test D", startDate, endDate, rate4, rate4, Currency.USD);
+        recurringInvoiceItemDao.create(item4);
 
         // create invoice 2 (subscriptions 1-3)
-        DefaultInvoice invoice2 = new DefaultInvoice(accountId, targetDate, Currency.USD);
+        DefaultInvoice invoice2 = new DefaultInvoice(accountId, targetDate, Currency.USD, clock);
         invoiceDao.create(invoice2);
 
         UUID invoiceId2 = invoice2.getId();
@@ -276,27 +298,431 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         startDate = endDate;
         endDate = startDate.plusMonths(1);
 
-        DefaultInvoiceItem item5 = new DefaultInvoiceItem(invoiceId2, subscriptionId1, startDate, endDate, "test A", rate1, rate1, Currency.USD);
-        invoiceItemDao.create(item5);
+        RecurringInvoiceItem item5 = new RecurringInvoiceItem(invoiceId2, subscriptionId1, "test plan", "test phase A", startDate, endDate, rate1, rate1, Currency.USD);
+        recurringInvoiceItemDao.create(item5);
 
-        DefaultInvoiceItem item6 = new DefaultInvoiceItem(invoiceId2, subscriptionId2, startDate, endDate, "test B", rate2, rate2, Currency.USD);
-        invoiceItemDao.create(item6);
+        RecurringInvoiceItem item6 = new RecurringInvoiceItem(invoiceId2, subscriptionId2, "test plan", "test phase B", startDate, endDate, rate2, rate2, Currency.USD);
+        recurringInvoiceItemDao.create(item6);
 
-        DefaultInvoiceItem item7 = new DefaultInvoiceItem(invoiceId2, subscriptionId3, startDate, endDate, "test C", rate3, rate3, Currency.USD);
-        invoiceItemDao.create(item7);
+        RecurringInvoiceItem item7 = new RecurringInvoiceItem(invoiceId2, subscriptionId3, "test plan", "test phase C", startDate, endDate, rate3, rate3, Currency.USD);
+        recurringInvoiceItemDao.create(item7);
 
         // check that each subscription returns the correct number of invoices
-        List<Invoice> items1 = invoiceDao.getInvoicesBySubscription(subscriptionId1.toString());
+        List<Invoice> items1 = invoiceDao.getInvoicesBySubscription(subscriptionId1);
         assertEquals(items1.size(), 2);
 
-        List<Invoice> items2 = invoiceDao.getInvoicesBySubscription(subscriptionId2.toString());
+        List<Invoice> items2 = invoiceDao.getInvoicesBySubscription(subscriptionId2);
         assertEquals(items2.size(), 2);
 
-        List<Invoice> items3 = invoiceDao.getInvoicesBySubscription(subscriptionId3.toString());
+        List<Invoice> items3 = invoiceDao.getInvoicesBySubscription(subscriptionId3);
         assertEquals(items3.size(), 2);
 
-        List<Invoice> items4 = invoiceDao.getInvoicesBySubscription(subscriptionId4.toString());
+        List<Invoice> items4 = invoiceDao.getInvoicesBySubscription(subscriptionId4);
         assertEquals(items4.size(), 1);
     }
-    
+
+    @Test
+    public void testGetInvoicesForAccountAfterDate() {
+        UUID accountId = UUID.randomUUID();
+        DateTime targetDate1 = new DateTime(2011, 10, 6, 0, 0, 0, 0);
+        Invoice invoice1 = new DefaultInvoice(accountId, targetDate1, Currency.USD, clock);
+        invoiceDao.create(invoice1);
+
+        DateTime targetDate2 = new DateTime(2011, 12, 6, 0, 0, 0, 0);
+        Invoice invoice2 = new DefaultInvoice(accountId, targetDate2, Currency.USD, clock);
+        invoiceDao.create(invoice2);
+
+
+        List<Invoice> invoices;
+        invoices = invoiceDao.getInvoicesByAccount(accountId, new DateTime(2011, 1, 1, 0, 0, 0, 0));
+        assertEquals(invoices.size(), 2);
+
+        invoices = invoiceDao.getInvoicesByAccount(accountId, new DateTime(2011, 10, 6, 0, 0, 0, 0));
+        assertEquals(invoices.size(), 2);
+
+        invoices = invoiceDao.getInvoicesByAccount(accountId, new DateTime(2011, 10, 11, 0, 0, 0, 0));
+        assertEquals(invoices.size(), 1);
+
+        invoices = invoiceDao.getInvoicesByAccount(accountId, new DateTime(2011, 12, 6, 0, 0, 0, 0));
+        assertEquals(invoices.size(), 1);
+
+        invoices = invoiceDao.getInvoicesByAccount(accountId, new DateTime(2012, 1, 1, 0, 0, 0, 0));
+        assertEquals(invoices.size(), 0);
+    }
+
+    @Test
+    public void testAccountBalance() {
+        UUID accountId = UUID.randomUUID();
+        DateTime targetDate1 = new DateTime(2011, 10, 6, 0, 0, 0, 0);
+        Invoice invoice1 = new DefaultInvoice(accountId, targetDate1, Currency.USD, clock);
+        invoiceDao.create(invoice1);
+
+        DateTime startDate = new DateTime(2011, 3, 1, 0, 0, 0, 0);
+        DateTime endDate = startDate.plusMonths(1);
+
+        BigDecimal rate1 = new BigDecimal("17.0");
+        BigDecimal rate2 = new BigDecimal("42.0");
+
+        RecurringInvoiceItem item1 = new RecurringInvoiceItem(invoice1.getId(), UUID.randomUUID(), "test plan", "test phase A", startDate, endDate, rate1, rate1, Currency.USD);
+        recurringInvoiceItemDao.create(item1);
+
+        RecurringInvoiceItem item2 = new RecurringInvoiceItem(invoice1.getId(), UUID.randomUUID(), "test plan", "test phase B", startDate, endDate, rate2, rate2, Currency.USD);
+        recurringInvoiceItemDao.create(item2);
+
+        BigDecimal payment1 = new BigDecimal("48.0");
+        InvoicePayment payment = new DefaultInvoicePayment(invoice1.getId(), new DateTime(), payment1, Currency.USD);
+        invoicePaymentDao.create(payment);
+
+        BigDecimal balance = invoiceDao.getAccountBalance(accountId);
+        assertEquals(balance.compareTo(rate1.add(rate2).subtract(payment1)), 0);
+    }
+
+    @Test
+    public void testAccountBalanceWithNoPayments() {
+        UUID accountId = UUID.randomUUID();
+        DateTime targetDate1 = new DateTime(2011, 10, 6, 0, 0, 0, 0);
+        Invoice invoice1 = new DefaultInvoice(accountId, targetDate1, Currency.USD, clock);
+        invoiceDao.create(invoice1);
+
+        DateTime startDate = new DateTime(2011, 3, 1, 0, 0, 0, 0);
+        DateTime endDate = startDate.plusMonths(1);
+
+        BigDecimal rate1 = new BigDecimal("17.0");
+        BigDecimal rate2 = new BigDecimal("42.0");
+
+        RecurringInvoiceItem item1 = new RecurringInvoiceItem(invoice1.getId(), UUID.randomUUID(), "test plan", "test phase A", startDate, endDate, rate1, rate1, Currency.USD);
+        recurringInvoiceItemDao.create(item1);
+
+        RecurringInvoiceItem item2 = new RecurringInvoiceItem(invoice1.getId(), UUID.randomUUID(), "test plan", "test phase B", startDate, endDate, rate2, rate2, Currency.USD);
+        recurringInvoiceItemDao.create(item2);
+
+        BigDecimal balance = invoiceDao.getAccountBalance(accountId);
+        assertEquals(balance.compareTo(rate1.add(rate2)), 0);
+    }
+
+    @Test
+    public void testAccountBalanceWithNoInvoiceItems() {
+        UUID accountId = UUID.randomUUID();
+        DateTime targetDate1 = new DateTime(2011, 10, 6, 0, 0, 0, 0);
+        Invoice invoice1 = new DefaultInvoice(accountId, targetDate1, Currency.USD, clock);
+        invoiceDao.create(invoice1);
+
+        BigDecimal payment1 = new BigDecimal("48.0");
+        InvoicePayment payment = new DefaultInvoicePayment(invoice1.getId(), new DateTime(), payment1, Currency.USD);
+        invoicePaymentDao.create(payment);
+
+        BigDecimal balance = invoiceDao.getAccountBalance(accountId);
+        assertEquals(balance.compareTo(BigDecimal.ZERO.subtract(payment1)), 0);
+    }
+
+    @Test
+    public void testGetUnpaidInvoicesByAccountId() {
+        UUID accountId = UUID.randomUUID();
+        DateTime targetDate1 = new DateTime(2011, 10, 6, 0, 0, 0, 0);
+        Invoice invoice1 = new DefaultInvoice(accountId, targetDate1, Currency.USD, clock);
+        invoiceDao.create(invoice1);
+
+        DateTime startDate = new DateTime(2011, 3, 1, 0, 0, 0, 0);
+        DateTime endDate = startDate.plusMonths(1);
+
+        BigDecimal rate1 = new BigDecimal("17.0");
+        BigDecimal rate2 = new BigDecimal("42.0");
+
+        RecurringInvoiceItem item1 = new RecurringInvoiceItem(invoice1.getId(), UUID.randomUUID(), "test plan", "test phase A", startDate, endDate, rate1, rate1, Currency.USD);
+        recurringInvoiceItemDao.create(item1);
+
+        RecurringInvoiceItem item2 = new RecurringInvoiceItem(invoice1.getId(), UUID.randomUUID(), "test plan", "test phase B", startDate, endDate, rate2, rate2, Currency.USD);
+        recurringInvoiceItemDao.create(item2);
+
+        DateTime upToDate;
+        Collection<Invoice> invoices;
+
+        upToDate = new DateTime(2011, 1, 1, 0, 0, 0, 0);
+        invoices = invoiceDao.getUnpaidInvoicesByAccountId(accountId, upToDate);
+        assertEquals(invoices.size(), 0);
+
+        upToDate = new DateTime(2012, 1, 1, 0, 0, 0, 0);
+        invoices = invoiceDao.getUnpaidInvoicesByAccountId(accountId, upToDate);
+        assertEquals(invoices.size(), 1);
+
+        DateTime targetDate2 = new DateTime(2011, 7, 1, 0, 0, 0, 0);
+        Invoice invoice2 = new DefaultInvoice(accountId, targetDate2, Currency.USD, clock);
+        invoiceDao.create(invoice2);
+
+        DateTime startDate2 = new DateTime(2011, 6, 1, 0, 0, 0, 0);
+        DateTime endDate2 = startDate2.plusMonths(3);
+
+        BigDecimal rate3 = new BigDecimal("21.0");
+
+        RecurringInvoiceItem item3 = new RecurringInvoiceItem(invoice2.getId(), UUID.randomUUID(), "test plan", "test phase C", startDate2, endDate2, rate3, rate3, Currency.USD);
+        recurringInvoiceItemDao.create(item3);
+
+        upToDate = new DateTime(2011, 1, 1, 0, 0, 0, 0);
+        invoices = invoiceDao.getUnpaidInvoicesByAccountId(accountId, upToDate);
+        assertEquals(invoices.size(), 0);
+
+        upToDate = new DateTime(2012, 1, 1, 0, 0, 0, 0);
+        invoices = invoiceDao.getUnpaidInvoicesByAccountId(accountId, upToDate);
+        assertEquals(invoices.size(), 2);
+    }
+
+    /*
+     *
+     * this test verifies that immediate changes give the correct results
+     *
+     */
+    @Test
+    public void testInvoiceGenerationForImmediateChanges() throws InvoiceApiException {
+
+        InvoiceGenerator generator = new DefaultInvoiceGenerator(clock);
+
+        UUID accountId = UUID.randomUUID();
+        InvoiceItemList invoiceItemList = new InvoiceItemList();
+        DateTime targetDate = new DateTime(2011, 2, 16, 0, 0, 0, 0);
+
+        // generate first invoice
+        DefaultPrice price1 = new DefaultPrice(TEN, Currency.USD);
+        MockInternationalPrice recurringPrice = new MockInternationalPrice(price1);
+        MockPlanPhase phase1 = new MockPlanPhase(recurringPrice, null, BillingPeriod.MONTHLY, PhaseType.TRIAL);
+        MockPlan plan1 = new MockPlan(phase1);
+
+        Subscription subscription = new MockSubscription();
+        DateTime effectiveDate1 = new DateTime(2011, 2, 1, 0, 0, 0, 0);
+        BillingEvent event1 = new DefaultBillingEvent(subscription, effectiveDate1, plan1, phase1, null,
+                                                      recurringPrice, BillingPeriod.MONTHLY, 1, BillingModeType.IN_ADVANCE,
+                                                      "testEvent1", SubscriptionTransitionType.CREATE);
+
+        BillingEventSet events = new BillingEventSet();
+        events.add(event1);
+
+        Invoice invoice1 = generator.generateInvoice(accountId, events, invoiceItemList, targetDate, Currency.USD);
+        assertEquals(invoice1.getBalance(), TEN);
+        invoiceItemList.addAll(invoice1.getInvoiceItems());
+
+        // generate second invoice
+        DefaultPrice price2 = new DefaultPrice(TWENTY, Currency.USD);
+        MockInternationalPrice recurringPrice2 = new MockInternationalPrice(price2);
+        MockPlanPhase phase2 = new MockPlanPhase(recurringPrice, null, BillingPeriod.MONTHLY, PhaseType.TRIAL);
+        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,
+                                                      recurringPrice2, BillingPeriod.MONTHLY, 1, BillingModeType.IN_ADVANCE,
+                                                      "testEvent2", SubscriptionTransitionType.CREATE);
+        events.add(event2);
+
+        // second invoice should be for one half (14/28 days) the difference between the rate plans
+        // this is a temporary state, since it actually contains an adjusting item that properly belong to invoice 1
+        Invoice invoice2 = generator.generateInvoice(accountId, events, invoiceItemList, targetDate, Currency.USD);
+        assertEquals(invoice2.getBalance(), FIVE);
+        invoiceItemList.addAll(invoice2.getInvoiceItems());
+
+        invoiceDao.create(invoice1);
+        invoiceDao.create(invoice2);
+
+        Invoice savedInvoice1 = invoiceDao.getById(invoice1.getId());
+        assertEquals(savedInvoice1.getTotalAmount(), ZERO);
+
+        Invoice savedInvoice2 = invoiceDao.getById(invoice2.getId());
+        assertEquals(savedInvoice2.getTotalAmount(), FIFTEEN);
+    }
+
+    @Test
+    public void testInvoiceForFreeTrial() throws InvoiceApiException {
+        DefaultPrice price = new DefaultPrice(BigDecimal.ZERO, Currency.USD);
+        MockInternationalPrice recurringPrice = new MockInternationalPrice(price);
+        MockPlanPhase phase = new MockPlanPhase(recurringPrice, null);
+        MockPlan plan = new MockPlan(phase);
+
+        Subscription subscription = new MockSubscription();
+        DateTime effectiveDate = buildDateTime(2011, 1, 1);
+
+        BillingEvent event = new DefaultBillingEvent(subscription, effectiveDate, plan, phase, null,
+                                                     recurringPrice, BillingPeriod.MONTHLY, 15, BillingModeType.IN_ADVANCE,
+                                                     "testEvent", SubscriptionTransitionType.CREATE);
+        BillingEventSet events = new BillingEventSet();
+        events.add(event);
+
+        DateTime targetDate = buildDateTime(2011, 1, 15);
+        InvoiceGenerator generator = new DefaultInvoiceGenerator(clock);
+        Invoice invoice = generator.generateInvoice(UUID.randomUUID(), events, null, targetDate, Currency.USD);
+
+        // expect one pro-ration item and one full-period item
+        assertEquals(invoice.getNumberOfItems(), 2);
+        assertEquals(invoice.getTotalAmount().compareTo(ZERO), 0);
+    }
+
+    @Test
+    public void testInvoiceForFreeTrialWithRecurringDiscount() throws InvoiceApiException {
+        DefaultPrice zeroPrice = new DefaultPrice(BigDecimal.ZERO, Currency.USD);
+        MockInternationalPrice fixedPrice = new MockInternationalPrice(zeroPrice);
+        MockPlanPhase phase1 = new MockPlanPhase(null, fixedPrice);
+
+        BigDecimal cheapAmount = new BigDecimal("24.95");
+        DefaultPrice cheapPrice = new DefaultPrice(cheapAmount, Currency.USD);
+        MockInternationalPrice recurringPrice = new MockInternationalPrice(cheapPrice);
+        MockPlanPhase phase2 = new MockPlanPhase(recurringPrice, null);
+
+        MockPlan plan = new MockPlan();
+        Subscription subscription = new MockSubscription();
+        DateTime effectiveDate1 = buildDateTime(2011, 1, 1);
+
+        BillingEvent event1 = new DefaultBillingEvent(subscription, effectiveDate1, plan, phase1, fixedPrice,
+                                                     null, BillingPeriod.MONTHLY, 1, BillingModeType.IN_ADVANCE,
+                                                     "testEvent1", SubscriptionTransitionType.CREATE);
+        BillingEventSet events = new BillingEventSet();
+        events.add(event1);
+
+        InvoiceGenerator generator = new DefaultInvoiceGenerator(clock);
+        Invoice invoice1 = generator.generateInvoice(UUID.randomUUID(), events, null, effectiveDate1, Currency.USD);
+        assertNotNull(invoice1);
+        assertEquals(invoice1.getNumberOfItems(), 1);
+        assertEquals(invoice1.getTotalAmount().compareTo(ZERO), 0);
+
+        List<InvoiceItem> existingItems = invoice1.getInvoiceItems();
+
+        DateTime effectiveDate2 = effectiveDate1.plusDays(30);
+        BillingEvent event2 = new DefaultBillingEvent(subscription, effectiveDate2, plan, phase2, null,
+                                                     recurringPrice, BillingPeriod.MONTHLY, 31, BillingModeType.IN_ADVANCE,
+                                                     "testEvent2", SubscriptionTransitionType.CHANGE);
+        events.add(event2);
+
+        Invoice invoice2 = generator.generateInvoice(UUID.randomUUID(), events, existingItems, effectiveDate2, Currency.USD);
+        assertNotNull(invoice2);
+        assertEquals(invoice2.getNumberOfItems(), 1);
+        assertEquals(invoice2.getTotalAmount().compareTo(cheapAmount), 0);
+
+        existingItems.addAll(invoice2.getInvoiceItems());
+
+        DateTime effectiveDate3 = effectiveDate2.plusMonths(1);
+        Invoice invoice3 = generator.generateInvoice(UUID.randomUUID(), events, existingItems, effectiveDate3, Currency.USD);
+        assertNotNull(invoice3);
+        assertEquals(invoice3.getNumberOfItems(), 1);
+        assertEquals(invoice3.getTotalAmount().compareTo(cheapAmount), 0);
+    }
+
+    @Test
+    public void testInvoiceForEmptyEventSet() throws InvoiceApiException {
+        InvoiceGenerator generator = new DefaultInvoiceGenerator(clock);
+        BillingEventSet events = new BillingEventSet();
+        Invoice invoice = generator.generateInvoice(UUID.randomUUID(), events, null, new DateTime(), Currency.USD);
+        assertNull(invoice);
+    }
+
+    @Test
+    public void testMixedModeInvoicePersistence() throws InvoiceApiException {
+        DefaultPrice zeroPrice = new DefaultPrice(BigDecimal.ZERO, Currency.USD);
+        MockInternationalPrice fixedPrice = new MockInternationalPrice(zeroPrice);
+        MockPlanPhase phase1 = new MockPlanPhase(null, fixedPrice);
+
+        BigDecimal cheapAmount = new BigDecimal("24.95");
+        DefaultPrice cheapPrice = new DefaultPrice(cheapAmount, Currency.USD);
+        MockInternationalPrice recurringPrice = new MockInternationalPrice(cheapPrice);
+        MockPlanPhase phase2 = new MockPlanPhase(recurringPrice, null);
+
+        MockPlan plan = new MockPlan();
+        Subscription subscription = new MockSubscription();
+        DateTime effectiveDate1 = buildDateTime(2011, 1, 1);
+
+        BillingEvent event1 = new DefaultBillingEvent(subscription, effectiveDate1, plan, phase1, fixedPrice,
+                                                     null, BillingPeriod.MONTHLY, 1, BillingModeType.IN_ADVANCE,
+                                                     "testEvent1", SubscriptionTransitionType.CREATE);
+        BillingEventSet events = new BillingEventSet();
+        events.add(event1);
+
+        DateTime effectiveDate2 = effectiveDate1.plusDays(30);
+        BillingEvent event2 = new DefaultBillingEvent(subscription, effectiveDate2, plan, phase2, null,
+                                                     recurringPrice, BillingPeriod.MONTHLY, 31, BillingModeType.IN_ADVANCE,
+                                                     "testEvent2", SubscriptionTransitionType.CHANGE);
+        events.add(event2);
+
+        InvoiceGenerator generator = new DefaultInvoiceGenerator(clock);
+        Invoice invoice = generator.generateInvoice(UUID.randomUUID(), events, null, effectiveDate2, Currency.USD);
+        assertNotNull(invoice);
+        assertEquals(invoice.getNumberOfItems(), 2);
+        assertEquals(invoice.getTotalAmount().compareTo(cheapAmount), 0);
+
+        invoiceDao.create(invoice);
+        Invoice savedInvoice = invoiceDao.getById(invoice.getId());
+
+        assertNotNull(savedInvoice);
+        assertEquals(savedInvoice.getNumberOfItems(), 2);
+        assertEquals(savedInvoice.getTotalAmount().compareTo(cheapAmount), 0);
+    }
+
+//    @Test
+//    public void testCancellationWithMultipleBillingPeriodsFollowing() throws InvoiceApiException {
+//        UUID accountId = UUID.randomUUID();
+//
+//        BigDecimal fixedValue = FIVE;
+//        DefaultPrice fixedAmount = new DefaultPrice(fixedValue, Currency.USD);
+//        MockInternationalPrice fixedPrice = new MockInternationalPrice(fixedAmount);
+//        MockPlanPhase plan1phase1 = new MockPlanPhase(null, fixedPrice);
+//
+//        BigDecimal trialValue = new BigDecimal("9.95");
+//        DefaultPrice trialAmount = new DefaultPrice(trialValue, Currency.USD);
+//        MockInternationalPrice trialPrice = new MockInternationalPrice(trialAmount);
+//        MockPlanPhase plan2phase1 = new MockPlanPhase(trialPrice, null);
+//
+//        BigDecimal discountValue = new BigDecimal("24.95");
+//        DefaultPrice discountAmount = new DefaultPrice(discountValue, Currency.USD);
+//        MockInternationalPrice discountPrice = new MockInternationalPrice(discountAmount);
+//        MockPlanPhase plan2phase2 = new MockPlanPhase(discountPrice, null);
+//
+//        MockPlan plan1 = new MockPlan();
+//        MockPlan plan2 = new MockPlan();
+//        Subscription subscription = new MockSubscription();
+//        DateTime effectiveDate1 = buildDateTime(2011, 1, 1);
+//
+//        BillingEvent creationEvent = new DefaultBillingEvent(subscription, effectiveDate1, plan1, plan1phase1, fixedPrice,
+//                                                     null, BillingPeriod.MONTHLY, 1, BillingModeType.IN_ADVANCE,
+//                                                     "trial", SubscriptionTransitionType.CREATE);
+//        BillingEventSet events = new BillingEventSet();
+//        events.add(creationEvent);
+//
+//        InvoiceGenerator generator = new DefaultInvoiceGenerator();
+//        InvoiceItemList existingItems;
+//
+//        existingItems = new InvoiceItemList(invoiceDao.getInvoiceItemsByAccount(accountId));
+//        Invoice invoice1 = generator.generateInvoice(accountId, events, existingItems, effectiveDate1, Currency.USD);
+//
+//        assertNotNull(invoice1);
+//        assertEquals(invoice1.getNumberOfItems(), 1);
+//        assertEquals(invoice1.getTotalAmount().compareTo(fixedValue), 0);
+//        invoiceDao.create(invoice1);
+//
+//        DateTime effectiveDate2 = effectiveDate1.plusSeconds(1);
+//        BillingEvent changeEvent = new DefaultBillingEvent(subscription, effectiveDate2, plan2, plan2phase1, null,
+//                                                     trialPrice, BillingPeriod.MONTHLY, 31, BillingModeType.IN_ADVANCE,
+//                                                     "discount", SubscriptionTransitionType.CHANGE);
+//        events.add(changeEvent);
+//
+//        existingItems = new InvoiceItemList(invoiceDao.getInvoiceItemsByAccount(accountId));
+//        Invoice invoice2 = generator.generateInvoice(accountId, events, existingItems, effectiveDate2, Currency.USD);
+//        assertNotNull(invoice2);
+//        assertEquals(invoice2.getNumberOfItems(), 2);
+//        assertEquals(invoice2.getTotalAmount().compareTo(trialValue), 0);
+//        invoiceDao.create(invoice2);
+//
+//        DateTime effectiveDate3 = effectiveDate2.plusMonths(1);
+//        BillingEvent phaseEvent = new DefaultBillingEvent(subscription, effectiveDate3, plan2, plan2phase2, null,
+//                                                     discountPrice, BillingPeriod.MONTHLY, 31, BillingModeType.IN_ADVANCE,
+//                                                     "discount", SubscriptionTransitionType.PHASE);
+//        events.add(phaseEvent);
+//
+//        existingItems = new InvoiceItemList(invoiceDao.getInvoiceItemsByAccount(accountId));
+//        Invoice invoice3 = generator.generateInvoice(accountId, events, existingItems, effectiveDate3, Currency.USD);
+//        assertNotNull(invoice3);
+//        assertEquals(invoice3.getNumberOfItems(), 1);
+//        assertEquals(invoice3.getTotalAmount().compareTo(discountValue), 0);
+//        invoiceDao.create(invoice3);
+//
+//        DateTime effectiveDate4 = effectiveDate3.plusMonths(1);
+//        existingItems = new InvoiceItemList(invoiceDao.getInvoiceItemsByAccount(accountId));
+//        Invoice invoice4 = generator.generateInvoice(accountId, events, existingItems, effectiveDate4, Currency.USD);
+//        assertNotNull(invoice4);
+//        assertEquals(invoice4.getNumberOfItems(), 1);
+//        assertEquals(invoice4.getTotalAmount().compareTo(discountValue), 0);
+//        invoiceDao.create(invoice4);
+//    }
 }
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 fa743ff..16e95fb 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,27 +16,28 @@
 
 package com.ning.billing.invoice.dao;
 
-import com.google.inject.Guice;
-import com.google.inject.Injector;
-import com.google.inject.Stage;
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.invoice.api.InvoiceItem;
-import com.ning.billing.invoice.glue.InvoiceModuleMock;
 import com.ning.billing.invoice.model.DefaultInvoice;
-import com.ning.billing.invoice.model.DefaultInvoiceItem;
-import org.apache.commons.io.IOUtils;
+import com.ning.billing.invoice.model.RecurringInvoiceItem;
+import com.ning.billing.util.clock.Clock;
+import com.ning.billing.util.clock.DefaultClock;
+
 import org.joda.time.DateTime;
-import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
 
-import java.io.IOException;
 import java.math.BigDecimal;
 import java.util.List;
 import java.util.UUID;
 
-import static org.testng.Assert.*;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+
 
 public class InvoiceItemDaoTests extends InvoiceDaoTestBase {
+
+    private final Clock clock = new DefaultClock();
+
     @Test
     public void testInvoiceItemCreation() {
         UUID invoiceId = UUID.randomUUID();
@@ -45,18 +46,17 @@ public class InvoiceItemDaoTests extends InvoiceDaoTestBase {
         DateTime endDate = new DateTime(2011, 11, 1, 0, 0, 0, 0);
         BigDecimal rate = new BigDecimal("20.00");
 
-        InvoiceItem item = new DefaultInvoiceItem(invoiceId, subscriptionId, startDate, endDate, "test", rate, rate, Currency.USD);
-        invoiceItemDao.create(item);
+        RecurringInvoiceItem item = new RecurringInvoiceItem(invoiceId, subscriptionId, "test plan", "test phase", startDate, endDate, rate, rate, Currency.USD);
+        recurringInvoiceItemDao.create(item);
 
-        InvoiceItem thisItem = invoiceItemDao.getById(item.getId().toString());
+        RecurringInvoiceItem thisItem = (RecurringInvoiceItem) recurringInvoiceItemDao.getById(item.getId().toString());
         assertNotNull(thisItem);
         assertEquals(thisItem.getId(), item.getId());
         assertEquals(thisItem.getInvoiceId(), item.getInvoiceId());
         assertEquals(thisItem.getSubscriptionId(), item.getSubscriptionId());
         assertEquals(thisItem.getStartDate(), item.getStartDate());
         assertEquals(thisItem.getEndDate(), item.getEndDate());
-        assertEquals(thisItem.getDescription(), item.getDescription());
-        assertEquals(thisItem.getAmount().compareTo(item.getAmount()), 0);
+        assertEquals(thisItem.getAmount().compareTo(item.getRate()), 0);
         assertEquals(thisItem.getRate().compareTo(item.getRate()), 0);
         assertEquals(thisItem.getCurrency(), item.getCurrency());
     }
@@ -69,11 +69,11 @@ public class InvoiceItemDaoTests extends InvoiceDaoTestBase {
 
         for (int i = 0; i < 3; i++) {
             UUID invoiceId = UUID.randomUUID();
-            DefaultInvoiceItem item = new DefaultInvoiceItem(invoiceId, subscriptionId, startDate.plusMonths(i), startDate.plusMonths(i + 1), "test", rate, rate, Currency.USD);
-            invoiceItemDao.create(item);
+            RecurringInvoiceItem item = new RecurringInvoiceItem(invoiceId, subscriptionId, "test plan", "test phase", startDate.plusMonths(i), startDate.plusMonths(i + 1), rate, rate, Currency.USD);
+            recurringInvoiceItemDao.create(item);
         }
 
-        List<InvoiceItem> items = invoiceItemDao.getInvoiceItemsBySubscription(subscriptionId.toString());
+        List<InvoiceItem> items = recurringInvoiceItemDao.getInvoiceItemsBySubscription(subscriptionId.toString());
         assertEquals(items.size(), 3);
     }
 
@@ -86,11 +86,11 @@ public class InvoiceItemDaoTests extends InvoiceDaoTestBase {
         for (int i = 0; i < 5; i++) {
             UUID subscriptionId = UUID.randomUUID();
             BigDecimal amount = rate.multiply(new BigDecimal(i + 1));
-            DefaultInvoiceItem item = new DefaultInvoiceItem(invoiceId, subscriptionId, startDate, startDate.plusMonths(1), "test", amount, amount, Currency.USD);
-            invoiceItemDao.create(item);
+            RecurringInvoiceItem item = new RecurringInvoiceItem(invoiceId, subscriptionId, "test plan", "test phase", startDate, startDate.plusMonths(1), amount, amount, Currency.USD);
+            recurringInvoiceItemDao.create(item);
         }
 
-        List<InvoiceItem> items = invoiceItemDao.getInvoiceItemsByInvoice(invoiceId.toString());
+        List<InvoiceItem> items = recurringInvoiceItemDao.getInvoiceItemsByInvoice(invoiceId.toString());
         assertEquals(items.size(), 5);
     }
 
@@ -98,7 +98,7 @@ public class InvoiceItemDaoTests extends InvoiceDaoTestBase {
     public void testGetInvoiceItemsByAccountId() {
         UUID accountId = UUID.randomUUID();
         DateTime targetDate = new DateTime(2011, 5, 23, 0, 0, 0, 0);
-        DefaultInvoice invoice = new DefaultInvoice(accountId, targetDate, Currency.USD);
+        DefaultInvoice invoice = new DefaultInvoice(accountId, targetDate, Currency.USD, clock);
 
         invoiceDao.create(invoice);
 
@@ -107,10 +107,10 @@ public class InvoiceItemDaoTests extends InvoiceDaoTestBase {
         BigDecimal rate = new BigDecimal("20.00");
 
         UUID subscriptionId = UUID.randomUUID();
-        DefaultInvoiceItem item = new DefaultInvoiceItem(invoiceId, subscriptionId, startDate, startDate.plusMonths(1), "test", rate, rate, Currency.USD);
-        invoiceItemDao.create(item);
+        RecurringInvoiceItem item = new RecurringInvoiceItem(invoiceId, subscriptionId, "test plan", "test phase", startDate, startDate.plusMonths(1), rate, rate, Currency.USD);
+        recurringInvoiceItemDao.create(item);
 
-        List<InvoiceItem> items = invoiceItemDao.getInvoiceItemsByAccount(accountId.toString());
+        List<InvoiceItem> items = recurringInvoiceItemDao.getInvoiceItemsByAccount(accountId.toString());
         assertEquals(items.size(), 1);
     }
 }
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
new file mode 100644
index 0000000..cf6c112
--- /dev/null
+++ b/invoice/src/test/java/com/ning/billing/invoice/dao/MockInvoiceDao.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.invoice.dao;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import com.ning.billing.invoice.api.InvoicePayment;
+import com.ning.billing.util.bus.Bus;
+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;
+
+public class MockInvoiceDao implements InvoiceDao {
+    private final Bus eventBus;
+    private final Object monitor = new Object();
+    private final Map<UUID, Invoice> invoices = new LinkedHashMap<UUID, Invoice>();
+
+    @Inject
+    public MockInvoiceDao(Bus eventBus) {
+        this.eventBus = eventBus;
+    }
+
+    @Override
+    public void create(Invoice invoice) {
+        synchronized (monitor) {
+            invoices.put(invoice.getId(), invoice);
+        }
+        try {
+            eventBus.post(new DefaultInvoiceCreationNotification(invoice.getId(), invoice.getAccountId(),
+                                                                 invoice.getBalance(), invoice.getCurrency(),
+                                                                 invoice.getInvoiceDate()));
+        }
+        catch (Bus.EventBusException ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+
+    @Override
+    public Invoice getById(UUID id) {
+        synchronized (monitor) {
+            return invoices.get(id);
+        }
+    }
+
+    @Override
+    public List<Invoice> get() {
+        synchronized (monitor) {
+            return new ArrayList<Invoice>(invoices.values());
+        }
+    }
+
+    @Override
+    public List<Invoice> getInvoicesByAccount(UUID accountId) {
+        List<Invoice> result = new ArrayList<Invoice>();
+
+        synchronized (monitor) {
+            for (Invoice invoice : invoices.values()) {
+                if (accountId.equals(invoice.getAccountId())) {
+                    result.add(invoice);
+                }
+            }
+        }
+        return result;
+    }
+
+    @Override
+    public List<Invoice> getInvoicesByAccount(UUID accountId, DateTime fromDate) {
+        List<Invoice> invoicesForAccount = new ArrayList<Invoice>();
+
+        synchronized (monitor) {
+            for (Invoice invoice : get()) {
+                if (accountId.equals(invoice.getAccountId()) && !invoice.getTargetDate().isBefore(fromDate)) {
+                    invoicesForAccount.add(invoice);
+                }
+            }
+        }
+
+        return invoicesForAccount;
+    }
+
+    @Override
+    public List<InvoiceItem> getInvoiceItemsByAccount(UUID accountId) {
+        List<InvoiceItem> invoiceItemsForAccount = new ArrayList<InvoiceItem>();
+
+        synchronized (monitor) {
+            for (Invoice invoice : get()) {
+                if (accountId.equals(invoice.getAccountId())) {
+                    invoiceItemsForAccount.addAll(invoice.getInvoiceItems());
+                }
+            }
+        }
+
+        return invoiceItemsForAccount;
+    }
+
+    @Override
+    public List<Invoice> getInvoicesBySubscription(UUID subscriptionId) {
+        List<Invoice> result = new ArrayList<Invoice>();
+
+        synchronized (monitor) {
+            for (Invoice invoice : invoices.values()) {
+                for (InvoiceItem item : invoice.getInvoiceItems()) {
+                    if (subscriptionId.equals(item.getSubscriptionId())) {
+                        result.add(invoice);
+                        break;
+                    }
+                }
+            }
+        }
+        return result;
+    }
+
+    @Override
+    public List<UUID> getInvoicesForPayment(DateTime targetDate, int numberOfDays) {
+        List<UUID> result = new ArrayList<UUID>();
+
+        synchronized (monitor) {
+            for (Invoice invoice : invoices.values()) {
+                if (invoice.isDueForPayment(targetDate, numberOfDays)) {
+                    result.add(invoice.getId());
+                }
+            }
+        }
+
+        return result;
+    }
+
+    @Override
+    public void test() {
+    }
+
+    @Override
+    public UUID getInvoiceIdByPaymentAttemptId(UUID paymentAttemptId) {
+        synchronized(monitor) {
+            for (Invoice invoice : invoices.values()) {
+                for (InvoicePayment payment : invoice.getPayments()) {
+                    if (paymentAttemptId.equals(payment.getPaymentAttemptId())) {
+                        return invoice.getId();
+                    }
+                }
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public InvoicePayment getInvoicePayment(UUID paymentAttemptId) {
+        synchronized(monitor) {
+            for (Invoice invoice : invoices.values()) {
+                for (InvoicePayment payment : invoice.getPayments()) {
+                    if (paymentAttemptId.equals(payment.getPaymentAttemptId())) {
+                        return payment;
+                    }
+                }
+            }
+        }
+
+        return null;
+    }
+
+    @Override
+    public void notifyOfPaymentAttempt(InvoicePayment invoicePayment) {
+        synchronized (monitor) {
+            Invoice invoice = invoices.get(invoicePayment.getInvoiceId());
+            if (invoice != null) {
+                invoice.addPayment(invoicePayment);
+            }
+        }
+    }
+
+    @Override
+    public BigDecimal getAccountBalance(UUID accountId) {
+        BigDecimal balance = BigDecimal.ZERO;
+
+        for (Invoice invoice : get()) {
+            if (accountId.equals(invoice.getAccountId())) {
+                balance = balance.add(invoice.getBalance());
+            }
+        }
+
+        return balance;
+    }
+
+    @Override
+    public List<Invoice> getUnpaidInvoicesByAccountId(UUID accountId, DateTime upToDate) {
+        List<Invoice> unpaidInvoices = new ArrayList<Invoice>();
+
+        for (Invoice invoice : get()) {
+            if (accountId.equals(invoice.getAccountId()) && (invoice.getBalance().compareTo(BigDecimal.ZERO) > 0)) {
+                unpaidInvoices.add(invoice);
+            }
+        }
+
+        return unpaidInvoices;
+    }
+}
diff --git a/invoice/src/test/java/com/ning/billing/invoice/dao/MockSubscription.java b/invoice/src/test/java/com/ning/billing/invoice/dao/MockSubscription.java
new file mode 100644
index 0000000..7a9253c
--- /dev/null
+++ b/invoice/src/test/java/com/ning/billing/invoice/dao/MockSubscription.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * 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.dao;
+
+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.entitlement.api.user.EntitlementUserApiException;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.api.user.SubscriptionTransition;
+import org.joda.time.DateTime;
+
+import java.util.List;
+import java.util.UUID;
+
+public class MockSubscription implements Subscription {
+    private final UUID subscriptionId = UUID.randomUUID();
+
+    @Override
+    public void cancel(DateTime requestedDate, boolean eot) throws EntitlementUserApiException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void uncancel() throws EntitlementUserApiException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void changePlan(String productName, BillingPeriod term, String planSet, DateTime requestedDate) throws EntitlementUserApiException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void pause() throws EntitlementUserApiException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void resume() throws EntitlementUserApiException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public UUID getId() {
+        return subscriptionId;
+    }
+
+    @Override
+    public UUID getBundleId() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public SubscriptionState getState() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public DateTime getStartDate() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public DateTime getEndDate() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Plan getCurrentPlan() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getCurrentPriceList() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public PlanPhase getCurrentPhase() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public DateTime getChargedThroughDate() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public DateTime getPaidThroughDate() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public List<SubscriptionTransition> getActiveTransitions() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public List<SubscriptionTransition> getAllTransitions() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public SubscriptionTransition getPendingTransition() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public SubscriptionTransition getPreviousTransition() {
+        return null;
+    }
+}
\ No newline at end of file
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
new file mode 100644
index 0000000..4ab3495
--- /dev/null
+++ b/invoice/src/test/java/com/ning/billing/invoice/glue/InvoiceModuleWithEmbeddedDb.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * 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.glue;
+
+import java.io.IOException;
+
+import com.ning.billing.invoice.api.test.InvoiceTestApi;
+import com.ning.billing.invoice.api.test.DefaultInvoiceTestApi;
+import com.ning.billing.invoice.dao.InvoicePaymentSqlDao;
+import com.ning.billing.invoice.dao.RecurringInvoiceItemSqlDao;
+import com.ning.billing.util.glue.GlobalLockerModule;
+import org.skife.jdbi.v2.IDBI;
+import com.ning.billing.account.glue.AccountModule;
+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.glue.BusModule;
+import com.ning.billing.util.notificationq.MockNotificationQueueService;
+import com.ning.billing.util.notificationq.NotificationQueueService;
+
+public class InvoiceModuleWithEmbeddedDb extends InvoiceModule {
+    private final MysqlTestingHelper helper = new MysqlTestingHelper();
+    private IDBI dbi;
+
+    public void startDb() throws IOException {
+        helper.startMysql();
+    }
+
+    public void initDb(final String ddl) throws IOException {
+        helper.initDb(ddl);
+    }
+
+    public void stopDb() {
+        helper.stopMysql();
+    }
+
+    public RecurringInvoiceItemSqlDao getInvoiceItemSqlDao() {
+        return dbi.onDemand(RecurringInvoiceItemSqlDao.class);
+    }
+
+    public InvoicePaymentSqlDao getInvoicePaymentSqlDao() {
+        return dbi.onDemand(InvoicePaymentSqlDao.class);
+    }
+
+    private void installNotificationQueue() {
+        bind(NotificationQueueService.class).to(MockNotificationQueueService.class).asEagerSingleton();
+    }
+
+    @Override
+    public void configure() {
+        dbi = helper.getDBI();
+        bind(IDBI.class).toInstance(dbi);
+
+        bind(Clock.class).to(DefaultClock.class).asEagerSingleton();
+        installNotificationQueue();
+        install(new AccountModule());
+        install(new CatalogModule());
+        install(new EntitlementModule());
+        install(new GlobalLockerModule());
+
+        super.configure();
+
+        bind(InvoiceTestApi.class).to(DefaultInvoiceTestApi.class).asEagerSingleton();
+
+        install(new BusModule());
+    }
+}
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
new file mode 100644
index 0000000..b180a03
--- /dev/null
+++ b/invoice/src/test/java/com/ning/billing/invoice/glue/InvoiceModuleWithMocks.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.invoice.glue;
+
+import com.ning.billing.invoice.dao.InvoiceDao;
+import com.ning.billing.invoice.dao.MockInvoiceDao;
+import com.ning.billing.util.globallocker.GlobalLocker;
+import com.ning.billing.util.globallocker.MockGlobalLocker;
+
+
+public class InvoiceModuleWithMocks extends InvoiceModule {
+    @Override
+    protected void installInvoiceDao() {
+        bind(MockInvoiceDao.class).asEagerSingleton();
+        bind(InvoiceDao.class).to(MockInvoiceDao.class);
+        bind(GlobalLocker.class).to(MockGlobalLocker.class).asEagerSingleton();
+    }
+
+    @Override
+    protected void installInvoiceListener() {
+
+    }
+
+    @Override
+    protected void installNotifier() {
+
+    }
+
+    @Override
+    protected void installInvoiceService() {
+
+    }
+}
diff --git a/invoice/src/test/java/com/ning/billing/invoice/notification/BrainDeadSubscription.java b/invoice/src/test/java/com/ning/billing/invoice/notification/BrainDeadSubscription.java
new file mode 100644
index 0000000..587144b
--- /dev/null
+++ b/invoice/src/test/java/com/ning/billing/invoice/notification/BrainDeadSubscription.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * 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 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.entitlement.api.user.EntitlementUserApiException;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.api.user.SubscriptionTransition;
+
+public class BrainDeadSubscription implements Subscription {
+
+	@Override
+	public void cancel(DateTime requestedDate, boolean eot)
+			throws EntitlementUserApiException {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public void uncancel() throws EntitlementUserApiException {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public void changePlan(String productName, BillingPeriod term,
+			String planSet, DateTime requestedDate)
+			throws EntitlementUserApiException {
+		throw new UnsupportedOperationException();
+
+		
+	}
+
+	@Override
+	public void pause() throws EntitlementUserApiException {
+		throw new UnsupportedOperationException();
+
+		
+	}
+
+	@Override
+	public void resume() throws EntitlementUserApiException {
+		throw new UnsupportedOperationException();
+		
+	}
+
+	@Override
+	public UUID getId() {
+		throw new UnsupportedOperationException();
+		
+	}
+
+	@Override
+	public UUID getBundleId() {
+		throw new UnsupportedOperationException();
+		
+	}
+
+	@Override
+	public SubscriptionState getState() {
+		throw new UnsupportedOperationException();
+		
+	}
+
+	@Override
+	public DateTime getStartDate() {
+		throw new UnsupportedOperationException();
+		
+	}
+
+	@Override
+	public DateTime getEndDate() {
+		throw new UnsupportedOperationException();
+		
+	}
+
+	@Override
+	public Plan getCurrentPlan() {
+		throw new UnsupportedOperationException();
+		
+	}
+
+	@Override
+	public String getCurrentPriceList() {
+		throw new UnsupportedOperationException();
+		
+	}
+
+	@Override
+	public PlanPhase getCurrentPhase() {
+		throw new UnsupportedOperationException();
+		
+	}
+
+	@Override
+	public DateTime getChargedThroughDate() {
+		throw new UnsupportedOperationException();
+		
+	}
+
+	@Override
+	public DateTime getPaidThroughDate() {
+		throw new UnsupportedOperationException();
+		
+	}
+
+	@Override
+	public List<SubscriptionTransition> getActiveTransitions() {
+		throw new UnsupportedOperationException();
+		
+	}
+
+	@Override
+	public List<SubscriptionTransition> getAllTransitions() {
+		throw new UnsupportedOperationException();
+		
+	}
+
+	@Override
+	public SubscriptionTransition getPendingTransition() {
+		throw new UnsupportedOperationException();
+		
+	}
+
+	@Override
+	public SubscriptionTransition getPreviousTransition() {
+		throw new UnsupportedOperationException();
+	}
+
+}
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
new file mode 100644
index 0000000..67c27fa
--- /dev/null
+++ b/invoice/src/test/java/com/ning/billing/invoice/notification/TestNextBillingDateNotifier.java
@@ -0,0 +1,308 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * 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 static com.jayway.awaitility.Awaitility.await;
+import static java.util.concurrent.TimeUnit.MINUTES;
+
+import java.io.IOException;
+import java.sql.SQLException;
+import java.util.List;
+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.skife.jdbi.v2.Transaction;
+import org.skife.jdbi.v2.TransactionStatus;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+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.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.migration.AccountMigrationData;
+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.engine.dao.EntitlementDao;
+import com.ning.billing.entitlement.engine.dao.EntitlementSqlDao;
+import com.ning.billing.entitlement.events.EntitlementEvent;
+import com.ning.billing.invoice.InvoiceListener;
+import com.ning.billing.invoice.dao.DefaultInvoiceDao;
+import com.ning.billing.lifecycle.KillbillService.ServiceException;
+import com.ning.billing.util.bus.Bus;
+import com.ning.billing.util.bus.InMemoryBus;
+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.notificationq.DummySqlTest;
+import com.ning.billing.util.notificationq.NotificationQueueService;
+import com.ning.billing.util.notificationq.dao.NotificationSqlDao;
+
+public class TestNextBillingDateNotifier {
+    private static Logger log = LoggerFactory.getLogger(TestNextBillingDateNotifier.class);
+	private Clock clock;
+	private DefaultNextBillingDateNotifier notifier;
+	private DummySqlTest dao;
+	private Bus eventBus;
+	private MysqlTestingHelper helper;
+	private InvoiceListenerMock listener = new InvoiceListenerMock();
+	private NotificationQueueService notificationQueueService;
+
+	private static final class InvoiceListenerMock extends InvoiceListener {
+		int eventCount = 0;
+		UUID latestSubscriptionId = null;
+
+		public InvoiceListenerMock() {
+			super(null);
+		}
+		
+
+		@Override
+		public void handleNextBillingDateEvent(UUID subscriptionId,
+				DateTime eventDateTime) {
+			eventCount++;
+			latestSubscriptionId=subscriptionId;
+		}
+		
+		public int getEventCount() {
+			return eventCount;
+		}
+		
+		public UUID getLatestSubscriptionId(){
+			return latestSubscriptionId;
+		}
+		
+	}
+	
+	private class MockEntitlementDao implements EntitlementDao {
+
+		@Override
+		public List<SubscriptionBundle> getSubscriptionBundleForAccount(
+				UUID accountId) {
+			throw new UnsupportedOperationException();
+		}
+
+		@Override
+		public SubscriptionBundle getSubscriptionBundleFromKey(String bundleKey) {
+			throw new UnsupportedOperationException();
+		}
+
+		@Override
+		public SubscriptionBundle getSubscriptionBundleFromId(UUID bundleId) {
+			throw new UnsupportedOperationException();
+		}
+
+		@Override
+		public SubscriptionBundle createSubscriptionBundle(
+				SubscriptionBundleData bundle) {
+			throw new UnsupportedOperationException();
+
+		}
+
+		@Override
+		public Subscription getSubscriptionFromId(UUID subscriptionId) {
+			return new BrainDeadSubscription();
+
+		}
+
+		@Override
+		public UUID getAccountIdFromSubscriptionId(UUID subscriptionId) {
+			throw new UnsupportedOperationException();
+
+		}
+
+		@Override
+		public Subscription getBaseSubscription(UUID bundleId) {
+			throw new UnsupportedOperationException();
+
+		}
+
+		@Override
+		public List<Subscription> getSubscriptions(UUID bundleId) {
+			throw new UnsupportedOperationException();
+
+		}
+
+		@Override
+		public List<Subscription> getSubscriptionsForKey(String bundleKey) {
+			throw new UnsupportedOperationException();
+
+		}
+
+		@Override
+		public void updateSubscription(SubscriptionData subscription) {
+			throw new UnsupportedOperationException();
+		}
+
+		@Override
+		public void createNextPhaseEvent(UUID subscriptionId,
+				EntitlementEvent nextPhase) {
+			throw new UnsupportedOperationException();
+		}
+
+		@Override
+		public EntitlementEvent getEventById(UUID eventId) {
+			throw new UnsupportedOperationException();
+
+		}
+
+		@Override
+		public List<EntitlementEvent> getEventsForSubscription(
+				UUID subscriptionId) {
+			throw new UnsupportedOperationException();
+
+		}
+
+		@Override
+		public List<EntitlementEvent> getPendingEventsForSubscription(
+				UUID subscriptionId) {
+			throw new UnsupportedOperationException();
+
+		}
+
+		@Override
+		public void createSubscription(SubscriptionData subscription,
+				List<EntitlementEvent> initialEvents) {
+			throw new UnsupportedOperationException();
+		}
+
+		@Override
+		public void cancelSubscription(UUID subscriptionId,
+				EntitlementEvent cancelEvent) {
+			throw new UnsupportedOperationException();
+
+		}
+
+		@Override
+		public void uncancelSubscription(UUID subscriptionId,
+				List<EntitlementEvent> uncancelEvents) {
+			throw new UnsupportedOperationException();
+	
+		}
+
+		@Override
+		public void changePlan(UUID subscriptionId,
+				List<EntitlementEvent> changeEvents) {
+			throw new UnsupportedOperationException();
+		}
+
+		@Override
+		public void migrate(UUID acountId, AccountMigrationData data) {
+			throw new UnsupportedOperationException();
+		}
+
+		@Override
+		public void undoMigration(UUID accountId) {
+			throw new UnsupportedOperationException();
+		}
+		
+	}
+	
+	@BeforeClass(groups={"setup"})
+	public void setup() throws ServiceException, IOException, ClassNotFoundException, SQLException {
+		//TestApiBase.loadSystemPropertiesFromClasspath("/entitlement.properties");
+        final Injector g = Guice.createInjector(Stage.PRODUCTION,  new AbstractModule() {
+			protected void configure() {
+				 bind(Clock.class).to(ClockMock.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(EntitlementDao.class).to(EntitlementSqlDao.class).asEagerSingleton();
+			}
+        });
+
+        clock = g.getInstance(Clock.class);
+        IDBI dbi = g.getInstance(IDBI.class);
+        dao = dbi.onDemand(DummySqlTest.class);
+        eventBus = g.getInstance(Bus.class);
+        helper = g.getInstance(MysqlTestingHelper.class);
+        notificationQueueService = g.getInstance(NotificationQueueService.class);
+        notifier = new DefaultNextBillingDateNotifier(notificationQueueService,g.getInstance(InvoiceConfig.class), new MockEntitlementDao(), listener);
+        startMysql();
+	}
+
+	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 {
+		final UUID subscriptionId = new UUID(0L,1L);
+		final DateTime now = new DateTime();
+		final DateTime readyTime = now.plusMillis(2000);
+		final NextBillingDatePoster poster = new DefaultNextBillingDatePoster(notificationQueueService); 
+
+		eventBus.start();
+		notifier.initialize();
+		notifier.start();
+		
+
+		dao.inTransaction(new Transaction<Void, DummySqlTest>() {
+			@Override
+			public Void inTransaction(DummySqlTest transactional,
+					TransactionStatus status) throws Exception {
+
+				poster.insertNextBillingNotification(transactional, subscriptionId, readyTime);
+				return null;
+			}
+		});
+		
+		
+		// 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);
+	}
+}
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 5d36d55..f0de1ce 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,18 +16,13 @@
 
 package com.ning.billing.invoice.tests;
 
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertNotNull;
-
-import java.math.BigDecimal;
-import java.util.UUID;
-
-import org.joda.time.DateTime;
-import org.testng.annotations.Test;
-
-import com.ning.billing.catalog.MockCatalog;
+import com.ning.billing.catalog.DefaultPrice;
+import com.ning.billing.catalog.MockInternationalPrice;
+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.PhaseType;
 import com.ning.billing.catalog.api.Plan;
 import com.ning.billing.catalog.api.PlanPhase;
 import com.ning.billing.entitlement.api.billing.BillingEvent;
@@ -36,90 +31,104 @@ 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.invoice.api.BillingEventSet;
+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;
+import com.ning.billing.invoice.dao.MockSubscription;
+import com.ning.billing.invoice.model.BillingEventSet;
 import com.ning.billing.invoice.model.DefaultInvoiceGenerator;
-import com.ning.billing.invoice.model.DefaultInvoiceItem;
+import com.ning.billing.invoice.model.FixedPriceInvoiceItem;
 import com.ning.billing.invoice.model.InvoiceGenerator;
 import com.ning.billing.invoice.model.InvoiceItemList;
+import com.ning.billing.invoice.model.RecurringInvoiceItem;
+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.List;
+import java.util.UUID;
 
-@Test(groups = {"invoicing", "invoiceGenerator"})
+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 InvoiceGenerator generator = new DefaultInvoiceGenerator();
+
+
+    private final InvoiceGenerator generator = new DefaultInvoiceGenerator(new DefaultClock());
 
     @Test
-    public void testWithNullEventSetAndNullInvoiceSet() {
+    public void testWithNullEventSetAndNullInvoiceSet() throws InvoiceApiException {
         UUID accountId = UUID.randomUUID();
-        Invoice invoice = generator.generateInvoice(accountId, null, null, new DateTime(), Currency.USD);
+        Invoice invoice = generator.generateInvoice(accountId, new BillingEventSet(), new InvoiceItemList(), new DateTime(), Currency.USD);
 
-        assertNotNull(invoice);
-        assertEquals(invoice.getNumberOfItems(), 0);
-        assertEquals(invoice.getTotalAmount(), ZERO);
+        assertNull(invoice);
     }
 
     @Test
-    public void testWithEmptyEventSet() {
+    public void testWithEmptyEventSet() throws InvoiceApiException {
         BillingEventSet events = new BillingEventSet();
 
         InvoiceItemList existingInvoiceItems = new InvoiceItemList();
         UUID accountId = UUID.randomUUID();
         Invoice invoice = generator.generateInvoice(accountId, events, existingInvoiceItems, new DateTime(), Currency.USD);
 
-        assertNotNull(invoice);
-        assertEquals(invoice.getNumberOfItems(), 0);
-        assertEquals(invoice.getTotalAmount(), ZERO);
+        assertNull(invoice);
     }
 
     @Test
-    public void testWithSingleMonthlyEvent() {
+    public void testWithSingleMonthlyEvent() throws InvoiceApiException {
         BillingEventSet events = new BillingEventSet();
 
         Subscription sub = new SubscriptionData(new SubscriptionBuilder().setId(UUID.randomUUID()));
         DateTime startDate = buildDateTime(2011, 9, 1);
-        MockCatalog catalog = new MockCatalog();
-        Plan plan = catalog.getCurrentPlans()[0];
-        PlanPhase phase = plan.getAllPhases()[0];
-        
-        BillingEvent event = new DefaultBillingEvent(sub, startDate, plan, phase,
-                new InternationalPriceMock(ZERO),new InternationalPriceMock(TEN), BillingPeriod.MONTHLY,
-                                               1, BillingModeType.IN_ADVANCE, "Test");
+
+        Plan plan = new MockPlan();
+        BigDecimal rate1 = TEN;
+        PlanPhase phase = createMockMonthlyPlanPhase(rate1);
+
+        BillingEvent event = createBillingEvent(sub.getId(), startDate, plan, phase, 1);
         events.add(event);
 
         InvoiceItemList existingInvoiceItems = new InvoiceItemList();
-        
+
         DateTime targetDate = buildDateTime(2011, 10, 3);
         UUID accountId = UUID.randomUUID();
         Invoice invoice = generator.generateInvoice(accountId, events, existingInvoiceItems, targetDate, Currency.USD);
 
         assertNotNull(invoice);
-        assertEquals(invoice.getNumberOfItems(), 1);
+        assertEquals(invoice.getNumberOfItems(), 2);
         assertEquals(invoice.getTotalAmount(), TWENTY);
+        assertEquals(invoice.getInvoiceItems().get(0).getSubscriptionId(), sub.getId());
     }
 
     @Test
-    public void testWithSingleMonthlyEventWithLeadingProRation() {
+    public void testWithSingleMonthlyEventWithLeadingProRation() throws InvoiceApiException {
         BillingEventSet events = new BillingEventSet();
 
         Subscription sub = new SubscriptionData(new SubscriptionBuilder().setId(UUID.randomUUID()));
         DateTime startDate = buildDateTime(2011, 9, 1);
-        MockCatalog catalog = new MockCatalog();
-        Plan plan = catalog.getCurrentPlans()[0];
-        PlanPhase phase = plan.getAllPhases()[0];
+
+        Plan plan = new MockPlan();
         BigDecimal rate = TEN;
-        BillingEvent event = new DefaultBillingEvent(sub, startDate, plan, phase,
-                new InternationalPriceMock(ZERO), new InternationalPriceMock(rate), BillingPeriod.MONTHLY,
-                                               15, BillingModeType.IN_ADVANCE,"Test");
+        PlanPhase phase = createMockMonthlyPlanPhase(rate);
+        BillingEvent event = createBillingEvent(sub.getId(), startDate, plan, phase, 15);
         events.add(event);
 
         InvoiceItemList existingInvoiceItems = new InvoiceItemList();
-        
-        DateTime targetDate = buildDateTime(2011, 10, 3);        
+
+        DateTime targetDate = buildDateTime(2011, 10, 3);
         UUID accountId = UUID.randomUUID();
         Invoice invoice = generator.generateInvoice(accountId, events, existingInvoiceItems, targetDate, Currency.USD);
 
         assertNotNull(invoice);
-        assertEquals(invoice.getNumberOfItems(), 1);
+        assertEquals(invoice.getNumberOfItems(), 2);
 
         BigDecimal expectedNumberOfBillingCycles;
         expectedNumberOfBillingCycles = ONE.add(FOURTEEN.divide(THIRTY_ONE, NUMBER_OF_DECIMALS, ROUNDING_METHOD));
@@ -128,27 +137,23 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
     }
 
     @Test
-    public void testTwoMonthlySubscriptionsWithAlignedBillingDates() {
+    public void testTwoMonthlySubscriptionsWithAlignedBillingDates() throws InvoiceApiException {
         BillingEventSet events = new BillingEventSet();
 
-        MockCatalog catalog = new MockCatalog();
-        Plan plan1 = catalog.getCurrentPlans()[0];
-        PlanPhase phase1 = plan1.getAllPhases()[0];
-        Plan plan2 = catalog.getCurrentPlans()[1];
-        PlanPhase phase2 = plan2.getAllPhases()[0];
-        
+        Plan plan1 = new MockPlan();
+        BigDecimal rate1 = FIVE;
+        PlanPhase phase1 = createMockMonthlyPlanPhase(rate1);
+
+        Plan plan2 = new MockPlan();
+        BigDecimal rate2 = TEN;
+        PlanPhase phase2 = createMockMonthlyPlanPhase(rate2);
+
         Subscription sub = new SubscriptionData(new SubscriptionBuilder().setId(UUID.randomUUID()));
 
-        BillingEvent event1 = new DefaultBillingEvent(sub, buildDateTime(2011, 9, 1),
-                                               plan1,phase1,
-                                               new InternationalPriceMock(ZERO), new InternationalPriceMock(FIVE), BillingPeriod.MONTHLY,
-                                               1, BillingModeType.IN_ADVANCE, "Test");
+        BillingEvent event1 = createBillingEvent(sub.getId(), buildDateTime(2011, 9, 1), plan1, phase1, 1);
         events.add(event1);
 
-        BillingEvent event2 = new DefaultBillingEvent(sub, buildDateTime(2011, 10, 1),
-                                               plan2,phase2,
-                                               new InternationalPriceMock(ZERO), new InternationalPriceMock(TEN), BillingPeriod.MONTHLY,
-                                               1, BillingModeType.IN_ADVANCE,"Test");
+        BillingEvent event2 = createBillingEvent(sub.getId(), buildDateTime(2011, 10, 1), plan2, phase2, 1);
         events.add(event2);
 
         InvoiceItemList existingInvoiceItems = new InvoiceItemList();
@@ -158,29 +163,24 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
 
         assertNotNull(invoice);
         assertEquals(invoice.getNumberOfItems(), 2);
-        assertEquals(invoice.getTotalAmount(), FIVE.multiply(TWO).add(TEN).setScale(NUMBER_OF_DECIMALS));
+        assertEquals(invoice.getTotalAmount(), rate1.add(rate2).setScale(NUMBER_OF_DECIMALS));
     }
 
     @Test
-    public void testOnePlan_TwoMonthlyPhases_ChangeImmediate() {
+    public void testOnePlan_TwoMonthlyPhases_ChangeImmediate() throws InvoiceApiException {
         BillingEventSet events = new BillingEventSet();
 
-        MockCatalog catalog = new MockCatalog();
-        Plan plan1 = catalog.getCurrentPlans()[0];
-        PlanPhase phase1 = plan1.getAllPhases()[0];
+        Plan plan1 = new MockPlan();
+        BigDecimal rate1 = FIVE;
+        PlanPhase phase1 = createMockMonthlyPlanPhase(rate1);
 
-        
         Subscription sub = new SubscriptionData(new SubscriptionBuilder().setId(UUID.randomUUID()));
-        BillingEvent event1 = new DefaultBillingEvent(sub, buildDateTime(2011, 9, 1),
-                                               plan1,phase1,
-                                               new InternationalPriceMock(ZERO),new InternationalPriceMock(FIVE), BillingPeriod.MONTHLY,
-                                               1, BillingModeType.IN_ADVANCE,"Test");
+        BillingEvent event1 = createBillingEvent(sub.getId(), buildDateTime(2011, 9, 1), plan1,phase1, 1);
         events.add(event1);
 
-        BillingEvent event2 = new DefaultBillingEvent(sub, buildDateTime(2011, 10, 15),
-                                               plan1,phase1, //technically should be a different phase but it doesn't impact the test
-                                               new InternationalPriceMock(ZERO),new InternationalPriceMock(TEN), BillingPeriod.MONTHLY,
-                                               15, BillingModeType.IN_ADVANCE,"Test");
+        BigDecimal rate2 = TEN;
+        PlanPhase phase2 = createMockMonthlyPlanPhase(rate2);
+        BillingEvent event2 = createBillingEvent(sub.getId(), buildDateTime(2011, 10, 15), plan1, phase2, 15);
         events.add(event2);
 
         InvoiceItemList existingInvoiceItems = new InvoiceItemList();
@@ -189,7 +189,7 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
         Invoice invoice = generator.generateInvoice(accountId, events, existingInvoiceItems, targetDate, Currency.USD);
 
         assertNotNull(invoice);
-        assertEquals(invoice.getNumberOfItems(), 2);
+        assertEquals(invoice.getNumberOfItems(), 4);
 
         BigDecimal numberOfCyclesEvent1;
         numberOfCyclesEvent1 = ONE.add(FOURTEEN.divide(THIRTY_ONE, NUMBER_OF_DECIMALS, ROUNDING_METHOD));
@@ -197,39 +197,33 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
         BigDecimal numberOfCyclesEvent2 = TWO;
 
         BigDecimal expectedValue;
-        expectedValue = numberOfCyclesEvent1.multiply(FIVE);
-        expectedValue = expectedValue.add(numberOfCyclesEvent2.multiply(TEN));
+        expectedValue = numberOfCyclesEvent1.multiply(rate1);
+        expectedValue = expectedValue.add(numberOfCyclesEvent2.multiply(rate2));
         expectedValue = expectedValue.setScale(NUMBER_OF_DECIMALS);
 
         assertEquals(invoice.getTotalAmount(), expectedValue);
     }
 
     @Test
-    public void testOnePlan_ThreeMonthlyPhases_ChangeEOT() {
+    public void testOnePlan_ThreeMonthlyPhases_ChangeEOT() throws InvoiceApiException {
         BillingEventSet events = new BillingEventSet();
 
-        MockCatalog catalog = new MockCatalog();
-        Plan plan1 = catalog.getCurrentPlans()[0];
-        PlanPhase phase1 = plan1.getAllPhases()[0];
-
+        Plan plan1 = new MockPlan();
+        BigDecimal rate1 = FIVE;
+        PlanPhase phase1 = createMockMonthlyPlanPhase(rate1);
 
         Subscription sub = new SubscriptionData(new SubscriptionBuilder().setId(UUID.randomUUID()));
-        BillingEvent event1 = new DefaultBillingEvent(sub, buildDateTime(2011, 9, 1),
-        										plan1,phase1,
-                                               new InternationalPriceMock(ZERO),new InternationalPriceMock(FIVE), BillingPeriod.MONTHLY,
-                                               1, BillingModeType.IN_ADVANCE,"Test");
+        BillingEvent event1 = createBillingEvent(sub.getId(), buildDateTime(2011, 9, 1), plan1, phase1, 1);
         events.add(event1);
 
-        BillingEvent event2 = new DefaultBillingEvent(sub, buildDateTime(2011, 10, 1),
-												plan1,phase1, //technically should be a different phase but it doesn't impact the test
-                                               new InternationalPriceMock(ZERO),new InternationalPriceMock(TEN), BillingPeriod.MONTHLY,
-                                               1, BillingModeType.IN_ADVANCE,"Test");
+        BigDecimal rate2 = TEN;
+        PlanPhase phase2 = createMockMonthlyPlanPhase(rate2);
+        BillingEvent event2 = createBillingEvent(sub.getId(), buildDateTime(2011, 10, 1), plan1, phase2, 1);
         events.add(event2);
 
-        BillingEvent event3 = new DefaultBillingEvent(sub, buildDateTime(2011, 11, 1),
-												plan1,phase1, //technically should be a different phase but it doesn't impact the test
-                                               new InternationalPriceMock(ZERO),new InternationalPriceMock(THIRTY), BillingPeriod.MONTHLY,
-                                               1, BillingModeType.IN_ADVANCE,"Test");
+        BigDecimal rate3 = THIRTY;
+        PlanPhase phase3 = createMockMonthlyPlanPhase(rate3);
+        BillingEvent event3 = createBillingEvent(sub.getId(), buildDateTime(2011, 11, 1), plan1, phase3, 1);
         events.add(event3);
 
         InvoiceItemList existingInvoiceItems = new InvoiceItemList();
@@ -238,44 +232,38 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
         Invoice invoice = generator.generateInvoice(accountId, events, existingInvoiceItems, targetDate, Currency.USD);
 
         assertNotNull(invoice);
-        assertEquals(invoice.getNumberOfItems(), 3);
-        assertEquals(invoice.getTotalAmount(), FIVE.add(TEN).add(TWO.multiply(THIRTY)).setScale(NUMBER_OF_DECIMALS));
+        assertEquals(invoice.getNumberOfItems(), 4);
+        assertEquals(invoice.getTotalAmount(), rate1.add(rate2).add(TWO.multiply(rate3)).setScale(NUMBER_OF_DECIMALS));
     }
 
     @Test
-    public void testSingleEventWithExistingInvoice() {
+    public void testSingleEventWithExistingInvoice() throws InvoiceApiException {
         BillingEventSet events = new BillingEventSet();
 
         Subscription sub = new SubscriptionData(new SubscriptionBuilder().setId(UUID.randomUUID()));
         DateTime startDate = buildDateTime(2011, 9, 1);
-        
-        MockCatalog catalog = new MockCatalog();
-        Plan plan1 = catalog.getCurrentPlans()[0];
-        PlanPhase phase1 = plan1.getAllPhases()[0];
-        
+
+        Plan plan1 = new MockPlan();
         BigDecimal rate = FIVE;
-        BillingEvent event1 = new DefaultBillingEvent(sub, startDate,
-        										plan1,phase1,
-                                               new InternationalPriceMock(ZERO),new InternationalPriceMock(rate), BillingPeriod.MONTHLY,
-                                               1, BillingModeType.IN_ADVANCE,"Test");
+        PlanPhase phase1 = createMockMonthlyPlanPhase(rate);
+
+        BillingEvent event1 = createBillingEvent(sub.getId(), startDate, plan1, phase1, 1);
         events.add(event1);
 
+        DateTime targetDate = buildDateTime(2011, 12, 1);
+        UUID accountId = UUID.randomUUID();
+        Invoice invoice1 = generator.generateInvoice(accountId, events, null, targetDate, Currency.USD);
         InvoiceItemList existingInvoiceItems = new InvoiceItemList();
-        InvoiceItem invoiceItem = new DefaultInvoiceItem(UUID.randomUUID(), sub.getId(), startDate, buildDateTime(2012, 1, 1), "",
-                                                 rate.multiply(FOUR), rate, Currency.USD);
-        existingInvoiceItems.add(invoiceItem);
+        existingInvoiceItems.addAll(invoice1.getInvoiceItems());
 
-        DateTime targetDate = buildDateTime(2011, 12, 3);
-        UUID accountId = UUID.randomUUID();
-        Invoice invoice = generator.generateInvoice(accountId, events, existingInvoiceItems, targetDate, Currency.USD);
+        targetDate = buildDateTime(2011, 12, 3);
+        Invoice invoice2 = generator.generateInvoice(accountId, events, existingInvoiceItems, targetDate, Currency.USD);
 
-        assertNotNull(invoice);
-        assertEquals(invoice.getNumberOfItems(), 0);
-        assertEquals(invoice.getTotalAmount(), ZERO);
+        assertNull(invoice2);
     }
 
     @Test
-    public void testMultiplePlansWithUtterChaos() {
+    public void testMultiplePlansWithUtterChaos() throws InvoiceApiException {
         // plan 1: change of phase from trial to discount followed by immediate cancellation; (covers phase change, cancel, pro-ration)
         // plan 2: single plan that moves from trial to discount to evergreen; BCD = 10 (covers phase change)
         // plan 3: change of term from monthly (BCD = 20) to annual (BCD = 31; immediate)
@@ -283,35 +271,44 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
         // plan 5: addon to plan 2, with bill cycle alignment to plan; immediate cancellation
 
         UUID subscriptionId1 = UUID.randomUUID();
-        String planName1 = "Change from trial to discount with immediate cancellation";
-        String plan1PhaseName1 = "Trial"; String plan1PhaseName2 = "Discount"; String plan1phase3 = "Cancel";
+        UUID subscriptionId2 = UUID.randomUUID();
+        UUID subscriptionId3 = UUID.randomUUID();
+        UUID subscriptionId4 = UUID.randomUUID();
+        UUID subscriptionId5 = UUID.randomUUID();
+
+        Plan plan1 = new MockPlan("Change from trial to discount with immediate cancellation");
+        PlanPhase plan1Phase1 = createMockMonthlyPlanPhase(EIGHT, PhaseType.TRIAL);
+        PlanPhase plan1Phase2 = createMockMonthlyPlanPhase(TWELVE, PhaseType.DISCOUNT);
+        PlanPhase plan1Phase3 = createMockMonthlyPlanPhase();
         DateTime plan1StartDate = buildDateTime(2011, 1, 5);
         DateTime plan1PhaseChangeDate = buildDateTime(2011, 4, 5);
         DateTime plan1CancelDate = buildDateTime(2011, 4, 29);
 
-        UUID subscriptionId2 = UUID.randomUUID();
-        String planName2 = "Change phase from trial to discount to evergreen";
-        String plan2PhaseName1 = "Trial"; String plan2PhaseName2 = "Discount"; String plan2PhaseName3 = "Evergreen";
+        Plan plan2 = new MockPlan("Change phase from trial to discount to evergreen");
+        PlanPhase plan2Phase1 = createMockMonthlyPlanPhase(TWENTY, PhaseType.TRIAL);
+        PlanPhase plan2Phase2 = createMockMonthlyPlanPhase(THIRTY, PhaseType.DISCOUNT);
+        PlanPhase plan2Phase3 = createMockMonthlyPlanPhase(FORTY, PhaseType.EVERGREEN);
         DateTime plan2StartDate = buildDateTime(2011, 3, 10);
         DateTime plan2PhaseChangeToDiscountDate = buildDateTime(2011, 6, 10);
         DateTime plan2PhaseChangeToEvergreenDate = buildDateTime(2011, 9, 10);
 
-        UUID subscriptionId3 = UUID.randomUUID();
-        String planName3 = "Upgrade with immediate change, BCD = 31";
-        String plan3PhaseName1 = "Evergreen monthly"; String plan3PhaseName2 = "Evergreen annual";
+        Plan plan3 = new MockPlan("Upgrade with immediate change, BCD = 31");
+        PlanPhase plan3Phase1 = createMockMonthlyPlanPhase(TEN, PhaseType.EVERGREEN);
+        PlanPhase plan3Phase2 = createMockAnnualPlanPhase(ONE_HUNDRED, PhaseType.EVERGREEN);
         DateTime plan3StartDate = buildDateTime(2011, 5, 20);
         DateTime plan3UpgradeToAnnualDate = buildDateTime(2011, 7, 31);
 
-        UUID subscriptionId4 = UUID.randomUUID();
-        String planName4a = "Plan change effective EOT; plan 1";
-        String planName4b = "Plan change effective EOT; plan 2";
-        String plan4PhaseName = "Evergreen";
+        Plan plan4a = new MockPlan("Plan change effective EOT; plan 1");
+        Plan plan4b = new MockPlan("Plan change effective EOT; plan 2");
+        PlanPhase plan4aPhase1 = createMockMonthlyPlanPhase(FIFTEEN);
+        PlanPhase plan4bPhase1 = createMockMonthlyPlanPhase(TWENTY_FOUR);
+
         DateTime plan4StartDate = buildDateTime(2011, 6, 7);
         DateTime plan4ChangeOfPlanDate = buildDateTime(2011, 8, 7);
 
-        UUID subscriptionId5 = UUID.randomUUID();
-        String planName5 = "Add-on";
-        String plan5PhaseName1 = "Evergreen"; String plan5PhaseName2 = "Cancel";
+        Plan plan5 = new MockPlan("Add-on");
+        PlanPhase plan5Phase1 = createMockMonthlyPlanPhase(TWENTY);
+        PlanPhase plan5Phase2 = createMockMonthlyPlanPhase();
         DateTime plan5StartDate = buildDateTime(2011, 6, 21);
         DateTime plan5CancelDate = buildDateTime(2011, 10, 7);
 
@@ -320,7 +317,7 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
         BillingEventSet events = new BillingEventSet();
 
         // on 1/5/2011, create subscription 1 (trial)
-        events.add(createBillingEvent(subscriptionId1, plan1StartDate, planName1, plan1PhaseName1, EIGHT, 5));
+        events.add(createBillingEvent(subscriptionId1, plan1StartDate, plan1, plan1Phase1, 5));
         expectedAmount = EIGHT;
         testInvoiceGeneration(events, invoiceItems, plan1StartDate, 1, expectedAmount);
 
@@ -333,12 +330,12 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
         testInvoiceGeneration(events, invoiceItems, buildDateTime(2011, 3, 5), 1, expectedAmount);
 
         // on 3/10/2011, create subscription 2 (trial)
-        events.add(createBillingEvent(subscriptionId2, plan2StartDate, planName2, plan2PhaseName1, TWENTY, 10));
+        events.add(createBillingEvent(subscriptionId2, plan2StartDate, plan2, plan2Phase1, 10));
         expectedAmount = TWENTY;
         testInvoiceGeneration(events, invoiceItems, plan2StartDate, 1, expectedAmount);
 
         // on 4/5/2011, invoice subscription 1 (discount)
-        events.add(createBillingEvent(subscriptionId1, plan1PhaseChangeDate, planName1, plan1PhaseName2, TWELVE, 5));
+        events.add(createBillingEvent(subscriptionId1, plan1PhaseChangeDate, plan1, plan1Phase2, 5));
         expectedAmount = TWELVE;
         testInvoiceGeneration(events, invoiceItems, plan1PhaseChangeDate, 1, expectedAmount);
 
@@ -347,7 +344,7 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
         testInvoiceGeneration(events, invoiceItems, buildDateTime(2011, 4, 10), 1, expectedAmount);
 
         // on 4/29/2011, cancel subscription 1
-        events.add(createBillingEvent(subscriptionId1, plan1CancelDate, planName1, plan1phase3, ZERO, 5));
+        events.add(createBillingEvent(subscriptionId1, plan1CancelDate, plan1, plan1Phase3, 5));
         expectedAmount = TWELVE.multiply(SIX.divide(THIRTY, NUMBER_OF_DECIMALS, ROUNDING_METHOD)).negate().setScale(NUMBER_OF_DECIMALS);
         testInvoiceGeneration(events, invoiceItems, plan1CancelDate, 2, expectedAmount);
 
@@ -356,17 +353,17 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
         testInvoiceGeneration(events, invoiceItems, buildDateTime(2011, 5, 10), 1, expectedAmount);
 
         // on 5/20/2011, create subscription 3 (monthly)
-        events.add(createBillingEvent(subscriptionId3, plan3StartDate, planName3, plan3PhaseName1, TEN, 20));
+        events.add(createBillingEvent(subscriptionId3, plan3StartDate, plan3, plan3Phase1, 20));
         expectedAmount = TEN;
         testInvoiceGeneration(events, invoiceItems, plan3StartDate, 1, expectedAmount);
 
         // on 6/7/2011, create subscription 4
-        events.add(createBillingEvent(subscriptionId4, plan4StartDate, planName4a, plan4PhaseName, FIFTEEN, 7));
+        events.add(createBillingEvent(subscriptionId4, plan4StartDate, plan4a, plan4aPhase1, 7));
         expectedAmount = FIFTEEN;
         testInvoiceGeneration(events, invoiceItems, plan4StartDate, 1, expectedAmount);
 
         // on 6/10/2011, invoice subscription 2 (discount)
-        events.add(createBillingEvent(subscriptionId2, plan2PhaseChangeToDiscountDate, planName2, plan2PhaseName2, THIRTY, 10));
+        events.add(createBillingEvent(subscriptionId2, plan2PhaseChangeToDiscountDate, plan2, plan2Phase2, 10));
         expectedAmount = THIRTY;
         testInvoiceGeneration(events, invoiceItems, plan2PhaseChangeToDiscountDate, 1, expectedAmount);
 
@@ -375,7 +372,7 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
         testInvoiceGeneration(events, invoiceItems, buildDateTime(2011, 6, 20), 1, expectedAmount);
 
         // on 6/21/2011, create add-on (subscription 5)
-        events.add(createBillingEvent(subscriptionId5, plan5StartDate, planName5, plan5PhaseName1, TWENTY, 10));
+        events.add(createBillingEvent(subscriptionId5, plan5StartDate, plan5, plan5Phase1, 10));
         expectedAmount = TWENTY.multiply(NINETEEN.divide(THIRTY, NUMBER_OF_DECIMALS, ROUNDING_METHOD)).setScale(NUMBER_OF_DECIMALS);
         testInvoiceGeneration(events, invoiceItems, plan5StartDate, 1, expectedAmount);
 
@@ -392,14 +389,14 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
         testInvoiceGeneration(events, invoiceItems, buildDateTime(2011, 7, 20), 1, expectedAmount);
 
         // on 7/31/2011, convert subscription 3 to annual
-        events.add(createAnnualBillingEvent(subscriptionId3, plan3UpgradeToAnnualDate, planName3, plan3PhaseName2, ONE_HUNDRED, 31));
+        events.add(createBillingEvent(subscriptionId3, plan3UpgradeToAnnualDate, plan3, plan3Phase2, 31));
         expectedAmount = ONE_HUNDRED.subtract(TEN);
         expectedAmount = expectedAmount.add(TEN.multiply(ELEVEN.divide(THIRTY_ONE, NUMBER_OF_DECIMALS, ROUNDING_METHOD)));
         expectedAmount = expectedAmount.setScale(NUMBER_OF_DECIMALS);
         testInvoiceGeneration(events, invoiceItems, plan3UpgradeToAnnualDate, 3, expectedAmount);
 
         // on 8/7/2011, invoice subscription 4 (plan 2)
-        events.add(createBillingEvent(subscriptionId4, plan4ChangeOfPlanDate, planName4b, plan4PhaseName, TWENTY_FOUR, 7));
+        events.add(createBillingEvent(subscriptionId4, plan4ChangeOfPlanDate, plan4b, plan4bPhase1, 7));
         expectedAmount = TWENTY_FOUR;
         testInvoiceGeneration(events, invoiceItems, plan4ChangeOfPlanDate, 1, expectedAmount);
 
@@ -412,12 +409,12 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
         testInvoiceGeneration(events, invoiceItems, buildDateTime(2011, 9, 7), 1, expectedAmount);
 
         // on 9/10/2011, invoice plan 2 (evergreen), invoice subscription 5
-        events.add(createBillingEvent(subscriptionId2, plan2PhaseChangeToEvergreenDate, planName2, plan2PhaseName3, FORTY, 10));
+        events.add(createBillingEvent(subscriptionId2, plan2PhaseChangeToEvergreenDate, plan2, plan2Phase3, 10));
         expectedAmount = FORTY.add(TWENTY);
         testInvoiceGeneration(events, invoiceItems, plan2PhaseChangeToEvergreenDate, 2, expectedAmount);
 
         // on 10/7/2011, invoice subscription 4 (plan 2), cancel subscription 5
-        events.add(createBillingEvent(subscriptionId5, plan5CancelDate, planName5, plan5PhaseName2, ZERO, 10));
+        events.add(createBillingEvent(subscriptionId5, plan5CancelDate, plan5, plan5Phase2, 10));
         expectedAmount = TWENTY_FOUR.add(TWENTY.multiply(THREE.divide(THIRTY)).negate().setScale(NUMBER_OF_DECIMALS));
         testInvoiceGeneration(events, invoiceItems, plan5CancelDate, 3, expectedAmount);
 
@@ -426,35 +423,192 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
         testInvoiceGeneration(events, invoiceItems, buildDateTime(2011, 10, 10), 1, expectedAmount);
     }
 
-    private DefaultBillingEvent createBillingEvent(UUID subscriptionId, DateTime startDate, String planName, String planPhaseName,
-                                            BigDecimal rate, int billCycleDay) {
-        MockCatalog catalog = new MockCatalog();
-        Plan plan = catalog.getCurrentPlans()[0];
-        PlanPhase phase = plan.getAllPhases()[0];
-        Subscription sub = new SubscriptionData(new SubscriptionBuilder().setId(UUID.randomUUID()));
-        return new DefaultBillingEvent(sub, startDate, plan, phase,
-                new InternationalPriceMock(ZERO),new InternationalPriceMock(rate), BillingPeriod.MONTHLY,
-                                billCycleDay, BillingModeType.IN_ADVANCE,"Test");
+    @Test
+    public void testZeroDollarEvents() throws InvoiceApiException {
+        Plan plan = new MockPlan();
+        PlanPhase planPhase = createMockMonthlyPlanPhase(ZERO);
+        BillingEventSet events = new BillingEventSet();
+        DateTime targetDate = buildDateTime(2011, 1, 1);
+        events.add(createBillingEvent(UUID.randomUUID(), targetDate, plan, planPhase, 1));
+
+        Invoice invoice = generator.generateInvoice(UUID.randomUUID(), events, null, targetDate, Currency.USD);
+
+        assertEquals(invoice.getNumberOfItems(), 1);
     }
 
-    private DefaultBillingEvent createAnnualBillingEvent(UUID subscriptionId, DateTime startDate, String planName, String planPhaseName,
-                                                  BigDecimal rate, int billCycleDay) {
-        MockCatalog catalog = new MockCatalog();
-        Plan plan = catalog.getCurrentPlans()[0];
-        PlanPhase phase = plan.getAllPhases()[0];
-        Subscription sub = new SubscriptionData(new SubscriptionBuilder().setId(UUID.randomUUID()));
-        return new DefaultBillingEvent(sub, startDate, plan, phase,
-                new InternationalPriceMock(ZERO),new InternationalPriceMock(rate), BillingPeriod.ANNUAL,
-                                billCycleDay, BillingModeType.IN_ADVANCE,"Test");
+    @Test
+    public void testEndDateIsCorrect() throws InvoiceApiException {
+        Plan plan = new MockPlan();
+        PlanPhase planPhase = createMockMonthlyPlanPhase(ZERO);
+        BillingEventSet events = new BillingEventSet();
+        DateTime targetDate = new DateTime();
+        events.add(createBillingEvent(UUID.randomUUID(), targetDate, plan, planPhase, targetDate.getDayOfMonth()));
+
+        Invoice invoice = generator.generateInvoice(UUID.randomUUID(), events, null, targetDate, Currency.USD);
+        RecurringInvoiceItem item = (RecurringInvoiceItem) invoice.getInvoiceItems().get(0);
+
+        // end date of the invoice item should be equal to exactly one month later
+        assertEquals(item.getEndDate().compareTo(targetDate.plusMonths(1)), 0);
+    }
+
+    @Test
+    public void testFixedPriceLifeCycle() throws InvoiceApiException {
+        UUID accountId = UUID.randomUUID();
+        Subscription subscription = new MockSubscription();
+        Plan plan = new MockPlan("plan 1");
+        MockInternationalPrice zeroPrice = new MockInternationalPrice(new DefaultPrice(ZERO, Currency.USD));
+        MockInternationalPrice cheapPrice = new MockInternationalPrice(new DefaultPrice(ONE, Currency.USD));
+
+        PlanPhase phase1 = new MockPlanPhase(null, zeroPrice, BillingPeriod.NO_BILLING_PERIOD, PhaseType.TRIAL);
+        PlanPhase phase2 = new MockPlanPhase(cheapPrice, null, BillingPeriod.MONTHLY, PhaseType.DISCOUNT);
+
+        DateTime changeDate = new DateTime("2012-04-1T00:00:00.000-08:00");
+
+        BillingEventSet events = new BillingEventSet();
+
+        BillingEvent event1 = new DefaultBillingEvent(subscription, new DateTime("2012-01-1T00:00:00.000-08:00"),
+                                                      plan, phase1,
+                                                      zeroPrice, null, BillingPeriod.NO_BILLING_PERIOD, 1,
+                                                      BillingModeType.IN_ADVANCE, "Test Event 1",
+                                                      SubscriptionTransitionType.CREATE);
+
+        BillingEvent event2 = new DefaultBillingEvent(subscription, changeDate,
+                                                      plan, phase2,
+                                                      zeroPrice, null, BillingPeriod.NO_BILLING_PERIOD, 1,
+                                                      BillingModeType.IN_ADVANCE, "Test Event 2",
+                                                      SubscriptionTransitionType.PHASE);
+
+        events.add(event2);
+        events.add(event1);
+        Invoice invoice1 = generator.generateInvoice(accountId, events, null, new DateTime("2012-02-01T00:01:00.000-08:00"), Currency.USD);
+        assertNotNull(invoice1);
+        assertEquals(invoice1.getNumberOfItems(), 1);
+
+        Invoice invoice2 = generator.generateInvoice(accountId, events, invoice1.getInvoiceItems(), new DateTime("2012-04-05T00:01:00.000-08:00"), Currency.USD);
+        assertNotNull(invoice2);
+        assertEquals(invoice2.getNumberOfItems(), 1);
+        FixedPriceInvoiceItem item = (FixedPriceInvoiceItem) invoice2.getInvoiceItems().get(0);
+        assertEquals(item.getStartDate().compareTo(changeDate), 0);
+   }
+
+    @Test
+    public void testMixedModeLifeCycle() throws InvoiceApiException {
+        // create a subscription with a fixed price and recurring price
+        Plan plan1 = new MockPlan();
+        BigDecimal monthlyRate = FIVE;
+        BigDecimal fixedCost = TEN;
+        PlanPhase phase1 = createMockMonthlyPlanPhase(monthlyRate, fixedCost, PhaseType.TRIAL);
+
+        BillingEventSet events = new BillingEventSet();
+        UUID subscriptionId = UUID.randomUUID();
+        UUID accountId = UUID.randomUUID();
+
+        DateTime startDate = new DateTime(2011, 1, 1, 3, 40, 27, 0);
+        BillingEvent event1 = createBillingEvent(subscriptionId, startDate, plan1, phase1, 1);
+        events.add(event1);
+
+        // ensure both components are invoiced
+        Invoice invoice1 = generator.generateInvoice(accountId, events, null, startDate, Currency.USD);
+        assertNotNull(invoice1);
+        assertEquals(invoice1.getNumberOfItems(), 2);
+        assertEquals(invoice1.getTotalAmount(), FIFTEEN);
+
+        List<InvoiceItem> existingItems = invoice1.getInvoiceItems();
+
+        // move forward in time one billing period
+        DateTime currentDate = startDate.plusMonths(1);
+
+        // ensure that only the recurring price is invoiced
+        Invoice invoice2 = generator.generateInvoice(accountId, events, existingItems, currentDate, Currency.USD);
+        assertNotNull(invoice2);
+        assertEquals(invoice2.getNumberOfItems(), 1);
+        assertEquals(invoice2.getTotalAmount(), FIVE);
     }
 
-    private void testInvoiceGeneration(BillingEventSet events, InvoiceItemList existingInvoiceItems, DateTime targetDate, int expectedNumberOfItems, BigDecimal expectedAmount) {
+    @Test
+    public void testFixedModePlanChange() throws InvoiceApiException {
+        // create a subscription with a fixed price and recurring price
+        Plan plan1 = new MockPlan();
+        BigDecimal fixedCost1 = TEN;
+        BigDecimal fixedCost2 = TWENTY;
+        PlanPhase phase1 = createMockMonthlyPlanPhase(null, fixedCost1, PhaseType.TRIAL);
+        PlanPhase phase2 = createMockMonthlyPlanPhase(null, fixedCost2, PhaseType.EVERGREEN);
+
+        BillingEventSet events = new BillingEventSet();
+        UUID subscriptionId = UUID.randomUUID();
+        UUID accountId = UUID.randomUUID();
+
+        DateTime startDate = new DateTime(2011, 1, 1, 3, 40, 27, 0);
+        BillingEvent event1 = createBillingEvent(subscriptionId, startDate, plan1, phase1, 1);
+        events.add(event1);
+
+        // ensure that a single invoice item is generated for the fixed cost
+        Invoice invoice1 = generator.generateInvoice(accountId, events, null, startDate, Currency.USD);
+        assertNotNull(invoice1);
+        assertEquals(invoice1.getNumberOfItems(), 1);
+        assertEquals(invoice1.getTotalAmount(), fixedCost1);
+
+        List<InvoiceItem> existingItems = invoice1.getInvoiceItems();
+
+        // move forward in time one billing period
+        DateTime phaseChangeDate = startDate.plusMonths(1);
+        BillingEvent event2 = createBillingEvent(subscriptionId, phaseChangeDate, plan1, phase2, 1);
+        events.add(event2);
+
+        // ensure that a single invoice item is generated for the fixed cost
+        Invoice invoice2 = generator.generateInvoice(accountId, events, existingItems, phaseChangeDate, Currency.USD);
+        assertNotNull(invoice2);
+        assertEquals(invoice2.getNumberOfItems(), 1);
+        assertEquals(invoice2.getTotalAmount(), fixedCost2);
+    }
+
+    private MockPlanPhase createMockMonthlyPlanPhase() {
+        return new MockPlanPhase(null, null, BillingPeriod.MONTHLY);
+    }
+
+    private MockPlanPhase createMockMonthlyPlanPhase(@Nullable final BigDecimal recurringRate) {
+        return new MockPlanPhase(new MockInternationalPrice(new DefaultPrice(recurringRate, Currency.USD)),
+                                 null, BillingPeriod.MONTHLY);
+    }
+
+    private MockPlanPhase createMockMonthlyPlanPhase(final BigDecimal recurringRate, final PhaseType phaseType) {
+        return new MockPlanPhase(new MockInternationalPrice(new DefaultPrice(recurringRate, Currency.USD)),
+                                 null, BillingPeriod.MONTHLY, phaseType);
+    }
+
+    private MockPlanPhase createMockMonthlyPlanPhase(@Nullable BigDecimal recurringRate, final BigDecimal fixedCost,
+                                                     final PhaseType phaseType) {
+        MockInternationalPrice recurringPrice = (recurringRate == null) ? null : new MockInternationalPrice(new DefaultPrice(recurringRate, Currency.USD));
+        MockInternationalPrice fixedPrice = (fixedCost == null) ? null : new MockInternationalPrice(new DefaultPrice(fixedCost, Currency.USD));
+
+        return new MockPlanPhase(recurringPrice, fixedPrice, BillingPeriod.MONTHLY, phaseType);
+    }
+
+    private MockPlanPhase createMockAnnualPlanPhase(final BigDecimal recurringRate, final PhaseType phaseType) {
+        return new MockPlanPhase(new MockInternationalPrice(new DefaultPrice(recurringRate, Currency.USD)),
+                                 null, BillingPeriod.ANNUAL, phaseType);
+    }
+
+    private DefaultBillingEvent createBillingEvent(final UUID subscriptionId, final DateTime startDate,
+                                                   final Plan plan, final PlanPhase planPhase, final int billCycleDay) {
+        Subscription sub = new SubscriptionData(new SubscriptionBuilder().setId(subscriptionId));
+
+        return new DefaultBillingEvent(sub, startDate, plan, planPhase,
+                                       planPhase.getFixedPrice(),
+                                       planPhase.getRecurringPrice(), planPhase.getBillingPeriod(),
+                                       billCycleDay, BillingModeType.IN_ADVANCE,"Test", SubscriptionTransitionType.CREATE);
+    }
+
+    private void testInvoiceGeneration(final BillingEventSet events, final InvoiceItemList existingInvoiceItems,
+                                       final DateTime targetDate, final int expectedNumberOfItems,
+                                       final BigDecimal expectedAmount) throws InvoiceApiException {
         Currency currency = Currency.USD;
         UUID accountId = UUID.randomUUID();
         Invoice invoice = generator.generateInvoice(accountId, events, existingInvoiceItems, targetDate, currency);
-        existingInvoiceItems.addAll(invoice.getItems());
         assertNotNull(invoice);
         assertEquals(invoice.getNumberOfItems(), expectedNumberOfItems);
+
+        existingInvoiceItems.addAll(invoice.getInvoiceItems());
         assertEquals(invoice.getTotalAmount(), expectedAmount);
     }
 
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 84bf795..64fa95c 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
@@ -24,7 +24,7 @@ import org.testng.annotations.Test;
 
 import java.math.BigDecimal;
 
-@Test(groups = {"invoicing", "proRation"})
+@Test(groups = {"fast", "invoicing", "proRation"})
 public class DoubleProRationTests extends ProRationInAdvanceTestBase {
     @Override
     protected BillingPeriod getBillingPeriod() {
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 f612f10..8d0eb06 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
@@ -22,7 +22,7 @@ import org.testng.annotations.Test;
 
 import java.math.BigDecimal;
 
-@Test(groups = {"invoicing", "proRation"})
+@Test(groups = {"fast", "invoicing", "proRation"})
 public class GenericProRationTests extends GenericProRationTestBase {
     @Override
     protected BillingPeriod getBillingPeriod() {
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 f44876e..a009fba 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
@@ -24,7 +24,7 @@ import org.testng.annotations.Test;
 
 import java.math.BigDecimal;
 
-@Test(groups = {"invoicing", "proRation"})
+@Test(groups = {"fast", "invoicing", "proRation"})
 public class LeadingProRationTests extends ProRationInAdvanceTestBase {
     @Override
     protected BillingPeriod getBillingPeriod() {
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 274a8e7..f882005 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
@@ -24,7 +24,7 @@ import org.testng.annotations.Test;
 
 import java.math.BigDecimal;
 
-@Test(groups = {"invoicing", "proRation"})
+@Test(groups = {"fast", "invoicing", "proRation"})
 public class ProRationTests extends ProRationInAdvanceTestBase {
     @Override
     protected BillingPeriod getBillingPeriod() {
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 0689c69..e0216b5 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
@@ -24,7 +24,7 @@ import org.testng.annotations.Test;
 
 import java.math.BigDecimal;
 
-@Test(groups = {"invoicing", "proRation"})
+@Test(groups = {"fast", "invoicing", "proRation"})
 public class TrailingProRationTests extends ProRationInAdvanceTestBase {
     @Override
     protected BillingPeriod getBillingPeriod() {
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 d4e4c24..60892e6 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
@@ -22,7 +22,7 @@ import org.testng.annotations.Test;
 
 import java.math.BigDecimal;
 
-@Test(groups = {"invoicing", "proRation"})
+@Test(groups = {"fast", "invoicing", "proRation"})
 public abstract class GenericProRationTestBase extends ProRationInAdvanceTestBase {
     /**
      * used for testing cancellation in less than a single billing period
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 4799e40..240b8aa 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
@@ -24,7 +24,7 @@ import org.testng.annotations.Test;
 
 import java.math.BigDecimal;
 
-@Test(groups = {"invoicing", "proRation"})
+@Test(groups = {"fast", "invoicing", "proRation"})
 public class DoubleProRationTests extends ProRationInAdvanceTestBase {
     @Override
     protected BillingPeriod getBillingPeriod() {
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 eb3b1c9..8b40db8 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
@@ -22,7 +22,7 @@ import org.testng.annotations.Test;
 
 import java.math.BigDecimal;
 
-@Test(groups = {"invoicing", "proRation"})
+@Test(groups = {"fast", "invoicing", "proRation"})
 public class GenericProRationTests extends GenericProRationTestBase {
     @Override
     protected BillingPeriod getBillingPeriod() {
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 5db6911..998e566 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
@@ -24,7 +24,7 @@ import org.testng.annotations.Test;
 
 import java.math.BigDecimal;
 
-@Test(groups = {"invoicing", "proRation"})
+@Test(groups = {"fast", "invoicing", "proRation"})
 public class LeadingProRationTests extends ProRationInAdvanceTestBase {
     @Override
     protected BillingPeriod getBillingPeriod() {
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 13c75df..c3748d1 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
@@ -24,7 +24,7 @@ import org.testng.annotations.Test;
 
 import java.math.BigDecimal;
 
-@Test(groups = {"invoicing", "proRation"})
+@Test(groups = {"fast", "invoicing", "proRation"})
 public class ProRationTests extends ProRationInAdvanceTestBase {
     @Override
     protected BillingPeriod getBillingPeriod() {
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 9a8e635..6a5e5ef 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
@@ -24,7 +24,7 @@ import org.testng.annotations.Test;
 
 import java.math.BigDecimal;
 
-@Test(groups = {"invoicing", "proRation"})
+@Test(groups = {"fast", "invoicing", "proRation"})
 public class TrailingProRationTests extends ProRationInAdvanceTestBase {
     @Override
     protected BillingPeriod getBillingPeriod() {
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 14392a8..18bd096 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
@@ -21,7 +21,7 @@ import com.ning.billing.invoice.model.InAdvanceBillingMode;
 import com.ning.billing.invoice.tests.ProRationTestBase;
 import org.testng.annotations.Test;
 
-@Test(groups = {"invoicing", "proRation"})
+@Test(groups = {"fast", "invoicing", "proRation"})
 public abstract class ProRationInAdvanceTestBase extends ProRationTestBase {
     @Override
     protected BillingMode getBillingMode() {
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 68e29fd..c1d6085 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
@@ -24,7 +24,7 @@ import org.testng.annotations.Test;
 
 import java.math.BigDecimal;
 
-@Test(groups = {"invoicing", "proRation"})
+@Test(groups = {"fast", "invoicing", "proRation"})
 public class DoubleProRationTests extends ProRationInAdvanceTestBase {
     @Override
     protected BillingPeriod getBillingPeriod() {
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 8306716..c4237a6 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
@@ -22,7 +22,7 @@ import org.testng.annotations.Test;
 
 import java.math.BigDecimal;
 
-@Test(groups = {"invoicing", "proRation"})
+@Test(groups = {"fast", "invoicing", "proRation"})
 public class GenericProRationTests extends GenericProRationTestBase {
     @Override
     protected BillingPeriod getBillingPeriod() {
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 fe614a9..04ec683 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
@@ -24,7 +24,7 @@ import org.testng.annotations.Test;
 
 import java.math.BigDecimal;
 
-@Test(groups = {"invoicing", "proRation"})
+@Test(groups = {"fast", "invoicing", "proRation"})
 public class LeadingProRationTests extends ProRationInAdvanceTestBase {
     @Override
     protected BillingPeriod getBillingPeriod() {
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 c99aa5c..e13db0d 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
@@ -24,7 +24,7 @@ import org.testng.annotations.Test;
 
 import java.math.BigDecimal;
 
-@Test(groups = {"invoicing", "proRation"})
+@Test(groups = {"fast", "invoicing", "proRation"})
 public class ProRationTests extends ProRationInAdvanceTestBase {
     @Override
     protected BillingPeriod getBillingPeriod() {
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 89e138e..8f63010 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
@@ -24,7 +24,7 @@ import org.testng.annotations.Test;
 
 import java.math.BigDecimal;
 
-@Test(groups = {"invoicing", "proRation"})
+@Test(groups = {"fast", "invoicing", "proRation"})
 public class TrailingProRationTests extends ProRationInAdvanceTestBase {
     @Override
     protected BillingPeriod getBillingPeriod() {
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 9de01bd..bd8a38d 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
@@ -28,7 +28,7 @@ import java.math.BigDecimal;
 
 import static org.testng.Assert.assertEquals;
 
-@Test(groups = {"invoicing", "proRation"})
+@Test(groups = {"fast", "invoicing", "proRation"})
 public class ValidationProRationTests extends ProRationTestBase {
     protected BillingPeriod getBillingPeriod() {
         return BillingPeriod.MONTHLY;
@@ -39,14 +39,6 @@ public class ValidationProRationTests extends ProRationTestBase {
         return new InAdvanceBillingMode();
     }
 
-    protected BigDecimal calculateNumberOfBillingCycles(DateTime startDate, DateTime targetDate, int billingCycleDay) throws InvalidDateSequenceException {
-        return getBillingMode().calculateNumberOfBillingCycles(startDate, targetDate, billingCycleDay, getBillingPeriod());
-    }
-
-    protected BigDecimal calculateNumberOfBillingCycles(DateTime startDate, DateTime endDate, DateTime targetDate, int billingCycleDay) throws InvalidDateSequenceException {
-        return getBillingMode().calculateNumberOfBillingCycles(startDate, endDate, targetDate, billingCycleDay, getBillingPeriod());
-    }
-
     @Test(expectedExceptions = InvalidDateSequenceException.class)
     public void testTargetStartEnd() throws InvalidDateSequenceException {
         DateTime startDate = buildDateTime(2011, 1, 30);
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/InternationalPriceMock.java b/invoice/src/test/java/com/ning/billing/invoice/tests/InternationalPriceMock.java
index 8577544..ab11024 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/InternationalPriceMock.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/InternationalPriceMock.java
@@ -43,4 +43,9 @@ public class InternationalPriceMock implements InternationalPrice {
         return rate;
     }
 
+	@Override
+	public boolean isZero() {
+		return rate.compareTo(BigDecimal.ZERO) == 0;
+	}
+
 }
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 afc9d1c..c90bf69 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
@@ -24,7 +24,7 @@ import java.math.BigDecimal;
 
 public abstract class InvoicingTestBase {
     protected static final int NUMBER_OF_DECIMALS = InvoicingConfiguration.getNumberOfDecimals();
-    protected static final int ROUNDING_METHOD = InvoicingConfiguration.getRoundingMethod();
+    protected static final int ROUNDING_METHOD = InvoicingConfiguration.getRoundingMode();
 
     protected static final BigDecimal ZERO = new BigDecimal("0.0").setScale(NUMBER_OF_DECIMALS);
     protected static final BigDecimal ONE_HALF = new BigDecimal("0.5").setScale(NUMBER_OF_DECIMALS);
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 174017b..a24b0c0 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
@@ -19,23 +19,25 @@ 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 java.math.BigDecimal;
+import java.util.List;
 
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.fail;
 
-public abstract class ProRationTestBase extends InvoicingTestBase{
+public abstract class ProRationTestBase extends InvoicingTestBase {
     protected abstract BillingMode getBillingMode();
     protected abstract BillingPeriod getBillingPeriod();
 
     protected void testCalculateNumberOfBillingCycles(DateTime startDate, DateTime targetDate, int billingCycleDay, BigDecimal expectedValue) throws InvalidDateSequenceException {
         try {
             BigDecimal numberOfBillingCycles;
-            numberOfBillingCycles = getBillingMode().calculateNumberOfBillingCycles(startDate, targetDate, billingCycleDay, getBillingPeriod());
+            numberOfBillingCycles = calculateNumberOfBillingCycles(startDate, targetDate, billingCycleDay);
 
-            assertEquals(numberOfBillingCycles, expectedValue);
+            assertEquals(numberOfBillingCycles.compareTo(expectedValue), 0, "Actual: " + numberOfBillingCycles.toString() + "; expected: " + expectedValue.toString());
         } catch (InvalidDateSequenceException idse) {
             throw idse;
         } catch (Exception e) {
@@ -46,13 +48,35 @@ public abstract class ProRationTestBase extends InvoicingTestBase{
     protected void testCalculateNumberOfBillingCycles(DateTime startDate, DateTime endDate, DateTime targetDate, int billingCycleDay, BigDecimal expectedValue) throws InvalidDateSequenceException {
         try {
             BigDecimal numberOfBillingCycles;
-            numberOfBillingCycles = getBillingMode().calculateNumberOfBillingCycles(startDate, endDate, targetDate, billingCycleDay, getBillingPeriod());
+            numberOfBillingCycles = calculateNumberOfBillingCycles(startDate, endDate, targetDate, billingCycleDay);
 
-            assertEquals(numberOfBillingCycles, expectedValue);
+            assertEquals(numberOfBillingCycles.compareTo(expectedValue), 0);
         } catch (InvalidDateSequenceException idse) {
             throw idse;
         } catch (Exception e) {
             fail("Unexpected exception: " + e.getMessage());
         }
     }
+
+    protected BigDecimal calculateNumberOfBillingCycles(DateTime startDate, DateTime endDate, DateTime targetDate, int billingCycleDay) throws InvalidDateSequenceException {
+        List<RecurringInvoiceItemData> items = getBillingMode().calculateInvoiceItemData(startDate, endDate, targetDate, billingCycleDay, getBillingPeriod());
+
+        BigDecimal numberOfBillingCycles = ZERO;
+        for (RecurringInvoiceItemData item : items) {
+            numberOfBillingCycles = numberOfBillingCycles.add(item.getNumberOfCycles());
+        }
+
+        return numberOfBillingCycles;
+    }
+
+    protected BigDecimal calculateNumberOfBillingCycles(DateTime startDate, DateTime targetDate, int billingCycleDay) throws InvalidDateSequenceException {
+        List<RecurringInvoiceItemData> items = getBillingMode().calculateInvoiceItemData(startDate, targetDate, billingCycleDay, getBillingPeriod());
+
+        BigDecimal numberOfBillingCycles = ZERO;
+        for (RecurringInvoiceItemData item : items) {
+            numberOfBillingCycles = numberOfBillingCycles.add(item.getNumberOfCycles());
+        }
+
+        return numberOfBillingCycles;
+    }
 }
\ No newline at end of file
diff --git a/invoice/src/test/resources/log4j.xml b/invoice/src/test/resources/log4j.xml
new file mode 100644
index 0000000..33b9662
--- /dev/null
+++ b/invoice/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.invoice">
+        <level value="info"/>
+    </logger>
+
+    <root>
+        <priority value="info"/>
+        <appender-ref ref="stdout"/>
+    </root>
+</log4j:configuration>

payment/pom.xml 100(+97 -3)

diff --git a/payment/pom.xml b/payment/pom.xml
index 3d6356e..bdb30c8 100644
--- a/payment/pom.xml
+++ b/payment/pom.xml
@@ -13,14 +13,108 @@
     <parent>
         <groupId>com.ning.billing</groupId>
         <artifactId>killbill</artifactId>
-        <version>0.1.2-SNAPSHOT</version>
+        <version>0.1.6-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-payment</artifactId>
     <name>killbill-payment</name>
     <packaging>jar</packaging>
     <dependencies>
+        <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-invoice</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-account</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-util</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.google.inject</groupId>
+            <artifactId>guice</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.google.inject.extensions</groupId>
+            <artifactId>guice-multibindings</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>joda-time</groupId>
+            <artifactId>joda-time</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>commons-lang</groupId>
+            <artifactId>commons-lang</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>commons-io</groupId>
+            <artifactId>commons-io</artifactId>
+            <version>2.0.1</version>
+        </dependency>
+        <dependency>
+            <groupId>commons-collections</groupId>
+            <artifactId>commons-collections</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.testng</groupId>
+            <artifactId>testng</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.jayway.awaitility</groupId>
+            <artifactId>awaitility</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-util</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-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>com.mysql</groupId>
+             <artifactId>management</artifactId>
+             <scope>test</scope>
+         </dependency>
+         <dependency>
+             <groupId>com.mysql</groupId>
+             <artifactId>management-dbfiles</artifactId>
+             <scope>test</scope>
+         </dependency>
     </dependencies>
-    <build>
-    </build>
 </project>
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
new file mode 100644
index 0000000..a8364bf
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/api/DefaultPaymentApi.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * 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.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import javax.annotation.Nullable;
+
+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.AccountUserApi;
+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.dao.PaymentDao;
+import com.ning.billing.payment.provider.PaymentProviderPlugin;
+import com.ning.billing.payment.provider.PaymentProviderPluginRegistry;
+
+public class DefaultPaymentApi implements PaymentApi {
+    private final PaymentProviderPluginRegistry pluginRegistry;
+    private final AccountUserApi accountUserApi;
+    private final InvoicePaymentApi invoicePaymentApi;
+    private final PaymentDao paymentDao;
+
+    private static final Logger log = LoggerFactory.getLogger(DefaultPaymentApi.class);
+
+    @Inject
+    public DefaultPaymentApi(PaymentProviderPluginRegistry pluginRegistry,
+                             AccountUserApi accountUserApi,
+                             InvoicePaymentApi invoicePaymentApi,
+                             PaymentDao paymentDao) {
+        this.pluginRegistry = pluginRegistry;
+        this.accountUserApi = accountUserApi;
+        this.invoicePaymentApi = invoicePaymentApi;
+        this.paymentDao = paymentDao;
+    }
+
+    @Override
+    public Either<PaymentError, PaymentMethodInfo> getPaymentMethod(@Nullable String accountKey, String paymentMethodId) {
+        final PaymentProviderPlugin plugin = getPaymentProviderPlugin(accountKey);
+        return plugin.getPaymentMethodInfo(paymentMethodId);
+    }
+
+    private PaymentProviderPlugin getPaymentProviderPlugin(String accountKey) {
+        String paymentProviderName = null;
+
+        if (accountKey != null) {
+            final Account account = accountUserApi.getAccountByKey(accountKey);
+            if (account != null) {
+                return getPaymentProviderPlugin(account);
+            }
+        }
+
+        return pluginRegistry.getPlugin(paymentProviderName);
+    }
+
+    private PaymentProviderPlugin getPaymentProviderPlugin(Account account) {
+        String paymentProviderName = null;
+
+        if (account != null) {
+            paymentProviderName = account.getPaymentProviderName();
+        }
+
+        return pluginRegistry.getPlugin(paymentProviderName);
+    }
+
+    @Override
+    public Either<PaymentError, List<PaymentMethodInfo>> getPaymentMethods(String accountKey) {
+        final PaymentProviderPlugin plugin = getPaymentProviderPlugin(accountKey);
+        return plugin.getPaymentMethods(accountKey);
+    }
+
+    @Override
+    public Either<PaymentError, Void> updatePaymentGateway(String accountKey) {
+        final PaymentProviderPlugin plugin = getPaymentProviderPlugin(accountKey);
+        return plugin.updatePaymentGateway(accountKey);
+    }
+
+    @Override
+    public Either<PaymentError, PaymentProviderAccount> getPaymentProviderAccount(String accountKey) {
+        final PaymentProviderPlugin plugin = getPaymentProviderPlugin(accountKey);
+        return plugin.getPaymentProviderAccount(accountKey);
+    }
+
+    @Override
+    public Either<PaymentError, String> addPaymentMethod(@Nullable String accountKey, PaymentMethodInfo paymentMethod) {
+        final PaymentProviderPlugin plugin = getPaymentProviderPlugin(accountKey);
+        return plugin.addPaymentMethod(accountKey, paymentMethod);
+    }
+
+    @Override
+    public Either<PaymentError, Void> deletePaymentMethod(String accountKey, String paymentMethodId) {
+        final PaymentProviderPlugin plugin = getPaymentProviderPlugin(accountKey);
+        return plugin.deletePaymentMethod(accountKey, paymentMethodId);
+    }
+
+    @Override
+    public Either<PaymentError, PaymentMethodInfo> updatePaymentMethod(String accountKey, PaymentMethodInfo paymentMethodInfo) {
+        final PaymentProviderPlugin plugin = getPaymentProviderPlugin(accountKey);
+        return plugin.updatePaymentMethod(accountKey, paymentMethodInfo);
+    }
+
+    @Override
+    public List<Either<PaymentError, PaymentInfo>> createPayment(String accountKey, List<String> invoiceIds) {
+        final Account account = accountUserApi.getAccountByKey(accountKey);
+        return createPayment(account, invoiceIds);
+    }
+
+    @Override
+    public List<Either<PaymentError, PaymentInfo>> createPayment(Account account, List<String> invoiceIds) {
+        final PaymentProviderPlugin plugin = getPaymentProviderPlugin(account);
+
+        List<Either<PaymentError, PaymentInfo>> processedPaymentsOrErrors = new ArrayList<Either<PaymentError, PaymentInfo>>(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 outstanding amount of 0 {} ", invoice);
+            }
+            else if (invoiceId.equals(paymentDao.getPaymentAttemptForInvoiceId(invoiceId))) {
+                //TODO: do equals on invoice instead and only reject when invoice is exactly the same?
+                log.info("Duplicate invoice payment event, already received invoice {} ", invoice);
+            }
+            else {
+                PaymentAttempt paymentAttempt = paymentDao.createPaymentAttempt(invoice);
+                Either<PaymentError, PaymentInfo> paymentOrError = plugin.processInvoice(account, invoice);
+                processedPaymentsOrErrors.add(paymentOrError);
+
+                PaymentInfo paymentInfo = null;
+
+                if (paymentOrError.isRight()) {
+                    paymentInfo = paymentOrError.getRight();
+                    paymentDao.savePaymentInfo(paymentInfo);
+
+                    Either<PaymentError, PaymentMethodInfo> paymentMethodInfoOrError = plugin.getPaymentMethodInfo(paymentInfo.getPaymentMethodId());
+
+                    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());
+                        }
+                        else if (paymentMethodInfo instanceof PaypalPaymentMethodInfo) {
+                            PaypalPaymentMethodInfo paypalPaymentMethodInfo = (PaypalPaymentMethodInfo)paymentMethodInfo;
+                            paymentDao.updatePaymentInfo(paypalPaymentMethodInfo.getType(), paymentInfo.getPaymentId(), null, null);
+                        }
+                    }
+
+
+                    if (paymentInfo.getPaymentId() != null) {
+                        paymentDao.updatePaymentAttemptWithPaymentId(paymentAttempt.getPaymentAttemptId(), paymentInfo.getPaymentId());
+                    }
+                }
+
+                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()));
+
+            }
+        }
+
+        return processedPaymentsOrErrors;
+    }
+
+    @Override
+    public Either<PaymentError, String> createPaymentProviderAccount(Account account) {
+        final PaymentProviderPlugin plugin = getPaymentProviderPlugin((Account)null);
+        return plugin.createPaymentProviderAccount(account);
+    }
+
+    @Override
+    public Either<PaymentError, Void> updatePaymentProviderAccountContact(String externalKey) {
+    	Account account = accountUserApi.getAccountByKey(externalKey);
+        final PaymentProviderPlugin plugin = getPaymentProviderPlugin(account);
+        return plugin.updatePaymentProviderAccountExistingContact(account);
+    }
+
+    @Override
+    public PaymentAttempt getPaymentAttemptForPaymentId(String id) {
+        return paymentDao.getPaymentAttemptForPaymentId(id);
+    }
+
+    @Override
+    public List<Either<PaymentError, PaymentInfo>> createRefund(Account account, List<String> invoiceIds) {
+        //TODO
+        throw new UnsupportedOperationException();
+    }
+
+}
diff --git a/payment/src/main/java/com/ning/billing/payment/dao/DefaultPaymentDao.java b/payment/src/main/java/com/ning/billing/payment/dao/DefaultPaymentDao.java
new file mode 100644
index 0000000..eacc226
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/dao/DefaultPaymentDao.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.payment.dao;
+
+import java.util.UUID;
+
+import org.skife.jdbi.v2.IDBI;
+
+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;
+
+public class DefaultPaymentDao implements PaymentDao {
+    private final PaymentSqlDao sqlDao;
+
+    @Inject
+    public DefaultPaymentDao(IDBI dbi) {
+        this.sqlDao = dbi.onDemand(PaymentSqlDao.class);
+    }
+
+    @Override
+    public PaymentAttempt getPaymentAttemptForPaymentId(String paymentId) {
+        return sqlDao.getPaymentAttemptForPaymentId(paymentId);
+    }
+
+    @Override
+    public PaymentAttempt getPaymentAttemptForInvoiceId(String invoiceId) {
+        return sqlDao.getPaymentAttemptForInvoiceId(invoiceId);
+    }
+
+    @Override
+    public PaymentAttempt createPaymentAttempt(Invoice invoice) {
+        final PaymentAttempt paymentAttempt = new PaymentAttempt(UUID.randomUUID(), invoice);
+
+        sqlDao.insertPaymentAttempt(paymentAttempt);
+        return paymentAttempt;
+    }
+
+    @Override
+    public void savePaymentInfo(PaymentInfo info) {
+        sqlDao.insertPaymentInfo(info);
+    }
+
+    @Override
+    public void updatePaymentAttemptWithPaymentId(UUID paymentAttemptId, String paymentId) {
+        sqlDao.updatePaymentAttemptWithPaymentId(paymentAttemptId.toString(), paymentId);
+    }
+
+    @Override
+    public void updatePaymentInfo(String type, String paymentId, String cardType, String cardCountry) {
+        sqlDao.updatePaymentInfo(type, paymentId, cardType, cardCountry);
+    }
+
+}
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
new file mode 100644
index 0000000..5cf065b
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/dao/PaymentDao.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.payment.dao;
+
+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;
+
+public interface PaymentDao {
+
+    PaymentAttempt createPaymentAttempt(Invoice invoice);
+
+    void savePaymentInfo(PaymentInfo right);
+
+    PaymentAttempt getPaymentAttemptForPaymentId(String paymentId);
+
+    void updatePaymentAttemptWithPaymentId(UUID paymentAttemptId, String paymentId);
+
+    PaymentAttempt getPaymentAttemptForInvoiceId(String invoiceId);
+
+    void updatePaymentInfo(String paymentMethodType, String paymentId, String cardType, String cardCountry);
+
+}
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
new file mode 100644
index 0000000..972ee64
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/dao/PaymentSqlDao.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.payment.dao;
+
+import java.math.BigDecimal;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Timestamp;
+import java.util.Date;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+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.mixins.Transmogrifier;
+import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
+import org.skife.jdbi.v2.tweak.ResultSetMapper;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.payment.api.PaymentAttempt;
+import com.ning.billing.payment.api.PaymentInfo;
+
+@ExternalizedSqlViaStringTemplate3()
+public interface PaymentSqlDao extends Transactional<PaymentSqlDao>, CloseMe, Transmogrifier {
+    @SqlUpdate
+    void insertPaymentAttempt(@Bind(binder = PaymentAttemptBinder.class) PaymentAttempt paymentAttempt);
+
+    @SqlQuery
+    @Mapper(PaymentAttemptMapper.class)
+    PaymentAttempt getPaymentAttemptForPaymentId(@Bind("payment_id") String paymentId);
+
+    @SqlQuery
+    @Mapper(PaymentAttemptMapper.class)
+    PaymentAttempt getPaymentAttemptForInvoiceId(@Bind("invoice_id") String invoiceId);
+
+    @SqlUpdate
+    void updatePaymentAttemptWithPaymentId(@Bind("payment_attempt_id") String paymentAttemptId,
+                                           @Bind("payment_id") String paymentId);
+
+    @SqlUpdate
+    void updatePaymentInfo(@Bind("payment_method") String paymentMethod,
+                           @Bind("payment_id") String paymentId,
+                           @Bind("card_type") String cardType,
+                           @Bind("card_country") String cardCountry);
+
+    @SqlUpdate
+    void insertPaymentInfo(@Bind(binder = PaymentInfoBinder.class) PaymentInfo paymentInfo);
+
+    public static final class PaymentAttemptBinder implements Binder<Bind, PaymentAttempt> {
+
+        private Date getDate(DateTime dateTime) {
+            return dateTime == null ? null : dateTime.toDate();
+        }
+
+        @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("next_retry_dt", getDate(paymentAttempt.getNextRetryDate()));
+            stmt.bind("created_dt", getDate(paymentAttempt.getCreatedDate()));
+            stmt.bind("updated_dt", getDate(paymentAttempt.getUpdatedDate()));
+        }
+    }
+
+    public static class PaymentAttemptMapper implements ResultSetMapper<PaymentAttempt> {
+
+        private DateTime getDate(ResultSet rs, String fieldName) throws SQLException {
+            final Timestamp resultStamp = rs.getTimestamp(fieldName);
+            return rs.wasNull() ? null : new DateTime(resultStamp).toDateTime(DateTimeZone.UTC);
+        }
+
+        @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 nextRetryDate = getDate(rs, "next_retry_dt");
+            DateTime createdDate = getDate(rs, "created_dt");
+            DateTime updatedDate = getDate(rs, "updated_dt");
+
+            return new PaymentAttempt(paymentAttemptId,
+                                      invoiceId,
+                                      accountId,
+                                      amount,
+                                      currency,
+                                      invoiceDate,
+                                      paymentAttemptDate,
+                                      paymentId,
+                                      retryCount,
+                                      nextRetryDate,
+                                      createdDate,
+                                      updatedDate);
+        }
+    }
+
+    public static final class PaymentInfoBinder implements Binder<Bind, PaymentInfo> {
+
+        private Date getDate(DateTime dateTime) {
+            return dateTime == null ? null : dateTime.toDate();
+        }
+
+        @Override
+        public void bind(@SuppressWarnings("rawtypes") SQLStatement stmt, Bind bind, PaymentInfo paymentInfo) {
+            stmt.bind("payment_id", paymentInfo.getPaymentId().toString());
+            stmt.bind("amount", paymentInfo.getAmount());
+            stmt.bind("refund_amount", paymentInfo.getRefundAmount());
+            stmt.bind("payment_number", paymentInfo.getPaymentNumber());
+            stmt.bind("bank_identification_number", paymentInfo.getBankIdentificationNumber());
+            stmt.bind("status", paymentInfo.getStatus());
+            stmt.bind("payment_type", paymentInfo.getType());
+            stmt.bind("reference_id", paymentInfo.getReferenceId());
+            stmt.bind("payment_method_id", paymentInfo.getPaymentMethodId());
+            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()));
+        }
+    }
+
+    public static class PaymentInfoMapper implements ResultSetMapper<PaymentInfo> {
+
+        private DateTime getDate(ResultSet rs, String fieldName) throws SQLException {
+            final Timestamp resultStamp = rs.getTimestamp(fieldName);
+            return rs.wasNull() ? null : new DateTime(resultStamp).toDateTime(DateTimeZone.UTC);
+        }
+
+        @Override
+        public PaymentInfo map(int index, ResultSet rs, StatementContext ctx) throws SQLException {
+
+            String paymentId = rs.getString("payment_id");
+            BigDecimal amount = rs.getBigDecimal("amount");
+            BigDecimal refundAmount = rs.getBigDecimal("refund_amount");
+            String paymentNumber = rs.getString("payment_number");
+            String bankIdentificationNumber = rs.getString("bank_identification_number");
+            String status = rs.getString("status");
+            String type = rs.getString("payment_type");
+            String referenceId = rs.getString("reference_id");
+            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");
+
+            return new PaymentInfo(paymentId,
+                                   amount,
+                                   refundAmount,
+                                   bankIdentificationNumber,
+                                   paymentNumber,
+                                   status,
+                                   type,
+                                   referenceId,
+                                   paymentMethodId,
+                                   paymentMethod,
+                                   cardType,
+                                   cardCountry,
+                                   effectiveDate,
+                                   createdDate,
+                                   updatedDate);
+        }
+    }
+
+}
diff --git a/payment/src/main/java/com/ning/billing/payment/PaymentAttempt.java b/payment/src/main/java/com/ning/billing/payment/PaymentAttempt.java
new file mode 100644
index 0000000..1c8e9f3
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/PaymentAttempt.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * 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.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+
+import com.ning.billing.invoice.api.Invoice;
+
+public class PaymentAttempt {
+    private final UUID paymentAttemptId;
+    private final UUID accountId;
+    private final UUID invoiceId;
+    private final BigDecimal paymentAttemptAmount;
+    private final DateTime paymentAttemptDate;
+    private final Integer retryCount;
+    private final DateTime nextRetryDate;
+
+    public PaymentAttempt(UUID paymentAttemptId, Invoice invoice) {
+        this(paymentAttemptId, invoice, null, null);
+    }
+
+    public PaymentAttempt(UUID paymentAttemptId, Invoice invoice, Integer retryCount, DateTime nextRetryDate) {
+        this.paymentAttemptId = paymentAttemptId;
+        this.accountId = invoice.getAccountId();
+        this.invoiceId = invoice.getId();
+        this.paymentAttemptAmount = invoice.getBalance();
+        this.paymentAttemptDate = new DateTime(DateTimeZone.UTC);
+        this.retryCount = retryCount;
+        this.nextRetryDate = nextRetryDate;
+    }
+
+    public UUID getPaymentAttemptId() {
+        return paymentAttemptId;
+    }
+
+    public UUID getAccountId() {
+        return accountId;
+    }
+
+    public UUID getInvoiceId() {
+        return invoiceId;
+    }
+
+    public BigDecimal getPaymentAttemptAmount() {
+        return paymentAttemptAmount;
+    }
+
+    public DateTime getPaymentAttemptDate() {
+        return paymentAttemptDate;
+    }
+
+    public Integer getRetryCount() {
+        return retryCount;
+    }
+
+    public DateTime getNextRetryDate() {
+        return nextRetryDate;
+    }
+
+    @Override
+    public String toString() {
+        return "PaymentAttempt [paymentAttemptId=" + paymentAttemptId + ", accountId=" + accountId + ", invoiceId=" + invoiceId + ", paymentAttemptAmount=" + paymentAttemptAmount + ", paymentAttemptDate=" + paymentAttemptDate + ", retryCount=" + retryCount + ", nextRetryDate=" + nextRetryDate + "]";
+    }
+
+}
diff --git a/payment/src/main/java/com/ning/billing/payment/PaymentInfoRequest.java b/payment/src/main/java/com/ning/billing/payment/PaymentInfoRequest.java
new file mode 100644
index 0000000..5a999e2
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/PaymentInfoRequest.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.payment;
+
+import java.util.UUID;
+
+import com.ning.billing.util.bus.BusEvent;
+
+public class PaymentInfoRequest implements BusEvent {
+    private final UUID accountId;
+    private final String paymentId;
+
+    public PaymentInfoRequest(UUID accountId, String paymentId) {
+        this.accountId = accountId;
+        this.paymentId = paymentId;
+    }
+
+    public UUID getAccountId() {
+        return accountId;
+    }
+
+    public String getPaymentId() {
+        return paymentId;
+    }
+
+    @Override
+    public String toString() {
+        return "PaymentInfoRequest [accountId=" + accountId + ", paymentId=" + paymentId + "]";
+    }
+
+}
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
new file mode 100644
index 0000000..71c7e41
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/provider/NoOpPaymentProviderPlugin.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2010-2012 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * 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.provider;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+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.Either;
+import com.ning.billing.payment.api.PaymentError;
+import com.ning.billing.payment.api.PaymentInfo;
+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())
+                                             .setAmount(invoice.getBalance())
+                                             .setStatus("Processed")
+                                             .setCreatedDate(new DateTime(DateTimeZone.UTC))
+                                             .setEffectiveDate(new DateTime(DateTimeZone.UTC))
+                                             .setType("Electronic")
+                                             .build();
+        return Either.right(payment);
+    }
+
+    @Override
+    public Either<PaymentError, PaymentInfo> 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"));
+    }
+
+    @Override
+    public Either<PaymentError, PaymentProviderAccount> getPaymentProviderAccount(String accountKey) {
+        return Either.right(null);
+    }
+
+    @Override
+    public Either<PaymentError, String> addPaymentMethod(String accountKey, PaymentMethodInfo paymentMethod) {
+        return Either.right(null);
+    }
+
+    public void setDefaultPaymentMethodOnAccount(PaymentProviderAccount account, String paymentMethodId) {
+        // NO-OP
+    }
+
+    @Override
+    public Either<PaymentError, PaymentMethodInfo> updatePaymentMethod(String accountKey, PaymentMethodInfo paymentMethod) {
+        return Either.right(paymentMethod);
+    }
+
+    @Override
+    public Either<PaymentError, Void> deletePaymentMethod(String accountKey, String paymentMethodId) {
+        return Either.right(null);
+    }
+
+    @Override
+    public Either<PaymentError, PaymentMethodInfo> getPaymentMethodInfo(String paymentMethodId) {
+        return Either.right(null);
+    }
+
+    @Override
+    public Either<PaymentError, List<PaymentMethodInfo>> getPaymentMethods(final String accountKey) {
+        return Either.right(Arrays.<PaymentMethodInfo>asList());
+    }
+
+    @Override
+    public Either<PaymentError, Void> updatePaymentGateway(String accountKey) {
+        return Either.right(null);
+    }
+
+    @Override
+    public Either<PaymentError, Void> updatePaymentProviderAccountExistingContact(Account account) {
+        return Either.right(null);
+    }
+
+    @Override
+    public Either<PaymentError, Void> updatePaymentProviderAccountWithNewContact(Account account) {
+        return Either.right(null);
+    }
+
+}
diff --git a/payment/src/main/java/com/ning/billing/payment/provider/NoOpPaymentProviderPluginModule.java b/payment/src/main/java/com/ning/billing/payment/provider/NoOpPaymentProviderPluginModule.java
new file mode 100644
index 0000000..a403274
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/provider/NoOpPaymentProviderPluginModule.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2010-2012 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * 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.provider;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.name.Names;
+
+public class NoOpPaymentProviderPluginModule extends AbstractModule {
+    private final String instanceName;
+
+    public NoOpPaymentProviderPluginModule(String instanceName) {
+        this.instanceName = instanceName;
+    }
+
+    @Override
+    protected void configure() {
+        bind(NoOpPaymentProviderPlugin.class)
+            .annotatedWith(Names.named(instanceName))
+            .toProvider(new NoOpPaymentProviderPluginProvider(instanceName))
+            .asEagerSingleton();
+    }
+}
diff --git a/payment/src/main/java/com/ning/billing/payment/provider/NoOpPaymentProviderPluginProvider.java b/payment/src/main/java/com/ning/billing/payment/provider/NoOpPaymentProviderPluginProvider.java
new file mode 100644
index 0000000..5ba98b7
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/provider/NoOpPaymentProviderPluginProvider.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2010-2012 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * 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.provider;
+
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+public class NoOpPaymentProviderPluginProvider implements Provider<NoOpPaymentProviderPlugin> {
+    private PaymentProviderPluginRegistry registry;
+    private final String instanceName;
+
+    public NoOpPaymentProviderPluginProvider(String instanceName) {
+        this.instanceName = instanceName;
+    }
+
+    @Inject
+    public void setPaymentProviderPluginRegistry(PaymentProviderPluginRegistry registry) {
+        this.registry = registry;
+    }
+
+    @Override
+    public NoOpPaymentProviderPlugin get() {
+        NoOpPaymentProviderPlugin plugin = new NoOpPaymentProviderPlugin();
+
+        registry.register(plugin, instanceName);
+        return plugin;
+    }
+
+}
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
new file mode 100644
index 0000000..7a9ae71
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/provider/PaymentProviderPlugin.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * 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.provider;
+
+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.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<PaymentError, PaymentInfo> getPaymentInfo(String paymentId);
+    Either<PaymentError, PaymentProviderAccount> getPaymentProviderAccount(String accountKey);
+    Either<PaymentError, 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<PaymentError, Void> updatePaymentProviderAccountExistingContact(Account account);
+    Either<PaymentError, Void> updatePaymentProviderAccountWithNewContact(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
new file mode 100644
index 0000000..fc9c149
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/provider/PaymentProviderPluginRegistry.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.provider;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.commons.lang.StringUtils;
+
+import com.google.inject.Inject;
+import com.ning.billing.payment.setup.PaymentConfig;
+
+public class PaymentProviderPluginRegistry {
+    private final String defaultPlugin;
+    private final Map<String, PaymentProviderPlugin> pluginsByName = new ConcurrentHashMap<String, PaymentProviderPlugin>();
+
+    @Inject
+    public PaymentProviderPluginRegistry(PaymentConfig config) {
+        this.defaultPlugin = config.getDefaultPaymentProvider();
+    }
+
+    public void register(PaymentProviderPlugin plugin, String name) {
+        pluginsByName.put(name.toLowerCase(), plugin);
+    }
+
+    public PaymentProviderPlugin getPlugin(String name) {
+        PaymentProviderPlugin plugin = pluginsByName.get(StringUtils.defaultIfEmpty(name, defaultPlugin).toLowerCase());
+
+        if (plugin == null) {
+            throw new IllegalArgumentException("No payment provider plugin is configured for " + name);
+        }
+
+        return plugin;
+    }
+}
diff --git a/payment/src/main/java/com/ning/billing/payment/RequestProcessor.java b/payment/src/main/java/com/ning/billing/payment/RequestProcessor.java
new file mode 100644
index 0000000..892d424
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/RequestProcessor.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.payment;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.eventbus.Subscribe;
+import com.google.inject.Inject;
+import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.AccountUserApi;
+import com.ning.billing.invoice.api.InvoiceCreationNotification;
+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.provider.PaymentProviderPlugin;
+import com.ning.billing.payment.provider.PaymentProviderPluginRegistry;
+import com.ning.billing.util.bus.Bus;
+import com.ning.billing.util.bus.Bus.EventBusException;
+
+public class RequestProcessor {
+    public static final String PAYMENT_PROVIDER_KEY = "paymentProvider";
+    private final AccountUserApi accountUserApi;
+    private final PaymentApi paymentApi;
+    private final PaymentProviderPluginRegistry pluginRegistry;
+    private final Bus eventBus;
+
+    private static final Logger log = LoggerFactory.getLogger(RequestProcessor.class);
+
+    @Inject
+    public RequestProcessor(AccountUserApi accountUserApi,
+                            PaymentApi paymentApi,
+                            PaymentProviderPluginRegistry pluginRegistry,
+                            Bus eventBus) {
+        this.accountUserApi = accountUserApi;
+        this.paymentApi = paymentApi;
+        this.pluginRegistry = pluginRegistry;
+        this.eventBus = eventBus;
+    }
+
+    @Subscribe
+    public void receiveInvoice(InvoiceCreationNotification event) {
+        log.info("Received invoice creation notification for account {} and invoice {}", event.getAccountId(), event.getInvoiceId());
+        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 {
+                List<Either<PaymentError, PaymentInfo>> results = paymentApi.createPayment(account, Arrays.asList(event.getInvoiceId().toString()));
+                if (!results.isEmpty()) {
+                    Either<PaymentError, PaymentInfo> result = results.get(0);
+                    eventBus.post(result.isLeft() ? result.getLeft() : result.getRight());
+                }
+            }
+        }
+        catch (EventBusException ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+
+    @Subscribe
+    public void receivePaymentInfoRequest(PaymentInfoRequest paymentInfoRequest) throws EventBusException {
+        final Account account = accountUserApi.getAccountById(paymentInfoRequest.getAccountId());
+        if (account == null) {
+            log.info("could not process payment info request: could not find a valid account for event {}", paymentInfoRequest);
+        }
+        else {
+            final String paymentProviderName = account.getFieldValue(PAYMENT_PROVIDER_KEY);
+            final PaymentProviderPlugin plugin = pluginRegistry.getPlugin(paymentProviderName);
+
+            Either<PaymentError, PaymentInfo> result = plugin.getPaymentInfo(paymentInfoRequest.getPaymentId());
+
+            eventBus.post(result.isLeft() ? result.getLeft() : result.getRight());
+        }
+    }
+}
diff --git a/payment/src/main/java/com/ning/billing/payment/setup/DefaultPaymentService.java b/payment/src/main/java/com/ning/billing/payment/setup/DefaultPaymentService.java
new file mode 100644
index 0000000..eae1baa
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/setup/DefaultPaymentService.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.payment.setup;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.inject.Inject;
+import com.ning.billing.lifecycle.KillbillService;
+import com.ning.billing.lifecycle.LifecycleHandlerType;
+import com.ning.billing.payment.RequestProcessor;
+import com.ning.billing.payment.api.PaymentApi;
+import com.ning.billing.payment.api.PaymentService;
+import com.ning.billing.util.bus.Bus;
+
+public class DefaultPaymentService implements PaymentService {
+    private static final Logger log = LoggerFactory.getLogger(DefaultPaymentService.class);
+
+    private static final String SERVICE_NAME = "payment-service";
+
+    private final RequestProcessor requestProcessor;
+    private final Bus eventBus;
+    private final PaymentApi api;
+
+    @Inject
+    public DefaultPaymentService(final RequestProcessor requestProcessor, final PaymentApi api, final Bus eventBus) {
+        this.requestProcessor = requestProcessor;
+        this.eventBus = eventBus;
+        this.api = api;
+    }
+
+    @Override
+    public String getName() {
+        return SERVICE_NAME;
+    }
+
+    @LifecycleHandlerType(LifecycleHandlerType.LifecycleLevel.REGISTER_EVENTS)
+    public void registerForNotifications() {
+        try {
+            eventBus.register(requestProcessor);
+        }
+        catch (Bus.EventBusException e) {
+            log.error("Unable to register with the EventBus!", e);
+        }
+    }
+
+    @Override
+    public PaymentApi getPaymentApi() {
+        return api;
+    }
+
+}
diff --git a/payment/src/main/java/com/ning/billing/payment/setup/PaymentConfig.java b/payment/src/main/java/com/ning/billing/payment/setup/PaymentConfig.java
new file mode 100644
index 0000000..cdc5384
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/setup/PaymentConfig.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.payment.setup;
+
+import org.skife.config.Config;
+import org.skife.config.DefaultNull;
+
+public interface PaymentConfig {
+    @Config("killbill.payment.provider.default")
+    @DefaultNull
+    public String getDefaultPaymentProvider();
+}
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
new file mode 100644
index 0000000..935b968
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/setup/PaymentModule.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * 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.setup;
+
+import java.util.Properties;
+
+import org.skife.config.ConfigurationObjectFactory;
+
+import com.google.inject.AbstractModule;
+import com.ning.billing.payment.RequestProcessor;
+import com.ning.billing.payment.api.DefaultPaymentApi;
+import com.ning.billing.payment.api.PaymentApi;
+import com.ning.billing.payment.api.PaymentService;
+import com.ning.billing.payment.dao.DefaultPaymentDao;
+import com.ning.billing.payment.dao.PaymentDao;
+import com.ning.billing.payment.provider.PaymentProviderPluginRegistry;
+
+public class PaymentModule extends AbstractModule {
+    private final Properties props;
+
+    public PaymentModule() {
+        this.props = System.getProperties();
+    }
+
+    public PaymentModule(Properties props) {
+        this.props = props;
+    }
+
+    protected void installPaymentDao() {
+        bind(PaymentDao.class).to(DefaultPaymentDao.class).asEagerSingleton();
+    }
+
+    protected void installPaymentProviderPlugins(PaymentConfig config) {
+    }
+
+    @Override
+    protected void configure() {
+        final ConfigurationObjectFactory factory = new ConfigurationObjectFactory(props);
+        final PaymentConfig paymentConfig = factory.build(PaymentConfig.class);
+
+        bind(PaymentConfig.class).toInstance(paymentConfig);
+        bind(PaymentProviderPluginRegistry.class).asEagerSingleton();
+        bind(PaymentApi.class).to(DefaultPaymentApi.class).asEagerSingleton();
+        bind(RequestProcessor.class).asEagerSingleton();
+        bind(PaymentService.class).to(DefaultPaymentService.class).asEagerSingleton();
+        installPaymentProviderPlugins(paymentConfig);
+        installPaymentDao();
+    }
+}
diff --git a/payment/src/main/java/com/ning/billing/payment/util/EventBusFuture.java b/payment/src/main/java/com/ning/billing/payment/util/EventBusFuture.java
new file mode 100644
index 0000000..3cbe802
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/util/EventBusFuture.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.payment.util;
+
+import javax.annotation.Nullable;
+
+import com.google.common.eventbus.Subscribe;
+import com.google.common.util.concurrent.AbstractFuture;
+import com.ning.billing.util.bus.Bus;
+import com.ning.billing.util.bus.Bus.EventBusException;
+
+public class EventBusFuture<T, V extends EventBusResponse<T>> extends AbstractFuture<V> {
+    public static <V, W extends EventBusRequest<V>, X extends EventBusResponse<V>> EventBusFuture<V, X> post(final Bus eventBus, final W event) throws EventBusException {
+        final EventBusFuture<V, X> responseFuture = new EventBusFuture<V, X>(eventBus, event.getId());
+
+        eventBus.register(responseFuture);
+        eventBus.post(event);
+        return responseFuture;
+    }
+
+    private final Bus eventBus;
+    private final T requestId;
+
+    private EventBusFuture(Bus eventBus, T requestId) {
+        this.eventBus = eventBus;
+        this.requestId = requestId;
+    }
+
+    @Subscribe
+    public void handleResponse(V response) {
+        if (requestId.equals(response.getRequestId())) {
+            set(response);
+        }
+    }
+
+    @Override
+    public boolean set(@Nullable V value) {
+        boolean result = super.set(value);
+
+        try {
+            eventBus.unregister(this);
+        }
+        catch (EventBusException ex) {
+            throw new RuntimeException(ex);
+        }
+        return result;
+    }
+
+    @Override
+    public boolean setException(Throwable throwable) {
+        boolean result = super.setException(throwable);
+
+        try {
+            eventBus.unregister(this);
+        }
+        catch (EventBusException ex) {
+            throw new RuntimeException(ex);
+        }
+        return result;
+    }
+}
diff --git a/payment/src/main/java/com/ning/billing/payment/util/EventBusRequest.java b/payment/src/main/java/com/ning/billing/payment/util/EventBusRequest.java
new file mode 100644
index 0000000..a895afc
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/util/EventBusRequest.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.payment.util;
+
+import com.ning.billing.util.bus.BusEvent;
+
+public interface EventBusRequest<T> extends BusEvent {
+    T getId();
+}
diff --git a/payment/src/main/java/com/ning/billing/payment/util/EventBusResponse.java b/payment/src/main/java/com/ning/billing/payment/util/EventBusResponse.java
new file mode 100644
index 0000000..ff5b62c
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/util/EventBusResponse.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.payment.util;
+
+import com.ning.billing.util.bus.BusEvent;
+
+public interface EventBusResponse<T> extends BusEvent {
+    T getRequestId();
+}
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
new file mode 100644
index 0000000..a62c366
--- /dev/null
+++ b/payment/src/main/resources/com/ning/billing/payment/dao/PaymentSqlDao.sql.stg
@@ -0,0 +1,71 @@
+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>next_retry_dt,
+    <prefix>created_dt,
+    <prefix>updated_dt
+>>
+
+paymentInfoFields(prefix) ::= <<
+    <prefix>payment_id,
+    <prefix>amount,
+    <prefix>refund_amount,
+    <prefix>bank_identification_number,
+    <prefix>payment_number,
+    <prefix>payment_type,
+    <prefix>status,
+    <prefix>reference_id,
+    <prefix>payment_method,
+    <prefix>card_type,
+    <prefix>card_country,
+    <prefix>effective_dt,
+    <prefix>created_dt,
+    <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, :next_retry_dt, :created_dt, :updated_dt);
+>>
+
+getPaymentAttemptForPaymentId() ::= <<
+    SELECT <paymentAttemptFields()>
+      FROM payment_attempts
+     WHERE payment_id = :payment_id
+>>
+
+getPaymentAttemptForInvoiceId() ::= <<
+    SELECT <paymentAttemptFields()>
+      FROM payment_attempts
+     WHERE invoice_id = :invoice_id
+>>
+
+updatePaymentAttemptWithPaymentId() ::= <<
+    UPDATE payment_attempts
+       SET payment_id = :payment_id,
+           updated_dt = NOW()
+     WHERE payment_attempt_id = :payment_attempt_id
+>>
+
+insertPaymentInfo() ::= <<
+    INSERT INTO payments (<paymentInfoFields()>)
+    VALUES (:payment_id, :amount, :refund_amount, :bank_identification_number, :payment_number, :payment_type, :status, :reference_id, :payment_method, :card_type, :card_country, :effective_dt, :created_dt, :updated_dt);
+>>
+
+updatePaymentInfo() ::= <<
+    UPDATE payments
+       SET payment_method = :payment_method,
+           card_type = :card_type,
+           card_country = :card_country,
+           updated_dt = NOW()
+     WHERE payment_id = :payment_id
+>>
\ No newline at end of file
diff --git a/payment/src/main/resources/com/ning/billing/payment/ddl.sql b/payment/src/main/resources/com/ning/billing/payment/ddl.sql
new file mode 100644
index 0000000..abbd8a6
--- /dev/null
+++ b/payment/src/main/resources/com/ning/billing/payment/ddl.sql
@@ -0,0 +1,36 @@
+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,
+      next_retry_dt datetime,
+      invoice_dt datetime NOT NULL,
+      created_dt datetime NOT NULL,
+      updated_dt datetime NOT NULL,
+      PRIMARY KEY (payment_attempt_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
+
+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_dt datetime NOT NULL,
+      updated_dt datetime NOT NULL,
+      PRIMARY KEY (payment_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
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
new file mode 100644
index 0000000..b9f761a
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/api/TestMockPaymentApi.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.payment.api;
+
+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.ning.billing.payment.setup.PaymentTestModuleWithMocks;
+
+@Guice(modules = { PaymentTestModuleWithMocks.class, AccountModuleWithMocks.class, InvoiceModuleWithMocks.class })
+@Test(groups = "fast")
+public class TestMockPaymentApi extends TestPaymentApi {
+
+}
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
new file mode 100644
index 0000000..8c9d623
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/api/TestPaymentApi.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * 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 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.math.BigDecimal;
+import java.util.Arrays;
+import java.util.List;
+import java.util.UUID;
+
+import org.apache.commons.lang.RandomStringUtils;
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+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.payment.TestHelper;
+import com.ning.billing.util.bus.Bus;
+import com.ning.billing.util.bus.Bus.EventBusException;
+
+public abstract class TestPaymentApi {
+    @Inject
+    private Bus eventBus;
+    @Inject
+    protected PaymentApi paymentApi;
+    @Inject
+    protected TestHelper testHelper;
+
+    @BeforeMethod(alwaysRun = true)
+    public void setUp() throws EventBusException {
+        eventBus.start();
+    }
+
+    @AfterMethod(alwaysRun = true)
+    public void tearDown() throws EventBusException {
+        eventBus.stop();
+    }
+
+    @Test(enabled=true)
+    public void testCreateCreditCardPayment() throws AccountApiException {
+        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 UUID subscriptionId = UUID.randomUUID();
+
+        invoice.addInvoiceItem(new RecurringInvoiceItem(invoice.getId(),
+                                                       subscriptionId,
+                                                       "test plan", "test phase",
+                                                       now,
+                                                       now.plusMonths(1),
+                                                       amount,
+                                                       new BigDecimal("1.0"),
+                                                       Currency.USD));
+
+        List<Either<PaymentError, PaymentInfo>> results = paymentApi.createPayment(account.getExternalKey(), Arrays.asList(invoice.getId().toString()));
+
+        assertEquals(results.size(), 1);
+        assertTrue(results.get(0).isRight());
+
+        PaymentInfo paymentInfo = results.get(0).getRight();
+
+        assertNotNull(paymentInfo.getPaymentId());
+        assertTrue(paymentInfo.getAmount().compareTo(amount) == 0);
+        assertNotNull(paymentInfo.getPaymentNumber());
+        assertFalse(paymentInfo.getStatus().equals("Error"));
+
+        PaymentAttempt paymentAttempt = paymentApi.getPaymentAttemptForPaymentId(paymentInfo.getPaymentId());
+        assertNotNull(paymentAttempt);
+        assertNotNull(paymentAttempt.getPaymentAttemptId());
+        assertEquals(paymentAttempt.getInvoiceId(), invoice.getId());
+        assertTrue(paymentAttempt.getAmount().compareTo(amount) == 0);
+        assertEquals(paymentAttempt.getCurrency(), Currency.USD);
+        assertEquals(paymentAttempt.getPaymentId(), paymentInfo.getPaymentId());
+        assertEquals(paymentAttempt.getPaymentAttemptDate().withMillisOfSecond(0).withSecondOfMinute(0), now.withMillisOfSecond(0).withSecondOfMinute(0));
+
+    }
+
+    private PaymentProviderAccount setupAccountWithPaymentMethod() throws AccountApiException {
+        final Account account = testHelper.createTestPayPalAccount();
+        paymentApi.createPaymentProviderAccount(account);
+
+        String accountKey = account.getExternalKey();
+
+        PaypalPaymentMethodInfo paymentMethod = new PaypalPaymentMethodInfo.Builder()
+                                                                           .setBaid("12345")
+                                                                           .setEmail(account.getEmail())
+                                                                           .setDefaultMethod(true)
+                                                                           .build();
+        Either<PaymentError, String> paymentMethodIdOrError = paymentApi.addPaymentMethod(accountKey, paymentMethod);
+
+        assertTrue(paymentMethodIdOrError.isRight());
+        assertNotNull(paymentMethodIdOrError.getRight());
+
+        Either<PaymentError, PaymentMethodInfo> paymentMethodInfoOrError = paymentApi.getPaymentMethod(accountKey, paymentMethodIdOrError.getRight());
+
+        assertTrue(paymentMethodInfoOrError.isRight());
+        assertNotNull(paymentMethodInfoOrError.getRight());
+
+        Either<PaymentError, PaymentProviderAccount> accountOrError = paymentApi.getPaymentProviderAccount(accountKey);
+
+        assertTrue(accountOrError.isRight());
+
+        return accountOrError.getRight();
+    }
+
+    @Test(enabled=true)
+    public void testCreatePaymentMethod() throws AccountApiException {
+        PaymentProviderAccount account = setupAccountWithPaymentMethod();
+        assertNotNull(account);
+    }
+
+    @Test(enabled=true)
+    public void testUpdatePaymentProviderAccountContact() throws AccountApiException {
+        final Account account = testHelper.createTestPayPalAccount();
+        paymentApi.createPaymentProviderAccount(account);
+
+        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());
+        assertTrue(voidOrError.isRight());
+    }
+
+    @Test(enabled=true)
+    public void testCannotDeleteDefaultPaymentMethod() throws AccountApiException {
+        PaymentProviderAccount account = setupAccountWithPaymentMethod();
+
+        Either<PaymentError, Void> errorOrVoid = paymentApi.deletePaymentMethod(account.getAccountKey(), account.getDefaultPaymentMethodId());
+
+        assertTrue(errorOrVoid.isLeft());
+    }
+
+    @Test(enabled=true)
+    public void testDeleteNonDefaultPaymentMethod() throws AccountApiException {
+        final Account account = testHelper.createTestPayPalAccount();
+        paymentApi.createPaymentProviderAccount(account);
+
+        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);
+
+        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);
+
+        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());
+        Either<PaymentError, Void> errorOrVoid2 = paymentApi.deletePaymentMethod(accountKey, paymentMethodIdOrError2.getRight());
+
+        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
new file mode 100644
index 0000000..bb83927
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/dao/MockPaymentDao.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.payment.dao;
+
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.payment.api.PaymentAttempt;
+import com.ning.billing.payment.api.PaymentInfo;
+
+public class MockPaymentDao implements PaymentDao {
+    private final Map<String, PaymentInfo> payments = new ConcurrentHashMap<String, PaymentInfo>();
+    private final Map<UUID, PaymentAttempt> paymentAttempts = new ConcurrentHashMap<UUID, PaymentAttempt>();
+
+    @Override
+    public PaymentAttempt getPaymentAttemptForPaymentId(String paymentId) {
+        for (PaymentAttempt paymentAttempt : paymentAttempts.values()) {
+            if (paymentId.equals(paymentAttempt.getPaymentId())) {
+                return paymentAttempt;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public PaymentAttempt createPaymentAttempt(Invoice invoice) {
+        PaymentAttempt paymentAttempt = new PaymentAttempt(UUID.randomUUID(), invoice);
+        paymentAttempts.put(paymentAttempt.getPaymentAttemptId(), paymentAttempt);
+        return paymentAttempt;
+    }
+
+    @Override
+    public void savePaymentInfo(PaymentInfo paymentInfo) {
+        payments.put(paymentInfo.getPaymentId(), paymentInfo);
+    }
+
+    @Override
+    public void updatePaymentAttemptWithPaymentId(UUID paymentAttemptId, String paymentId) {
+        PaymentAttempt existingPaymentAttempt = paymentAttempts.get(paymentAttemptId);
+
+        if (existingPaymentAttempt != null) {
+            paymentAttempts.put(existingPaymentAttempt.getPaymentAttemptId(),
+                                existingPaymentAttempt.cloner().setPaymentId(paymentId).build());
+        }
+    }
+
+    @Override
+    public PaymentAttempt getPaymentAttemptForInvoiceId(String invoiceId) {
+        for (PaymentAttempt paymentAttempt : paymentAttempts.values()) {
+            if (invoiceId.equals(paymentAttempt.getInvoiceId())) {
+                return paymentAttempt;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public void updatePaymentInfo(String paymentMethodType, String paymentId, String cardType, String cardCountry) {
+        // TODO Auto-generated method stub
+
+    }
+
+}
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
new file mode 100644
index 0000000..6c57c77
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/dao/TestPaymentDao.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.payment.dao;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.testng.annotations.Test;
+
+import com.ning.billing.payment.api.PaymentInfo;
+
+public abstract class TestPaymentDao {
+
+    protected PaymentDao dao;
+
+    @Test
+    public void testCreatePayment() {
+        PaymentInfo paymentInfo = new PaymentInfo.Builder().setPaymentId(UUID.randomUUID().toString())
+                                                           .setAmount(BigDecimal.TEN)
+                                                           .setStatus("Processed")
+                                                           .setBankIdentificationNumber("1234")
+                                                           .setPaymentNumber("12345")
+                                                           .setPaymentMethodId("12345")
+                                                           .setReferenceId("12345")
+                                                           .setType("Electronic")
+                                                           .setCreatedDate(new DateTime(DateTimeZone.UTC))
+                                                           .setUpdatedDate(new DateTime(DateTimeZone.UTC))
+                                                           .setEffectiveDate(new DateTime(DateTimeZone.UTC))
+                                                           .build();
+
+        dao.savePaymentInfo(paymentInfo);
+    }
+
+    @Test
+    public void testUpdatePayment() {
+        PaymentInfo paymentInfo = new PaymentInfo.Builder().setPaymentId(UUID.randomUUID().toString())
+                                                           .setAmount(BigDecimal.TEN)
+                                                           .setStatus("Processed")
+                                                           .setBankIdentificationNumber("1234")
+                                                           .setPaymentNumber("12345")
+                                                           .setPaymentMethodId("12345")
+                                                           .setReferenceId("12345")
+                                                           .setType("Electronic")
+                                                           .setCreatedDate(new DateTime(DateTimeZone.UTC))
+                                                           .setUpdatedDate(new DateTime(DateTimeZone.UTC))
+                                                           .setEffectiveDate(new DateTime(DateTimeZone.UTC))
+                                                           .build();
+
+        dao.savePaymentInfo(paymentInfo);
+
+        dao.updatePaymentInfo("CreditCard", paymentInfo.getPaymentId(), "Visa", "US");
+
+    }
+
+}
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
new file mode 100644
index 0000000..da48c03
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/dao/TestPaymentDaoWithEmbeddedDb.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.payment.dao;
+
+import java.io.IOException;
+
+import org.apache.commons.io.IOUtils;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.ning.billing.dbi.MysqlTestingHelper;
+
+public class TestPaymentDaoWithEmbeddedDb
+{
+    @Test(enabled = true, groups = { "slow", "database" })
+    public class TestPaymentDaoWithEmbeddedDB extends TestPaymentDao {
+        private final MysqlTestingHelper helper = new MysqlTestingHelper();
+
+        @BeforeClass(alwaysRun = true)
+        public void startMysql() throws IOException {
+            final String paymentddl = IOUtils.toString(MysqlTestingHelper.class.getResourceAsStream("/com/ning/billing/payment/ddl.sql"));
+
+            helper.startMysql();
+            helper.initDb(paymentddl);
+        }
+
+        @AfterClass(alwaysRun = true)
+        public void stopMysql() {
+            helper.stopMysql();
+        }
+
+        @BeforeMethod(alwaysRun = true)
+        public void setUp() throws IOException {
+            dao = new DefaultPaymentDao(helper.getDBI());
+        }
+    }
+}
diff --git a/payment/src/test/java/com/ning/billing/payment/dao/TestPaymentDaoWithMock.java b/payment/src/test/java/com/ning/billing/payment/dao/TestPaymentDaoWithMock.java
new file mode 100644
index 0000000..f5af240
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/dao/TestPaymentDaoWithMock.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.payment.dao;
+
+import java.io.IOException;
+
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+@Test(groups = { "fast" })
+public class TestPaymentDaoWithMock extends TestPaymentDao {
+    @BeforeMethod(alwaysRun = true)
+    public void setUp() throws IOException {
+        dao = new MockPaymentDao();
+    }
+}
\ No newline at end of file
diff --git a/payment/src/test/java/com/ning/billing/payment/MockPaymentInfoReceiver.java b/payment/src/test/java/com/ning/billing/payment/MockPaymentInfoReceiver.java
new file mode 100644
index 0000000..cf2494d
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/MockPaymentInfoReceiver.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.payment;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import com.google.common.eventbus.Subscribe;
+import com.ning.billing.payment.api.PaymentError;
+import com.ning.billing.payment.api.PaymentInfo;
+
+public class MockPaymentInfoReceiver {
+    private final List<PaymentInfo> processedPayments = Collections.synchronizedList(new ArrayList<PaymentInfo>());
+    private final List<PaymentError> errors = Collections.synchronizedList(new ArrayList<PaymentError>());
+
+    @Subscribe
+    public void processedPayment(PaymentInfo paymentInfo) {
+        processedPayments.add(paymentInfo);
+    }
+
+    @Subscribe
+    public void processedPaymentError(PaymentError paymentError) {
+        errors.add(paymentError);
+    }
+
+    public List<PaymentInfo> getProcessedPayments() {
+        return new ArrayList<PaymentInfo>(processedPayments);
+    }
+
+    public List<PaymentError> getErrors() {
+        return new ArrayList<PaymentError>(errors);
+    }
+
+    public void clear() {
+        processedPayments.clear();
+        errors.clear();
+    }
+}
\ No newline at end of file
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
new file mode 100644
index 0000000..375bcfd
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/provider/MockPaymentProviderPlugin.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * 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.provider;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+
+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.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.Either;
+import com.ning.billing.payment.api.PaymentError;
+import com.ning.billing.payment.api.PaymentInfo;
+import com.ning.billing.payment.api.PaymentMethodInfo;
+import com.ning.billing.payment.api.PaymentProviderAccount;
+import com.ning.billing.payment.api.PaypalPaymentMethodInfo;
+
+public class MockPaymentProviderPlugin implements PaymentProviderPlugin {
+    private final Map<String, PaymentInfo> payments = new ConcurrentHashMap<String, PaymentInfo>();
+    private final Map<String, PaymentProviderAccount> accounts = new ConcurrentHashMap<String, PaymentProviderAccount>();
+    private final Map<String, PaymentMethodInfo> paymentMethods = new ConcurrentHashMap<String, PaymentMethodInfo>();
+
+    @Override
+    public Either<PaymentError, PaymentInfo> processInvoice(Account account, Invoice invoice) {
+        PaymentInfo payment = new PaymentInfo.Builder().setPaymentId(UUID.randomUUID().toString())
+                                             .setAmount(invoice.getBalance())
+                                             .setStatus("Processed")
+                                             .setBankIdentificationNumber("1234")
+                                             .setCreatedDate(new DateTime())
+                                             .setEffectiveDate(new DateTime())
+                                             .setPaymentNumber("12345")
+                                             .setReferenceId("12345")
+                                             .setType("Electronic")
+                                             .build();
+        payments.put(payment.getPaymentId(), payment);
+        return Either.right(payment);
+    }
+
+    @Override
+    public Either<PaymentError, PaymentInfo> getPaymentInfo(String paymentId) {
+        PaymentInfo payment = payments.get(paymentId);
+
+        if (payment == null) {
+            return Either.left(new PaymentError("notfound", "No payment found for id " + paymentId));
+        }
+        else {
+            return Either.right(payment);
+        }
+    }
+
+    @Override
+    public Either<PaymentError, String> createPaymentProviderAccount(Account account) {
+        if (account != null) {
+            String id = String.valueOf(RandomStringUtils.randomAlphanumeric(10));
+            accounts.put(account.getExternalKey(),
+                         new PaymentProviderAccount.Builder().setAccountKey(account.getExternalKey())
+                                                             .setId(id)
+                                                             .build());
+
+            return Either.right(id);
+        }
+        else {
+            return Either.left(new PaymentError("unknown", "Did not get account to create payment provider account"));
+        }
+    }
+
+    @Override
+    public Either<PaymentError, 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));
+        }
+    }
+
+    @Override
+    public Either<PaymentError, String> addPaymentMethod(String accountKey, PaymentMethodInfo paymentMethod) {
+        if (paymentMethod != null) {
+            PaymentProviderAccount account = accounts.get(accountKey);
+
+            if (account != null && account.getId() != null) {
+                String existingDefaultMethod = account.getDefaultPaymentMethodId();
+
+                String paymentMethodId = RandomStringUtils.randomAlphanumeric(10);
+                boolean shouldBeDefault = Boolean.TRUE.equals(paymentMethod.getDefaultMethod()) || existingDefaultMethod == null;
+                PaymentMethodInfo realPaymentMethod = null;
+
+                if (paymentMethod instanceof PaypalPaymentMethodInfo) {
+                    PaypalPaymentMethodInfo paypalPaymentMethod = (PaypalPaymentMethodInfo)paymentMethod;
+
+                    realPaymentMethod = new PaypalPaymentMethodInfo.Builder(paypalPaymentMethod)
+                                                                   .setId(paymentMethodId)
+                                                                   .setAccountId(accountKey)
+                                                                   .setDefaultMethod(shouldBeDefault)
+                                                                   .setBaid(paypalPaymentMethod.getBaid())
+                                                                   .setEmail(paypalPaymentMethod.getEmail())
+                                                                   .build();
+                }
+                else if (paymentMethod instanceof CreditCardPaymentMethodInfo) {
+                    CreditCardPaymentMethodInfo ccPaymentMethod = (CreditCardPaymentMethodInfo)paymentMethod;
+                    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"));
+                }
+                else {
+                    if (shouldBeDefault) {
+                        setDefaultPaymentMethodOnAccount(account, paymentMethodId);
+                    }
+                    paymentMethods.put(paymentMethodId, realPaymentMethod);
+                    return Either.right(paymentMethodId);
+                }
+            }
+                else {
+                    return Either.left(new PaymentError("noaccount", "Could not retrieve account for accountKey " + accountKey));
+                }
+        }
+        else {
+            return Either.left(new PaymentError("unknown", "Could not create add payment method " + paymentMethod + " for " + accountKey));
+        }
+    }
+
+    public void setDefaultPaymentMethodOnAccount(PaymentProviderAccount account, String paymentMethodId) {
+        if (paymentMethodId != null && account != null) {
+            accounts.put(account.getAccountKey(),
+                new PaymentProviderAccount.Builder()
+                                          .copyFrom(account)
+                                          .setDefaultPaymentMethod(paymentMethodId)
+                                          .build());
+            List<PaymentMethodInfo> paymentMethodsToUpdate = new ArrayList<PaymentMethodInfo>();
+            for (PaymentMethodInfo paymentMethod : paymentMethods.values()) {
+                if (account.getAccountKey().equals(paymentMethod.getAccountId()) && !paymentMethodId.equals(paymentMethod.getId())) {
+                    if (paymentMethod instanceof PaypalPaymentMethodInfo) {
+                        PaypalPaymentMethodInfo paypalPaymentMethod = (PaypalPaymentMethodInfo)paymentMethod;
+                        paymentMethodsToUpdate.add(new PaypalPaymentMethodInfo.Builder(paypalPaymentMethod).setDefaultMethod(false).build());
+                    }
+                    else if (paymentMethod instanceof CreditCardPaymentMethodInfo) {
+                        CreditCardPaymentMethodInfo ccPaymentMethod = (CreditCardPaymentMethodInfo)paymentMethod;
+                        paymentMethodsToUpdate.add(new CreditCardPaymentMethodInfo.Builder(ccPaymentMethod).setDefaultMethod(false).build());
+                    }
+                }
+            }
+            for (PaymentMethodInfo paymentMethod : paymentMethodsToUpdate) {
+                paymentMethods.put(paymentMethod.getId(), paymentMethod);
+            }
+        }
+    }
+
+    @Override
+    public Either<PaymentError, PaymentMethodInfo> updatePaymentMethod(String accountKey, PaymentMethodInfo paymentMethod) {
+        if (paymentMethod != null) {
+            PaymentMethodInfo realPaymentMethod = null;
+
+            if (paymentMethod instanceof PaypalPaymentMethodInfo) {
+                PaypalPaymentMethodInfo paypalPaymentMethod = (PaypalPaymentMethodInfo)paymentMethod;
+                realPaymentMethod = new PaypalPaymentMethodInfo.Builder(paypalPaymentMethod).build();
+            }
+            else if (paymentMethod instanceof CreditCardPaymentMethodInfo) {
+                CreditCardPaymentMethodInfo ccPaymentMethod = (CreditCardPaymentMethodInfo)paymentMethod;
+                realPaymentMethod = new CreditCardPaymentMethodInfo.Builder(ccPaymentMethod).build();
+            }
+            if (realPaymentMethod == null) {
+                return Either.left(new PaymentError("unsupported", "Payment method " + paymentMethod.getType() + " not supported by the plugin"));
+            }
+            else {
+                paymentMethods.put(paymentMethod.getId(), paymentMethod);
+                return Either.right(realPaymentMethod);
+            }
+        }
+        else {
+            return Either.left(new PaymentError("unknown", "Could not create add payment method " + paymentMethod + " for " + accountKey));
+        }
+    }
+
+    @Override
+    public Either<PaymentError, 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"));
+                }
+            }
+            else {
+                return Either.left(new PaymentError("error", "Cannot delete default payment method"));
+            }
+        }
+        return Either.right(null);
+    }
+
+    @Override
+    public Either<PaymentError, PaymentMethodInfo> getPaymentMethodInfo(String paymentMethodId) {
+        if (paymentMethodId == null) {
+            return Either.left(new PaymentError("unknown", "Could not retrieve payment method for paymentMethodId " + paymentMethodId));
+        }
+
+        return Either.right(paymentMethods.get(paymentMethodId));
+    }
+
+    @Override
+    public Either<PaymentError, List<PaymentMethodInfo>> getPaymentMethods(final String accountKey) {
+
+        Collection<PaymentMethodInfo> filteredPaymentMethods = Collections2.filter(paymentMethods.values(), new Predicate<PaymentMethodInfo>() {
+            @Override
+            public boolean apply(PaymentMethodInfo input) {
+                return accountKey.equals(input.getAccountId());
+            }
+        });
+        List<PaymentMethodInfo> result = new ArrayList<PaymentMethodInfo>(filteredPaymentMethods);
+        return Either.right(result);
+    }
+
+    @Override
+    public Either<PaymentError, Void> updatePaymentGateway(String accountKey) {
+        return Either.right(null);
+    }
+
+    @Override
+    public Either<PaymentError, Void> updatePaymentProviderAccountExistingContact(Account account) {
+        // nothing to do here
+        return Either.right(null);
+    }
+
+    @Override
+    public Either<PaymentError, Void> updatePaymentProviderAccountWithNewContact(Account account) {
+        // nothing to do here
+        return Either.right(null);
+    }
+
+}
diff --git a/payment/src/test/java/com/ning/billing/payment/provider/MockPaymentProviderPluginModule.java b/payment/src/test/java/com/ning/billing/payment/provider/MockPaymentProviderPluginModule.java
new file mode 100644
index 0000000..441fcf8
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/provider/MockPaymentProviderPluginModule.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.payment.provider;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.name.Names;
+
+public class MockPaymentProviderPluginModule extends AbstractModule {
+    private final String instanceName;
+
+    public MockPaymentProviderPluginModule(String instanceName) {
+        this.instanceName = instanceName;
+    }
+
+    @Override
+    protected void configure() {
+        bind(MockPaymentProviderPlugin.class)
+            .annotatedWith(Names.named(instanceName))
+            .toProvider(new MockPaymentProviderPluginProvider(instanceName))
+            .asEagerSingleton();
+    }
+}
diff --git a/payment/src/test/java/com/ning/billing/payment/provider/MockPaymentProviderPluginProvider.java b/payment/src/test/java/com/ning/billing/payment/provider/MockPaymentProviderPluginProvider.java
new file mode 100644
index 0000000..1170007
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/provider/MockPaymentProviderPluginProvider.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.payment.provider;
+
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+public class MockPaymentProviderPluginProvider implements Provider<MockPaymentProviderPlugin> {
+    private PaymentProviderPluginRegistry registry;
+    private final String instanceName;
+
+    public MockPaymentProviderPluginProvider(String instanceName) {
+        this.instanceName = instanceName;
+    }
+
+    @Inject
+    public void setPaymentProviderPluginRegistry(PaymentProviderPluginRegistry registry) {
+        this.registry = registry;
+    }
+
+    @Override
+    public MockPaymentProviderPlugin get() {
+        MockPaymentProviderPlugin plugin = new MockPaymentProviderPlugin();
+
+        registry.register(plugin, instanceName);
+        return plugin;
+    }
+}
diff --git a/payment/src/test/java/com/ning/billing/payment/setup/PaymentModuleWithMocks.java b/payment/src/test/java/com/ning/billing/payment/setup/PaymentModuleWithMocks.java
new file mode 100644
index 0000000..e051440
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/setup/PaymentModuleWithMocks.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.payment.setup;
+
+import com.ning.billing.payment.dao.MockPaymentDao;
+import com.ning.billing.payment.dao.PaymentDao;
+
+public class PaymentModuleWithMocks extends PaymentModule {
+    @Override
+    protected void installPaymentDao() {
+        bind(PaymentDao.class).to(MockPaymentDao.class).asEagerSingleton();
+    }
+}
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
new file mode 100644
index 0000000..9d9372c
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/setup/PaymentTestModuleWithEmbeddedDb.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.payment.setup;
+
+import com.ning.billing.util.bus.Bus;
+import org.apache.commons.collections.MapUtils;
+
+import com.google.common.collect.ImmutableMap;
+import com.ning.billing.payment.provider.MockPaymentProviderPluginModule;
+import com.ning.billing.util.bus.InMemoryBus;
+
+public class PaymentTestModuleWithEmbeddedDb extends PaymentModule {
+    public PaymentTestModuleWithEmbeddedDb() {
+        super(MapUtils.toProperties(ImmutableMap.of("killbill.payment.provider.default", "my-mock")));
+    }
+
+    @Override
+    protected void installPaymentProviderPlugins(PaymentConfig config) {
+        install(new MockPaymentProviderPluginModule("my-mock"));
+    }
+
+    @Override
+    protected void configure() {
+        super.configure();
+        bind(Bus.class).to(InMemoryBus.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
new file mode 100644
index 0000000..144afa4
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/setup/PaymentTestModuleWithMocks.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.payment.setup;
+
+import com.ning.billing.util.bus.InMemoryBus;
+import org.apache.commons.collections.MapUtils;
+
+import com.google.common.collect.ImmutableMap;
+import com.ning.billing.account.dao.AccountDao;
+import com.ning.billing.account.dao.MockAccountDao;
+import com.ning.billing.invoice.dao.InvoiceDao;
+import com.ning.billing.invoice.dao.MockInvoiceDao;
+
+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;
+
+public class PaymentTestModuleWithMocks extends PaymentModule {
+    public PaymentTestModuleWithMocks() {
+        super(MapUtils.toProperties(ImmutableMap.of("killbill.payment.provider.default", "my-mock")));
+    }
+
+    @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);
+    }
+}
diff --git a/payment/src/test/java/com/ning/billing/payment/TestHelper.java b/payment/src/test/java/com/ning/billing/payment/TestHelper.java
new file mode 100644
index 0000000..ac05da4
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/TestHelper.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * 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.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.AccountApiException;
+import com.ning.billing.account.api.user.AccountBuilder;
+import com.ning.billing.account.dao.AccountDao;
+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.dao.InvoiceDao;
+import com.ning.billing.invoice.model.DefaultInvoice;
+import com.ning.billing.invoice.model.RecurringInvoiceItem;
+
+public class TestHelper {
+    protected final AccountDao accountDao;
+    protected final InvoiceDao invoiceDao;
+
+    @Inject
+    public TestHelper(AccountDao accountDao, InvoiceDao invoiceDao) {
+        this.accountDao = accountDao;
+        this.invoiceDao = invoiceDao;
+    }
+
+    // These helper methods can be overridden in a plugin implementation
+    public Account createTestCreditCardAccount() throws AccountApiException {
+        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@example.com")
+                                                                     .currency(Currency.USD)
+                                                                     .billingCycleDay(1)
+                                                                     .build();
+        accountDao.create(account);
+        return account;
+    }
+
+    public Account createTestPayPalAccount() throws AccountApiException {
+        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);
+        return account;
+    }
+
+    public Invoice createTestInvoice(Account account,
+                                     DateTime targetDate,
+                                     Currency currency,
+                                     InvoiceItem... items) {
+        Invoice invoice = new DefaultInvoice(UUID.randomUUID(), account.getId(), new DateTime(), targetDate, currency);
+
+        for (InvoiceItem item : items) {
+            if (item instanceof RecurringInvoiceItem) {
+                RecurringInvoiceItem recurringInvoiceItem = (RecurringInvoiceItem) item;
+                invoice.addInvoiceItem(new RecurringInvoiceItem(invoice.getId(),
+                                                               recurringInvoiceItem.getSubscriptionId(),
+                                                               recurringInvoiceItem.getPlanName(),
+                                                               recurringInvoiceItem.getPhaseName(),
+                                                               recurringInvoiceItem.getStartDate(),
+                                                               recurringInvoiceItem.getEndDate(),
+                                                               recurringInvoiceItem.getAmount(),
+                                                               recurringInvoiceItem.getRate(),
+                                                               recurringInvoiceItem.getCurrency()));
+            }
+        }
+        invoiceDao.create(invoice);
+        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, 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/payment/src/test/java/com/ning/billing/payment/TestNotifyInvoicePaymentApi.java b/payment/src/test/java/com/ning/billing/payment/TestNotifyInvoicePaymentApi.java
new file mode 100644
index 0000000..80de67f
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/TestNotifyInvoicePaymentApi.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.payment;
+
+import static org.testng.Assert.assertNotNull;
+
+import java.util.UUID;
+
+import com.ning.billing.invoice.api.InvoicePayment;
+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.account.api.AccountApiException;
+import com.ning.billing.account.glue.AccountModuleWithMocks;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoicePaymentApi;
+import com.ning.billing.invoice.glue.InvoiceModuleWithMocks;
+import com.ning.billing.payment.setup.PaymentTestModuleWithMocks;
+import com.ning.billing.util.bus.Bus;
+import com.ning.billing.util.bus.Bus.EventBusException;
+
+@Test
+@Guice(modules = { PaymentTestModuleWithMocks.class, AccountModuleWithMocks.class, InvoiceModuleWithMocks.class })
+public class TestNotifyInvoicePaymentApi {
+    @Inject
+    private Bus eventBus;
+    @Inject
+    private RequestProcessor invoiceProcessor;
+    @Inject
+    private InvoicePaymentApi invoicePaymentApi;
+    @Inject
+    private TestHelper testHelper;
+
+    @BeforeMethod(alwaysRun = true)
+    public void setUp() throws EventBusException {
+        eventBus.start();
+        eventBus.register(invoiceProcessor);
+    }
+
+    @AfterMethod(alwaysRun = true)
+    public void tearDown() throws EventBusException {
+        eventBus.unregister(invoiceProcessor);
+        eventBus.stop();
+    }
+
+    @Test
+    public void testNotifyPaymentSuccess() throws AccountApiException {
+        final Account account = testHelper.createTestCreditCardAccount();
+        final Invoice invoice = testHelper.createTestInvoice(account);
+
+        PaymentAttempt paymentAttempt = new PaymentAttempt(UUID.randomUUID(), invoice);
+
+        invoicePaymentApi.notifyOfPaymentAttempt(invoice.getId(),
+                                     invoice.getBalance(),
+                                     invoice.getCurrency(),
+                                     paymentAttempt.getPaymentAttemptId(),
+                                     paymentAttempt.getPaymentAttemptDate());
+
+        InvoicePayment invoicePayment = invoicePaymentApi.getInvoicePayment(paymentAttempt.getPaymentAttemptId());
+
+        assertNotNull(invoicePayment);
+    }
+
+    @Test
+    public void testNotifyPaymentFailure() throws AccountApiException {
+        final Account account = testHelper.createTestCreditCardAccount();
+        final Invoice invoice = testHelper.createTestInvoice(account);
+
+        PaymentAttempt paymentAttempt = new PaymentAttempt(UUID.randomUUID(), invoice);
+        invoicePaymentApi.notifyOfPaymentAttempt(invoice.getId(),
+                                                 paymentAttempt.getPaymentAttemptId(),
+                                                 paymentAttempt.getPaymentAttemptDate());
+
+        InvoicePayment invoicePayment = invoicePaymentApi.getInvoicePayment(paymentAttempt.getPaymentAttemptId());
+
+        assertNotNull(invoicePayment);
+    }
+
+}
diff --git a/payment/src/test/java/com/ning/billing/payment/TestPaymentInvoiceIntegration.java b/payment/src/test/java/com/ning/billing/payment/TestPaymentInvoiceIntegration.java
new file mode 100644
index 0000000..8768117
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/TestPaymentInvoiceIntegration.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * 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 static com.jayway.awaitility.Awaitility.await;
+import static java.util.concurrent.TimeUnit.MINUTES;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.concurrent.Callable;
+
+import com.ning.billing.invoice.glue.InvoiceModuleWithMocks;
+import org.apache.commons.io.IOUtils;
+import org.joda.time.DateTime;
+import org.skife.jdbi.v2.IDBI;
+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 org.testng.annotations.Test;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.ning.billing.account.api.Account;
+import com.ning.billing.account.glue.AccountModule;
+import com.ning.billing.dbi.MysqlTestingHelper;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoicePaymentApi;
+import com.ning.billing.payment.api.PaymentApi;
+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.setup.PaymentTestModuleWithEmbeddedDb;
+import com.ning.billing.util.bus.Bus;
+import com.ning.billing.util.bus.Bus.EventBusException;
+import com.ning.billing.util.clock.MockClockModule;
+
+public class TestPaymentInvoiceIntegration {
+    // create payment for received invoice and save it -- positive and negative
+    // check that notification for payment attempt is created
+    // check that invoice-payment is saved
+    @Inject
+    private Bus eventBus;
+    @Inject
+    private RequestProcessor invoiceProcessor;
+    @Inject
+    private InvoicePaymentApi invoicePaymentApi;
+    @Inject
+    private PaymentApi paymentApi;
+    @Inject
+    private TestHelper testHelper;
+
+    private MockPaymentInfoReceiver paymentInfoReceiver;
+
+    private IDBI dbi;
+    private MysqlTestingHelper helper;
+
+    @BeforeClass(alwaysRun = true)
+    public void startMysql() throws IOException {
+        final String accountddl = IOUtils.toString(MysqlTestingHelper.class.getResourceAsStream("/com/ning/billing/account/ddl.sql"));
+        final String utilddl = IOUtils.toString(MysqlTestingHelper.class.getResourceAsStream("/com/ning/billing/util/ddl.sql"));
+        final String invoiceddl = IOUtils.toString(MysqlTestingHelper.class.getResourceAsStream("/com/ning/billing/invoice/ddl.sql"));
+        final String paymentddl = IOUtils.toString(MysqlTestingHelper.class.getResourceAsStream("/com/ning/billing/payment/ddl.sql"));
+
+        helper = new MysqlTestingHelper();
+        helper.startMysql();
+        helper.initDb(accountddl + "\n" + invoiceddl + "\n" + utilddl + "\n" + paymentddl);
+        dbi = helper.getDBI();
+    }
+
+    @AfterClass(alwaysRun = true)
+    public void stopMysql() {
+        helper.stopMysql();
+    }
+
+    @BeforeMethod(alwaysRun = true)
+    public void setUp() throws EventBusException {
+        Injector injector = Guice.createInjector(new PaymentTestModuleWithEmbeddedDb(),
+                                                 new AccountModule(),
+                                                 new InvoiceModuleWithMocks(),
+                                                 new MockClockModule(),
+                                                 new AbstractModule() {
+                                                    @Override
+                                                    protected void configure() {
+                                                        bind(IDBI.class).toInstance(dbi);
+                                                    }
+                                                });
+        injector.injectMembers(this);
+
+        paymentInfoReceiver = new MockPaymentInfoReceiver();
+
+        eventBus.start();
+        eventBus.register(invoiceProcessor);
+        eventBus.register(paymentInfoReceiver);
+    }
+
+    @AfterMethod(alwaysRun = true)
+    public void tearDown() throws EventBusException {
+        eventBus.unregister(invoiceProcessor);
+        eventBus.unregister(paymentInfoReceiver);
+        eventBus.stop();
+    }
+
+    @Test
+    public void testInvoiceIntegration() throws Exception {
+        final Account account = testHelper.createTestCreditCardAccount();
+        final Invoice invoice = testHelper.createTestInvoice(account);
+
+        await().atMost(1, MINUTES).until(new Callable<Boolean>() {
+            @Override
+            public Boolean call() throws Exception {
+                List<PaymentInfo> processedPayments = paymentInfoReceiver.getProcessedPayments();
+                List<PaymentError> errors = paymentInfoReceiver.getErrors();
+
+                return processedPayments.size() == 1 || errors.size() == 1;
+            }
+        });
+
+        assertFalse(paymentInfoReceiver.getProcessedPayments().isEmpty());
+        assertTrue(paymentInfoReceiver.getErrors().isEmpty());
+
+        List<PaymentInfo> payments = paymentInfoReceiver.getProcessedPayments();
+        PaymentAttempt paymentAttempt = paymentApi.getPaymentAttemptForPaymentId(payments.get(0).getPaymentId());
+        Assert.assertNotNull(paymentAttempt);
+
+        Invoice invoiceForPayment = invoicePaymentApi.getInvoiceForPaymentAttemptId(paymentAttempt.getPaymentAttemptId());
+
+        Assert.assertNotNull(invoiceForPayment);
+        Assert.assertEquals(invoiceForPayment.getId(), invoice.getId());
+        Assert.assertEquals(invoiceForPayment.getAccountId(), account.getId());
+
+        DateTime invoicePaymentAttempt = invoiceForPayment.getLastPaymentAttempt();
+        DateTime correctedDate = invoicePaymentAttempt.minus(invoicePaymentAttempt.millisOfSecond().get());
+        Assert.assertTrue(correctedDate.isEqual(paymentAttempt.getPaymentAttemptDate()));
+
+        Assert.assertEquals(invoiceForPayment.getBalance().floatValue(), new BigDecimal("0").floatValue());
+        Assert.assertEquals(invoiceForPayment.getAmountPaid().floatValue(), invoice.getAmountPaid().floatValue());
+    }
+}
diff --git a/payment/src/test/java/com/ning/billing/payment/TestPaymentProvider.java b/payment/src/test/java/com/ning/billing/payment/TestPaymentProvider.java
new file mode 100644
index 0000000..2c0aa13
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/TestPaymentProvider.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * 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 static com.jayway.awaitility.Awaitility.await;
+import static java.util.concurrent.TimeUnit.MINUTES;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+
+import java.util.List;
+import java.util.concurrent.Callable;
+
+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.account.glue.AccountModuleWithMocks;
+import com.ning.billing.invoice.glue.InvoiceModuleWithMocks;
+import com.ning.billing.payment.api.PaymentError;
+import com.ning.billing.payment.api.PaymentInfo;
+import com.ning.billing.payment.setup.PaymentTestModuleWithMocks;
+import com.ning.billing.util.bus.Bus;
+import com.ning.billing.util.bus.Bus.EventBusException;
+
+@Guice(modules = { PaymentTestModuleWithMocks.class, AccountModuleWithMocks.class, InvoiceModuleWithMocks.class })
+public class TestPaymentProvider {
+    @Inject
+    private Bus eventBus;
+    @Inject
+    private RequestProcessor invoiceProcessor;
+    @Inject
+    private TestHelper testHelper;
+
+    private MockPaymentInfoReceiver paymentInfoReceiver;
+
+    @BeforeMethod(alwaysRun = true)
+    public void setUp() throws EventBusException {
+        paymentInfoReceiver = new MockPaymentInfoReceiver();
+
+        eventBus.start();
+        eventBus.register(invoiceProcessor);
+        eventBus.register(paymentInfoReceiver);
+
+        assertTrue(true);
+    }
+
+    @AfterMethod(alwaysRun = true)
+    public void tearDown() throws EventBusException {
+        eventBus.unregister(invoiceProcessor);
+        eventBus.unregister(paymentInfoReceiver);
+        eventBus.stop();
+
+        assertTrue(true);
+    }
+
+    @Test
+    public void testSimpleInvoice() throws Exception {
+        final Account account = testHelper.createTestCreditCardAccount();
+
+        testHelper.createTestInvoice(account);
+
+        await().atMost(1, MINUTES).until(new Callable<Boolean>() {
+            @Override
+            public Boolean call() throws Exception {
+                List<PaymentInfo> processedPayments = paymentInfoReceiver.getProcessedPayments();
+                List<PaymentError> errors = paymentInfoReceiver.getErrors();
+
+                return processedPayments.size() == 1 || errors.size() == 1;
+            }
+        });
+
+        assertFalse(paymentInfoReceiver.getProcessedPayments().isEmpty());
+        assertTrue(paymentInfoReceiver.getErrors().isEmpty());
+
+        final PaymentInfo paymentInfo = paymentInfoReceiver.getProcessedPayments().get(0);
+        final PaymentInfoRequest paymentInfoRequest = new PaymentInfoRequest(account.getId(), paymentInfo.getPaymentId());
+
+        paymentInfoReceiver.clear();
+        eventBus.post(paymentInfoRequest);
+        await().atMost(5, MINUTES).until(new Callable<Boolean>() {
+            @Override
+            public Boolean call() throws Exception {
+                List<PaymentInfo> processedPayments = paymentInfoReceiver.getProcessedPayments();
+                List<PaymentError> errors = paymentInfoReceiver.getErrors();
+
+                return processedPayments.size() == 1 || errors.size() == 1;
+            }
+        });
+
+        assertFalse(paymentInfoReceiver.getProcessedPayments().isEmpty());
+        assertTrue(paymentInfoReceiver.getErrors().isEmpty());
+        assertEquals(paymentInfoReceiver.getProcessedPayments().get(0), paymentInfo);
+    }
+}
diff --git a/payment/src/test/java/com/ning/billing/payment/util/TestSyncWaitOnEventBus.java b/payment/src/test/java/com/ning/billing/payment/util/TestSyncWaitOnEventBus.java
new file mode 100644
index 0000000..0e76771
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/util/TestSyncWaitOnEventBus.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.payment.util;
+
+import static org.testng.Assert.assertEquals;
+
+import java.util.UUID;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.eventbus.Subscribe;
+import com.ning.billing.util.bus.Bus;
+import com.ning.billing.util.bus.InMemoryBus;
+
+@Test
+public class TestSyncWaitOnEventBus {
+    private static final class TestEvent implements EventBusRequest<UUID> {
+        private final UUID id;
+        private final String msg;
+
+        public TestEvent(UUID id, String msg) {
+            this.id = id;
+            this.msg = msg;
+        }
+
+        @Override
+        public UUID getId() {
+            return id;
+        }
+
+        public String getMsg() {
+            return msg;
+        }
+    }
+
+    private static final class TestResponse implements EventBusResponse<UUID> {
+        private final UUID id;
+        private final String msg;
+
+        public TestResponse(UUID id, String msg) {
+            this.id = id;
+            this.msg = msg;
+        }
+
+        @Override
+        public UUID getRequestId() {
+            return id;
+        }
+
+        public String getMsg() {
+            return msg;
+        }
+    }
+
+    private Bus eventBus;
+
+    @BeforeMethod(alwaysRun = true)
+    public void setUp() throws Exception {
+        eventBus = new InMemoryBus();
+        eventBus.start();
+        eventBus.register(new Object() {
+            @Subscribe
+            public void handleEvent(TestEvent event) throws Exception {
+                Thread.sleep(100);
+                eventBus.post(new TestResponse(event.getId(), event.getMsg()));
+            }
+        });
+    }
+
+    @AfterMethod(alwaysRun = true)
+    public void tearDown() {
+        eventBus.stop();
+    }
+
+    public void test() throws Exception {
+        final TestEvent event = new TestEvent(UUID.randomUUID(), "Hello World!");
+
+        Future<TestResponse> future = EventBusFuture.post(eventBus, event);
+        TestResponse response = future.get(1, TimeUnit.SECONDS);
+
+        assertEquals(response.getRequestId(), event.getId());
+        assertEquals(response.getMsg(), event.getMsg());
+    }
+}
diff --git a/payment/src/test/resources/log4j.xml b/payment/src/test/resources/log4j.xml
new file mode 100644
index 0000000..82b5a26
--- /dev/null
+++ b/payment/src/test/resources/log4j.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+  ~ Copyright 2010 Ning, Inc.
+  ~
+  ~ Ning licenses this file to you under the Apache License, version 2.0
+  ~ (the "License"); you may not use this file except in compliance with the
+  ~ License.  You may obtain a copy of the License at:
+  ~
+  ~    http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+  ~ 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 "http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/xml/doc-files/log4j.dtd">
+<log4j:configuration debug="false"
+                     xmlns:log4j='http://jakarta.apache.org/log4j/'>
+    <appender name="CONSOLE" class="org.apache.log4j.ConsoleAppender">
+        <layout class="org.apache.log4j.PatternLayout">
+            <param name="ConversionPattern" value="%p	%d{ISO8601}	%t	%c	%m%n"/>
+        </layout>
+    </appender>
+
+    <root>
+        <level value="info"/>
+        <appender-ref ref="CONSOLE"/>
+    </root>
+</log4j:configuration>

pom.xml 100(+88 -12)

diff --git a/pom.xml b/pom.xml
index 8c8fbb3..179d4c3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -17,7 +17,7 @@
     <groupId>com.ning.billing</groupId>
     <artifactId>killbill</artifactId>
     <packaging>pom</packaging>
-    <version>0.1.2-SNAPSHOT</version>
+    <version>0.1.6-SNAPSHOT</version>
     <name>killbill</name>
     <description>Library for managing recurring subscriptions and the associated billing</description>
     <url>http://github.com/ning/killbill</url>
@@ -29,7 +29,7 @@
         </license>
     </licenses>
     <scm>
-        <connection>scm:git:git://github.com/stephane/killbill.git</connection>
+        <connection>scm:git:git://github.com/ning/killbill.git</connection>
         <developerConnection>scm:git:git@github.com:ning/killbill.git</developerConnection>
         <url>http://github.com/ning/killbill/tree/master</url>
     </scm>
@@ -56,6 +56,12 @@
             </dependency>
             <dependency>
                 <groupId>com.ning.billing</groupId>
+                <artifactId>killbill-api</artifactId>
+                <version>${project.version}</version>
+                <type>test-jar</type>
+            </dependency>
+            <dependency>
+                <groupId>com.ning.billing</groupId>
                 <artifactId>killbill-account</artifactId>
                 <version>${project.version}</version>
             </dependency>
@@ -64,7 +70,6 @@
                 <artifactId>killbill-account</artifactId>
                 <version>${project.version}</version>
                 <type>test-jar</type>
-                <scope>test</scope>
             </dependency>
             <dependency>
                 <groupId>com.ning.billing</groupId>
@@ -73,6 +78,23 @@
             </dependency>
             <dependency>
                 <groupId>com.ning.billing</groupId>
+                <artifactId>killbill-entitlement</artifactId>
+                <version>${project.version}</version>
+                <type>test-jar</type>
+            </dependency>
+            <dependency>
+                <groupId>com.ning.billing</groupId>
+                <artifactId>killbill-payment</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.ning.billing</groupId>
+                <artifactId>killbill-payment</artifactId>
+                <version>${project.version}</version>
+                <type>test-jar</type>
+            </dependency>
+            <dependency>
+                <groupId>com.ning.billing</groupId>
                 <artifactId>killbill-catalog</artifactId>
                 <version>${project.version}</version>
             </dependency>
@@ -81,14 +103,17 @@
                 <artifactId>killbill-catalog</artifactId>
                 <version>${project.version}</version>
                 <type>test-jar</type>
-                <scope>test</scope>
             </dependency>
             <dependency>
                 <groupId>com.ning.billing</groupId>
-                <artifactId>killbill-util</artifactId>
+                <artifactId>killbill-invoice</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.ning.billing</groupId>
+                <artifactId>killbill-invoice</artifactId>
                 <version>${project.version}</version>
                 <type>test-jar</type>
-                <scope>test</scope>
             </dependency>
             <dependency>
                 <groupId>com.ning.billing</groupId>
@@ -96,19 +121,25 @@
                 <version>${project.version}</version>
             </dependency>
             <dependency>
+                <groupId>com.ning.billing</groupId>
+                <artifactId>killbill-util</artifactId>
+                <version>${project.version}</version>
+                <type>test-jar</type>
+            </dependency>
+            <dependency>
                 <groupId>org.codehaus.jackson</groupId>
                 <artifactId>jackson-core-asl</artifactId>
-                <version>1.9.0</version>
+                <version>1.9.2</version>
             </dependency>
             <dependency>
                 <groupId>org.codehaus.jackson</groupId>
                 <artifactId>jackson-jaxrs</artifactId>
-                <version>1.9.0</version>
+                <version>1.9.2</version>
             </dependency>
             <dependency>
                 <groupId>org.codehaus.jackson</groupId>
                 <artifactId>jackson-mapper-asl</artifactId>
-                <version>1.9.0</version>
+                <version>1.9.2</version>
             </dependency>
             <dependency>
                 <groupId>com.jolbox</groupId>
@@ -134,9 +165,15 @@
                 <scope>provided</scope>
             </dependency>
             <dependency>
+                <groupId>com.google.inject.extensions</groupId>
+                <artifactId>guice-multibindings</artifactId>
+                <version>3.0</version>
+                <scope>provided</scope>
+            </dependency>
+            <dependency>
                 <groupId>com.mogwee</groupId>
                 <artifactId>mogwee-executors</artifactId>
-                <version>1.1.0</version>
+                <version>1.2.0</version>
             </dependency>
             <dependency>
                 <groupId>com.mysql</groupId>
@@ -176,6 +213,11 @@
                 <version>2.5</version>
             </dependency>
             <dependency>
+                <groupId>commons-collections</groupId>
+                <artifactId>commons-collections</artifactId>
+                <version>3.2.1</version>
+            </dependency>
+            <dependency>
                 <groupId>joda-time</groupId>
                 <artifactId>joda-time</artifactId>
                 <version>2.0</version>
@@ -201,7 +243,7 @@
             <dependency>
                 <groupId>org.jdbi</groupId>
                 <artifactId>jdbi</artifactId>
-                <version>2.27</version>
+                <version>2.31.2</version>
             </dependency>
             <dependency>
                 <groupId>org.skife.config</groupId>
@@ -231,7 +273,13 @@
             <dependency>
                 <groupId>org.testng</groupId>
                 <artifactId>testng</artifactId>
-                <version>6.0</version>
+                <version>6.3.1</version>
+                <scope>test</scope>
+            </dependency>
+            <dependency>
+                <groupId>com.jayway.awaitility</groupId>
+                <artifactId>awaitility</artifactId>
+                <version>1.3.3</version>
                 <scope>test</scope>
             </dependency>
         </dependencies>
@@ -284,6 +332,18 @@
                 </configuration>
             </plugin>
             <plugin>
+              <groupId>org.apache.maven.plugins</groupId>
+              <artifactId>maven-jar-plugin</artifactId>
+              <version>2.2</version>
+              <executions>
+                <execution>
+                  <goals>
+                    <goal>test-jar</goal>
+                  </goals>
+               </execution>
+              </executions>
+            </plugin>
+            <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-dependency-plugin</artifactId>
                 <version>2.3</version>
@@ -321,6 +381,7 @@
                                 <exclude>**/.project</exclude>
                                 <exclude>.git/**</exclude>
                                 <exclude>.gitignore</exclude>
+                                <exclude>ignore/**</exclude>
                                 <exclude>API.txt</exclude>
                                 <exclude>RELEASE.sh</exclude>
                                 <exclude>deploy.sh</exclude>
@@ -391,6 +452,21 @@
                     <attachClasses>true</attachClasses>
                 </configuration>
             </plugin>
+            <plugin>
+              <groupId>org.apache.maven.plugins</groupId>
+              <artifactId>maven-source-plugin</artifactId>
+              <version>2.1.2</version>
+              <executions>
+                <execution>
+                  <id>attach-sources</id>
+                  <phase>verify</phase>
+                  <goals>
+                    <goal>jar</goal>
+                    <goal>test-jar</goal>
+                  </goals>
+                </execution>
+              </executions>
+            </plugin>
         </plugins>
     </build>
     <profiles>

util/pom.xml 23(+21 -2)

diff --git a/util/pom.xml b/util/pom.xml
index 74d243c..68648ef 100644
--- a/util/pom.xml
+++ b/util/pom.xml
@@ -13,7 +13,7 @@
     <parent>
         <groupId>com.ning.billing</groupId>
         <artifactId>killbill</artifactId>
-        <version>0.1.2-SNAPSHOT</version>
+        <version>0.1.6-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-util</artifactId>
@@ -55,6 +55,15 @@
             <scope>test</scope>
         </dependency>
         <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.antlr</groupId>
+            <artifactId>stringtemplate</artifactId>
+            <scope>runtime</scope>
+        </dependency>
+        <dependency>
             <groupId>org.testng</groupId>
             <artifactId>testng</artifactId>
             <scope>test</scope>
@@ -62,7 +71,12 @@
         <dependency>
             <groupId>com.mysql</groupId>
             <artifactId>management</artifactId>
-            <version>5.0.11</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.mysql</groupId>
+            <artifactId>management-dbfiles</artifactId>
+            <scope>test</scope>
         </dependency>
         <dependency>
             <groupId>commons-io</groupId>
@@ -83,6 +97,11 @@
             <artifactId>management-dbfiles</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>com.jayway.awaitility</groupId>
+            <artifactId>awaitility</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
     <build>
         <plugins>
diff --git a/util/src/main/java/com/ning/billing/util/customfield/dao/FieldStoreDao.java b/util/src/main/java/com/ning/billing/util/customfield/dao/FieldStoreDao.java
index 680a826..14c123a 100644
--- a/util/src/main/java/com/ning/billing/util/customfield/dao/FieldStoreDao.java
+++ b/util/src/main/java/com/ning/billing/util/customfield/dao/FieldStoreDao.java
@@ -33,6 +33,7 @@ import org.skife.jdbi.v2.sqlobject.BinderFactory;
 import org.skife.jdbi.v2.sqlobject.BindingAnnotation;
 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 org.skife.jdbi.v2.tweak.ResultSetMapper;
@@ -42,10 +43,11 @@ import com.ning.billing.util.entity.EntityCollectionDao;
 
 @ExternalizedSqlViaStringTemplate3
 @RegisterMapper(FieldStoreDao.CustomFieldMapper.class)
-public interface FieldStoreDao extends EntityCollectionDao<CustomField>, Transmogrifier {
+public interface FieldStoreDao extends EntityCollectionDao<CustomField>, Transactional<FieldStoreDao>, Transmogrifier {
+
     @Override
-    @SqlBatch
-    public void save(@Bind("objectId") final String objectId,
+    @SqlBatch(transactional=false)
+    public void batchSaveFromTransaction(@Bind("objectId") final String objectId,
                      @Bind("objectType") final String objectType,
                      @CustomFieldBinder final List<CustomField> entities);
 
@@ -65,8 +67,10 @@ public interface FieldStoreDao extends EntityCollectionDao<CustomField>, Transmo
     @Target({ElementType.PARAMETER})
     public @interface CustomFieldBinder {
         public static class CustomFieldBinderFactory implements BinderFactory {
+            @Override
             public Binder build(Annotation annotation) {
                 return new Binder<CustomFieldBinder, CustomField>() {
+                    @Override
                     public void bind(SQLStatement q, CustomFieldBinder bind, CustomField customField) {
                         q.bind("id", customField.getId().toString());
                         q.bind("fieldName", customField.getName());
diff --git a/util/src/main/java/com/ning/billing/util/entity/EntityCollectionDao.java b/util/src/main/java/com/ning/billing/util/entity/EntityCollectionDao.java
index 8134203..11d149c 100644
--- a/util/src/main/java/com/ning/billing/util/entity/EntityCollectionDao.java
+++ b/util/src/main/java/com/ning/billing/util/entity/EntityCollectionDao.java
@@ -26,8 +26,9 @@ import java.util.List;
  * @param <T>
  */
 public interface EntityCollectionDao<T extends Entity> {
-    @SqlBatch
-    public void save(@Bind("objectId") final String objectId,
+
+    @SqlBatch(transactional=false)
+    public void batchSaveFromTransaction(@Bind("objectId") final String objectId,
                      @Bind("objectType") final String objectType,
                      @BindBean final List<T> entities);
 
diff --git a/util/src/main/java/com/ning/billing/util/entity/EntityDao.java b/util/src/main/java/com/ning/billing/util/entity/EntityDao.java
index 3b5dd46..3e68158 100644
--- a/util/src/main/java/com/ning/billing/util/entity/EntityDao.java
+++ b/util/src/main/java/com/ning/billing/util/entity/EntityDao.java
@@ -16,20 +16,21 @@
 
 package com.ning.billing.util.entity;
 
+import java.util.List;
+
 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.SqlUpdate;
 
-import java.util.List;
+import com.ning.billing.account.api.AccountApiException;
 
 public interface EntityDao<T extends Entity> {
     @SqlUpdate
-    public void create(@BindBean T entity);
+    public void create(@BindBean final T entity) throws AccountApiException;
 
     @SqlUpdate
-    public void update(@BindBean T entity);
+    public void update(@BindBean final T entity) throws AccountApiException;
 
     @SqlQuery
     public T getById(@Bind("id") final String id);
@@ -39,4 +40,7 @@ public interface EntityDao<T extends Entity> {
 
     @SqlUpdate
     public void test();
+
+    @SqlUpdate
+    public void deleteByKey(String key) throws AccountApiException;
 }
diff --git a/util/src/main/java/com/ning/billing/util/globallocker/GlobalLock.java b/util/src/main/java/com/ning/billing/util/globallocker/GlobalLock.java
new file mode 100644
index 0000000..12ebc0d
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/globallocker/GlobalLock.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.util.globallocker;
+
+public interface GlobalLock
+{
+    public void release();
+}
\ No newline at end of file
diff --git a/util/src/main/java/com/ning/billing/util/globallocker/GlobalLocker.java b/util/src/main/java/com/ning/billing/util/globallocker/GlobalLocker.java
new file mode 100644
index 0000000..5312e09
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/globallocker/GlobalLocker.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.util.globallocker;
+
+public interface GlobalLocker {
+
+    GlobalLock lockWithNumberOfTries(final LockerService service, final String lockKey, final int retry);
+    Boolean isFree(final LockerService service, final String lockKey);
+
+    public enum LockerService {
+
+        // Only service needing global lock
+        INVOICE("invoice");
+
+        private final String svcName;
+
+        LockerService(String svcName) {
+            this.svcName = svcName;
+        }
+
+        @Override
+        public String toString() {
+            return svcName;
+        }
+    }
+}
\ No newline at end of file
diff --git a/util/src/main/java/com/ning/billing/util/globallocker/MySqlGlobalLocker.java b/util/src/main/java/com/ning/billing/util/globallocker/MySqlGlobalLocker.java
new file mode 100644
index 0000000..f9de358
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/globallocker/MySqlGlobalLocker.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.util.globallocker;
+
+import com.google.inject.Inject;
+import org.skife.jdbi.v2.Handle;
+import org.skife.jdbi.v2.IDBI;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class MySqlGlobalLocker implements GlobalLocker {
+
+    private final static Logger logger = LoggerFactory.getLogger(MySqlGlobalLocker.class);
+
+    private final static long DEFAULT_TIMEOUT = 3L; // 3 seconds
+
+    private final IDBI dbi;
+    private long timeout;
+
+    @Inject
+    public MySqlGlobalLocker(IDBI dbi) {
+        this.dbi = dbi;
+        this.timeout = DEFAULT_TIMEOUT;
+    }
+
+    public void setTimeout(final long timeout) {
+        this.timeout = timeout;
+    }
+
+    @Override
+    public GlobalLock lockWithNumberOfTries(final LockerService service, final String lockKey, final int retry) {
+
+        final String lockName = getLockName(service, lockKey);
+        int tries_left = retry;
+        while (tries_left-- > 0) {
+            GlobalLock lock = lock(lockName);
+            if (lock != null) {
+                return lock;
+            }
+        }
+        logger.error(String.format("Failed to acquire lock %s for service %s after %d retry", lockKey, service, retry));
+        throw new LockFailedException();
+    }
+
+    private GlobalLock lock(final String lockName) throws LockFailedException {
+
+        final Handle h = dbi.open();
+        final MySqlGlobalLockerDao dao = h.attach(MySqlGlobalLockerDao.class);
+
+        final boolean obtained = dao.lock(lockName, timeout);
+        if (obtained) {
+            return new GlobalLock() {
+                @Override
+                public void release() {
+                    try {
+                        dao.releaseLock(lockName);
+                    }
+                    finally {
+                        if (h != null) {
+                            h.close();
+                        }
+                    }
+                }
+            };
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    public Boolean isFree(final LockerService service, final String lockKey) {
+
+        final String lockName = getLockName(service, lockKey);
+        final Handle h = dbi.open();
+        try {
+            final MySqlGlobalLockerDao dao = h.attach(MySqlGlobalLockerDao.class);
+            return dao.isFree(lockName);
+        } finally {
+            if (h != null) {
+                h.close();
+            }
+        }
+    }
+
+    private String getLockName(final LockerService service, final String lockKey) {
+        StringBuilder tmp = new StringBuilder()
+            .append(service.toString())
+            .append("-")
+            .append(lockKey);
+        return tmp.toString();
+    }
+}
diff --git a/util/src/main/java/com/ning/billing/util/globallocker/MySqlGlobalLockerDao.java b/util/src/main/java/com/ning/billing/util/globallocker/MySqlGlobalLockerDao.java
new file mode 100644
index 0000000..876f6e1
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/globallocker/MySqlGlobalLockerDao.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * 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.globallocker;
+
+import org.skife.jdbi.v2.StatementContext;
+import org.skife.jdbi.v2.sqlobject.Bind;
+import org.skife.jdbi.v2.sqlobject.SqlQuery;
+import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
+import org.skife.jdbi.v2.tweak.ResultSetMapper;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+@RegisterMapper(MySqlGlobalLockerDao.LockMapper.class)
+public interface MySqlGlobalLockerDao {
+
+    @SqlQuery("Select GET_LOCK(:lockName, :timeout);")
+    public Boolean lock(@Bind("lockName") final String lockName, @Bind("timeout") final long timeout);
+
+    @SqlQuery("Select RELEASE_LOCK(:lockName);")
+    public Boolean releaseLock(@Bind("lockName") final String lockName);
+
+    @SqlQuery("Select IS_FREE_LOCK(:lockName);")
+    public Boolean isFree(@Bind("lockName") final String lockName);
+
+    class LockMapper implements ResultSetMapper<Boolean> {
+         @Override
+         public Boolean map(int index, ResultSet r, StatementContext ctx) throws SQLException {
+            return (r.getByte(1) == 1);
+         }
+    }
+}
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
new file mode 100644
index 0000000..d6f7a37
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/glue/BusModule.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.glue;
+
+import com.google.inject.AbstractModule;
+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.InMemoryBus;
+
+public class BusModule extends AbstractModule {
+
+    @Override
+    protected void configure() {
+        bind(BusService.class).to(DefaultBusService.class);
+        bind(Bus.class).to(InMemoryBus.class).asEagerSingleton();
+
+    }
+
+}
diff --git a/util/src/main/java/com/ning/billing/util/glue/ClockModule.java b/util/src/main/java/com/ning/billing/util/glue/ClockModule.java
index 187a500..e7c7c3f 100644
--- a/util/src/main/java/com/ning/billing/util/glue/ClockModule.java
+++ b/util/src/main/java/com/ning/billing/util/glue/ClockModule.java
@@ -22,8 +22,8 @@ import com.ning.billing.util.clock.DefaultClock;
 
 public class ClockModule extends AbstractModule {
 
-    @Override
-    protected void configure() {
-        bind(Clock.class).to(DefaultClock.class).asEagerSingleton();
-    }
+	@Override
+	protected void configure() {
+		bind(Clock.class).to(DefaultClock.class).asEagerSingleton();
+	}
 }
diff --git a/util/src/main/java/com/ning/billing/util/glue/TagDescriptionDaoProvider.java b/util/src/main/java/com/ning/billing/util/glue/TagDescriptionDaoProvider.java
index 31fb306..560a09f 100644
--- a/util/src/main/java/com/ning/billing/util/glue/TagDescriptionDaoProvider.java
+++ b/util/src/main/java/com/ning/billing/util/glue/TagDescriptionDaoProvider.java
@@ -18,10 +18,10 @@ package com.ning.billing.util.glue;
 
 import com.google.inject.Inject;
 import com.google.inject.Provider;
-import com.ning.billing.util.tag.dao.TagDescriptionDao;
+import com.ning.billing.util.tag.dao.TagDefinitionSqlDao;
 import org.skife.jdbi.v2.IDBI;
 
-public class TagDescriptionDaoProvider implements Provider<TagDescriptionDao>
+public class TagDescriptionDaoProvider implements Provider<TagDefinitionSqlDao>
 {
     private final IDBI dbi;
 
@@ -32,8 +32,8 @@ public class TagDescriptionDaoProvider implements Provider<TagDescriptionDao>
     }
 
     @Override
-    public TagDescriptionDao get()
+    public TagDefinitionSqlDao get()
     {
-        return dbi.onDemand(TagDescriptionDao.class);
+        return dbi.onDemand(TagDefinitionSqlDao.class);
     }
 }
diff --git a/util/src/main/java/com/ning/billing/util/glue/TagStoreDaoProvider.java b/util/src/main/java/com/ning/billing/util/glue/TagStoreDaoProvider.java
index 2c612e6..aa0f080 100644
--- a/util/src/main/java/com/ning/billing/util/glue/TagStoreDaoProvider.java
+++ b/util/src/main/java/com/ning/billing/util/glue/TagStoreDaoProvider.java
@@ -18,10 +18,10 @@ package com.ning.billing.util.glue;
 
 import com.google.inject.Inject;
 import com.google.inject.Provider;
-import com.ning.billing.util.tag.dao.TagStoreDao;
+import com.ning.billing.util.tag.dao.TagStoreSqlDao;
 import org.skife.jdbi.v2.IDBI;
 
-public class TagStoreDaoProvider implements Provider<TagStoreDao>
+public class TagStoreDaoProvider implements Provider<TagStoreSqlDao>
 {
     private final IDBI dbi;
 
@@ -32,8 +32,8 @@ public class TagStoreDaoProvider implements Provider<TagStoreDao>
     }
 
     @Override
-    public TagStoreDao get()
+    public TagStoreSqlDao get()
     {
-        return dbi.onDemand(TagStoreDao.class);
+        return dbi.onDemand(TagStoreSqlDao.class);
     }
 }
diff --git a/util/src/main/java/com/ning/billing/util/glue/TagStoreModule.java b/util/src/main/java/com/ning/billing/util/glue/TagStoreModule.java
index ae14782..10651be 100644
--- a/util/src/main/java/com/ning/billing/util/glue/TagStoreModule.java
+++ b/util/src/main/java/com/ning/billing/util/glue/TagStoreModule.java
@@ -17,15 +17,24 @@
 package com.ning.billing.util.glue;
 
 import com.google.inject.AbstractModule;
-import com.ning.billing.util.tag.dao.TagDescriptionDao;
-import com.ning.billing.util.tag.dao.TagStoreDao;
+import com.ning.billing.util.api.TagDefinitionUserApi;
+import com.ning.billing.util.clock.Clock;
+import com.ning.billing.util.clock.DefaultClock;
+import com.ning.billing.util.tag.api.DefaultTagDefinitionUserApi;
+import com.ning.billing.util.tag.dao.DefaultTagDefinitionDao;
+import com.ning.billing.util.tag.dao.TagDefinitionDao;
+import com.ning.billing.util.tag.dao.TagDefinitionSqlDao;
+import com.ning.billing.util.tag.dao.TagStoreSqlDao;
 
 public class TagStoreModule extends AbstractModule
 {
     @Override
     protected void configure()
     {
-        bind(TagDescriptionDao.class).toProvider(TagDescriptionDaoProvider.class).asEagerSingleton();
-        bind(TagStoreDao.class).toProvider(TagStoreDaoProvider.class).asEagerSingleton();
+        bind(TagDefinitionSqlDao.class).toProvider(TagDescriptionDaoProvider.class).asEagerSingleton();
+        bind(TagDefinitionDao.class).to(DefaultTagDefinitionDao.class).asEagerSingleton();
+        bind(TagStoreSqlDao.class).toProvider(TagStoreDaoProvider.class).asEagerSingleton();
+        bind(TagDefinitionUserApi.class).to(DefaultTagDefinitionUserApi.class).asEagerSingleton();
     }
+    
 }
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 818d831..2f511ec 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
@@ -49,13 +49,13 @@ public interface NotificationSqlDao extends Transactional<NotificationSqlDao>, C
     //
     @SqlQuery
     @Mapper(NotificationSqlMapper.class)
-    public List<Notification> getReadyNotifications(@Bind("now") Date now, @Bind("max") int max);
+    public List<Notification> getReadyNotifications(@Bind("now") Date now, @Bind("max") int max, @Bind("queue_name") String queueName);
 
     @SqlUpdate
-    public int claimNotification(@Bind("owner") String owner, @Bind("next_available") Date nextAvailable, @Bind("notification_id") String eventId, @Bind("now") Date now);
+    public int claimNotification(@Bind("owner") String owner, @Bind("next_available") Date nextAvailable, @Bind("id") long id, @Bind("now") Date now);
 
     @SqlUpdate
-    public void clearNotification(@Bind("notification_id") String eventId, @Bind("owner") String owner);
+    public void clearNotification(@Bind("id") long id, @Bind("owner") String owner);
 
     @SqlUpdate
     public void insertNotification(@Bind(binder = NotificationSqlDaoBinder.class) Notification evt);
@@ -71,12 +71,13 @@ public interface NotificationSqlDao extends Transactional<NotificationSqlDao>, C
 
         @Override
         public void bind(@SuppressWarnings("rawtypes") SQLStatement stmt, Bind bind, Notification evt) {
-            stmt.bind("notification_id", evt.getId().toString());
+            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", (String) null);
+            stmt.bind("processing_owner", evt.getOwner());
             stmt.bind("processing_state", NotificationLifecycleState.AVAILABLE.toString());
         }
     }
@@ -93,14 +94,16 @@ public interface NotificationSqlDao extends Transactional<NotificationSqlDao>, C
         public Notification map(int index, ResultSet r, StatementContext ctx)
         throws SQLException {
 
-            final UUID id = UUID.fromString(r.getString("notification_id"));
+            final long id = r.getLong("id");
+            final UUID uuid = UUID.fromString(r.getString("notification_id"));
             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 String processingOwner = r.getString("processing_owner");
             final NotificationLifecycleState processingState = NotificationLifecycleState.valueOf(r.getString("processing_state"));
 
-            return new DefaultNotification(id, processingOwner, nextAvailableDate,
+            return new DefaultNotification(id, uuid, 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 2946e13..26e6c4e 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
@@ -22,33 +22,41 @@ import org.joda.time.DateTime;
 
 public class DefaultNotification implements Notification {
 
-    private final UUID id;
+    private final long id;
+    private final UUID uuid;
     private final String owner;
+    private final String queueName;
     private final DateTime nextAvailableDate;
     private final NotificationLifecycleState lifecycleState;
     private final String notificationKey;
     private final DateTime effectiveDate;
 
 
-    public DefaultNotification(UUID id, String owner, DateTime nextAvailableDate,
+    public DefaultNotification(long id, UUID uuid, String owner, String queueName, DateTime nextAvailableDate,
             NotificationLifecycleState lifecycleState,
             String notificationKey, DateTime effectiveDate) {
         super();
         this.id = id;
+        this.uuid = uuid;
         this.owner = owner;
+        this.queueName = queueName;
         this.nextAvailableDate = nextAvailableDate;
         this.lifecycleState = lifecycleState;
         this.notificationKey = notificationKey;
         this.effectiveDate = effectiveDate;
     }
 
-    public DefaultNotification(String notificationKey, DateTime effectiveDate) {
-        this(UUID.randomUUID(), null, null, NotificationLifecycleState.AVAILABLE, notificationKey, 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);
+    }
     @Override
-    public UUID getId() {
-        return id;
+    public UUID getUUID() {
+        return uuid;
     }
 
     @Override
@@ -94,4 +102,10 @@ public class DefaultNotification implements Notification {
     public DateTime getEffectiveDate() {
         return effectiveDate;
     }
+
+	@Override
+	public String getQueueName() {
+		return queueName;
+	}
+
 }
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 80f7385..8e2aaf8 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
@@ -17,14 +17,10 @@
 package com.ning.billing.util.notificationq;
 
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.Date;
 import java.util.List;
-
 import org.joda.time.DateTime;
-import org.skife.jdbi.v2.DBI;
-import org.skife.jdbi.v2.Transaction;
-import org.skife.jdbi.v2.TransactionStatus;
+import org.skife.jdbi.v2.IDBI;
 import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
 
 import com.ning.billing.util.clock.Clock;
@@ -35,49 +31,49 @@ public class DefaultNotificationQueue extends NotificationQueueBase {
 
     protected final NotificationSqlDao dao;
 
-    public DefaultNotificationQueue(final DBI dbi, final Clock clock,  final String svcName, final String queueName, final NotificationQueueHandler handler, final NotificationConfig config) {
+    public DefaultNotificationQueue(final IDBI dbi, final Clock clock,  final String svcName, final String queueName, final NotificationQueueHandler handler, final NotificationConfig config) {
+
         super(clock, svcName, queueName, handler, config);
         this.dao = dbi.onDemand(NotificationSqlDao.class);
     }
 
     @Override
-    protected void doProcessEvents(int sequenceId) {
+    protected int doProcessEvents(final int sequenceId) {
+
+        logDebug("ENTER doProcessEvents");
         List<Notification> notifications = getReadyNotifications(sequenceId);
-        for (Notification cur : notifications) {
+        if (notifications.size() == 0) {
+            logDebug("EXIT doProcessEvents");
+            return 0;
+        }
+
+        logDebug("START processing %d events at time %s", notifications.size(), clock.getUTCNow().toDate());
+
+        int result = 0;
+        for (final Notification cur : notifications) {
             nbProcessedEvents.incrementAndGet();
-            handler.handleReadyNotification(cur.getNotificationKey());
+            logDebug("handling notification %s, key = %s for time %s",
+                    cur.getUUID(), 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());
         }
-        // If anything happens before we get to clear those notifications, somebody else will pick them up
-        clearNotifications(notifications);
+        return result;
     }
 
     @Override
     public void recordFutureNotificationFromTransaction(final Transmogrifier transactionalDao,
             final DateTime futureNotificationTime, final NotificationKey notificationKey) {
         NotificationSqlDao transactionalNotificationDao =  transactionalDao.become(NotificationSqlDao.class);
-        Notification notification = new DefaultNotification(notificationKey.toString(), futureNotificationTime);
+        Notification notification = new DefaultNotification(getFullQName(), notificationKey.toString(), futureNotificationTime);
         transactionalNotificationDao.insertNotification(notification);
     }
 
 
-    private void clearNotifications(final Collection<Notification> cleared) {
-
-        log.debug(String.format("NotificationQueue %s clearEventsReady START cleared size = %d",
-                getFullQName(),
-                cleared.size()));
-
-        dao.inTransaction(new Transaction<Void, NotificationSqlDao>() {
-
-            @Override
-            public Void inTransaction(NotificationSqlDao transactional,
-                    TransactionStatus status) throws Exception {
-                for (Notification cur : cleared) {
-                    transactional.clearNotification(cur.getId().toString(), hostname);
-                    log.debug(String.format("NotificationQueue %s cleared events %s", getFullQName(), cur.getId()));
-                }
-                return null;
-            }
-        });
+    private void clearNotification(final Notification cleared) {
+        dao.clearNotification(cleared.getId(), hostname);
     }
 
     private List<Notification> getReadyNotifications(final int seqId) {
@@ -85,35 +81,34 @@ public class DefaultNotificationQueue extends NotificationQueueBase {
         final Date now = clock.getUTCNow().toDate();
         final Date nextAvailable = clock.getUTCNow().plus(config.getDaoClaimTimeMs()).toDate();
 
-        log.debug(String.format("NotificationQueue %s getEventsReady START effectiveNow =  %s",  getFullQName(), now));
-
-        List<Notification> result = dao.inTransaction(new Transaction<List<Notification>, NotificationSqlDao>() {
-
-            @Override
-            public List<Notification> inTransaction(NotificationSqlDao transactionalDao,
-                    TransactionStatus status) throws Exception {
-
-                List<Notification> claimedNotifications = new ArrayList<Notification>();
-                List<Notification> input = transactionalDao.getReadyNotifications(now, config.getDaoMaxReadyEvents());
-                for (Notification cur : input) {
-                    final boolean claimed = (transactionalDao.claimNotification(hostname, nextAvailable, cur.getId().toString(), now) == 1);
-                    if (claimed) {
-                        claimedNotifications.add(cur);
-                        transactionalDao.insertClaimedHistory(seqId, hostname, now, cur.getId().toString());
-                    }
-                }
-                return claimedNotifications;
+        List<Notification> input = dao.getReadyNotifications(now, config.getDaoMaxReadyEvents(), 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);
+            logDebug("claimed notification %s, key = %s for time %s result = %s",
+                    cur.getUUID(), cur.getNotificationKey(), cur.getEffectiveDate(), Boolean.valueOf(claimed));
+            if (claimed) {
+                claimedNotifications.add(cur);
+                dao.insertClaimedHistory(seqId, hostname, now, cur.getUUID().toString());
             }
-        });
+        }
 
-        for (Notification cur : result) {
-            log.debug(String.format("NotificationQueue %sclaimed events %s",
-                    getFullQName(), cur.getId()));
+        for (Notification cur : claimedNotifications) {
             if (cur.getOwner() != null && !cur.getOwner().equals(hostname)) {
                 log.warn(String.format("NotificationQueue %s stealing notification %s from %s",
                         getFullQName(), cur, cur.getOwner()));
             }
         }
-        return result;
+        return claimedNotifications;
+    }
+
+    private void logDebug(String format, Object...args) {
+        if (log.isDebugEnabled()) {
+            String realDebug = String.format(format, args);
+            log.debug(String.format("Thread %d [queue = %s] %s", Thread.currentThread().getId(), getFullQName(), realDebug));
+        }
     }
 }
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 5181113..3b96ee4 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
@@ -16,17 +16,16 @@
 
 package com.ning.billing.util.notificationq;
 
-import org.skife.jdbi.v2.DBI;
-
+import org.skife.jdbi.v2.IDBI;
 import com.google.inject.Inject;
 import com.ning.billing.util.clock.Clock;
 
 public class DefaultNotificationQueueService extends NotificationQueueServiceBase {
 
-    private final DBI dbi;
+    private final IDBI dbi;
 
     @Inject
-    public DefaultNotificationQueueService(final DBI dbi, final Clock clock) {
+    public DefaultNotificationQueueService(final IDBI dbi, final Clock clock) {
         super(clock);
         this.dbi = dbi;
     }
@@ -37,5 +36,4 @@ public class DefaultNotificationQueueService extends NotificationQueueServiceBas
             NotificationConfig config) {
         return new DefaultNotificationQueue(dbi, clock, svcName, queueName, handler, config);
     }
-
 }
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 651469b..d59098b 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
@@ -23,9 +23,15 @@ import org.joda.time.DateTime;
 
 public interface Notification extends NotificationLifecycle {
 
-    public UUID getId();
+    public long getId();
+
+    public UUID getUUID();
 
     public String getNotificationKey();
 
     public DateTime getEffectiveDate();
+
+    public String getQueueName();
+
+
 }
diff --git a/util/src/main/java/com/ning/billing/util/notificationq/NotificationError.java b/util/src/main/java/com/ning/billing/util/notificationq/NotificationError.java
new file mode 100644
index 0000000..4e771ba
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/notificationq/NotificationError.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.util.notificationq;
+
+public class NotificationError extends Error {
+
+    private static final long serialVersionUID = 131398536;
+
+    public NotificationError() {
+        super();
+    }
+
+    public NotificationError(String msg, Throwable arg1) {
+        super(msg, arg1);
+    }
+
+    public NotificationError(String msg) {
+        super(msg);
+    }
+
+    public NotificationError(Throwable msg) {
+        super(msg);
+    }
+}
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 23f0de0..e1dcdbf 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
@@ -38,22 +38,28 @@ public interface NotificationQueue {
     * 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 void processReadyNotification();
+   public int processReadyNotification();
 
    /**
-    * Stops the queue.
+    * Stops the queue. Blocks until queue is completely stopped.
     *
     * @see NotificationQueueHandler.completedQueueStop to be notified when the notification thread exited
     */
    public void stopQueue();
 
    /**
-    * Starts the queue.
+    * 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 cefd1eb..cc1ea28 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,34 +17,28 @@
 package com.ning.billing.util.notificationq;
 
 import java.lang.Thread.UncaughtExceptionHandler;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Date;
-import java.util.List;
 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.joda.time.DateTime;
-import org.skife.jdbi.v2.DBI;
-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.ning.billing.util.Hostname;
 import com.ning.billing.util.clock.Clock;
 import com.ning.billing.util.notificationq.NotificationQueueService.NotificationQueueHandler;
-import com.ning.billing.util.notificationq.dao.NotificationSqlDao;
 
 
 public abstract class NotificationQueueBase 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);
+
     protected static final String NOTIFICATION_THREAD_PREFIX = "Notification-";
     protected final long STOP_WAIT_TIMEOUT_MS = 60000;
 
@@ -64,6 +58,9 @@ public abstract class NotificationQueueBase implements NotificationQueue {
     // 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;
@@ -91,15 +88,15 @@ public abstract class NotificationQueueBase implements NotificationQueue {
 
 
     @Override
-    public void processReadyNotification() {
-        doProcessEvents(sequenceId.incrementAndGet());
+    public int processReadyNotification() {
+        return doProcessEvents(sequenceId.incrementAndGet());
     }
 
 
     @Override
     public void stopQueue() {
         if (config.isNotificationProcessingOff()) {
-            handler.completedQueueStop();
+            completedQueueStop();
             return;
         }
 
@@ -113,7 +110,7 @@ public abstract class NotificationQueueBase implements NotificationQueue {
                 log.warn("NotificationQueue got interrupted exception when stopping notifications", e);
             }
         }
-
+        waitForNotificationStopCompletion();
     }
 
     @Override
@@ -125,7 +122,7 @@ public abstract class NotificationQueueBase implements NotificationQueue {
 
         if (config.isNotificationProcessingOff()) {
             log.warn(String.format("KILLBILL NOTIFICATION PROCESSING FOR SVC %s IS OFF !!!", getFullQName()));
-            handler.completedQueueStart();
+            completedQueueStart();
             return;
         }
         final NotificationQueueBase notificationQueue = this;
@@ -139,7 +136,7 @@ public abstract class NotificationQueueBase implements NotificationQueue {
                         Thread.currentThread().getId()));
 
                 // Thread is now started, notify the listener
-                handler.completedQueueStart();
+                completedQueueStart();
 
                 try {
                     while (true) {
@@ -171,7 +168,7 @@ public abstract class NotificationQueueBase implements NotificationQueue {
                     // Just to make it really obvious in the log
                     e.printStackTrace();
                 } finally {
-                    handler.completedQueueStop();
+                    completedQueueStop();
                     log.info(String.format("NotificationQueue thread  %s  [%d] exited...",
                             Thread.currentThread().getName(),
                             Thread.currentThread().getId()));
@@ -182,12 +179,64 @@ public abstract class NotificationQueueBase implements NotificationQueue {
                 Thread.sleep(config.getNotificationSleepTimeMs());
             }
         });
+        waitForNotificationStartCompletion();
+    }
+
+    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) {
 
-    protected String getFullQName() {
+        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 void doProcessEvents(int sequenceId);
+    protected abstract int doProcessEvents(int sequenceId);
 }
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 a18906b..4d56b03 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
@@ -16,33 +16,24 @@
 
 package com.ning.billing.util.notificationq;
 
-import java.util.NoSuchElementException;
+import org.joda.time.DateTime;
 
 
 public interface NotificationQueueService {
 
     public interface NotificationQueueHandler {
         /**
-         * Called when the Notification thread has been started
-         */
-        public void completedQueueStart();
-
-        /**
          * Called for each notification ready
          *
-         * @param key the notification key associated to that notification entry
-         */
-        public void handleReadyNotification(String notificationKey);
-        /**
-         * Called right before the Notification thread is about to exit
+         * @param notificationKey the notification key associated to that notification entry
          */
-        public void completedQueueStop();
-    }
+        public void handleReadyNotification(String notificationKey, DateTime eventDateTime);
+     }
 
-    public static final class NotficationQueueAlreadyExists extends Exception {
+    public static final class NotificationQueueAlreadyExists extends Exception {
         private static final long serialVersionUID = 1541281L;
 
-        public NotficationQueueAlreadyExists(String msg) {
+        public NotificationQueueAlreadyExists(String msg) {
             super(msg);
         }
     }
@@ -65,11 +56,11 @@ public interface NotificationQueueService {
      *
      * @return a new NotificationQueue
      *
-     * @throws NotficationQueueAlreadyExists is the queue associated with that service and name already exits
+     * @throws com.ning.billing.util.notificationq.NotificationQueueService.NotificationQueueAlreadyExists is the queue associated with that service and name already exits
      *
      */
-    NotificationQueue createNotificationQueue(final String svcName, final String queueName, final NotificationQueueHandler handler, final NotificationConfig config)
-        throws NotficationQueueAlreadyExists;
+    public NotificationQueue createNotificationQueue(final String svcName, final String queueName, final NotificationQueueHandler handler, final NotificationConfig config)
+        throws NotificationQueueAlreadyExists;
 
     /**
      * Retrieves an already created NotificationQueue by service and name if it exists
@@ -80,7 +71,14 @@ public interface NotificationQueueService {
      *
      * @throws NoSuchNotificationQueue if queue does not exist
      */
-    NotificationQueue getNotificationQueue(final String svcName, final String queueName)
+    public NotificationQueue getNotificationQueue(final String svcName, final String queueName)
         throws NoSuchNotificationQueue;
 
+
+    /**
+     *
+     * @param services
+     * @return the number of processed notifications
+     */
+    public int triggerManualQueueProcessing(final String [] services, final Boolean keepRunning);
 }
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 a4dc64e..3f8f26f 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
@@ -16,12 +16,16 @@
 
 package com.ning.billing.util.notificationq;
 
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
 import java.util.Map;
 import java.util.TreeMap;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.base.Joiner;
 import com.google.inject.Inject;
 import com.ning.billing.util.clock.Clock;
 
@@ -43,7 +47,7 @@ public abstract class NotificationQueueServiceBase implements NotificationQueueS
     @Override
     public NotificationQueue createNotificationQueue(String svcName,
             String queueName, NotificationQueueHandler handler,
-            NotificationConfig config) throws NotficationQueueAlreadyExists {
+            NotificationConfig config) throws NotificationQueueAlreadyExists {
         if (svcName == null || queueName == null || handler == null || config == null) {
             throw new RuntimeException("Need to specify all parameters");
         }
@@ -53,7 +57,7 @@ public abstract class NotificationQueueServiceBase implements NotificationQueueS
         synchronized(queues) {
             result = queues.get(compositeName);
             if (result != null) {
-                throw new NotficationQueueAlreadyExists(String.format("Queue for svc %s and name %s already exist",
+                throw new NotificationQueueAlreadyExists(String.format("Queue for svc %s and name %s already exist",
                         svcName, queueName));
             }
             result = createNotificationQueueInternal(svcName, queueName, handler, config);
@@ -79,6 +83,48 @@ public abstract class NotificationQueueServiceBase implements NotificationQueueS
     }
 
 
+    //
+    // Test ONLY
+    //
+    @Override
+    public int triggerManualQueueProcessing(final String [] services, final Boolean keepRunning) {
+
+        int result = 0;
+
+        List<NotificationQueue> manualQueues = null;
+        if (services == null) {
+            manualQueues = new ArrayList<NotificationQueue>(queues.values());
+        } else {
+            Joiner join = Joiner.on(",");
+            join.join(services);
+
+            log.info("Trigger manual processing for services {} ", join.toString());
+            manualQueues = new LinkedList<NotificationQueue>();
+            synchronized (queues) {
+                for (String svc : services) {
+                    addQueuesForService(manualQueues, svc);
+                }
+            }
+        }
+        for (NotificationQueue cur : manualQueues) {
+            int processedNotifications = 0;
+            do {
+                processedNotifications = cur.processReadyNotification();
+                log.info("Got {} results from queue {}", processedNotifications, cur.getFullQName());
+                result += processedNotifications;
+            } while(keepRunning && processedNotifications > 0);
+        }
+        return result;
+    }
+
+    private final void addQueuesForService(final List<NotificationQueue> result, final String svcName) {
+        for (String cur : queues.keySet()) {
+            if (cur.startsWith(svcName)) {
+                result.add(queues.get(cur));
+            }
+        }
+    }
+
     protected abstract NotificationQueue createNotificationQueueInternal(String svcName,
             String queueName, NotificationQueueHandler handler,
             NotificationConfig config);
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
new file mode 100644
index 0000000..0e1f99e
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/tag/api/DefaultTagDefinitionService.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.tag.api;
+
+import com.google.inject.Inject;
+import com.ning.billing.util.api.TagDefinitionService;
+import com.ning.billing.util.api.TagDefinitionUserApi;
+
+public class DefaultTagDefinitionService implements TagDefinitionService {
+    private static final String TAG_DEFINITION_SERVICE_NAME = "tag-service";
+    private final TagDefinitionUserApi api;
+
+    @Inject
+    public DefaultTagDefinitionService(final TagDefinitionUserApi api) {
+        this.api = api;
+    }
+
+    @Override
+    public TagDefinitionUserApi getTagDefinitionUserApi() {
+        return api;
+    }
+
+    @Override
+    public String getName() {
+        return TAG_DEFINITION_SERVICE_NAME;
+    }
+}
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
new file mode 100644
index 0000000..452eb5e
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/tag/api/DefaultTagDefinitionUserApi.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.util.tag.api;
+
+import java.util.List;
+import com.google.inject.Inject;
+import com.ning.billing.util.api.TagDefinitionApiException;
+import com.ning.billing.util.api.TagDefinitionUserApi;
+import com.ning.billing.util.tag.TagDefinition;
+import com.ning.billing.util.tag.dao.TagDefinitionDao;
+
+public class DefaultTagDefinitionUserApi implements TagDefinitionUserApi {
+    private TagDefinitionDao dao;
+
+    @Inject
+    public DefaultTagDefinitionUserApi(TagDefinitionDao dao) {
+        this.dao = dao;
+    }
+
+    @Override
+    public List<TagDefinition> getTagDefinitions() {
+        return dao.getTagDefinitions();
+    }
+
+    @Override
+    public TagDefinition create(final String name, final String description, final String createdBy) throws TagDefinitionApiException {
+        return dao.create(name, description, createdBy);
+    }
+
+    @Override
+    public void deleteAllTagsForDefinition(final String definitionName) throws TagDefinitionApiException {
+        dao.deleteAllTagsForDefinition(definitionName);
+    }
+
+    @Override
+    public void deleteTagDefinition(final String definitionName) throws TagDefinitionApiException {
+        dao.deleteAllTagsForDefinition(definitionName);
+    }
+}
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
new file mode 100644
index 0000000..57ca679
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/tag/dao/DefaultTagDefinitionDao.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * 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 java.util.ArrayList;
+import java.util.List;
+import org.skife.jdbi.v2.IDBI;
+import com.google.inject.Inject;
+import com.ning.billing.ErrorCode;
+import com.ning.billing.account.api.ControlTagType;
+import com.ning.billing.util.api.TagDefinitionApiException;
+import com.ning.billing.util.clock.Clock;
+import com.ning.billing.util.tag.DefaultTagDefinition;
+import com.ning.billing.util.tag.Tag;
+import com.ning.billing.util.tag.TagDefinition;
+
+public class DefaultTagDefinitionDao implements TagDefinitionDao {
+    private final TagDefinitionSqlDao dao;
+    private final Clock clock;
+
+    @Inject
+    public DefaultTagDefinitionDao(IDBI dbi, Clock clock) {
+        this.dao = dbi.onDemand(TagDefinitionSqlDao.class);
+        this.clock = clock;
+    }
+
+    @Override
+    public List<TagDefinition> getTagDefinitions() {
+        // get user definitions from the database
+        List<TagDefinition> definitionList = new ArrayList<TagDefinition>();
+        definitionList.addAll(dao.get());
+
+        // add control tag definitions
+        for (ControlTagType controlTag : ControlTagType.values()) {
+            definitionList.add(new DefaultTagDefinition(controlTag.toString(), controlTag.getDescription(), null, null));
+        }
+
+        return definitionList;
+    }
+
+    @Override
+    public TagDefinition getByName(final String definitionName) {
+        return dao.getByName(definitionName);
+    }
+
+    @Override
+    public TagDefinition create(final String definitionName, final String description, final String createdBy) throws TagDefinitionApiException {
+        if (isControlTagName(definitionName)) {
+            throw new TagDefinitionApiException(ErrorCode.TAG_DEFINITION_CONFLICTS_WITH_CONTROL_TAG, definitionName);
+        }
+
+        TagDefinition existingDefinition = dao.getByName(definitionName);
+
+        if (existingDefinition != null) {
+            throw new TagDefinitionApiException(ErrorCode.TAG_DEFINITION_ALREADY_EXISTS, definitionName);
+        }
+
+        TagDefinition definition = new DefaultTagDefinition(definitionName, description, createdBy, clock.getUTCNow());
+        dao.create(definition);
+        return definition;
+    }
+
+    private boolean isControlTagName(final String definitionName) {
+        for (ControlTagType controlTagName : ControlTagType.values()) {
+            if (controlTagName.toString().equals(definitionName)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    @Override
+    public void deleteAllTagsForDefinition(final String definitionName) throws TagDefinitionApiException {
+        TagDefinition existingDefinition = dao.getByName(definitionName);
+        if (existingDefinition == null) {
+            throw new TagDefinitionApiException(ErrorCode.TAG_DEFINITION_DOES_NOT_EXIST, definitionName);
+        }
+
+        dao.deleteAllTagsForDefinition(definitionName);
+    }
+
+    @Override
+    public void deleteTagDefinition(final String definitionName) throws TagDefinitionApiException {
+        if (dao.tagDefinitionUsageCount(definitionName) > 0) {
+            throw new TagDefinitionApiException(ErrorCode.TAG_DEFINITION_IN_USE, definitionName);
+        }
+
+        TagDefinition existingDefinition = dao.getByName(definitionName);
+
+        if (existingDefinition == null) {
+            throw new TagDefinitionApiException(ErrorCode.TAG_DEFINITION_DOES_NOT_EXIST, definitionName);
+        }
+
+        dao.deleteTagDefinition(definitionName);
+    }
+}
diff --git a/util/src/main/java/com/ning/billing/util/tag/dao/TagDefinitionDao.java b/util/src/main/java/com/ning/billing/util/tag/dao/TagDefinitionDao.java
new file mode 100644
index 0000000..3164518
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/tag/dao/TagDefinitionDao.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.tag.dao;
+
+import java.util.List;
+import com.ning.billing.util.api.TagDefinitionApiException;
+import com.ning.billing.util.tag.TagDefinition;
+
+public interface TagDefinitionDao {
+    public List<TagDefinition> getTagDefinitions();
+
+    public TagDefinition getByName(String definitionName);
+
+    public TagDefinition create(String definitionName, String description, String createdBy) throws TagDefinitionApiException;
+
+    public void deleteAllTagsForDefinition(String definitionName) throws TagDefinitionApiException;
+
+    public void deleteTagDefinition(String definitionName) throws TagDefinitionApiException;
+}
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
new file mode 100644
index 0000000..1296083
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/tag/dao/TagMapper.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.util.tag.dao;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.UUID;
+import org.joda.time.DateTime;
+import org.skife.jdbi.v2.StatementContext;
+import org.skife.jdbi.v2.tweak.ResultSetMapper;
+import com.ning.billing.account.api.ControlTagType;
+import com.ning.billing.util.tag.DefaultControlTag;
+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.TagDefinition;
+
+public class TagMapper implements ResultSetMapper<Tag> {
+    @Override
+    public Tag map(final int index, final ResultSet result, final StatementContext context) throws SQLException {
+        String name = result.getString("tag_definition_name");
+
+        UUID id = UUID.fromString(result.getString("id"));
+        String addedBy = result.getString("added_by");
+        DateTime addedDate = new DateTime(result.getTimestamp("added_date"));
+
+        Tag tag;
+        try {
+            ControlTagType controlTagType = ControlTagType.valueOf(name);
+            tag = new DefaultControlTag(id, addedBy, addedDate, controlTagType);
+        } catch (Throwable t) {
+            String description = result.getString("tag_description");
+            String createdBy = result.getString("created_by");
+            DateTime creationDate = new DateTime(result.getDate("creation_date"));
+
+            UUID tagDefinitionId = UUID.fromString(result.getString("tag_definition_id"));
+            TagDefinition tagDefinition = new DefaultTagDefinition(tagDefinitionId, name, description, createdBy, creationDate);
+            tag = new DescriptiveTag(id, tagDefinition, addedBy, addedDate);
+        }
+
+        return tag;
+    }
+}
diff --git a/util/src/main/java/com/ning/billing/util/tag/dao/TagStoreSqlDao.java b/util/src/main/java/com/ning/billing/util/tag/dao/TagStoreSqlDao.java
new file mode 100644
index 0000000..bf4ed62
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/tag/dao/TagStoreSqlDao.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.util.tag.dao;
+
+
+import java.util.List;
+
+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.stringtemplate.ExternalizedSqlViaStringTemplate3;
+import com.ning.billing.util.entity.EntityCollectionDao;
+import com.ning.billing.util.tag.Tag;
+
+@ExternalizedSqlViaStringTemplate3
+@RegisterMapper(TagMapper.class)
+public interface TagStoreSqlDao extends EntityCollectionDao<Tag>, Transactional<TagStoreSqlDao> {
+    @Override
+    @SqlBatch(transactional=false)
+    public void batchSaveFromTransaction(@Bind("objectId") final String objectId,
+                     @Bind("objectType") final String objectType,
+                     @TagBinder final List<Tag> entities);
+}
\ 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
new file mode 100644
index 0000000..707526d
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/tag/DefaultControlTag.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.tag;
+
+import java.util.UUID;
+import org.joda.time.DateTime;
+import com.ning.billing.account.api.ControlTagType;
+
+public class DefaultControlTag extends DescriptiveTag implements ControlTag {
+    private final ControlTagType controlTagType;
+
+    public DefaultControlTag(final String addedBy,
+                             final DateTime addedDate, final ControlTagType controlTagType) {
+        this(UUID.randomUUID(), addedBy, addedDate, controlTagType);
+    }
+
+    public DefaultControlTag(final UUID id, final String addedBy,
+                             final DateTime addedDate, final ControlTagType controlTagType) {
+
+        super(id, controlTagType.toString(), addedBy, addedDate);
+        this.controlTagType = controlTagType;
+    }
+
+    @Override
+    public ControlTagType getControlTagType() {
+        return controlTagType;
+    }
+}
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 5d49fb2..2fd8144 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,6 +17,7 @@
 package com.ning.billing.util.tag;
 
 import java.util.UUID;
+import com.ning.billing.account.api.ControlTagType;
 import com.ning.billing.util.entity.EntityCollectionBase;
 
 public class DefaultTagStore extends EntityCollectionBase<Tag> implements TagStore {
@@ -26,7 +27,7 @@ public class DefaultTagStore extends EntityCollectionBase<Tag> implements TagSto
 
     @Override
     public String getEntityKey(final Tag entity) {
-        return entity.getName();
+        return entity.getTagDefinitionName();
     }
 
     @Override
@@ -36,10 +37,14 @@ public class DefaultTagStore extends EntityCollectionBase<Tag> implements TagSto
      */
     public boolean processPayment() {
         for (Tag tag : entities.values()) {
-            if (!tag.getProcessPayment()) {
-                return false;
+            if (tag instanceof ControlTag) {
+                ControlTag controlTag = (ControlTag) tag;
+                if (controlTag.getControlTagType() == ControlTagType.AUTO_BILLING_OFF) {
+                    return false;
+                }
             }
         }
+
         return true;
     }
 
@@ -50,10 +55,14 @@ public class DefaultTagStore extends EntityCollectionBase<Tag> implements TagSto
     @Override
     public boolean generateInvoice() {
         for (Tag tag : entities.values()) {
-            if (!tag.getGenerateInvoice()) {
-                return false;
+            if (tag instanceof ControlTag) {
+                ControlTag controlTag = (ControlTag) tag;
+                if (controlTag.getControlTagType() == ControlTagType.AUTO_INVOICING_OFF) {
+                    return false;
+                }
             }
         }
+
         return true;
     }
 
@@ -65,7 +74,7 @@ public class DefaultTagStore extends EntityCollectionBase<Tag> implements TagSto
     @Override
     public boolean containsTag(final String tagName) {
         for (Tag tag : entities.values()) {
-            if (tag.getName().equals(tagName)) {
+            if (tag.getTagDefinitionName().equals(tagName)) {
                 return true;
             }
         }
diff --git a/util/src/main/java/com/ning/billing/util/tag/TagBuilder.java b/util/src/main/java/com/ning/billing/util/tag/TagBuilder.java
index cd4247f..626acd3 100644
--- a/util/src/main/java/com/ning/billing/util/tag/TagBuilder.java
+++ b/util/src/main/java/com/ning/billing/util/tag/TagBuilder.java
@@ -21,10 +21,7 @@ import org.joda.time.DateTime;
 
 public class TagBuilder {
     private UUID id = UUID.randomUUID();
-    private UUID tagDescriptionId;
     private String name;
-    private boolean processPayment;
-    private boolean generateInvoice;
     private String addedBy;
     private DateTime dateAdded;
 
@@ -33,23 +30,8 @@ public class TagBuilder {
         return this;
     }
 
-    public TagBuilder tagDescriptionId(UUID tagDescriptionId) {
-        this.tagDescriptionId = tagDescriptionId;
-        return this;
-    }
-
-    public TagBuilder name(String name) {
-        this.name = name;
-        return this;
-    }
-
-    public TagBuilder processPayment(boolean processPayment) {
-        this.processPayment = processPayment;
-        return this;
-    }
-
-    public TagBuilder generateInvoice(boolean generateInvoice) {
-        this.generateInvoice = generateInvoice;
+    public TagBuilder tagDescriptionName(String tagDescriptionName) {
+        this.name = tagDescriptionName;
         return this;
     }
 
@@ -64,6 +46,6 @@ public class TagBuilder {
     }
 
     public Tag build() {
-        return new DefaultTag(id, tagDescriptionId, name, processPayment, generateInvoice, addedBy, dateAdded);
+        return new DescriptiveTag(id, name, addedBy, dateAdded);
     }
 }
diff --git a/util/src/main/resources/com/ning/billing/util/customfield/dao/FieldStoreDao.sql.stg b/util/src/main/resources/com/ning/billing/util/customfield/dao/FieldStoreDao.sql.stg
index 57163a9..9d3e96e 100644
--- a/util/src/main/resources/com/ning/billing/util/customfield/dao/FieldStoreDao.sql.stg
+++ b/util/src/main/resources/com/ning/billing/util/customfield/dao/FieldStoreDao.sql.stg
@@ -1,6 +1,6 @@
-group IFieldStoreDao;
+group FieldStoreDao;
 
-save() ::= <<
+batchSaveFromTransaction() ::= <<
   INSERT INTO custom_fields(id, object_id, object_type, field_name, field_value)
   VALUES (:id, :objectId, :objectType, :fieldName, :fieldValue)
   ON DUPLICATE KEY UPDATE
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 30471c7..a0ef302 100644
--- a/util/src/main/resources/com/ning/billing/util/ddl.sql
+++ b/util/src/main/resources/com/ning/billing/util/ddl.sql
@@ -11,30 +11,29 @@ CREATE INDEX custom_fields_object_id_object_type ON custom_fields(object_id, obj
 CREATE UNIQUE INDEX custom_fields_unique ON custom_fields(object_id, object_type, field_name);
 
 DROP TABLE IF EXISTS tag_descriptions;
-CREATE TABLE tag_descriptions (
+DROP TABLE IF EXISTS tag_definitions;
+CREATE TABLE tag_definitions (
   id char(36) NOT NULL,
   name varchar(20) NOT NULL,
   created_by varchar(50) NOT NULL,
   creation_date datetime NOT NULL,
   description varchar(200) NOT NULL,
-  generate_invoice boolean DEFAULT false,
-  process_payment boolean DEFAULT false,
   PRIMARY KEY(id)
 ) ENGINE=innodb;
-CREATE UNIQUE INDEX tag_descriptions_name ON tag_descriptions(name);
+CREATE UNIQUE INDEX tag_definitions_name ON tag_definitions(name);
 
 DROP TABLE IF EXISTS tags;
 CREATE TABLE tags (
   id char(36) NOT NULL,
-  tag_description_id char(36) NOT NULL,
+  tag_definition_name varchar(20) NOT NULL,
   object_id char(36) NOT NULL,
   object_type varchar(30) NOT NULL,
-  date_added datetime NOT NULL,
+  added_date datetime NOT NULL,
   added_by varchar(50) NOT NULL,
   PRIMARY KEY(id)
 ) ENGINE = innodb;
 CREATE INDEX tags_by_object ON tags(object_id);
-CREATE UNIQUE INDEX tags_unique ON tags(tag_description_id, object_id);
+CREATE UNIQUE INDEX tags_unique ON tags(tag_definition_name, object_id);
 
 DROP TABLE IF EXISTS notifications;
 CREATE TABLE notifications (
@@ -43,14 +42,14 @@ CREATE TABLE notifications (
     created_dt datetime NOT NULL,
 	notification_key varchar(256) NOT NULL,
     effective_dt datetime NOT NULL,
-    processing_owner char(36) DEFAULT NULL,
+    queue_name char(64) NOT NULL,
+    processing_owner char(50) DEFAULT NULL,
     processing_available_dt datetime DEFAULT NULL,
     processing_state varchar(14) DEFAULT 'AVAILABLE',
     PRIMARY KEY(id)
 ) ENGINE=innodb;
-CREATE INDEX  `idx_comp_where` ON notifications (`effective_dt`,`processing_state`,`processing_owner`,`processing_available_dt`);
-CREATE INDEX  `idx_update` ON notifications (`notification_id`,`processing_state`,`processing_owner`,`processing_available_dt`);
-CREATE INDEX  `idx_update1` ON notifications (`notification_id`,`processing_owner`);
+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`);
 
 DROP TABLE IF EXISTS claimed_notifications;
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 5a44431..7a7ecab 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
@@ -2,48 +2,49 @@ group NotificationSqlDao;
 
 getReadyNotifications(now, max) ::= <<
     select
-      notification_id
-    , notification_key
+      id
+      ,  notification_id
+      , notification_key
       , created_dt
       , effective_dt
+      , queue_name
       , processing_owner
       , processing_available_dt
       , processing_state
     from notifications
     where
       effective_dt \<= :now
+      and queue_name = :queue_name
       and processing_state != 'PROCESSED'
       and (processing_owner IS NULL OR processing_available_dt \<= :now)
     order by
       effective_dt asc
       , created_dt asc
-      , id asc
+      , id
     limit :max
     ;
 >>
 
 
-claimNotification(owner, next_available, notification_id, now) ::= <<
+claimNotification(owner, next_available, id, now) ::= <<
     update notifications
     set
       processing_owner = :owner
       , processing_available_dt = :next_available
       , processing_state = 'IN_PROCESSING'
     where
-      notification_id = :notification_id
+      id = :id
       and processing_state != 'PROCESSED'
       and (processing_owner IS NULL OR processing_available_dt \<= :now)
     ;
 >>
 
-clearNotification(notification_id, owner) ::= <<
+clearNotification(id, owner) ::= <<
     update notifications
     set
-      processing_owner = NULL
-      , processing_state = 'PROCESSED'
+      processing_state = 'PROCESSED'
     where
-      notification_id = :notification_id
-      and processing_owner = :owner
+      id = :id
     ;
 >>
 
@@ -53,6 +54,7 @@ insertNotification() ::= <<
     , notification_key
       , created_dt
       , effective_dt
+      , queue_name
       , processing_owner
       , processing_available_dt
       , processing_state
@@ -61,6 +63,7 @@ insertNotification() ::= <<
       , :notification_key
       , :created_dt
       , :effective_dt
+      , :queue_name
       , :processing_owner
       , :processing_available_dt
       , :processing_state
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
new file mode 100644
index 0000000..72268d0
--- /dev/null
+++ b/util/src/main/resources/com/ning/billing/util/tag/dao/TagDefinitionSqlDao.sql.stg
@@ -0,0 +1,49 @@
+group TagDefinitionDao;
+
+get() ::= <<
+  SELECT id, name, created_by, creation_date, description
+  FROM tag_definitions;
+>>
+
+create() ::= <<
+  INSERT INTO tag_definitions(id, name, created_by, creation_date, description)
+  VALUES(:id, :name, :createdBy, :creationDate, :description);
+>>
+
+update() ::= <<
+  UPDATE tag_definitions
+  SET name = :name, created_by = :createdBy, creation_date = :creationDate,
+      description = :description)
+  WHERE id = :id;
+>>
+
+load() ::= <<
+  SELECT id, name, created_by, creation_date, description
+  FROM tag_definitions
+  WHERE id = :id;
+>>
+
+deleteAllTagsForDefinition() ::= <<
+  DELETE FROM tags
+  WHERE tag_definition_name = :name;
+>>
+
+deleteTagDefinition() ::= <<
+  DELETE FROM tag_definitions
+  WHERE name = :name;
+>>
+
+tagDefinitionUsageCount() ::= <<
+  SELECT COUNT(id)
+  FROM tags
+  WHERE tag_definition_name = :name
+>>
+
+getByName() ::= <<
+  SELECT id, name, created_by, creation_date, description
+  FROM tag_definitions
+  WHERE name = :name;
+>>
+;
+
+
diff --git a/util/src/test/java/com/ning/billing/dbi/DBIProvider.java b/util/src/test/java/com/ning/billing/dbi/DBIProvider.java
index a4a7b61..d83c033 100644
--- a/util/src/test/java/com/ning/billing/dbi/DBIProvider.java
+++ b/util/src/test/java/com/ning/billing/dbi/DBIProvider.java
@@ -25,13 +25,14 @@ import com.ning.jdbi.metrics.MetricsTimingCollector;
 import com.ning.jdbi.metrics.SqlJdbiGroupStrategy;
 import com.yammer.metrics.core.MetricsRegistry;
 import org.skife.jdbi.v2.DBI;
+import org.skife.jdbi.v2.IDBI;
 import org.skife.jdbi.v2.TimingCollector;
 import org.skife.jdbi.v2.logging.Log4JLog;
 import org.skife.jdbi.v2.tweak.SQLLog;
 
 import java.util.concurrent.TimeUnit;
 
-public class DBIProvider implements Provider<DBI>
+public class DBIProvider implements Provider<IDBI>
 {
     private final MetricsRegistry metricsRegistry;
     private final DbiConfig config;
@@ -44,7 +45,7 @@ public class DBIProvider implements Provider<DBI>
     }
 
     @Override
-    public DBI get()
+    public IDBI get()
     {
         final BoneCPConfig dbConfig = new BoneCPConfig();
         dbConfig.setJdbcUrl(config.getJdbcUrl());
@@ -54,7 +55,7 @@ public class DBIProvider implements Provider<DBI>
         dbConfig.setMaxConnectionsPerPartition(config.getMaxActive());
         dbConfig.setConnectionTimeout(config.getConnectionTimeout().getPeriod(), config.getConnectionTimeout().getUnit());
         dbConfig.setPartitionCount(1);
-        dbConfig.setDefaultTransactionIsolation("READ_COMMITTED");
+        dbConfig.setDefaultTransactionIsolation("REPEATABLE_READ");
         dbConfig.setDisableJMX(false);
 
         final BoneCPDataSource ds = new BoneCPDataSource(dbConfig);
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 e8a77f6..5ee7e88 100644
--- a/util/src/test/java/com/ning/billing/dbi/MysqlTestingHelper.java
+++ b/util/src/test/java/com/ning/billing/dbi/MysqlTestingHelper.java
@@ -16,8 +16,12 @@
 
 package com.ning.billing.dbi;
 
-import com.mysql.management.MysqldResource;
-import com.mysql.management.MysqldResourceI;
+import java.io.File;
+import java.io.IOException;
+import java.net.ServerSocket;
+import java.util.HashMap;
+import java.util.Map;
+
 import org.apache.commons.io.FileUtils;
 import org.skife.jdbi.v2.DBI;
 import org.skife.jdbi.v2.Handle;
@@ -27,17 +31,17 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.testng.Assert;
 
-import java.io.File;
-import java.io.IOException;
-import java.net.ServerSocket;
-import java.util.HashMap;
-import java.util.Map;
+import com.mysql.management.MysqldResource;
+import com.mysql.management.MysqldResourceI;
 
 /**
  * Utility class to embed MySQL for testing purposes
  */
 public class MysqlTestingHelper
 {
+
+    public static final String USE_LOCAL_DB_PROP = "com.ning.billing.dbi.test.useLocalDb";
+
     private static final Logger log = LoggerFactory.getLogger(MysqlTestingHelper.class);
 
     private static final String DB_NAME = "test_killbill";
@@ -46,24 +50,38 @@ public class MysqlTestingHelper
 
     private File dbDir;
     private MysqldResource mysqldResource;
-    private int port = 0;
+    private int port;
 
     public MysqlTestingHelper()
     {
-        // New socket on any free port
-        final ServerSocket socket;
-        try {
-            socket = new ServerSocket(0);
-            port = socket.getLocalPort();
-            socket.close();
-        }
-        catch (IOException e) {
-            Assert.fail();
+        if (isUsingLocalInstance()) {
+            port = 3306;
+        } else {
+            // New socket on any free port
+            final ServerSocket socket;
+            try {
+                socket = new ServerSocket(0);
+                port = socket.getLocalPort();
+                socket.close();
+            }
+            catch (IOException e) {
+                Assert.fail();
+            }
         }
     }
 
+
+    public boolean isUsingLocalInstance() {
+        return (System.getProperty(USE_LOCAL_DB_PROP) != null);
+    }
+
     public void startMysql() throws IOException
     {
+
+        if (isUsingLocalInstance()) {
+            return;
+        }
+
         dbDir = File.createTempFile("mysql", "");
         dbDir.delete();
         dbDir.mkdir();
@@ -73,7 +91,7 @@ public class MysqlTestingHelper
         dbOpts.put(MysqldResourceI.PORT, Integer.toString(port));
         dbOpts.put(MysqldResourceI.INITIALIZE_USER, "true");
         dbOpts.put(MysqldResourceI.INITIALIZE_USER_NAME, USERNAME);
-        dbOpts.put(MysqldResourceI.INITIALIZE_PASSWORD, PASSWORD);
+        dbOpts.put("default-time-zone", "+00:00");
 
         mysqldResource.start("test-mysqld-thread", dbOpts);
         if (!mysqldResource.isRunning()) {
@@ -86,6 +104,12 @@ public class MysqlTestingHelper
 
     public void cleanupTable(final String table)
     {
+
+        if (!isUsingLocalInstance() && (mysqldResource == null || !mysqldResource.isRunning())) {
+            log.error("Asked to cleanup table " + table + " but MySQL is not running!");
+            return;
+        }
+
         if (mysqldResource == null || !mysqldResource.isRunning()) {
             log.error("Asked to cleanup table " + table + " but MySQL is not running!");
             return;
@@ -113,7 +137,7 @@ public class MysqlTestingHelper
         }
     }
 
-    public DBI getDBI()
+    public IDBI getDBI()
     {
         final String dbiString = "jdbc:mysql://localhost:" + port + "/" + DB_NAME + "?createDatabaseIfNotExist=true";
         return new DBI(dbiString, USERNAME, PASSWORD);
@@ -121,6 +145,9 @@ public class MysqlTestingHelper
 
     public void initDb(final String ddl) throws IOException
     {
+        if (isUsingLocalInstance()) {
+            return;
+        }
         final IDBI dbi = getDBI();
         dbi.withHandle(new HandleCallback<Void>()
         {
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 7698697..ad3e5d3 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
@@ -19,6 +19,8 @@ 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;
@@ -26,6 +28,8 @@ import java.util.List;
 // STEPH should really be in tests but not accessible from other sub modules
 public class ClockMock extends DefaultClock {
 
+    private static final Logger log = LoggerFactory.getLogger(ClockMock.class);
+
     private enum DeltaType {
         DELTA_NONE,
         DELTA_DURATION,
@@ -34,13 +38,13 @@ public class ClockMock extends DefaultClock {
 
     private long deltaFromRealityMs;
     private List<Duration> deltaFromRealityDuration;
-    private long deltaFromRealitDurationEpsilon;
+    private long deltaFromRealityDurationEpsilon;
     private DeltaType deltaType;
 
     public ClockMock() {
         deltaType = DeltaType.DELTA_NONE;
         deltaFromRealityMs = 0;
-        deltaFromRealitDurationEpsilon = 0;
+        deltaFromRealityDurationEpsilon = 0;
         deltaFromRealityDuration = null;
     }
 
@@ -54,41 +58,53 @@ public class ClockMock extends DefaultClock {
         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);
-        deltaFromRealitDurationEpsilon = epsilon;
+        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;
-        deltaFromRealitDurationEpsilon = 0;
+        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;
-        deltaFromRealitDurationEpsilon = 0;
+        deltaFromRealityDurationEpsilon = 0;
         deltaFromRealityMs += delta;
+        logClockAdjustment(prev, getUTCNow());
     }
 
     public synchronized void resetDeltaFromReality() {
         deltaType = DeltaType.DELTA_NONE;
         deltaFromRealityDuration = null;
-        deltaFromRealitDurationEpsilon = 0;
+        deltaFromRealityDurationEpsilon = 0;
         deltaFromRealityMs = 0;
     }
 
@@ -109,8 +125,6 @@ public class ClockMock extends DefaultClock {
 
         DateTime result = input;
         for (Duration cur : deltaFromRealityDuration) {
-
-            int length = cur.getNumber();
             switch (cur.getUnit()) {
             case DAYS:
                 result = result.plusDays(cur.getNumber());
@@ -126,11 +140,11 @@ public class ClockMock extends DefaultClock {
 
             case UNLIMITED:
             default:
-                throw new RuntimeException("ClockMock is adjusting an unlimtited time period");
+                throw new RuntimeException("ClockMock is adjusting an unlimited time period");
             }
         }
-        if (deltaFromRealitDurationEpsilon != 0) {
-            result = result.plus(deltaFromRealitDurationEpsilon);
+        if (deltaFromRealityDurationEpsilon != 0) {
+            result = result.plus(deltaFromRealityDurationEpsilon);
         }
         return result;
     }
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
new file mode 100644
index 0000000..89de280
--- /dev/null
+++ b/util/src/test/java/com/ning/billing/util/clock/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.util.clock;
+
+import com.google.inject.AbstractModule;
+
+
+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/util/customfield/TestFieldStore.java b/util/src/test/java/com/ning/billing/util/customfield/TestFieldStore.java
new file mode 100644
index 0000000..4512a5d
--- /dev/null
+++ b/util/src/test/java/com/ning/billing/util/customfield/TestFieldStore.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * 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.customfield;
+
+import java.io.IOException;
+import java.util.UUID;
+import org.apache.commons.io.IOUtils;
+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 org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+import com.ning.billing.dbi.MysqlTestingHelper;
+import com.ning.billing.util.customfield.dao.FieldStoreDao;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.fail;
+
+@Test(groups={"util"})
+public class TestFieldStore {
+    Logger log = LoggerFactory.getLogger(TestFieldStore.class);
+    private final MysqlTestingHelper helper = new MysqlTestingHelper();
+    private IDBI dbi;
+
+    @BeforeClass(alwaysRun = true)
+    protected void setup() throws IOException {
+        // Health check test to make sure MySQL is setup properly
+        try {
+            final String utilDdl = IOUtils.toString(TestFieldStore.class.getResourceAsStream("/com/ning/billing/util/ddl.sql"));
+
+            helper.startMysql();
+            helper.initDb(utilDdl);
+
+            dbi = helper.getDBI();
+        }
+        catch (Throwable t) {
+            log.error("Setup failed", t);
+            fail(t.toString());
+        }
+    }
+
+    @AfterClass(alwaysRun = true)
+    public void stopMysql()
+    {
+        helper.stopMysql();
+    }
+
+    @Test
+    public void testFieldStore() {
+        final UUID id = UUID.randomUUID();
+        final String objectType = "Test widget";
+
+        final FieldStore fieldStore1 = new DefaultFieldStore(id, objectType);
+
+        String fieldName = "TestField1";
+        String fieldValue = "Kitty Hawk";
+        fieldStore1.setValue(fieldName, fieldValue);
+
+        FieldStoreDao fieldStoreDao = dbi.onDemand(FieldStoreDao.class);
+        fieldStoreDao.inTransaction(new Transaction<Void, FieldStoreDao>() {
+            @Override
+            public Void inTransaction(FieldStoreDao transactional,
+                    TransactionStatus status) throws Exception {
+                transactional.batchSaveFromTransaction(id.toString(), objectType, fieldStore1.getEntityList());
+                return null;
+            }
+        });
+
+
+        final FieldStore fieldStore2 = DefaultFieldStore.create(id, objectType);
+        fieldStore2.add(fieldStoreDao.load(id.toString(), objectType));
+
+        assertEquals(fieldStore2.getValue(fieldName), fieldValue);
+
+        fieldValue = "Cape Canaveral";
+        fieldStore2.setValue(fieldName, fieldValue);
+        assertEquals(fieldStore2.getValue(fieldName), fieldValue);
+        fieldStoreDao.inTransaction(new Transaction<Void, FieldStoreDao>() {
+            @Override
+            public Void inTransaction(FieldStoreDao transactional,
+                    TransactionStatus status) throws Exception {
+                transactional.batchSaveFromTransaction(id.toString(), objectType, fieldStore2.getEntityList());
+                return null;
+            }
+        });
+
+
+        final FieldStore fieldStore3 = DefaultFieldStore.create(id, objectType);
+        assertEquals(fieldStore3.getValue(fieldName), null);
+        fieldStore3.add(fieldStoreDao.load(id.toString(), objectType));
+
+        assertEquals(fieldStore3.getValue(fieldName), fieldValue);
+    }
+}
diff --git a/util/src/test/java/com/ning/billing/util/globallocker/MockGlobalLocker.java b/util/src/test/java/com/ning/billing/util/globallocker/MockGlobalLocker.java
new file mode 100644
index 0000000..b1f3378
--- /dev/null
+++ b/util/src/test/java/com/ning/billing/util/globallocker/MockGlobalLocker.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.util.globallocker;
+
+import com.ning.billing.util.globallocker.GlobalLock;
+import com.ning.billing.util.globallocker.GlobalLocker;
+
+public class MockGlobalLocker implements GlobalLocker {
+
+    @Override
+    public GlobalLock lockWithNumberOfTries(LockerService service,
+            String lockKey, int retry) {
+        return new GlobalLock() {
+            @Override
+            public void release() {
+            }
+        };
+    }
+
+    @Override
+    public Boolean isFree(LockerService service, String lockKey) {
+        return Boolean.TRUE;
+    }
+}
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
new file mode 100644
index 0000000..1fd8290
--- /dev/null
+++ b/util/src/test/java/com/ning/billing/util/globallocker/TestMysqlGlobalLocker.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.util.globallocker;
+
+import java.io.IOException;
+import java.util.UUID;
+
+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.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.AbstractModule;
+import com.google.inject.Inject;
+import com.ning.billing.dbi.MysqlTestingHelper;
+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.MySqlGlobalLocker;
+import com.ning.billing.util.globallocker.GlobalLocker.LockerService;
+
+@Guice(modules=TestMysqlGlobalLocker.TestMysqlGlobalLockerModule.class)
+public class TestMysqlGlobalLocker {
+
+    @Inject
+    private IDBI dbi;
+
+    @Inject
+    private MysqlTestingHelper helper;
+
+    @BeforeClass(alwaysRun=true)
+    public void setup() throws IOException  {
+        helper.startMysql();
+        createSimpleTable(dbi);
+    }
+
+    @AfterClass(alwaysRun=true)
+    public void tearDown() {
+        helper.stopMysql();
+    }
+
+    // Used as a manual test to validate the simple DAO by stepping through that locking is done and release correctly
+    @Test(groups= "slow", enabled = true)
+    public void testSimpleLocking() {
+
+        final String lockName = UUID.randomUUID().toString();
+
+        GlobalLocker locker = new MySqlGlobalLocker(dbi);
+        GlobalLock lock = locker.lockWithNumberOfTries(LockerService.INVOICE, lockName, 3);
+
+        dbi.inTransaction(new TransactionCallback<Void>() {
+            @Override
+            public Void inTransaction(Handle conn, TransactionStatus status)
+                    throws Exception {
+                conn.execute("insert into dummy (dummy_id) values ('" + UUID.randomUUID().toString()  + "')");
+                return null;
+            }
+        });
+        Assert.assertEquals(locker.isFree(LockerService.INVOICE, lockName), Boolean.FALSE);
+
+        boolean gotException = false;
+        try {
+            locker.lockWithNumberOfTries(LockerService.INVOICE, lockName, 1);
+        } catch (LockFailedException e) {
+            gotException = true;
+        }
+        Assert.assertTrue(gotException);
+
+        lock.release();
+
+        Assert.assertEquals(locker.isFree(LockerService.INVOICE, lockName), Boolean.TRUE);
+    }
+
+    private void createSimpleTable(IDBI dbi) {
+        dbi.inTransaction(new TransactionCallback<Void>() {
+
+            @Override
+            public Void inTransaction(Handle h, TransactionStatus status)
+                    throws Exception {
+                h.execute("create table dummy " +
+                        "(id int(11) unsigned NOT NULL AUTO_INCREMENT, " +
+                        "dummy_id char(36) NOT NULL, " +
+                        "PRIMARY KEY(id)" +
+                		") ENGINE=innodb;");
+                return null;
+            }
+        });
+    }
+
+    public final static class TestMysqlGlobalLockerModule extends AbstractModule {
+
+        @Override
+        protected void configure() {
+            MysqlTestingHelper helper = new MysqlTestingHelper();
+            bind(MysqlTestingHelper.class).toInstance(helper);
+            final IDBI dbi = helper.getDBI();
+            bind(IDBI.class).toInstance(dbi);
+        }
+    }
+}
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 89dfd76..cb01e00 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
@@ -16,49 +16,42 @@
 
 package com.ning.billing.util.notificationq.dao;
 
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertNotNull;
-import static org.testng.Assert.assertNull;
-
 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.config.ConfigurationObjectFactory;
 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.testng.Assert;
-import org.testng.annotations.AfterClass;
 import org.testng.annotations.AfterSuite;
-import org.testng.annotations.BeforeClass;
 import org.testng.annotations.BeforeSuite;
 import org.testng.annotations.BeforeTest;
 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.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 static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+
 @Guice(modules = TestNotificationSqlDao.TestNotificationSqlDaoModule.class)
 public class TestNotificationSqlDao {
 
     private static AtomicInteger sequenceId = new AtomicInteger();
 
     @Inject
-    private DBI dbi;
+    private IDBI dbi;
 
     @Inject
     MysqlTestingHelper helper;
@@ -110,12 +103,12 @@ public class TestNotificationSqlDao {
 
         String notificationKey = UUID.randomUUID().toString();
         DateTime effDt = new DateTime();
-        Notification notif = new DefaultNotification(notificationKey, effDt);
+        Notification notif = new DefaultNotification("testBasic",notificationKey, effDt);
         dao.insertNotification(notif);
 
         Thread.sleep(1000);
         DateTime now = new DateTime();
-        List<Notification> notifications = dao.getReadyNotifications(now.toDate(), 3);
+        List<Notification> notifications = dao.getReadyNotifications(now.toDate(), 3, "testBasic");
         assertNotNull(notifications);
         assertEquals(notifications.size(), 1);
 
@@ -127,23 +120,23 @@ public class TestNotificationSqlDao {
         assertEquals(notification.getNextAvailableDate(), null);
 
         DateTime nextAvailable = now.plusMinutes(5);
-        int res = dao.claimNotification(ownerId, nextAvailable.toDate(), notification.getId().toString(), now.toDate());
+        int res = dao.claimNotification(ownerId, nextAvailable.toDate(), notification.getId(), now.toDate());
         assertEquals(res, 1);
-        dao.insertClaimedHistory(sequenceId.incrementAndGet(), ownerId, now.toDate(), notification.getId().toString());
+        dao.insertClaimedHistory(sequenceId.incrementAndGet(), ownerId, now.toDate(), notification.getUUID().toString());
 
-        notification = fetchNotification(notification.getId().toString());
+        notification = fetchNotification(notification.getUUID().toString());
         assertEquals(notification.getNotificationKey(), notificationKey);
         validateDate(notification.getEffectiveDate(), effDt);
         assertEquals(notification.getOwner().toString(), ownerId);
         assertEquals(notification.getProcessingState(), NotificationLifecycleState.IN_PROCESSING);
         validateDate(notification.getNextAvailableDate(), nextAvailable);
 
-        dao.clearNotification(notification.getId().toString(), ownerId);
+        dao.clearNotification(notification.getId(), ownerId);
 
-        notification = fetchNotification(notification.getId().toString());
+        notification = fetchNotification(notification.getUUID().toString());
         assertEquals(notification.getNotificationKey(), notificationKey);
         validateDate(notification.getEffectiveDate(), effDt);
-        assertEquals(notification.getOwner(), null);
+        //assertEquals(notification.getOwner(), null);
         assertEquals(notification.getProcessingState(), NotificationLifecycleState.PROCESSED);
         validateDate(notification.getNextAvailableDate(), nextAvailable);
 
@@ -155,10 +148,12 @@ public class TestNotificationSqlDao {
             @Override
             public Notification withHandle(Handle handle) throws Exception {
                 Notification res = handle.createQuery("   select" +
-                		" notification_id" +
+                        " id " +
+                		", notification_id" +
                 		", notification_key" +
                 		", created_dt" +
                 		", effective_dt" +
+                		", queue_name" +
                 		", processing_owner" +
                 		", processing_available_dt" +
                 		", processing_state" +
@@ -199,8 +194,8 @@ public class TestNotificationSqlDao {
 
             final MysqlTestingHelper helper = new MysqlTestingHelper();
             bind(MysqlTestingHelper.class).toInstance(helper);
-            DBI dbi = helper.getDBI();
-            bind(DBI.class).toInstance(dbi);
+            IDBI dbi = helper.getDBI();
+            bind(IDBI.class).toInstance(dbi);
 
             /*
             bind(DBI.class).toProvider(DBIProvider.class).asEagerSingleton();
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 7ee2e10..e96d2cf 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
@@ -52,14 +52,16 @@ public class MockNotificationQueue extends NotificationQueueBase implements Noti
     public void recordFutureNotificationFromTransaction(
             Transmogrifier transactionalDao, DateTime futureNotificationTime,
             NotificationKey notificationKey) {
-        Notification notification = new DefaultNotification(notificationKey.toString(), futureNotificationTime);
+        Notification notification = new DefaultNotification("MockQueue", notificationKey.toString(), futureNotificationTime);
         synchronized(notifications) {
             notifications.add(notification);
         }
     }
 
     @Override
-    protected void doProcessEvents(int sequenceId) {
+    protected int doProcessEvents(int sequenceId) {
+
+        int result = 0;
 
         List<Notification> processedNotifications = new ArrayList<Notification>();
         List<Notification> oldNotifications = new ArrayList<Notification>();
@@ -73,9 +75,11 @@ public class MockNotificationQueue extends NotificationQueueBase implements Noti
                     readyNotifications.add(cur);
                 }
             }
+
+            result = readyNotifications.size();
             for (Notification cur : readyNotifications) {
-                handler.handleReadyNotification(cur.getNotificationKey());
-                DefaultNotification processedNotification = new DefaultNotification(cur.getId(), hostname, clock.getUTCNow().plus(config.getDaoClaimTimeMs()), NotificationLifecycleState.PROCESSED, cur.getNotificationKey(), cur.getEffectiveDate());
+                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());
                 oldNotifications.add(cur);
                 processedNotifications.add(processedNotification);
 
@@ -87,5 +91,6 @@ public class MockNotificationQueue extends NotificationQueueBase implements Noti
                 notifications.addAll(processedNotifications);
             }
         }
+        return result;
     }
 }
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 2e3bb3c..fbcf8f9 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
@@ -16,8 +16,9 @@
 
 package com.ning.billing.util.notificationq;
 
+import static com.jayway.awaitility.Awaitility.await;
+import static java.util.concurrent.TimeUnit.MINUTES;
 import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertTrue;
 
 import java.io.IOException;
 import java.sql.SQLException;
@@ -25,21 +26,19 @@ import java.util.Collection;
 import java.util.Map;
 import java.util.TreeMap;
 import java.util.UUID;
+import java.util.concurrent.Callable;
+import java.util.concurrent.TimeUnit;
 
 import org.apache.commons.io.IOUtils;
 import org.joda.time.DateTime;
-import org.skife.config.ConfigurationObjectFactory;
-import org.skife.jdbi.v2.DBI;
 import org.skife.jdbi.v2.Handle;
+import org.skife.jdbi.v2.IDBI;
 import org.skife.jdbi.v2.Transaction;
 import org.skife.jdbi.v2.TransactionStatus;
 import org.skife.jdbi.v2.tweak.HandleCallback;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-
-import org.testng.annotations.AfterSuite;
-import org.testng.annotations.AfterTest;
-import org.testng.annotations.BeforeClass;
+import org.testng.Assert;
 import org.testng.annotations.BeforeSuite;
 import org.testng.annotations.BeforeTest;
 import org.testng.annotations.Guice;
@@ -49,8 +48,7 @@ import com.google.common.base.Predicate;
 import com.google.common.collect.Collections2;
 import com.google.inject.AbstractModule;
 import com.google.inject.Inject;
-import com.ning.billing.dbi.DBIProvider;
-import com.ning.billing.dbi.DbiConfig;
+import com.google.inject.name.Names;
 import com.ning.billing.dbi.MysqlTestingHelper;
 import com.ning.billing.util.clock.Clock;
 import com.ning.billing.util.clock.ClockMock;
@@ -59,370 +57,367 @@ import com.ning.billing.util.notificationq.dao.NotificationSqlDao;
 
 @Guice(modules = TestNotificationQueue.TestNotificationQueueModule.class)
 public class TestNotificationQueue {
-
-    private final static Logger log = LoggerFactory.getLogger(TestNotificationQueue.class);
-
-    @Inject
-    private DBI dbi;
-
+	Logger log = LoggerFactory.getLogger(TestNotificationQueue.class);
     @Inject
-    MysqlTestingHelper helper;
-
-    @Inject
-    private Clock clock;
-
-    private DummySqlTest dao;
-
-   // private NotificationQueue queue;
-
-    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);
-    }
-
-    @BeforeSuite(alwaysRun = true)
-    public void setup() throws Exception {
-        startMysql();
-        dao = dbi.onDemand(DummySqlTest.class);
-    }
-
-    @BeforeTest
-    public void beforeTest() {
-        dbi.withHandle(new HandleCallback<Void>() {
-
-            @Override
-            public Void withHandle(Handle handle) throws Exception {
-                handle.execute("delete from notifications");
-                handle.execute("delete from claimed_notifications");
-                handle.execute("delete from dummy");
-                return null;
-            }
-        });
-        // Reset time to real value
-        ((ClockMock) clock).resetDeltaFromReality();
-    }
-
-
-
-    /**
-     * Verify that we can call start/stop on a disabled queue and that both start/stop callbacks are called
-     *
-     * @throws InterruptedException
-     */
-    @Test
-    public void testSimpleQueueDisabled() throws InterruptedException {
-
-        final TestStartStop testStartStop = new TestStartStop(false, false);
-        DefaultNotificationQueue queue = new DefaultNotificationQueue(dbi, clock, "test-svc", "dead",
-                new NotificationQueueHandler() {
-                    @Override
-                    public void handleReadyNotification(String notificationKey) {
-                    }
-                    @Override
-                    public void completedQueueStop() {
-                        testStartStop.stopped();
-                    }
-                    @Override
-                    public void completedQueueStart() {
-                        testStartStop.started();
-                    }
-                },
-                getNotificationConfig(true, 100, 1, 10000));
-
-        executeTest(testStartStop, queue, new WithTest() {
+    private IDBI dbi;
+
+	@Inject
+	MysqlTestingHelper helper;
+
+	@Inject
+	private Clock clock;
+
+	private DummySqlTest dao;
+
+	private int eventsReceived;
+
+	// private NotificationQueue queue;
+
+	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);
+	}
+
+	@BeforeSuite(alwaysRun = true)
+	public void setup() throws Exception {
+		startMysql();
+		dao = dbi.onDemand(DummySqlTest.class);
+	}
+
+	@BeforeTest
+	public void beforeTest() {
+		dbi.withHandle(new HandleCallback<Void>() {
+
+			@Override
+			public Void withHandle(Handle handle) throws Exception {
+				handle.execute("delete from notifications");
+				handle.execute("delete from claimed_notifications");
+				handle.execute("delete from dummy");
+				return null;
+			}
+		});
+		// Reset time to real value
+		((ClockMock) clock).resetDeltaFromReality();
+	}
+
+
+
+	/**
+	 * Test that we can post a notification in the future from a transaction and get the notification
+	 * callback with the correct key when the time is ready
+	 * @throws Exception
+	 */
+	@Test(groups={"fast"}, enabled = true)
+	public void testSimpleNotification() throws Exception {
+
+		final Map<String, Boolean> expectedNotifications = new TreeMap<String, Boolean>();
+
+		final DefaultNotificationQueue queue = new DefaultNotificationQueue(dbi, clock, "test-svc", "foo",
+				new NotificationQueueHandler() {
+			@Override
+			public void handleReadyNotification(String notificationKey, DateTime eventDateTime) {
+				synchronized (expectedNotifications) {
+	            	log.info("Handler received key: " + notificationKey);
+
+					expectedNotifications.put(notificationKey.toString(), Boolean.TRUE);
+					expectedNotifications.notify();
+				}
+			}
+		},
+		getNotificationConfig(false, 100, 1, 10000));
+
+
+		queue.startQueue();
+
+		final UUID key = UUID.randomUUID();
+		final DummyObject obj = new DummyObject("foo", key);
+		final DateTime now = new DateTime();
+		final DateTime readyTime = now.plusMillis(2000);
+		final NotificationKey notificationKey = new NotificationKey() {
+			@Override
+			public String toString() {
+				return key.toString();
+			}
+		};
+		expectedNotifications.put(notificationKey.toString(), Boolean.FALSE);
+
+
+		// Insert dummy to be processed in 2 sec'
+		dao.inTransaction(new Transaction<Void, DummySqlTest>() {
+			@Override
+			public Void inTransaction(DummySqlTest transactional,
+					TransactionStatus status) throws Exception {
+
+				transactional.insertDummy(obj);
+				queue.recordFutureNotificationFromTransaction(transactional,
+						readyTime, notificationKey);
+            	log.info("Posted key: " + notificationKey);
+
+				return null;
+			}
+		});
+
+		// Move time in the future after the notification effectiveDate
+		((ClockMock) clock).setDeltaFromReality(3000);
+
+		// Notification should have kicked but give it at least a sec' for thread scheduling
+	    await().atMost(1, MINUTES).until(new Callable<Boolean>() {
             @Override
-            public void test(final DefaultNotificationQueue readyQueue) throws InterruptedException {
-                // Do nothing
+            public Boolean call() throws Exception {
+                return expectedNotifications.get(notificationKey.toString());
             }
         });
-        assertTrue(true);
-    }
-
-    /**
-     * Test that we can post a notification in the future from a transaction and get the notification
-     * callback with the correct key when the time is ready
-     *
-     * @throws InterruptedException
-     */
-    @Test
-    public void testSimpleNotification() throws InterruptedException {
-
-        final Map<String, Boolean> expectedNotifications = new TreeMap<String, Boolean>();
-
-        final TestStartStop testStartStop = new TestStartStop(false, false);
-        DefaultNotificationQueue queue = new DefaultNotificationQueue(dbi, clock, "test-svc", "foo",
-                new NotificationQueueHandler() {
-                    @Override
-                    public void handleReadyNotification(String notificationKey) {
-                        synchronized (expectedNotifications) {
-                            expectedNotifications.put(notificationKey, Boolean.TRUE);
-                            expectedNotifications.notify();
-                        }
-                    }
-                    @Override
-                    public void completedQueueStop() {
-                        testStartStop.stopped();
-                    }
-                    @Override
-                    public void completedQueueStart() {
-                        testStartStop.started();
-                    }
-                },
-                getNotificationConfig(false, 100, 1, 10000));
-
-
-        executeTest(testStartStop, queue, new WithTest() {
-            @Override
-            public void test(final DefaultNotificationQueue readyQueue) throws InterruptedException {
-
-                final UUID key = UUID.randomUUID();
-                final DummyObject obj = new DummyObject("foo", key);
-                final DateTime now = new DateTime();
-                final DateTime readyTime = now.plusMillis(2000);
-                final NotificationKey notificationKey = new NotificationKey() {
-                    @Override
-                    public String toString() {
-                        return key.toString();
-                    }
-                };
-                expectedNotifications.put(notificationKey.toString(), Boolean.FALSE);
-
-
-                // Insert dummy to be processed in 2 sec'
-                dao.inTransaction(new Transaction<Void, DummySqlTest>() {
-                    @Override
-                    public Void inTransaction(DummySqlTest transactional,
-                            TransactionStatus status) throws Exception {
-
-                        transactional.insertDummy(obj);
-                        readyQueue.recordFutureNotificationFromTransaction(transactional,
-                                readyTime, notificationKey);
-                        return null;
-                    }
-                });
-
-                // Move time in the future after the notification effectiveDate
-                ((ClockMock) clock).setDeltaFromReality(3000);
-
-                // Notification should have kicked but give it at least a sec' for thread scheduling
-                int nbTry = 1;
-                boolean success = false;
-                do {
-                    synchronized(expectedNotifications) {
-                        if (expectedNotifications.get(notificationKey.toString())) {
-                            success = true;
-                            break;
-                        }
-                        expectedNotifications.wait(1000);
-                    }
-                } while (nbTry-- > 0);
-                assertEquals(success, true);
-            }
-        });
-    }
-
-    @Test
-    public void testManyNotifications() throws InterruptedException {
-        final Map<String, Boolean> expectedNotifications = new TreeMap<String, Boolean>();
-
-        final TestStartStop testStartStop = new TestStartStop(false, false);
-        DefaultNotificationQueue queue = new DefaultNotificationQueue(dbi, clock, "test-svc", "many",
-                new NotificationQueueHandler() {
-                    @Override
-                    public void handleReadyNotification(String notificationKey) {
-                        synchronized (expectedNotifications) {
-                            expectedNotifications.put(notificationKey, Boolean.TRUE);
-                            expectedNotifications.notify();
-                        }
-                    }
-                    @Override
-                    public void completedQueueStop() {
-                        testStartStop.stopped();
-                    }
-                    @Override
-                    public void completedQueueStart() {
-                        testStartStop.started();
-                    }
-                },
-                getNotificationConfig(false, 100, 10, 10000));
-
-
-        executeTest(testStartStop, queue, new WithTest() {
-            @Override
-            public void test(final DefaultNotificationQueue readyQueue) throws InterruptedException {
-
-                final DateTime now = clock.getUTCNow();
-                final int MAX_NOTIFICATIONS = 100;
-                for (int i = 0; i < MAX_NOTIFICATIONS; i++) {
-
-                    final int nextReadyTimeIncrementMs = 1000;
-
-                    final UUID key = UUID.randomUUID();
-                    final DummyObject obj = new DummyObject("foo", key);
-                    final int currentIteration = i;
-
-                    final NotificationKey notificationKey = new NotificationKey() {
-                        @Override
-                        public String toString() {
-                            return key.toString();
-                        }
-                    };
-                    expectedNotifications.put(notificationKey.toString(), Boolean.FALSE);
-
-                    dao.inTransaction(new Transaction<Void, DummySqlTest>() {
-                        @Override
-                        public Void inTransaction(DummySqlTest transactional,
-                                TransactionStatus status) throws Exception {
-
-                            transactional.insertDummy(obj);
-                            readyQueue.recordFutureNotificationFromTransaction(transactional,
-                                    now.plus((currentIteration + 1) * nextReadyTimeIncrementMs), notificationKey);
-                            return null;
-                        }
-                    });
-
-                    // Move time in the future after the notification effectiveDate
-                    if (i == 0) {
-                        ((ClockMock) clock).setDeltaFromReality(nextReadyTimeIncrementMs);
-                    } else {
-                        ((ClockMock) clock).addDeltaFromReality(nextReadyTimeIncrementMs);
-                    }
-                }
-
-                // Wait a little longer since there are a lot of callback that need to happen
-                int nbTry = MAX_NOTIFICATIONS + 1;
-                boolean success = false;
-                do {
-                    synchronized(expectedNotifications) {
-
-                        Collection<Boolean> completed =  Collections2.filter(expectedNotifications.values(), new Predicate<Boolean>() {
-                            @Override
-                            public boolean apply(Boolean input) {
-                                return input;
-                            }
-                        });
-
-                        if (completed.size() == MAX_NOTIFICATIONS) {
-                            success = true;
-                            break;
-                        }
-                        //log.debug(String.format("BEFORE WAIT : Got %d notifications at time %s (real time %s)", completed.size(), clock.getUTCNow(), new DateTime()));
-                        expectedNotifications.wait(1000);
-                    }
-                } while (nbTry-- > 0);
-                assertEquals(success, true);
-            }
-        });
-    }
-
 
-    NotificationConfig getNotificationConfig(final boolean off,
-            final long sleepTime, final int maxReadyEvents, final long claimTimeMs) {
-        return new NotificationConfig() {
+	Assert.assertTrue(expectedNotifications.get(notificationKey.toString()));
+	}
+
+	@Test
+	public void testManyNotifications() throws InterruptedException {
+		final Map<String, Boolean> expectedNotifications = new TreeMap<String, Boolean>();
+
+		final DefaultNotificationQueue queue = new DefaultNotificationQueue(dbi, clock, "test-svc", "many",
+				new NotificationQueueHandler() {
+			@Override
+			public void handleReadyNotification(String notificationKey, DateTime eventDateTime) {
+				synchronized (expectedNotifications) {
+					expectedNotifications.put(notificationKey, Boolean.TRUE);
+					expectedNotifications.notify();
+				}
+			}
+		},
+		getNotificationConfig(false, 100, 10, 10000));
+
+
+		queue.startQueue();
+
+		final DateTime now = clock.getUTCNow();
+		final int MAX_NOTIFICATIONS = 100;
+		for (int i = 0; i < MAX_NOTIFICATIONS; i++) {
+
+			final int nextReadyTimeIncrementMs = 1000;
+
+			final UUID key = UUID.randomUUID();
+			final DummyObject obj = new DummyObject("foo", key);
+			final int currentIteration = i;
+
+			final NotificationKey notificationKey = new NotificationKey() {
+				@Override
+				public String toString() {
+					return key.toString();
+				}
+			};
+			expectedNotifications.put(notificationKey.toString(), Boolean.FALSE);
+
+			dao.inTransaction(new Transaction<Void, DummySqlTest>() {
+				@Override
+				public Void inTransaction(DummySqlTest transactional,
+						TransactionStatus status) throws Exception {
+
+					transactional.insertDummy(obj);
+					queue.recordFutureNotificationFromTransaction(transactional,
+							now.plus((currentIteration + 1) * nextReadyTimeIncrementMs), notificationKey);
+					return null;
+				}
+			});
+
+			// Move time in the future after the notification effectiveDate
+			if (i == 0) {
+				((ClockMock) clock).setDeltaFromReality(nextReadyTimeIncrementMs);
+			} else {
+				((ClockMock) clock).addDeltaFromReality(nextReadyTimeIncrementMs);
+			}
+		}
+
+		// Wait a little longer since there are a lot of callback that need to happen
+		int nbTry = MAX_NOTIFICATIONS + 1;
+		boolean success = false;
+		do {
+			synchronized(expectedNotifications) {
+
+				Collection<Boolean> completed =  Collections2.filter(expectedNotifications.values(), new Predicate<Boolean>() {
+					@Override
+					public boolean apply(Boolean input) {
+						return input;
+					}
+				});
+
+				if (completed.size() == MAX_NOTIFICATIONS) {
+					success = true;
+					break;
+				}
+				//log.debug(String.format("BEFORE WAIT : Got %d notifications at time %s (real time %s)", completed.size(), clock.getUTCNow(), new DateTime()));
+				expectedNotifications.wait(1000);
+			}
+		} while (nbTry-- > 0);
+		assertEquals(success, true);
+
+	}
+
+	/**
+	 * Test that we can post a notification in the future from a transaction and get the notification
+	 * callback with the correct key when the time is ready
+	 * @throws Exception
+	 */
+	@Test(groups={"fast"}, enabled = true)
+	public void testMultipleHandlerNotification() throws Exception {
+
+		final Map<String, Boolean> expectedNotificationsFred = new TreeMap<String, Boolean>();
+		final Map<String, Boolean> expectedNotificationsBarney = new TreeMap<String, Boolean>();
+
+		NotificationQueueService notificationQueueService = new DefaultNotificationQueueService(dbi,  clock);
+
+		NotificationConfig config=new NotificationConfig() {
             @Override
             public boolean isNotificationProcessingOff() {
-                return off;
+                return false;
             }
             @Override
             public long getNotificationSleepTimeMs() {
-                return sleepTime;
+                return 10;
             }
             @Override
             public int getDaoMaxReadyEvents() {
-                return maxReadyEvents;
+                return 1;
             }
             @Override
             public long getDaoClaimTimeMs() {
-                return claimTimeMs;
-            }
-        };
-    }
-
-    private static class TestStartStop {
-        private boolean started;
-        private boolean stopped;
-
-        public TestStartStop(boolean started, boolean stopped) {
-            super();
-            this.started = started;
-            this.stopped = stopped;
-        }
-
-        public void started() {
-            synchronized(this) {
-                started = true;
-                notify();
-            }
-        }
-
-        public void stopped() {
-            synchronized(this) {
-                stopped = true;
-                notify();
-            }
-        }
-
-        public boolean waitForStartComplete(int timeoutMs) throws InterruptedException {
-            return waitForEventCompletion(timeoutMs, true);
-        }
-
-        public boolean waitForStopComplete(int timeoutMs) throws InterruptedException {
-            return waitForEventCompletion(timeoutMs, false);
-        }
-
-        private boolean waitForEventCompletion(int timeoutMs, boolean start) throws InterruptedException {
-            DateTime init = new DateTime();
-            synchronized(this) {
-                while (! ((start ? started : stopped))) {
-                    wait(timeoutMs);
-                    if (init.plusMillis(timeoutMs).isAfterNow()) {
-                        break;
-                    }
-                }
+                return 60000;
             }
-            return (start ? started : stopped);
-        }
-    }
+		};
 
-    private interface WithTest {
-        public void test(DefaultNotificationQueue readyQueue) throws InterruptedException;
-    }
 
-    private void executeTest(final TestStartStop testStartStop,
-            DefaultNotificationQueue queue, WithTest test) throws InterruptedException{
-
-        queue.startQueue();
-        boolean started = testStartStop.waitForStartComplete(3000);
-        assertEquals(started, true);
-
-        test.test(queue);
-
-        queue.stopQueue();
-        boolean stopped = testStartStop.waitForStopComplete(3000);
-        assertEquals(stopped, true);
-    }
-
-
-    public static class TestNotificationQueueModule extends AbstractModule {
-        @Override
-        protected void configure() {
-
-            bind(Clock.class).to(ClockMock.class);
+		final NotificationQueue queueFred = notificationQueueService.createNotificationQueue("UtilTest", "Fred", new NotificationQueueHandler() {
+                @Override
+                public void handleReadyNotification(String notificationKey, DateTime eventDateTime)  {
+                	log.info("Fred received key: " + notificationKey);
+                	expectedNotificationsFred.put(notificationKey, Boolean.TRUE);
+                	eventsReceived++;
+                }
+            },
+            config);
 
-            final MysqlTestingHelper helper = new MysqlTestingHelper();
-            bind(MysqlTestingHelper.class).toInstance(helper);
-            DBI dbi = helper.getDBI();
-            bind(DBI.class).toInstance(dbi);
-            /*
+		final NotificationQueue queueBarney = notificationQueueService.createNotificationQueue("UtilTest", "Barney", new NotificationQueueHandler() {
+            @Override
+            public void handleReadyNotification(String notificationKey, DateTime eventDateTime) {
+             	log.info("Barney received key: " + notificationKey);
+            	expectedNotificationsBarney.put(notificationKey, Boolean.TRUE);
+            	eventsReceived++;
+            }
+        },
+        config);
+
+		queueFred.startQueue();
+//		We don't start Barney so it can never pick up notifications
+
+
+		final UUID key = UUID.randomUUID();
+		final DummyObject obj = new DummyObject("foo", key);
+		final DateTime now = new DateTime();
+		final DateTime readyTime = now.plusMillis(2000);
+		final NotificationKey notificationKeyFred = new NotificationKey() {
+			@Override
+			public String toString() {
+				return "Fred" ;
+			}
+		};
+
+
+		final NotificationKey notificationKeyBarney = new NotificationKey() {
+			@Override
+			public String toString() {
+				return "Barney" ;
+			}
+		};
+
+		expectedNotificationsFred.put(notificationKeyFred.toString(), Boolean.FALSE);
+		expectedNotificationsFred.put(notificationKeyBarney.toString(), Boolean.FALSE);
+
+
+		// Insert dummy to be processed in 2 sec'
+		dao.inTransaction(new Transaction<Void, DummySqlTest>() {
+			@Override
+			public Void inTransaction(DummySqlTest transactional,
+					TransactionStatus status) throws Exception {
+
+				transactional.insertDummy(obj);
+				queueFred.recordFutureNotificationFromTransaction(transactional,
+						readyTime, notificationKeyFred);
+				log.info("posted key: " + notificationKeyFred.toString());
+				queueBarney.recordFutureNotificationFromTransaction(transactional,
+						readyTime, notificationKeyBarney);
+				log.info("posted key: " + notificationKeyBarney.toString());
+
+				return null;
+			}
+		});
+
+		// Move time in the future after the notification effectiveDate
+		((ClockMock) clock).setDeltaFromReality(3000);
+
+		// Note the timeout is short on this test, but expected behaviour is that it times out.
+		// We are checking that the Fred queue does not pick up the Barney event
+		try {
+			await().atMost(5, TimeUnit.SECONDS).until(new Callable<Boolean>() {
+				@Override
+				public Boolean call() throws Exception {
+					return eventsReceived >= 2;
+				}
+			});
+			Assert.fail("There should only have been one event for the queue to pick up - it got more than that");
+		} catch (Exception e) {
+			// expected behavior
+		}
+
+		Assert.assertTrue(expectedNotificationsFred.get(notificationKeyFred.toString()));
+		Assert.assertFalse(expectedNotificationsFred.get(notificationKeyBarney.toString()));
+
+	}
+
+	NotificationConfig getNotificationConfig(final boolean off,
+			final long sleepTime, final int maxReadyEvents, final long claimTimeMs) {
+		return new NotificationConfig() {
+			@Override
+			public boolean isNotificationProcessingOff() {
+				return off;
+			}
+			@Override
+			public long getNotificationSleepTimeMs() {
+				return sleepTime;
+			}
+			@Override
+			public int getDaoMaxReadyEvents() {
+				return maxReadyEvents;
+			}
+			@Override
+			public long getDaoClaimTimeMs() {
+				return claimTimeMs;
+			}
+		};
+	}
+
+
+	public static class TestNotificationQueueModule extends AbstractModule {
+		@Override
+		protected void configure() {
+
+			bind(Clock.class).to(ClockMock.class);
+
+			final MysqlTestingHelper helper = new MysqlTestingHelper();
+			bind(MysqlTestingHelper.class).toInstance(helper);
+			IDBI dbi = helper.getDBI();
+			bind(IDBI.class).toInstance(dbi);
+			IDBI otherDbi = helper.getDBI();
+			bind(IDBI.class).annotatedWith(Names.named("global-lock")).toInstance(otherDbi);
+			/*
             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/tag/TestTagStore.java b/util/src/test/java/com/ning/billing/util/tag/TestTagStore.java
new file mode 100644
index 0000000..c913791
--- /dev/null
+++ b/util/src/test/java/com/ning/billing/util/tag/TestTagStore.java
@@ -0,0 +1,358 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * 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;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.UUID;
+import org.apache.commons.io.IOUtils;
+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 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.account.api.ControlTagType;
+import com.ning.billing.util.api.TagDefinitionApiException;
+import com.ning.billing.util.clock.Clock;
+import com.ning.billing.util.clock.DefaultClock;
+import com.ning.billing.util.clock.MockClockModule;
+import com.ning.billing.util.tag.dao.TagDefinitionDao;
+import com.ning.billing.util.tag.dao.TagStoreSqlDao;
+
+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 static org.testng.Assert.fail;
+
+@Test(groups={"util"})
+public class TestTagStore {
+    private final static String ACCOUNT_TYPE = "ACCOUNT";
+    private final Clock clock = new DefaultClock();
+    private IDBI dbi;
+    private TagDefinition tag1;
+    private TagDefinition tag2;
+    private TagStoreModuleMock module;
+    private TagStoreSqlDao tagStoreSqlDao;
+    private TagDefinitionDao tagDefinitionDao;
+    private final Logger log = LoggerFactory.getLogger(TestTagStore.class);
+
+    @BeforeClass(alwaysRun = true)
+    protected void setup() throws IOException {
+        // Health check test to make sure MySQL is setup properly
+        try {
+            module = new TagStoreModuleMock();
+            final String utilDdl = IOUtils.toString(TestTagStore.class.getResourceAsStream("/com/ning/billing/util/ddl.sql"));
+
+            module.startDb();
+            module.initDb(utilDdl);
+
+            final Injector injector = Guice.createInjector(Stage.DEVELOPMENT, module, new MockClockModule());
+            dbi = injector.getInstance(IDBI.class);
+
+            tagStoreSqlDao = injector.getInstance(TagStoreSqlDao.class);
+            tagStoreSqlDao.test();
+
+            tagDefinitionDao = injector.getInstance(TagDefinitionDao.class);
+            tag1 = tagDefinitionDao.create("tag1", "First tag", "test");
+            tag2 = tagDefinitionDao.create("tag2", "Second tag", "test");
+        }
+        catch (Throwable t) {
+            log.error("Failed to start tag store tests", t);
+            fail(t.toString());
+        }
+    }
+
+    @AfterClass(alwaysRun = true)
+    public void stopMysql()
+    {
+        module.stopDb();
+    }
+
+    private void saveTags(final TagStoreSqlDao dao, final String objectType, final String accountId, final List<Tag> tagList)  {
+        dao.inTransaction(new Transaction<Void, TagStoreSqlDao>() {
+            @Override
+            public Void inTransaction(TagStoreSqlDao transactional,
+                    TransactionStatus status) throws Exception {
+                dao.batchSaveFromTransaction(accountId.toString(), objectType, tagList);
+                return null;
+            }
+        });
+    }
+
+    @Test
+    public void testTagCreationAndRetrieval() {
+        UUID accountId = UUID.randomUUID();
+
+        TagStore tagStore = new DefaultTagStore(accountId, ACCOUNT_TYPE);
+        Tag tag = new DescriptiveTag(tag2, "test", clock.getUTCNow());
+        tagStore.add(tag);
+
+        TagStoreSqlDao dao = dbi.onDemand(TagStoreSqlDao.class);
+        saveTags(dao, ACCOUNT_TYPE, accountId.toString(), tagStore.getEntityList());
+
+        List<Tag> savedTags = dao.load(accountId.toString(), ACCOUNT_TYPE);
+        assertEquals(savedTags.size(), 1);
+
+        Tag savedTag = savedTags.get(0);
+        assertEquals(savedTag.getAddedBy(), tag.getAddedBy());
+        assertEquals(savedTag.getAddedDate().compareTo(tag.getAddedDate()), 0);
+        assertEquals(savedTag.getTagDefinitionName(), tag.getTagDefinitionName());
+        assertEquals(savedTag.getId(), tag.getId());
+    }
+
+
+    @Test
+    public void testControlTagCreation() {
+        UUID accountId = UUID.randomUUID();
+        TagStore tagStore = new DefaultTagStore(accountId, ACCOUNT_TYPE);
+
+        ControlTag tag = new DefaultControlTag("testUser", clock.getUTCNow(), ControlTagType.AUTO_INVOICING_OFF);
+        tagStore.add(tag);
+        assertEquals(tagStore.generateInvoice(), false);
+
+        List<Tag> tagList = tagStore.getEntityList();
+        saveTags(tagStoreSqlDao, ACCOUNT_TYPE, accountId.toString(), tagList);
+
+        tagStore.clear();
+        assertEquals(tagStore.getEntityList().size(), 0);
+
+        tagList = tagStoreSqlDao.load(accountId.toString(), ACCOUNT_TYPE);
+        tagStore.add(tagList);
+        assertEquals(tagList.size(), 1);
+
+        assertEquals(tagStore.generateInvoice(), false);
+    }
+
+    @Test
+    public void testDescriptiveTagCreation() {
+        UUID accountId = UUID.randomUUID();
+        TagStore tagStore = new DefaultTagStore(accountId, ACCOUNT_TYPE);
+
+        String definitionName = "SomeTestTag";
+        TagDefinition tagDefinition = null;
+        try {
+            tagDefinition = tagDefinitionDao.create(definitionName, "Test tag for some test purpose", "testUser");
+        } catch (TagDefinitionApiException e) {
+            fail("Tag definition creation failed.", e);
+        }
+
+        DescriptiveTag tag = new DescriptiveTag(tagDefinition, "testUser", clock.getUTCNow());
+        tagStore.add(tag);
+        assertEquals(tagStore.generateInvoice(), true);
+
+        List<Tag> tagList = tagStore.getEntityList();
+        saveTags(tagStoreSqlDao, ACCOUNT_TYPE, accountId.toString(), tagList);
+
+        tagStore.clear();
+        assertEquals(tagStore.getEntityList().size(), 0);
+
+        tagList = tagStoreSqlDao.load(accountId.toString(), ACCOUNT_TYPE);
+        tagStore.add(tagList);
+        assertEquals(tagList.size(), 1);
+
+        assertEquals(tagStore.generateInvoice(), true);
+    }
+
+    @Test
+    public void testMixedTagCreation() {
+        UUID accountId = UUID.randomUUID();
+        TagStore tagStore = new DefaultTagStore(accountId, ACCOUNT_TYPE);
+
+        String definitionName = "MixedTagTest";
+        TagDefinition tagDefinition = null;
+        try {
+            tagDefinition = tagDefinitionDao.create(definitionName, "Test tag for some test purpose", "testUser");
+        } catch (TagDefinitionApiException e) {
+            fail("Tag definition creation failed.", e);
+        }
+
+        DescriptiveTag descriptiveTag = new DescriptiveTag(tagDefinition, "testUser", clock.getUTCNow());
+        tagStore.add(descriptiveTag);
+        assertEquals(tagStore.generateInvoice(), true);
+
+        ControlTag controlTag = new DefaultControlTag("testUser", clock.getUTCNow(), ControlTagType.AUTO_INVOICING_OFF);
+        tagStore.add(controlTag);
+        assertEquals(tagStore.generateInvoice(), false);
+
+        List<Tag> tagList = tagStore.getEntityList();
+        saveTags(tagStoreSqlDao, ACCOUNT_TYPE, accountId.toString(), tagList);
+
+        tagStore.clear();
+        assertEquals(tagStore.getEntityList().size(), 0);
+
+        tagList = tagStoreSqlDao.load(accountId.toString(), ACCOUNT_TYPE);
+        tagStore.add(tagList);
+        assertEquals(tagList.size(), 2);
+
+        assertEquals(tagStore.generateInvoice(), false);
+    }
+
+    @Test
+    public void testControlTags() {
+        UUID accountId = UUID.randomUUID();
+        TagStore tagStore = new DefaultTagStore(accountId, ACCOUNT_TYPE);
+        assertEquals(tagStore.generateInvoice(), true);
+        assertEquals(tagStore.processPayment(), true);
+
+        ControlTag invoiceTag = new DefaultControlTag("testUser", clock.getUTCNow(), ControlTagType.AUTO_INVOICING_OFF);
+        tagStore.add(invoiceTag);
+        assertEquals(tagStore.generateInvoice(), false);
+        assertEquals(tagStore.processPayment(), true);
+
+        ControlTag paymentTag = new DefaultControlTag("testUser", clock.getUTCNow(), ControlTagType.AUTO_BILLING_OFF);
+        tagStore.add(paymentTag);
+        assertEquals(tagStore.generateInvoice(), false);
+        assertEquals(tagStore.processPayment(), false);
+    }
+
+    @Test(expectedExceptions = TagDefinitionApiException.class)
+    public void testTagDefinitionCreationWithControlTagName() throws TagDefinitionApiException {
+        String definitionName = ControlTagType.AUTO_BILLING_OFF.toString();
+        tagDefinitionDao.create(definitionName, "This should break", "test");
+    }
+
+    @Test
+    public void testTagDefinitionDeletionForUnusedDefinition() throws TagDefinitionApiException {
+        String definitionName = "TestTag1234";
+        tagDefinitionDao.create(definitionName, "Some test tag", "test");
+
+        TagDefinition tagDefinition = tagDefinitionDao.getByName(definitionName);
+        assertNotNull(tagDefinition);
+
+        tagDefinitionDao.deleteTagDefinition(definitionName);
+        tagDefinition = tagDefinitionDao.getByName(definitionName);
+        assertNull(tagDefinition);
+    }
+
+    @Test(expectedExceptions = TagDefinitionApiException.class)
+    public void testTagDefinitionDeletionForDefinitionInUse() throws TagDefinitionApiException {
+        String definitionName = "TestTag12345";
+        tagDefinitionDao.create(definitionName, "Some test tag", "test");
+
+        TagDefinition tagDefinition = tagDefinitionDao.getByName(definitionName);
+        assertNotNull(tagDefinition);
+
+        UUID objectId = UUID.randomUUID();
+        String objectType = "TestType";
+        TagStore tagStore = new DefaultTagStore(objectId, objectType);
+        Tag tag = new DescriptiveTag(tagDefinition, "test", clock.getUTCNow());
+        tagStore.add(tag);
+
+        saveTags(tagStoreSqlDao, objectType, objectId.toString(), tagStore.getEntityList());
+
+        List<Tag> tags = tagStoreSqlDao.load(objectId.toString(), objectType);
+        assertEquals(tags.size(), 1);
+
+        tagDefinitionDao.deleteTagDefinition(definitionName);
+    }
+
+    @Test
+    public void testDeleteAllTagsForDefinitionInUse() {
+        String definitionName = "TestTag1234567";
+        try {
+            tagDefinitionDao.create(definitionName, "Some test tag", "test");
+        } catch (TagDefinitionApiException e) {
+            fail("Could not create tag definition", e);
+        }
+
+        TagDefinition tagDefinition = tagDefinitionDao.getByName(definitionName);
+        assertNotNull(tagDefinition);
+
+        UUID objectId = UUID.randomUUID();
+        String objectType = "TestType";
+        TagStore tagStore = new DefaultTagStore(objectId, objectType);
+        Tag tag = new DescriptiveTag(tagDefinition, "test", clock.getUTCNow());
+        tagStore.add(tag);
+
+        saveTags(tagStoreSqlDao, objectType, objectId.toString(), tagStore.getEntityList());
+
+        List<Tag> tags = tagStoreSqlDao.load(objectId.toString(), objectType);
+        assertEquals(tags.size(), 1);
+
+        try {
+            tagDefinitionDao.deleteAllTagsForDefinition(definitionName);
+        } catch (TagDefinitionApiException e) {
+            fail("Could not delete tags for tag definition", e);
+        }
+
+        try {
+            tagDefinitionDao.deleteTagDefinition(definitionName);
+        } catch (TagDefinitionApiException e) {
+            fail("Could not delete tag definition", e);
+        }
+    }
+
+    @Test
+    public void testDeleteAllTagsForDefinitionNotInUse() {
+        String definitionName = "TestTag4321";
+        try {
+            tagDefinitionDao.create(definitionName, "Some test tag", "test");
+        } catch (TagDefinitionApiException e) {
+            fail("Could not create tag definition", e);
+        }
+
+        TagDefinition tagDefinition = tagDefinitionDao.getByName(definitionName);
+        assertNotNull(tagDefinition);
+
+        try {
+            tagDefinitionDao.deleteAllTagsForDefinition(definitionName);
+        } catch (TagDefinitionApiException e) {
+            fail("Could not delete tags for tag definition", e);
+        }
+
+        try {
+            tagDefinitionDao.deleteTagDefinition(definitionName);
+        } catch (TagDefinitionApiException e) {
+            fail("Could not delete tag definition", e);
+        }
+    }
+
+    @Test(expectedExceptions = TagDefinitionApiException.class)
+    public void testDeleteAllTagsForDefinitionWithWrongName() throws TagDefinitionApiException {
+        String definitionName = "TestTag654321";
+        String wrongDefinitionName = "TestTag564321";
+        try {
+            tagDefinitionDao.create(definitionName, "Some test tag", "test");
+        } catch (TagDefinitionApiException e) {
+            fail("Could not create tag definition", e);
+        }
+
+        TagDefinition tagDefinition = tagDefinitionDao.getByName(definitionName);
+        assertNotNull(tagDefinition);
+
+        tagDefinitionDao.deleteAllTagsForDefinition(wrongDefinitionName);
+
+        try {
+            tagDefinitionDao.deleteTagDefinition(definitionName);
+        } catch (TagDefinitionApiException e) {
+            fail("Could not delete tag definition", e);
+        }
+    }
+
+    @Test
+    public void testGetTagDefinitions() {
+        List<TagDefinition> definitionList = tagDefinitionDao.getTagDefinitions();
+        assertTrue(definitionList.size() >= ControlTagType.values().length);
+    }
+}