killbill-aplcache

Merge branch 'inv-ent-integration' of github.com:ning/killbill

1/30/2012 8:18:54 PM

Changes

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

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

beatrix/pom.xml 4(+2 -2)

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

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

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

pom.xml 79(+66 -13)

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 2(+1 -1)

diff --git a/account/pom.xml b/account/pom.xml
index 44b4543..141e6cb 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.5-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-account</artifactId>
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 b8c4314..3fb68dd 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,151 +16,254 @@
 
 package com.ning.billing.account.api;
 
-import java.math.BigDecimal;
 import java.util.List;
 import java.util.UUID;
+
+import com.google.inject.Inject;
+import com.ning.billing.util.clock.Clock;
 import org.joda.time.DateTime;
 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;
+import org.joda.time.DateTimeZone;
+ 
 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(final 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(final UUID id, final AccountData data) {
-        this(id, data.getExternalKey(), data.getEmail(), data.getName(),
-                data.getFirstNameLength(), data.getPhone(), data.getCurrency(), data.getBillCycleDay(),
-                data.getPaymentProviderName(), BigDecimal.ZERO);
-    }
-
-    public DefaultAccount(final UUID id, final String externalKey, final String email,
-                          final String name, final int firstNameLength,
-                          final String phone, final Currency currency, final int billCycleDay, final String paymentProviderName,
-                          final 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(final String tagName) {
-        return tags.containsTag(tagName);
-    }
-
-    @Override
-    public void addTag(final TagDescription description, final String addedBy, final DateTime dateAdded) {
-        Tag tag = new DefaultTag(description, addedBy, dateAdded);
-        tags.add(tag) ;
-    }
-
-    @Override
-    public void addTags(final List<Tag> tags) {
-        if (tags != null) {
-            this.tags.add(tags);
-        }
-    }
-
-    @Override
-    public void clearTags() {
-        this.tags.clear();
-    }
-
-    @Override
-    public void removeTag(final 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;
+
+	public DefaultAccount(final AccountData data) {
+		this(UUID.randomUUID(), data, null, null);
+	}
+
+	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);
+	}
+
+	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.updatedDate = updatedDate == null ? new DateTime(DateTimeZone.UTC) : updatedDate;
+		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..d128fc5 100644
--- a/account/src/main/java/com/ning/billing/account/api/user/DefaultAccountUserApi.java
+++ b/account/src/main/java/com/ning/billing/account/api/user/DefaultAccountUserApi.java
@@ -19,7 +19,6 @@ 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;
@@ -38,19 +37,12 @@ public class DefaultAccountUserApi implements com.ning.billing.account.api.Accou
 
     @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);
+        Account account = new DefaultAccount(data);
+        account.addFields(fields);
+        account.addTags(tags);
 
-        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);
-        }
+        dao.create(account);
+        return account;
     }
 
     @Override
@@ -69,12 +61,17 @@ 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 deleteAccountByKey(String externalKey) throws AccountApiException {
+		dao.deleteByKey(externalKey);
+	}
 }
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 587a72a..2e264c0 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
@@ -22,17 +22,18 @@ 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.Bus;
+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;
@@ -48,7 +49,7 @@ public class DefaultAccountDao implements AccountDao {
     public Account getAccountByKey(final String key) {
         return accountDao.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,7 +61,10 @@ public class DefaultAccountDao implements AccountDao {
     }
 
     @Override
-    public UUID getIdFromKey(final String externalKey) {
+    public UUID getIdFromKey(final String externalKey) throws AccountApiException {
+        if (externalKey == null) {
+            throw new AccountApiException(ErrorCode.ACCOUNT_CANNOT_MAP_NULL_KEY, "");
+        }
         return accountDao.getIdFromKey(externalKey);
     }
 
@@ -68,7 +72,7 @@ public class DefaultAccountDao implements AccountDao {
     public Account getById(final String id) {
         return accountDao.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.getById(id);
                 if (account != null) {
                     setCustomFieldsFromWithinTransaction(account, accountSqlDao);
@@ -86,56 +90,93 @@ public class DefaultAccountDao implements AccountDao {
     }
 
     @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 {
+            accountDao.inTransaction(new Transaction<Void, AccountSqlDao>() {
+                @Override
+                public Void inTransaction(final AccountSqlDao accountSqlDao, final TransactionStatus status) throws AccountApiException, Bus.EventBusException {
+                    Account currentAccount = accountSqlDao.getAccountByKey(key);
+                    if (currentAccount != null) {
+                        throw new AccountApiException(ErrorCode.ACCOUNT_ALREADY_EXISTS, key);
+                    }
+                    accountSqlDao.create(account);
+
+                    saveTagsFromWithinTransaction(account, accountSqlDao, true);
+                    saveCustomFieldsFromWithinTransaction(account, accountSqlDao, 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);
-
-                FieldStoreDao fieldStoreDao = accountSqlDao.become(FieldStoreDao.class);
-                fieldStoreDao.clear(accountId, objectType);
-                fieldStoreDao.save(accountId, objectType, account.getFieldList());
+    public void update(final Account account) throws AccountApiException {
+        try {
+            accountDao.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;
+            }
+        }
+    }
+    
+    @Override
+	public void deleteByKey(final String externalKey) throws AccountApiException {
+    	try {
+            accountDao.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() {
@@ -148,14 +189,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 +202,36 @@ public class DefaultAccountDao implements AccountDao {
             account.addTags(tags);
         }
     }
+
+    private void saveCustomFieldsFromWithinTransaction(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.save(accountId, objectType, tagList);
+        }
+    }
+
+    private void saveTagsFromWithinTransaction(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.save(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..433a663
--- /dev/null
+++ b/account/src/test/java/com/ning/billing/account/api/MockAccountUserApi.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.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.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);
+        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());
+            }
+        }	
+		
+	}
+}
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 9dcce76..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,22 +16,24 @@
 
 package com.ning.billing.account.dao;
 
+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 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.BusService;
-import com.ning.billing.util.eventbus.DefaultEventBusService;
-
-import static org.testng.Assert.fail;
+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;
 
@@ -39,7 +41,7 @@ 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 utilDdl = IOUtils.toString(AccountSqlDao.class.getResourceAsStream("/com/ning/billing/util/ddl.sql"));
 
@@ -54,7 +56,7 @@ public abstract class AccountDaoTestBase {
             accountDao.test();
 
             BusService busService = injector.getInstance(BusService.class);
-            ((DefaultEventBusService) busService).startBus();
+            ((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..fd5350b 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,37 +172,87 @@ 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);
+        Account updatedAccount = new DefaultAccount(account.getId(), accountData, null, null);
         accountDao.update(updatedAccount);
 
         Account savedAccount = accountDao.getAccountByKey(account.getExternalKey());
@@ -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..fb72404
--- /dev/null
+++ b/account/src/test/java/com/ning/billing/account/glue/AccountModuleWithMocks.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.account.glue;
+
+import com.ning.billing.account.dao.AccountDao;
+import com.ning.billing.account.dao.MockAccountDao;
+
+public class AccountModuleWithMocks extends AccountModule {
+    @Override
+    protected void installAccountDao() {
+        bind(MockAccountDao.class).asEagerSingleton();
+        bind(AccountDao.class).to(MockAccountDao.class);
+    }
+}
diff --git a/analytics/pom.xml b/analytics/pom.xml
index 278348b..18bd687 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.5-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-analytics</artifactId>
diff --git a/analytics/src/main/java/com/ning/billing/analytics/api/AnalyticsService.java b/analytics/src/main/java/com/ning/billing/analytics/api/AnalyticsService.java
index e537841..4adb21e 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/api/AnalyticsService.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/api/AnalyticsService.java
@@ -19,7 +19,7 @@ package com.ning.billing.analytics.api;
 import com.google.inject.Inject;
 import com.ning.billing.analytics.AnalyticsListener;
 import com.ning.billing.lifecycle.LifecycleHandlerType;
-import com.ning.billing.util.eventbus.Bus;
+import com.ning.billing.util.bus.Bus;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
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/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/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/test/java/com/ning/billing/analytics/AnalyticsTestModule.java b/analytics/src/test/java/com/ning/billing/analytics/AnalyticsTestModule.java
index 4518731..ec55586 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/AnalyticsTestModule.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/AnalyticsTestModule.java
@@ -16,6 +16,7 @@
 
 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;
@@ -23,11 +24,9 @@ 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.EventBusModule;
+import com.ning.billing.util.glue.BusModule;
 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
 {
@@ -39,7 +38,7 @@ 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 TagStoreModule());
         install(new NotificationQueueModule());
@@ -49,8 +48,7 @@ public class AnalyticsTestModule extends AnalyticsModule
         // 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 2feffda..395d694 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.Bus;
-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,7 +84,7 @@ public class TestAnalyticsService
     private EntitlementUserApi entitlementApi;
 
     @Inject
-    private TagDescriptionDao tagDao;
+    private TagDefinitionSqlDao tagDao;
 
     @Inject
     private AnalyticsService service;
@@ -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/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/MockIAccountUserApi.java b/analytics/src/test/java/com/ning/billing/analytics/MockIAccountUserApi.java
index 791d191..2ce98b8 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/MockIAccountUserApi.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/MockIAccountUserApi.java
@@ -16,12 +16,10 @@
 
 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;
@@ -72,4 +70,9 @@ public class MockIAccountUserApi implements AccountUserApi
     public UUID getIdFromKey(String externalKey) {
         return id;
     }
+
+	@Override
+	public void deleteAccountByKey(String externalKey) {
+		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/MockSubscription.java b/analytics/src/test/java/com/ning/billing/analytics/MockSubscription.java
index 6420325..7197ec6 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/MockSubscription.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/MockSubscription.java
@@ -137,4 +137,14 @@ public class MockSubscription implements Subscription
     public SubscriptionTransition getPendingTransition() {
         throw new UnsupportedOperationException();
     }
+
+	@Override
+	public DateTime getChargedThroughDate() {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public DateTime getPaidThroughDate() {
+		throw new UnsupportedOperationException();
+	}
 }
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));
     }
 }

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

diff --git a/api/pom.xml b/api/pom.xml
index dce65e7..afbca6a 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.5-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/AccountChangeNotification.java b/api/src/main/java/com/ning/billing/account/api/AccountChangeNotification.java
index 2bc40f8..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,7 +16,7 @@
 
 package com.ning.billing.account.api;
 
-import com.ning.billing.util.eventbus.BusEvent;
+import com.ning.billing.util.bus.BusEvent;
 
 import java.util.List;
 import java.util.UUID;
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 22a1752..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,7 +16,7 @@
 
 package com.ning.billing.account.api;
 
-import com.ning.billing.util.eventbus.BusEvent;
+import com.ning.billing.util.bus.BusEvent;
 
 import java.util.UUID;
 
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..e208c83 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,12 @@ public interface AccountUserApi {
 
     public Account createAccount(AccountData data, List<CustomField> fields, List<Tag> tags) throws AccountApiException;
 
-    public void updateAccount(Account account);
+    /***
+     *
+     * Note: does not update the external key
+     * @param account
+     */
+    public void updateAccount(Account account) throws AccountApiException;
 
     public Account getAccountByKey(String key);
 
@@ -33,5 +38,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/entitlement/api/billing/DefaultBillingEvent.java b/api/src/main/java/com/ning/billing/entitlement/api/billing/DefaultBillingEvent.java
index 47df764..dc13776 100644
--- a/api/src/main/java/com/ning/billing/entitlement/api/billing/DefaultBillingEvent.java
+++ b/api/src/main/java/com/ning/billing/entitlement/api/billing/DefaultBillingEvent.java
@@ -130,5 +130,4 @@ public class DefaultBillingEvent implements BillingEvent {
     public InternationalPrice getRecurringPrice() {
         return recurringPrice;
     }
-
 }
\ No newline at end of file
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 8172ae4..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
@@ -20,6 +20,8 @@ import java.util.SortedSet;
 import java.util.UUID;
 
 import org.joda.time.DateTime;
+import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
+import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
 
 public interface EntitlementBillingApi {
 
@@ -32,8 +34,10 @@ public interface EntitlementBillingApi {
      */
     public SortedSet<BillingEvent> getBillingEventsForAccount(UUID accountId);
 
-    public UUID getAccountIdFromSubscriptionId(UUID subscriptionId) throws EntitlementBillingApiException;
+    public UUID getAccountIdFromSubscriptionId(UUID subscriptionId);
 
-    public void setChargedThroughDate(UUID subscriptionId, DateTime ctd) throws EntitlementBillingApiException;
+    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/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..41ecd78 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
@@ -64,6 +64,11 @@ public interface Subscription {
     public String getCurrentPriceList();
 
     public PlanPhase getCurrentPhase();
+    
+    public DateTime getChargedThroughDate();
+
+    public DateTime getPaidThroughDate();
+
 
     public List<SubscriptionTransition> getActiveTransitions();
 
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 449c368..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,7 +19,7 @@ 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.BusEvent;
+import com.ning.billing.util.bus.BusEvent;
 import org.joda.time.DateTime;
 
 import java.util.UUID;
diff --git a/api/src/main/java/com/ning/billing/ErrorCode.java b/api/src/main/java/com/ning/billing/ErrorCode.java
index c3238e2..4e1ffaa 100644
--- a/api/src/main/java/com/ning/billing/ErrorCode.java
+++ b/api/src/main/java/com/ning/billing/ErrorCode.java
@@ -69,7 +69,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'"),
     /*
@@ -106,16 +106,31 @@ public enum ErrorCode {
     */
     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_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_NO_ACCOUNT_ID_FOR_SUBSCRIPTION_ID(4003, "No account id was retrieved for subscription id %s")  
     ;
+
     private int code;
     private String format;
 
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 7903647..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.BusEvent;
-import org.joda.time.DateTime;
-
 import java.math.BigDecimal;
 import java.util.UUID;
 
+import org.joda.time.DateTime;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.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..a9070fd 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
@@ -34,9 +34,11 @@ public interface InvoiceItem extends Entity, Comparable<InvoiceItem> {
 
     String getDescription();
 
-    BigDecimal getAmount();
+    BigDecimal getRecurringAmount();
 
-    BigDecimal getRate();
+    BigDecimal getRecurringRate();
+
+    BigDecimal getFixedAmount();
 
     Currency getCurrency();
 
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
index a4acadb..b947762 100644
--- a/api/src/main/java/com/ning/billing/invoice/api/InvoicePayment.java
+++ b/api/src/main/java/com/ning/billing/invoice/api/InvoicePayment.java
@@ -22,12 +22,18 @@ import org.joda.time.DateTime;
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.util.entity.Entity;
 
-public interface InvoicePayment extends Entity {
+public interface InvoicePayment {
+    UUID getPaymentAttemptId();
+
     UUID getInvoiceId();
 
-    DateTime getPaymentDate();
+    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 8903584..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,9 +19,9 @@ 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);
@@ -30,12 +30,13 @@ public interface InvoiceUserApi {
 
     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..75a4ab2
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/payment/api/CreditCardPaymentMethodInfo.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.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;
+
+        public Builder() {
+            super(Builder.class);
+        }
+
+        public Builder(CreditCardPaymentMethodInfo src) {
+            super(Builder.class, src);
+        }
+
+        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 setMaskNumber(String maskNumber) {
+            this.maskNumber = maskNumber;
+            return this;
+        }
+
+        public CreditCardPaymentMethodInfo build() {
+            return new CreditCardPaymentMethodInfo(id, accountId, defaultMethod, cardHolderName, cardType, expirationDate, maskNumber);
+        }
+    }
+
+    private final String cardHolderName;
+    private final String cardType;
+    private final String expirationDate;
+    private final String maskNumber;
+
+    public CreditCardPaymentMethodInfo(String id,
+                                   String accountId,
+                                   Boolean defaultMethod,
+                                   String cardHolderName,
+                                   String cardType,
+                                   String expirationDate,
+                                   String maskNumber) {
+      super(id, accountId, defaultMethod, "CreditCard");
+      this.cardHolderName = cardHolderName;
+      this.cardType = cardType;
+      this.expirationDate = expirationDate;
+      this.maskNumber = maskNumber;
+    }
+
+    public String getCardHolderName() {
+      return cardHolderName;
+    }
+
+    public String getCardType() {
+      return cardType;
+    }
+
+    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..8e665ed
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/payment/api/PaymentApi.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.payment.api;
+
+import java.util.List;
+
+import javax.annotation.Nullable;
+
+import com.ning.billing.account.api.Account;
+
+public interface PaymentApi {
+    Either<PaymentError, PaymentMethodInfo> getPaymentMethod(@Nullable String accountKey, String paymentMethodId);
+
+    Either<PaymentError, List<PaymentMethodInfo>> getPaymentMethods(String accountKey);
+
+    Either<PaymentError, Void> deletePaymentMethod(String accountKey, String paymentMethodId);
+
+    Either<PaymentError, Void> updatePaymentGateway(String accountKey);
+
+    Either<PaymentError, String> addPaypalPaymentMethod(@Nullable String accountKey, PaypalPaymentMethodInfo paypalPaymentMethod);
+
+    Either<PaymentError, PaymentMethodInfo> updatePaymentMethod(String accountKey, PaymentMethodInfo paymentMethodInfo);
+
+    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, PaymentProviderAccount> updatePaymentProviderAccount(Account account);
+
+    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..48d89e9
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/payment/api/PaymentAttempt.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF 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 DateTime createdDate;
+    private final DateTime updatedDate;
+
+    public PaymentAttempt(UUID paymentAttemptId,
+                          UUID invoiceId,
+                          UUID accountId,
+                          BigDecimal amount,
+                          Currency currency,
+                          DateTime invoiceDate,
+                          DateTime paymentAttemptDate,
+                          String paymentId,
+                          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.createdDate = createdDate;
+        this.updatedDate = updatedDate;
+    }
+
+    public PaymentAttempt(UUID paymentAttemptId,
+                          UUID invoiceId,
+                          UUID accountId,
+                          BigDecimal amount,
+                          Currency currency,
+                          DateTime invoiceDate,
+                          DateTime paymentAttemptDate,
+                          String paymentId) {
+        this(paymentAttemptId, invoiceId, accountId, amount, currency, invoiceDate, paymentAttemptDate, paymentId, new DateTime(DateTimeZone.UTC), new DateTime(DateTimeZone.UTC));
+    }
+
+    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);
+    }
+
+    public PaymentAttempt(UUID paymentAttemptId, UUID invoiceId, UUID accountId, DateTime invoiceDate, DateTime paymentAttemptDate) {
+        this(paymentAttemptId, invoiceId, accountId, null, null, invoiceDate, paymentAttemptDate, null);
+    }
+
+    public PaymentAttempt(UUID paymentAttemptId, Invoice invoice) {
+        this(paymentAttemptId, invoice.getId(), invoice.getAccountId(), invoice.getBalance(), invoice.getCurrency(), invoice.getInvoiceDate(), 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;
+    }
+
+    @Override
+    public String toString() {
+        return "PaymentAttempt [paymentAttemptId=" + paymentAttemptId + ", invoiceId=" + invoiceId + ", amount=" + amount + ", currency=" + currency + ", paymentId=" + paymentId + ", paymentAttemptDate=" + paymentAttemptDate + "]";
+    }
+
+    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 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.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 PaymentAttempt build() {
+            return new PaymentAttempt(paymentAttemptId,
+                                      invoiceId,
+                                      accountId,
+                                      amount,
+                                      currency,
+                                      invoiceDate,
+                                      paymentAttemptDate,
+                                      paymentId,
+                                      createdDate,
+                                      updatedDate);
+        }
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(paymentAttemptId,
+                                invoiceId,
+                                accountId,
+                                amount,
+                                currency,
+                                invoiceDate,
+                                paymentAttemptDate,
+                                paymentId);
+    }
+
+    @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(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..b8dc8cd
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/payment/api/PaymentInfo.java
@@ -0,0 +1,271 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF 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 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 DateTime effectiveDate;
+    private final DateTime createdDate;
+    private final DateTime updatedDate;
+
+    public PaymentInfo(PaymentInfo src) {
+        this.paymentId = src.paymentId;
+        this.amount = src.amount;
+        this.refundAmount = src.refundAmount;
+        this.paymentNumber = src.paymentNumber;
+        this.bankIdentificationNumber = src.bankIdentificationNumber;
+        this.status = src.status;
+        this.type = src.type;
+        this.referenceId = src.referenceId;
+        this.effectiveDate = src.effectiveDate;
+        this.createdDate = src.createdDate;
+        this.updatedDate = src.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("effectiveDate") DateTime effectiveDate,
+                       @JsonProperty("createdDate") DateTime createdDate,
+                       @JsonProperty("updatedDate") DateTime updatedDate) {
+        this.paymentId = paymentId;
+        this.amount = amount;
+        this.refundAmount = refundAmount;
+        this.bankIdentificationNumber = bankIdentificationNumber;
+        this.effectiveDate = effectiveDate;
+        this.paymentNumber = paymentNumber;
+        this.referenceId = referenceId;
+        this.status = status;
+        this.type = type;
+        this.createdDate = createdDate;
+        this.updatedDate = 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 getReferenceId() {
+        return referenceId;
+    }
+
+    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 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.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 setUpdatedDate(DateTime updatedDate) {
+            this.updatedDate = updatedDate;
+            return this;
+        }
+
+        public PaymentInfo build() {
+            return new PaymentInfo(paymentId,
+                                   amount,
+                                   refundAmount,
+                                   bankIdentificationNumber,
+                                   paymentNumber,
+                                   type,
+                                   status,
+                                   referenceId,
+                                   effectiveDate,
+                                   createdDate,
+                                   updatedDate);
+        }
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(amount,
+                                bankIdentificationNumber,
+                                createdDate,
+                                effectiveDate,
+                                paymentId,
+                                paymentNumber,
+                                referenceId,
+                                refundAmount,
+                                status,
+                                type,
+                                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(createdDate, other.createdDate) &&
+                       Objects.equal(effectiveDate, other.effectiveDate) &&
+                       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(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 + ", 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..553d6a2
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/payment/api/PaymentProviderAccount.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF 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 accountNumber;
+    private final String accountName;
+    private final String phoneNumber;
+    private final String defaultPaymentMethodId;
+
+    public PaymentProviderAccount(String id,
+                                  String accountNumber,
+                                  String accountName,
+                                  String phoneNumber,
+                                  String defaultPaymentMethodId) {
+        this.id = id;
+        this.accountNumber = accountNumber;
+        this.accountName = accountName;
+        this.phoneNumber = phoneNumber;
+        this.defaultPaymentMethodId = defaultPaymentMethodId;
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public String getAccountNumber() {
+        return accountNumber;
+    }
+
+    public String getAccountName() {
+        return accountName;
+    }
+
+    public String getPhoneNumber() {
+        return phoneNumber;
+    }
+
+    public String getDefaultPaymentMethodId() {
+        return defaultPaymentMethodId;
+    }
+
+    public static class Builder {
+        private String id;
+        private String accountNumber;
+        private String accountName;
+        private String phoneNumber;
+        private String defaultPaymentMethodId;
+
+        public Builder setId(String id) {
+            this.id = id;
+            return this;
+        }
+
+        public Builder setAccountNumber(String accountNumber) {
+            this.accountNumber = accountNumber;
+            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, accountNumber, accountName, phoneNumber, defaultPaymentMethodId);
+        }
+
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(id,
+                                accountNumber,
+                                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(accountNumber, other.accountNumber) &&
+                       Objects.equal(phoneNumber, other.phoneNumber) &&
+                       Objects.equal(defaultPaymentMethodId, other.defaultPaymentMethodId);
+            }
+        }
+        return false;
+    }
+
+}
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..65c36e9
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/payment/api/PaypalPaymentMethodInfo.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.api;
+
+import com.google.common.base.Strings;
+
+
+public final class PaypalPaymentMethodInfo extends PaymentMethodInfo {
+    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);
+        }
+
+        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;
+
+    public PaypalPaymentMethodInfo(String id,
+                                   String accountId,
+                                   Boolean defaultMethod,
+                                   String baid,
+                                   String email) {
+        super(id, accountId, defaultMethod, "PayPal");
+
+        if (Strings.isNullOrEmpty(accountId) || Strings.isNullOrEmpty(baid) || Strings.isNullOrEmpty(email)) {
+            throw new IllegalArgumentException("accountId, baid and email should be present");
+        }
+
+        this.baid = baid;
+        this.email = email;
+    }
+
+    public String getBaid() {
+        return baid;
+    }
+
+    public String getEmail() {
+        return 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/TagDefinitionService.java b/api/src/main/java/com/ning/billing/util/api/TagDefinitionService.java
new file mode 100644
index 0000000..1434f8f
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/util/api/TagDefinitionService.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.util.api;
+
+import com.ning.billing.lifecycle.KillbillService;
+
+public interface TagDefinitionService extends KillbillService {
+    public TagDefinitionUserApi getTagDefinitionUserApi();
+}
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/tag/ControlTag.java b/api/src/main/java/com/ning/billing/util/tag/ControlTag.java
new file mode 100644
index 0000000..a933cff
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/util/tag/ControlTag.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.util.tag;
+
+import com.ning.billing.account.api.ControlTagType;
+
+public interface ControlTag extends Tag {
+    public ControlTagType getControlTagType();
+}
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 4(+2 -2)

diff --git a/beatrix/pom.xml b/beatrix/pom.xml
index e0e4352..ded6f0f 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.5-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-beatrix</artifactId>
@@ -32,7 +32,7 @@
             <groupId>com.ning.billing</groupId>
             <artifactId>killbill-invoice</artifactId>
         </dependency>
-       <dependency>
+        <dependency>
             <groupId>com.ning.billing</groupId>
             <artifactId>killbill-catalog</artifactId>
         </dependency>
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/inv_ent/MockModule.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/inv_ent/MockModule.java
index 5fc81c6..8816190 100644
--- a/beatrix/src/test/java/com/ning/billing/beatrix/integration/inv_ent/MockModule.java
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/inv_ent/MockModule.java
@@ -23,7 +23,6 @@ import java.net.URL;
 import java.util.Set;
 
 import org.skife.config.ConfigurationObjectFactory;
-import org.skife.jdbi.v2.DBI;
 import org.skife.jdbi.v2.IDBI;
 
 import com.google.common.collect.ImmutableSet;
@@ -44,8 +43,8 @@ import com.ning.billing.invoice.glue.InvoiceModule;
 import com.ning.billing.lifecycle.KillbillService;
 import com.ning.billing.util.clock.Clock;
 import com.ning.billing.util.clock.ClockMock;
-import com.ning.billing.util.eventbus.BusService;
-import com.ning.billing.util.glue.EventBusModule;
+import com.ning.billing.util.bus.BusService;
+import com.ning.billing.util.glue.BusModule;
 import com.ning.billing.util.glue.NotificationQueueModule;
 
 
@@ -59,11 +58,10 @@ public class MockModule extends AbstractModule {
         bind(Clock.class).to(ClockMock.class).asEagerSingleton();
         bind(ClockMock.class).asEagerSingleton();
         bind(Lifecycle.class).to(SubsetDefaultLifecycle.class).asEagerSingleton();
-        bind(IDBI.class).to(DBI.class).asEagerSingleton();
-        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);
-        install(new EventBusModule());
+        install(new BusModule());
         install(new NotificationQueueModule());
         install(new AccountModule());
         install(new CatalogModule());
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/inv_ent/TestBasic.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/inv_ent/TestBasic.java
index 3ed97e1..e7ffda4 100644
--- a/beatrix/src/test/java/com/ning/billing/beatrix/integration/inv_ent/TestBasic.java
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/inv_ent/TestBasic.java
@@ -22,6 +22,7 @@ import static org.testng.Assert.assertTrue;
 import java.util.UUID;
 
 import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
 import org.joda.time.Interval;
 import org.skife.jdbi.v2.Handle;
 import org.skife.jdbi.v2.IDBI;
@@ -59,7 +60,7 @@ 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.eventbus.BusService;
+import com.ning.billing.util.bus.BusService;
 
 @Guice(modules = {MockModule.class})
 public class TestBasic {
@@ -153,7 +154,7 @@ public class TestBasic {
                 h.execute("truncate table claimed_notifications");
                 h.execute("truncate table invoices");
                 h.execute("truncate table invoice_items");
-                h.execute("truncate table tag_descriptions");
+                h.execute("truncate table tag_definitions");
                 h.execute("truncate table tags");
                 return null;
             }
@@ -190,6 +191,7 @@ public class TestBasic {
                 new PlanPhaseSpecifier(productName, ProductCategory.BASE, term, planSetName, null), null);
         assertNotNull(subscription);
         assertTrue(busHandler.isCompleted(5000));
+        log.info("testSimple passed first busHandler checkpoint.");
 
         //
         // VERIFY CTD HAS BEEN SET
@@ -207,7 +209,7 @@ public class TestBasic {
         String newProductName = "Assault-Rifle";
         subscription.changePlan(newProductName, newTerm, newPlanSetName, clock.getUTCNow());
         assertTrue(busHandler.isCompleted(5000));
-
+        log.info("testSimple passed second busHandler checkpoint.");
 
         //
         // VERIFY AGAIN CTD HAS BEEN SET
@@ -237,6 +239,7 @@ public class TestBasic {
         clock.addDeltaFromReality(ctd.getMillis() - clock.getUTCNow().getMillis());
         //clock.setDeltaFromReality(AT_LEAST_ONE_MONTH_MS + 1000);
         assertTrue(busHandler.isCompleted(5000));
+        log.info("testSimple passed third busHandler checkpoint.");
 
         //
         // MOVE TIME AFTER NEXT BILL CYCLE DAY AND EXPECT EVENT : NextEvent.INVOICE
@@ -246,12 +249,13 @@ public class TestBasic {
         do {
             clock.addDeltaFromReality(AT_LEAST_ONE_MONTH_MS + 1000);
             busHandler.pushExpectedEvent(NextEvent.INVOICE);
+            busHandler.pushExpectedEvent(NextEvent.INVOICE);
             assertTrue(busHandler.isCompleted(5000));
             lastCtd = checkAndGetCTD(subscription.getId());
         } while (maxCycles-- > 0);
 
         //
-        // FINALY CANCEL SUBSCRIPTION EOT
+        // FINALLY CANCEL SUBSCRIPTION EOT
         //
         subscription.cancel(clock.getUTCNow(), false);
 
@@ -305,6 +309,51 @@ public class TestBasic {
             public String getPaymentProviderName() {
                 return "Paypal";
             }
+
+            @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/inv_ent/TestBusHandler.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/inv_ent/TestBusHandler.java
index fafcf68..307131b 100644
--- a/beatrix/src/test/java/com/ning/billing/beatrix/integration/inv_ent/TestBusHandler.java
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/inv_ent/TestBusHandler.java
@@ -144,13 +144,13 @@ public class TestBusHandler {
         log.debug("notifyIfStackEmpty EXIT");
     }
 
-    private void assertEqualsNicely(NextEvent expected) {
+    private void assertEqualsNicely(NextEvent received) {
 
         boolean foundIt = false;
         Iterator<NextEvent> it = nextExpectedEvent.iterator();
         while (it.hasNext()) {
             NextEvent ev = it.next();
-            if (ev == expected) {
+            if (ev == received) {
                 it.remove();
                 foundIt = true;
                 break;
@@ -158,7 +158,7 @@ public class TestBusHandler {
         }
         if (!foundIt) {
             Joiner joiner = Joiner.on(" ");
-            System.err.println("Expected event " + expected + " got " + joiner.join(nextExpectedEvent));
+            System.err.println("Received event " + received + "; expected " + joiner.join(nextExpectedEvent));
             System.exit(1);
         }
     }
diff --git a/beatrix/src/test/resources/resource.properties b/beatrix/src/test/resources/resource.properties
index cc82754..1cf5ad0 100644
--- a/beatrix/src/test/resources/resource.properties
+++ b/beatrix/src/test/resources/resource.properties
@@ -1,4 +1,4 @@
-killbill.catalog.uri=file:src/test/resources/catalogSample.xml
+killbill.catalog.uri=file:beatrix/src/test/resources/catalogSample.xml
 killbill.entitlement.dao.claim.time=60000
 killbill.entitlement.dao.ready.max=1
 killbill.entitlement.engine.notifications.sleep=500

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

diff --git a/catalog/pom.xml b/catalog/pom.xml
index 1b5b8d6..635fac3 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.5-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/DefaultInternationalPrice.java b/catalog/src/main/java/com/ning/billing/catalog/DefaultInternationalPrice.java
index 6990d3c..1c505f9 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() {
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..adf9307 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/DefaultPlanPhase.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/DefaultPlanPhase.java
@@ -38,7 +38,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 +127,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;
 
+        return errors;
 	}
 	
 	@Override
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/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..2ef5768 100644
--- a/catalog/src/test/java/com/ning/billing/catalog/MockPlanPhase.java
+++ b/catalog/src/test/java/com/ning/billing/catalog/MockPlanPhase.java
@@ -20,6 +20,8 @@ import com.ning.billing.catalog.api.BillingPeriod;
 import com.ning.billing.catalog.api.PhaseType;
 import com.ning.billing.catalog.api.TimeUnit;
 
+import javax.annotation.Nullable;
+
 public class MockPlanPhase extends DefaultPlanPhase {
 
     public MockPlanPhase(
@@ -36,11 +38,29 @@ public class MockPlanPhase extends DefaultPlanPhase {
 	}
     
     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);
+		setReccuringPrice(recurringPrice);
+		setFixedPrice(fixedPrice);
 		setPlan(new MockPlan(this));
 	}
 
diff --git a/entitlement/pom.xml b/entitlement/pom.xml
index f0d0aae..805436d 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.5-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-entitlement</artifactId>
@@ -52,6 +52,11 @@
         </dependency>
         <dependency>
             <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-account</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.ning.billing</groupId>
             <artifactId>killbill-util</artifactId>
             <type>test-jar</type>
             <scope>test</scope>
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 09df8ae..4c5e0af 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,35 +16,24 @@
 
 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;
 import com.ning.billing.account.api.AccountUserApi;
-import com.ning.billing.catalog.api.BillingAlignment;
-import com.ning.billing.catalog.api.Catalog;
-import com.ning.billing.catalog.api.CatalogApiException;
-import com.ning.billing.catalog.api.CatalogService;
-import com.ning.billing.catalog.api.Plan;
-import com.ning.billing.catalog.api.PlanPhase;
-import com.ning.billing.catalog.api.PlanPhaseSpecifier;
-import com.ning.billing.catalog.api.Product;
-import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
+import com.ning.billing.catalog.api.*;
 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;
 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.*;
 
 public class DefaultEntitlementBillingApi implements EntitlementBillingApi {
 	private static final Logger log = LoggerFactory.getLogger(DefaultEntitlementBillingApi.class);
@@ -75,10 +64,13 @@ public class DefaultEntitlementBillingApi implements EntitlementBillingApi {
         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: " + 
         					transition.getId().toString(), e);
+                } catch (Exception e) {
+                    log.warn("Failed while getting BillingEvent", e);
         		}
         	}
         }
@@ -86,7 +78,7 @@ public class DefaultEntitlementBillingApi implements EntitlementBillingApi {
     }
 
     @Override
-    public UUID getAccountIdFromSubscriptionId(final UUID subscriptionId) throws EntitlementBillingApiException {
+    public UUID getAccountIdFromSubscriptionId(final UUID subscriptionId) {
         return dao.getAccountIdFromSubscriptionId(subscriptionId);
     }
 
@@ -127,17 +119,29 @@ public class DefaultEntitlementBillingApi implements EntitlementBillingApi {
     		
     }
     
-
     @Override
-    public void setChargedThroughDate(final UUID subscriptionId, final DateTime ctd) throws EntitlementBillingApiException {
+    public void setChargedThroughDate(final UUID subscriptionId, final DateTime ctd) {
         SubscriptionData subscription = (SubscriptionData) dao.getSubscriptionFromId(subscriptionId);
-        if (subscription == null) {
-            throw new EntitlementBillingApiException(ErrorCode.ENT_INVALID_SUBSCRIPTION_ID, subscriptionId.toString());
-        }
 
         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();
+
+            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 f54f902..ed05b02 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
@@ -89,7 +89,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();
@@ -102,7 +101,6 @@ public class DefaultEntitlementUserApi implements EntitlementUserApi {
 
             Plan plan = catalogService.getFullCatalog().findPlan(spec.getProductName(), spec.getBillingPeriod(), realPriceList, requestedDate);
 
-
             PlanPhase phase = (plan.getInitialPhases() != null) ? plan.getInitialPhases()[0] : plan.getFinalPhase();
             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",
@@ -148,4 +146,21 @@ public class DefaultEntitlementUserApi implements EntitlementUserApi {
             throw new EntitlementUserApiException(e);
         }
     }
+
+	@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/engine/core/Engine.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/Engine.java
index 86fd78b..ce70ba7 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
@@ -45,12 +45,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.Bus;
-import com.ning.billing.util.eventbus.Bus.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 {
@@ -132,7 +132,7 @@ public class Engine implements EventListener, EntitlementService {
                     return config.getDaoMaxReadyEvents();
                 }
             });
-        } catch (NotficationQueueAlreadyExists e) {
+        } catch (NotificationQueueAlreadyExists e) {
             throw new RuntimeException(e);
         }
     }
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 c658ee6..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
@@ -43,7 +43,7 @@ public interface EntitlementDao {
     public Subscription getSubscriptionFromId(UUID subscriptionId);
 
     // Account retrieval
-    public UUID getAccountIdFromSubscriptionId(UUID subscriptionId) throws EntitlementBillingApiException;
+    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 f67d607..e5f15ed 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,7 +21,14 @@ 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;
@@ -29,7 +36,6 @@ 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.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;
@@ -47,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 {
@@ -69,7 +68,7 @@ public class EntitlementSqlDao implements EntitlementDao {
     private final NotificationQueueService notificationQueueService;
 
     @Inject
-    public EntitlementSqlDao(final DBI dbi, final Clock clock, final SubscriptionFactory factory,
+    public EntitlementSqlDao(final IDBI dbi, final Clock clock, final SubscriptionFactory factory,
                              final NotificationQueueService notificationQueueService) {
         this.clock = clock;
         this.factory = factory;
@@ -112,15 +111,23 @@ public class EntitlementSqlDao implements EntitlementDao {
     }
 
     @Override
-    public UUID getAccountIdFromSubscriptionId(final UUID subscriptionId) throws EntitlementBillingApiException {
-        UUID bundleId = subscriptionsDao.getSubscriptionFromId(subscriptionId.toString()).getBundleId();
+    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) {
-            throw new EntitlementBillingApiException(ErrorCode.ENT_GET_NO_BUNDLE_FOR_SUBSCRIPTION, subscriptionId.toString());
+            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) {
-            throw new EntitlementBillingApiException(ErrorCode.ENT_GET_INVALID_BUNDLE_ID, bundleId.toString());
+            log.error(String.format(ErrorCode.ENT_GET_INVALID_BUNDLE_ID.getFormat(), bundleId.toString()));
+            return null;
         }
 
         return bundle.getAccountId();
@@ -213,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() {
@@ -436,9 +443,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/test/java/com/ning/billing/entitlement/api/ApiTestListener.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/ApiTestListener.java
index 7241611..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.Bus;
+import com.ning.billing.util.bus.Bus;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
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..967d8b6 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
@@ -60,4 +60,10 @@ public class BrainDeadAccountUserApi implements AccountUserApi {
 		throw new UnsupportedOperationException();
 	}
 
+	@Override
+	public void deleteAccountByKey(String externalKey)
+			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 a186bff..7b49fde 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
@@ -58,20 +58,18 @@ class BrainDeadMockEntitlementDao implements EntitlementDao {
 	}
 
     @Override
-    public UUID getAccountIdFromSubscriptionId(final UUID subscriptionId) throws EntitlementBillingApiException {
+    public UUID getAccountIdFromSubscriptionId(final UUID subscriptionId) {
         throw new UnsupportedOperationException();
     }
 
     @Override
 	public Subscription getBaseSubscription(final UUID bundleId) {
 		throw new UnsupportedOperationException();
-
 	}
 
 	@Override
 	public List<Subscription> getSubscriptions(final UUID bundleId) {
 		throw new UnsupportedOperationException();
-
 	}
 
 	@Override
@@ -102,7 +100,6 @@ class BrainDeadMockEntitlementDao implements EntitlementDao {
 		throw new UnsupportedOperationException();
 	}
 
-
 	@Override
 	public void createSubscription(final SubscriptionData subscription,
 			final List<EntitlementEvent> initialEvents) {
@@ -113,7 +110,6 @@ class BrainDeadMockEntitlementDao implements EntitlementDao {
 	public void cancelSubscription(final UUID subscriptionId,
 			final EntitlementEvent cancelEvent) {
 		throw new UnsupportedOperationException();
-
 	}
 
 	@Override
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 21b982b..46ad9a2 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
@@ -59,6 +59,8 @@ import com.ning.billing.lifecycle.KillbillService.ServiceException;
 import com.ning.billing.util.clock.Clock;
 import com.ning.billing.util.clock.DefaultClock;
 
+import static org.testng.Assert.assertTrue;
+
 public class TestDefaultEntitlementBillingApi {
 	private static final UUID zeroId = new UUID(0L,0L);
 	private static final UUID oneId = new UUID(1L,0L);
@@ -128,7 +130,7 @@ public class TestDefaultEntitlementBillingApi {
 			}
 
             @Override
-            public UUID getAccountIdFromSubscriptionId(final UUID subscriptionId) throws EntitlementBillingApiException {
+            public UUID getAccountIdFromSubscriptionId(final UUID subscriptionId) {
                 throw new UnsupportedOperationException();
             }
 
@@ -138,6 +140,7 @@ public class TestDefaultEntitlementBillingApi {
 			}
 		};
 
+        assertTrue(true);
 	}
 	
     @Test(enabled=true, groups="fast")
@@ -149,7 +152,7 @@ public class TestDefaultEntitlementBillingApi {
 			}
 
             @Override
-            public UUID getAccountIdFromSubscriptionId(final UUID subscriptionId) throws EntitlementBillingApiException {
+            public UUID getAccountIdFromSubscriptionId(final UUID subscriptionId) {
                 throw new UnsupportedOperationException();
             }
 
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 adcf53f..c00b4e7 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.BusService;
-
-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 {
@@ -109,7 +110,7 @@ public abstract class TestApiBase {
     public void tearDown() {
         try {
             busService.getBus().register(testListener);
-            ((DefaultEventBusService) busService).stopBus();
+            ((DefaultBusService) busService).stopBus();
         } catch (Exception e) {
             log.warn("Failed to tearDown test properly ", e);
         }
@@ -129,9 +130,8 @@ public abstract class TestApiBase {
         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) {
@@ -290,26 +290,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/engine/dao/MockEntitlementDaoMemory.java b/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/MockEntitlementDaoMemory.java
index d5719e4..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
@@ -134,7 +134,7 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
     }
 
     @Override
-    public UUID getAccountIdFromSubscriptionId(final UUID subscriptionId) throws EntitlementBillingApiException {
+    public UUID getAccountIdFromSubscriptionId(final UUID subscriptionId) {
         throw new UnsupportedOperationException();
     }
 
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..2204274 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);
     }
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 b57d15b..8743274 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,12 +16,11 @@
 
 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.glue.BusModule;
 
 public class MockEngineModule extends EntitlementModule {
 
@@ -30,9 +29,9 @@ public class MockEngineModule extends EntitlementModule {
     protected void configure() {
         super.configure();
         bind(Clock.class).to(ClockMock.class).asEagerSingleton();
-        install(new EventBusModule());
+        install(new BusModule());
         install(new CatalogModule());
-        install(new AccountModuleMock());
+        install(new AccountModuleWithMocks());
     }
 
 }
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 c2cc95c..54e8b66 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 {
 
@@ -37,7 +37,7 @@ public class MockEngineModuleSql extends MockEngineModule {
 
 
     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 3(+1 -2)

diff --git a/invoice/pom.xml b/invoice/pom.xml
index 3cc522b..e685de0 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.5-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-invoice</artifactId>
@@ -53,7 +53,6 @@
             <type>test-jar</type>
             <scope>test</scope>
         </dependency>
-        
         <dependency>
             <groupId>org.testng</groupId>
             <artifactId>testng</artifactId>
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 df43d71..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
@@ -21,7 +21,7 @@ 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.eventbus.Bus;
+import com.ning.billing.util.bus.Bus;
 
 public class DefaultInvoiceService implements InvoiceService {
 
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 835fce9..340e682 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
@@ -16,16 +17,18 @@
 
 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;
+import org.joda.time.DateTime;
+
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.UUID;
 
 public class DefaultInvoicePaymentApi implements InvoicePaymentApi {
     private final InvoiceDao dao;
@@ -36,15 +39,14 @@ public class DefaultInvoicePaymentApi implements InvoicePaymentApi {
     }
 
     @Override
-    public void paymentSuccessful(final UUID invoiceId, final BigDecimal amount, final Currency currency,
-                                  final UUID paymentId, final DateTime paymentAttemptDate) {
-        dao.notifySuccessfulPayment(invoiceId, amount, currency, paymentId, paymentAttemptDate);
+    public void notifyOfPaymentAttempt(InvoicePayment invoicePayment) {
+        dao.notifyOfPaymentAttempt(invoicePayment);
     }
 
-    @Override
-    public void paymentFailed(final UUID invoiceId, final UUID paymentId, final DateTime paymentAttemptDate) {
-        dao.notifyFailedPayment(invoiceId, paymentId, paymentAttemptDate);
-    }
+//    @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) {
@@ -55,4 +57,27 @@ public class DefaultInvoicePaymentApi implements InvoicePaymentApi {
     public Invoice getInvoice(final UUID invoiceId) {
         return dao.getById(invoiceId);
     }
-}
+
+    @Override
+    public Invoice getInvoiceForPaymentAttemptId(UUID paymentAttemptId) {
+        UUID invoiceIdStr = dao.getInvoiceIdByPaymentAttemptId(paymentAttemptId);
+        return invoiceIdStr == null ? null : dao.getById(invoiceIdStr);
+    }
+
+    @Override
+    public InvoicePayment getInvoicePayment(UUID paymentAttemptId) {
+        return dao.getInvoicePayment(paymentAttemptId);
+    }
+
+    @Override
+    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 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 d1f795e..629f939 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
@@ -19,9 +19,10 @@ package com.ning.billing.invoice.api.user;
 import java.math.BigDecimal;
 import java.util.List;
 import java.util.UUID;
+
+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.InvoiceItem;
 import com.ning.billing.invoice.api.InvoiceUserApi;
@@ -51,6 +52,17 @@ public class DefaultInvoiceUserApi implements InvoiceUserApi {
     }
 
     @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);
     }
@@ -61,13 +73,7 @@ public class DefaultInvoiceUserApi implements InvoiceUserApi {
     }
 
     @Override
-    public void paymentAttemptFailed(final UUID invoiceId, final UUID paymentId, final DateTime paymentAttemptDate) {
-        dao.notifyFailedPayment(invoiceId, paymentId, paymentAttemptDate);
-    }
-
-    @Override
-    public void paymentAttemptSuccessful(final UUID invoiceId, final BigDecimal amount, final Currency currency,
-                                         final UUID paymentId, final DateTime paymentDate) {
-        dao.notifySuccessfulPayment(invoiceId, amount, currency, paymentId, paymentDate);
+    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 d4415ef..4d963bb 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
@@ -17,32 +17,42 @@
 package com.ning.billing.invoice.dao;
 
 import java.math.BigDecimal;
+import java.util.Collection;
 import java.util.List;
 import java.util.UUID;
+
+import com.ning.billing.entitlement.api.billing.EntitlementBillingApi;
 import org.joda.time.DateTime;
 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.catalog.api.Currency;
 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.util.eventbus.Bus;
+import com.ning.billing.invoice.notification.NextBillingDateNotifier;
+import com.ning.billing.util.bus.Bus;
 
 public class DefaultInvoiceDao implements InvoiceDao {
     private final InvoiceSqlDao invoiceSqlDao;
     private final InvoiceItemSqlDao invoiceItemSqlDao;
+    private final InvoicePaymentSqlDao invoicePaymentSqlDao;
+    private final NextBillingDateNotifier notifier;
+    private final EntitlementBillingApi entitlementBillingApi;
 
     private final Bus eventBus;
 
     @Inject
-    public DefaultInvoiceDao(final IDBI dbi, final Bus eventBus) {
+    public DefaultInvoiceDao(final IDBI dbi, final Bus eventBus,
+                             final NextBillingDateNotifier notifier, final EntitlementBillingApi entitlementBillingApi) {
         this.invoiceSqlDao = dbi.onDemand(InvoiceSqlDao.class);
         this.invoiceItemSqlDao = dbi.onDemand(InvoiceItemSqlDao.class);
+        this.invoicePaymentSqlDao = dbi.onDemand(InvoicePaymentSqlDao.class);
         this.eventBus = eventBus;
+        this.notifier = notifier;
+        this.entitlementBillingApi = entitlementBillingApi;
     }
 
     @Override
@@ -53,7 +63,6 @@ public class DefaultInvoiceDao implements InvoiceDao {
                 List<Invoice> invoices = invoiceDao.getInvoicesByAccount(accountId.toString());
 
                 getInvoiceItemsWithinTransaction(invoices, invoiceDao);
-
                 getInvoicePaymentsWithinTransaction(invoices, invoiceDao);
 
                 return invoices;
@@ -132,6 +141,9 @@ public class DefaultInvoiceDao implements InvoiceDao {
                     InvoiceItemSqlDao invoiceItemDao = invoiceDao.become(InvoiceItemSqlDao.class);
                     invoiceItemDao.create(invoiceItems);
 
+                    notifyOfFutureBillingEvents(invoiceSqlDao, invoiceItems);
+                    setChargedThroughDates(invoiceSqlDao, invoiceItems);
+
                     List<InvoicePayment> invoicePayments = invoice.getPayments();
                     InvoicePaymentSqlDao invoicePaymentSqlDao = invoiceDao.become(InvoicePaymentSqlDao.class);
                     invoicePaymentSqlDao.create(invoicePayments);
@@ -140,7 +152,7 @@ public class DefaultInvoiceDao implements InvoiceDao {
                     event = new DefaultInvoiceCreationNotification(invoice.getId(), invoice.getAccountId(),
                                                                   invoice.getBalance(), invoice.getCurrency(),
                                                                   invoice.getInvoiceDate());
-                    eventBus.post(event);
+                    eventBus.postFromTransaction(event, invoiceDao);
                 }
 
                 return null;
@@ -169,15 +181,38 @@ public class DefaultInvoiceDao implements InvoiceDao {
     }
 
     @Override
-    public void notifySuccessfulPayment(final UUID invoiceId, final BigDecimal paymentAmount,
-                                        final Currency currency, final UUID paymentId, final DateTime paymentDate) {
-        invoiceSqlDao.notifySuccessfulPayment(invoiceId.toString(), paymentAmount, currency.toString(),
-                                              paymentId.toString(), paymentDate.toDate());
+    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 UUID getInvoiceIdByPaymentAttemptId(UUID paymentAttemptId) {
+        return invoiceSqlDao.getInvoiceIdByPaymentAttemptId(paymentAttemptId.toString());
     }
 
     @Override
-    public void notifyFailedPayment(final UUID invoiceId, final UUID paymentId, final DateTime paymentAttemptDate) {
-        invoiceSqlDao.notifyFailedPayment(invoiceId.toString(), paymentId.toString(), paymentAttemptDate.toDate());
+    public InvoicePayment getInvoicePayment(UUID paymentAttemptId) {
+        return invoicePaymentSqlDao.getInvoicePayment(paymentAttemptId);
     }
 
     @Override
@@ -201,4 +236,20 @@ public class DefaultInvoiceDao implements InvoiceDao {
             invoice.addPayments(invoicePayments);
         }
     }
+
+    private void notifyOfFutureBillingEvents(final InvoiceSqlDao dao, final List<InvoiceItem> invoiceItems) {
+        for (final InvoiceItem item : invoiceItems) {
+            if (item.getEndDate() != null) {
+                notifier.insertNextBillingNotification(dao, item.getSubscriptionId(), item.getEndDate());
+            }
+        }
+    }
+
+    private void setChargedThroughDates(final InvoiceSqlDao dao, final Collection<InvoiceItem> invoiceItems) {
+        for (InvoiceItem invoiceItem : invoiceItems) {
+            if (invoiceItem.getEndDate() != null) {
+                entitlementBillingApi.setChargedThroughDateFromTransaction(dao, invoiceItem.getSubscriptionId(), invoiceItem.getEndDate());
+            }
+        }
+    }
 }
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 77f12fe..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,13 +16,14 @@
 
 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.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;
 
 public interface InvoiceDao {
     void create(Invoice invoice);
@@ -42,15 +43,15 @@ public interface InvoiceDao {
     List<UUID> getInvoicesForPayment(final DateTime targetDate,
                                      final int numberOfDays);
 
-    void notifySuccessfulPayment(final UUID invoiceId,
-                                 final BigDecimal paymentAmount,
-                                 final Currency currency,
-                                 final UUID paymentId,
-                                 final DateTime paymentDate);
+    UUID getInvoiceIdByPaymentAttemptId(final UUID paymentAttemptId);
+
+    InvoicePayment getInvoicePayment(final UUID paymentAttemptId);
+
+    void notifyOfPaymentAttempt(final InvoicePayment invoicePayment);
+
+    BigDecimal getAccountBalance(final UUID accountId);
 
-    void notifyFailedPayment(final UUID invoiceId,
-                             final UUID paymentId,
-                             final DateTime paymentAttemptDate);
+    List<Invoice> getUnpaidInvoicesByAccountId(final UUID accountId, final DateTime upToDate);
 
     void test();
 }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceItemSqlDao.java b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceItemSqlDao.java
index 6f4e47b..76e0dbf 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceItemSqlDao.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceItemSqlDao.java
@@ -19,17 +19,26 @@ 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.DefaultInvoiceItem;
-import com.ning.billing.util.entity.EntityCollectionDao;
 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.*;
+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.*;
+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;
@@ -71,10 +80,11 @@ public interface InvoiceItemSqlDao extends EntityDao<InvoiceItem> {
                         q.bind("invoiceId", item.getInvoiceId().toString());
                         q.bind("subscriptionId", item.getSubscriptionId().toString());
                         q.bind("startDate", item.getStartDate().toDate());
-                        q.bind("endDate", item.getEndDate().toDate());
+                        q.bind("endDate", item.getEndDate() == null ? null : item.getEndDate().toDate());
                         q.bind("description", item.getDescription());
-                        q.bind("amount", item.getAmount());
-                        q.bind("rate", item.getRate());
+                        q.bind("recurringAmount", item.getRecurringAmount() == null ? BigDecimal.ZERO : item.getRecurringAmount());
+                        q.bind("recurringRate", item.getRecurringRate() == null ? BigDecimal.ZERO : item.getRecurringRate());
+                        q.bind("fixedAmount", item.getFixedAmount() == null ? BigDecimal.ZERO : item.getFixedAmount());
                         q.bind("currency", item.getCurrency().toString());
                     }
                 };
@@ -91,11 +101,13 @@ public interface InvoiceItemSqlDao extends EntityDao<InvoiceItem> {
             DateTime startDate = new DateTime(result.getTimestamp("start_date"));
             DateTime endDate = new DateTime(result.getTimestamp("end_date"));
             String description = result.getString("description");
-            BigDecimal amount = result.getBigDecimal("amount");
-            BigDecimal rate = result.getBigDecimal("rate");
+            BigDecimal recurringAmount = result.getBigDecimal("recurring_amount");
+            BigDecimal recurringRate = result.getBigDecimal("recurring_rate");
+            BigDecimal fixedAmount = result.getBigDecimal("fixed_amount");
             Currency currency = Currency.valueOf(result.getString("currency"));
 
-            return new DefaultInvoiceItem(id, invoiceId, subscriptionId, startDate, endDate, description, amount, rate , currency);
+            return new DefaultInvoiceItem(id, invoiceId, subscriptionId, startDate, endDate,
+                                          description, recurringAmount, recurringRate, fixedAmount, currency);
         }
     }
 }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoicePaymentSqlDao.java b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoicePaymentSqlDao.java
index 2efc98d..7a05d5c 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoicePaymentSqlDao.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoicePaymentSqlDao.java
@@ -24,64 +24,80 @@ 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.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.*;
 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.catalog.api.Currency;
+
 import com.ning.billing.invoice.api.InvoicePayment;
-import com.ning.billing.util.entity.EntityDao;
 
 @ExternalizedSqlViaStringTemplate3
 @RegisterMapper(InvoicePaymentSqlDao.InvoicePaymentMapper.class)
-public interface InvoicePaymentSqlDao extends EntityDao<InvoicePayment> {
-    @Override
+public interface InvoicePaymentSqlDao {
+    @SqlQuery
+    public InvoicePayment getByPaymentAttemptId(@Bind("paymentAttempt") final String paymentAttemptId);
+
+    @SqlQuery
+    public List<InvoicePayment> get();
+
     @SqlUpdate
-    public void create(@InvoicePaymentBinder final InvoicePayment invoicePayment);
+    public void create(@InvoicePaymentBinder  InvoicePayment invoicePayment);
 
     @SqlBatch
-    void create(@InvoicePaymentBinder final List<InvoicePayment> items);
+    void create(@InvoicePaymentBinder List<InvoicePayment> items);
 
-    @Override
     @SqlUpdate
-    public void update(@InvoicePaymentBinder final InvoicePayment invoicePayment);
+    public void update(@InvoicePaymentBinder  InvoicePayment invoicePayment);
+
+    @SqlQuery
+    public List<InvoicePayment> getPaymentsForInvoice(@Bind("invoiceId") String invoiceId);
 
     @SqlQuery
-    public List<InvoicePayment> getPaymentsForInvoice(@Bind("invoiceId") final String invoiceId);
+    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 id = UUID.fromString(result.getString("payment_id"));
+            final UUID paymentAttemptId = UUID.fromString(result.getString("payment_attempt_id"));
             final UUID invoiceId = UUID.fromString(result.getString("invoice_id"));
-            final DateTime paymentDate = new DateTime(result.getTimestamp("payment_date"));
+            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  DateTime now = new DateTime();
+
                 @Override
-                public UUID getId() {
-                    return id;
+                public UUID getPaymentAttemptId() {
+                    return paymentAttemptId;
                 }
                 @Override
                 public UUID getInvoiceId() {
                     return invoiceId;
                 }
                 @Override
-                public DateTime getPaymentDate() {
-                    return paymentDate;
+                public DateTime getPaymentAttemptDate() {
+                    return paymentAttemptDate;
                 }
                 @Override
                 public BigDecimal getAmount() {
@@ -91,6 +107,14 @@ public interface InvoicePaymentSqlDao extends EntityDao<InvoicePayment> {
                 public Currency getCurrency() {
                     return currency;
                 }
+                @Override
+                public DateTime getCreatedDate() {
+                    return createdDate ;
+                }
+                @Override
+                public DateTime getUpdatedDate() {
+                    return updatedDate;
+                }
             };
         }
     }
@@ -104,11 +128,15 @@ public interface InvoicePaymentSqlDao extends EntityDao<InvoicePayment> {
                 return new Binder<InvoicePaymentBinder, InvoicePayment>() {
                     public void bind(SQLStatement q, InvoicePaymentBinder bind, InvoicePayment payment) {
                         q.bind("invoiceId", payment.getInvoiceId().toString());
-                        q.bind("paymentId", payment.getId().toString());
-                        q.bind("paymentDate", payment.getAmount());
+                        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 3a5ed2a..7b730ab 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,8 +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.api.InvoicePayment;
 import com.ning.billing.invoice.model.DefaultInvoice;
 import com.ning.billing.util.UuidMapper;
 import com.ning.billing.util.entity.EntityDao;
@@ -39,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
@@ -71,33 +71,40 @@ public interface InvoiceSqlDao extends EntityDao<Invoice>, Transactional<Invoice
     List<Invoice> getInvoicesBySubscription(@Bind("subscriptionId") final String subscriptionId);
 
     @SqlQuery
-    List<UUID> getInvoicesForPayment(@Bind("targetDate") final Date targetDate,
-                                     @Bind("numberOfDays") final int numberOfDays);
-
-    @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);
+    @RegisterMapper(UuidMapper.class)
+    UUID getInvoiceIdByPaymentAttemptId(@Bind("paymentAttemptId") final String paymentAttemptId);
 
-    @SqlUpdate
-    void notifyFailedPayment(@Bind("invoiceId") final String invoiceId,
-                             @Bind("paymentId") final String paymentId,
-                             @Bind("paymentAttemptDate") final Date paymentAttemptDate);
+    @SqlQuery
+    @RegisterMapper(UuidMapper.class)
+    List<UUID> getInvoicesForPayment(@Bind("targetDate") final Date targetDate,
+                                    @Bind("numberOfDays") final int numberOfDays);
+    
+    @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.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());
                     }
                 };
@@ -117,5 +124,25 @@ public interface InvoiceSqlDao extends EntityDao<Invoice>, Transactional<Invoice
             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 amount_invoiced = result.getBigDecimal("amount_invoiced");
+            BigDecimal amount_paid = result.getBigDecimal("amount_paid");
+
+            if (amount_invoiced == null) {
+                amount_invoiced = BigDecimal.ZERO;
+            }
+
+            if (amount_paid == null) {
+                amount_paid = BigDecimal.ZERO;
+            }
+
+            return amount_invoiced.subtract(amount_paid);
+        }
+    }
+
+
 }
 
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 174a21c..d902932 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
@@ -17,16 +17,15 @@
 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;
@@ -35,8 +34,7 @@ import com.ning.billing.invoice.notification.DefaultNextBillingDateNotifier;
 import com.ning.billing.invoice.notification.NextBillingDateNotifier;
 
 public class InvoiceModule extends AbstractModule {
-
-    private void installInvoiceDao() {
+    protected void installInvoiceDao() {
         bind(InvoiceDao.class).to(DefaultInvoiceDao.class).asEagerSingleton();
     }
 
@@ -53,11 +51,23 @@ public class InvoiceModule extends AbstractModule {
         bind(InvoiceConfig.class).toInstance(config);
     }
 
-    @Override
-    protected void configure() {
+    protected void installInvoiceService() {
         bind(InvoiceService.class).to(DefaultInvoiceService.class).asEagerSingleton();
+    }
+
+    protected void installNotifier() {
         bind(NextBillingDateNotifier.class).to(DefaultNextBillingDateNotifier.class).asEagerSingleton();
+    }
+
+    protected void installInvoiceListener() {
         bind(InvoiceListener.class).asEagerSingleton();
+    }
+
+    @Override
+    protected void configure() {
+        installInvoiceService();
+        installNotifier();
+        installInvoiceListener();
         bind(InvoiceGenerator.class).to(DefaultInvoiceGenerator.class).asEagerSingleton();
         installConfig();
         installInvoiceDao();
diff --git a/invoice/src/main/java/com/ning/billing/invoice/InvoiceListener.java b/invoice/src/main/java/com/ning/billing/invoice/InvoiceListener.java
index f305441..ec42757 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/InvoiceListener.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/InvoiceListener.java
@@ -19,6 +19,9 @@ package com.ning.billing.invoice;
 import java.util.List;
 import java.util.SortedSet;
 import java.util.UUID;
+
+import com.ning.billing.invoice.model.BillingEventSet;
+import com.ning.billing.invoice.notification.NextBillingDateEvent;
 import org.joda.time.DateTime;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -30,16 +33,14 @@ 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.billing.EntitlementBillingApiException;
 import com.ning.billing.entitlement.api.user.SubscriptionTransition;
-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 com.ning.billing.invoice.api.InvoiceUserApi;
 import com.ning.billing.invoice.dao.InvoiceDao;
 import com.ning.billing.invoice.model.InvoiceGenerator;
 import com.ning.billing.invoice.model.InvoiceItemList;
+import com.ning.billing.util.clock.Clock;
 
 public class InvoiceListener {
     private final static Logger log = LoggerFactory.getLogger(InvoiceListener.class);
@@ -47,37 +48,43 @@ public class InvoiceListener {
     private final InvoiceGenerator generator;
     private final EntitlementBillingApi entitlementBillingApi;
     private final AccountUserApi accountUserApi;
-    private final InvoiceUserApi invoiceUserApi;
     private final InvoiceDao invoiceDao;
 
     @Inject
     public InvoiceListener(final InvoiceGenerator generator, final AccountUserApi accountUserApi,
                            final EntitlementBillingApi entitlementBillingApi,
-                           final InvoiceUserApi invoiceUserApi,
                            final InvoiceDao invoiceDao) {
         this.generator = generator;
         this.entitlementBillingApi = entitlementBillingApi;
         this.accountUserApi = accountUserApi;
-        this.invoiceUserApi = invoiceUserApi;
         this.invoiceDao = invoiceDao;
     }
 
     @Subscribe
     public void handleSubscriptionTransition(final SubscriptionTransition transition) {
+        processSubscription(transition);
+    }
+
+    @Subscribe
+    public void handleNextBillingDateEvent(final NextBillingDateEvent event) {
+        processSubscription(event.getSubscriptionId(), new DateTime());
+    }
+
+    private void processSubscription(final SubscriptionTransition transition) {
         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);
+    }
+
+    private void processSubscription(final UUID subscriptionId, final DateTime targetDate) {
         if (subscriptionId == null) {
             log.error("Failed handling entitlement change.", new InvoiceApiException(ErrorCode.INVOICE_INVALID_TRANSITION));
             return;
         }
 
-        UUID accountId = null;
-        try {
-            accountId = entitlementBillingApi.getAccountIdFromSubscriptionId(subscriptionId);
-        } catch (EntitlementBillingApiException e) {
-            log.error("Failed handling entitlement change.", e);
-            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()));
@@ -92,13 +99,12 @@ public class InvoiceListener {
         }
 
         SortedSet<BillingEvent> events = entitlementBillingApi.getBillingEventsForAccount(accountId);
-        BillingEventSet billingEvents = new BillingEventSet();
-        billingEvents.addAll(events);
+        BillingEventSet billingEvents = new BillingEventSet(events);
 
-        DateTime targetDate = new DateTime();
         Currency targetCurrency = account.getCurrency();
 
-        InvoiceItemList invoiceItemList = new InvoiceItemList(invoiceUserApi.getInvoiceItemsByAccount(accountId));
+        List<InvoiceItem> items = invoiceDao.getInvoiceItemsByAccount(accountId);
+        InvoiceItemList invoiceItemList = new InvoiceItemList(items);
         Invoice invoice = generator.generateInvoice(accountId, billingEvents, invoiceItemList, targetDate, targetCurrency);
 
         if (invoice != null) {
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..d7235c7 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
@@ -27,6 +27,10 @@ public abstract class BillingModeBase implements BillingMode {
         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);
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 ae10d41..8ee40e3 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
@@ -28,14 +28,21 @@ 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 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 final Currency currency;
 
     public DefaultInvoice(UUID accountId, DateTime targetDate, Currency currency) {
         this(UUID.randomUUID(), accountId, new DefaultClock().getUTCNow(), targetDate, currency);
@@ -120,7 +127,7 @@ public class DefaultInvoice implements Invoice {
         DateTime lastPaymentAttempt = null;
 
         for (final InvoicePayment paymentAttempt : payments) {
-            DateTime paymentAttemptDate = paymentAttempt.getPaymentDate();
+            DateTime paymentAttemptDate = paymentAttempt.getPaymentAttemptDate();
             if (lastPaymentAttempt == null) {
                 lastPaymentAttempt = paymentAttemptDate;
             }
@@ -167,5 +174,11 @@ public class DefaultInvoice implements Invoice {
 
         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 cb30d15..47f5d67 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
@@ -22,41 +22,49 @@ import java.util.ArrayList;
 import java.util.Collections;
 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.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.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 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,
+                                   @Nullable 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);}
+        if (events == null) {return null;}
+        if (events.size() == 0) {return null;}
 
         DefaultInvoice invoice = new DefaultInvoice(accountId, targetDate, targetCurrency);
         InvoiceItemList currentItems = generateInvoiceItems(events, invoice.getId(), targetDate, targetCurrency);
         InvoiceItemList itemsToPost = reconcileInvoiceItems(invoice.getId(), currentItems, existingItems);
-        invoice.addInvoiceItems(itemsToPost);
 
-        return invoice;
+        if (itemsToPost.size() == 0) {
+            return null;
+        } else {
+            invoice.addInvoiceItems(itemsToPost);
+            return invoice;
+        }
     }
 
     private InvoiceItemList reconcileInvoiceItems(final UUID invoiceId, final InvoiceItemList currentInvoiceItems,
                                                   final InvoiceItemList existingInvoiceItems) {
+        if (existingInvoiceItems == null) {
+            return currentInvoiceItems;
+        }
+
         InvoiceItemList currentItems = new InvoiceItemList();
         for (final InvoiceItem item : currentInvoiceItems) {
             currentItems.add(new DefaultInvoiceItem(item, invoiceId));
@@ -78,20 +86,18 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
                 }
             }
         }
-
         existingItems.removeAll(existingItemsToRemove);
 
         // remove cancelling pairs of invoice items
         existingItems.removeCancellingPairs();
 
-        // remove zero-dollar invoice items
-        currentItems.removeZeroDollarItems();
-
         // add existing items that aren't covered by current items as credit items
         for (final InvoiceItem existingItem : existingItems) {
-            currentItems.add(existingItem.asCredit(invoiceId));
+            currentItems.add(existingItem.asCredit(existingItem.getInvoiceId()));
         }
 
+        currentItems.cleanupDuplicatedItems();
+
         return currentItems;
     }
 
@@ -126,13 +132,21 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
     private void processEvent(final UUID invoiceId, final BillingEvent event, final List<InvoiceItem> items,
                               final DateTime targetDate, final 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());
+            BigDecimal recurringRate = event.getRecurringPrice() == null ? null : event.getRecurringPrice().getPrice(targetCurrency);
+            BigDecimal fixedPrice = event.getFixedPrice() == null ? null : event.getFixedPrice().getPrice(targetCurrency);
+
+    		BigDecimal numberOfBillingPeriods;
+            BigDecimal recurringAmount =null;
 
-    		addInvoiceItem(invoiceId, items, event, billThroughDate, invoiceItemAmount, rate, targetCurrency);
+            if (recurringRate != null) {
+                numberOfBillingPeriods = calculateNumberOfBillingPeriods(event, targetDate);
+                recurringAmount = numberOfBillingPeriods.multiply(recurringRate);
+            }
+
+            BillingMode billingMode = getBillingMode(event.getBillingMode());
+            DateTime billThroughDate = billingMode.calculateEffectiveEndDate(event.getEffectiveDate(), targetDate, event.getBillCycleDay(), event.getBillingPeriod());
+
+            addInvoiceItem(invoiceId, items, event, billThroughDate, recurringAmount, recurringRate, fixedPrice, targetCurrency);
     	} catch (CatalogApiException e) {
             log.error(String.format("Encountered a catalog error processing invoice %s for billing event on date %s", 
                     invoiceId.toString(), 
@@ -142,49 +156,53 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
 
     private void processEvents(final UUID invoiceId, final BillingEvent firstEvent, final BillingEvent secondEvent,
                                final List<InvoiceItem> items, final DateTime targetDate, final 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());
+            BigDecimal recurringRate = firstEvent.getRecurringPrice() == null ? null : firstEvent.getRecurringPrice().getPrice(targetCurrency);
+            BigDecimal fixedPrice = firstEvent.getFixedPrice() == null ? null : firstEvent.getFixedPrice().getPrice(targetCurrency);
 
-    		addInvoiceItem(invoiceId, items, firstEvent, billThroughDate, invoiceItemAmount, rate, targetCurrency);
+            BigDecimal numberOfBillingPeriods;
+            BigDecimal recurringAmount =null;
+
+            if (recurringRate != null) {
+                numberOfBillingPeriods = calculateNumberOfBillingPeriods(firstEvent, secondEvent, targetDate);
+                recurringAmount = numberOfBillingPeriods.multiply(recurringRate);
+            }
+
+            BillingMode billingMode = getBillingMode(firstEvent.getBillingMode());
+            DateTime billThroughDate = billingMode.calculateEffectiveEndDate(firstEvent.getEffectiveDate(), secondEvent.getEffectiveDate(), targetDate, firstEvent.getBillCycleDay(), firstEvent.getBillingPeriod());
+
+            addInvoiceItem(invoiceId, items, firstEvent, billThroughDate, recurringAmount, recurringRate, fixedPrice, targetCurrency);
     	} catch (CatalogApiException e) {
-    		log.error(String.format("Encountered a catalog error processing invoice %s for billing event on date %s", 
-                    invoiceId.toString(), 
+    		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(final UUID invoiceId, final List<InvoiceItem> items, final BillingEvent event,
                                 final DateTime billThroughDate, final BigDecimal amount, final BigDecimal rate,
-                                final 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);
-        }
+                                final BigDecimal fixedAmount, final Currency currency) {
+        DefaultInvoiceItem item = new DefaultInvoiceItem(invoiceId, event.getSubscription().getId(), event.getEffectiveDate(),
+                                  billThroughDate, event.getDescription(), amount, rate, fixedAmount, currency);
+        items.add(item);
     }
 
-    private BigDecimal calculateInvoiceItemAmount(final BillingEvent event, final DateTime targetDate,
-                                                  final BigDecimal rate){
+    private BigDecimal calculateNumberOfBillingPeriods(final BillingEvent event, final DateTime targetDate){
         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);
+            return billingMode.calculateNumberOfBillingCycles(startDate, targetDate, billingCycleDay, billingPeriod);
         } catch (InvalidDateSequenceException e) {
             // TODO: Jeff -- log issue
             return BigDecimal.ZERO;
         }
     }
 
-    private BigDecimal calculateInvoiceItemAmount(final BillingEvent firstEvent, final BillingEvent secondEvent,
-                                                  final DateTime targetDate, final BigDecimal rate) {
+    private BigDecimal calculateNumberOfBillingPeriods(final BillingEvent firstEvent, final BillingEvent secondEvent,
+                                                  final DateTime targetDate) {
         BillingMode billingMode = getBillingMode(firstEvent.getBillingMode());
         DateTime startDate = firstEvent.getEffectiveDate();
         int billingCycleDay = firstEvent.getBillCycleDay();
@@ -193,9 +211,7 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
         DateTime endDate = secondEvent.getEffectiveDate();
 
         try {
-            BigDecimal numberOfBillingCycles;
-            numberOfBillingCycles = billingMode.calculateNumberOfBillingCycles(startDate, endDate, targetDate, billingCycleDay, billingPeriod);
-            return numberOfBillingCycles.multiply(rate);
+            return billingMode.calculateNumberOfBillingCycles(startDate, endDate, targetDate, billingCycleDay, billingPeriod);
         } catch (InvalidDateSequenceException e) {
             // TODO: Jeff -- log issue
             return BigDecimal.ZERO;
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoiceItem.java b/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoiceItem.java
index eb54aeb..136967c 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoiceItem.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoiceItem.java
@@ -20,6 +20,7 @@ import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.invoice.api.InvoiceItem;
 import org.joda.time.DateTime;
 
+import javax.annotation.Nullable;
 import java.math.BigDecimal;
 import java.util.UUID;
 
@@ -30,23 +31,30 @@ public class DefaultInvoiceItem implements InvoiceItem {
     private DateTime startDate;
     private DateTime endDate;
     private final String description;
-    private BigDecimal amount;
-    private final BigDecimal rate;
+    private BigDecimal recurringAmount;
+    private final BigDecimal recurringRate;
+    private BigDecimal fixedAmount;
     private final Currency currency;
 
-    public DefaultInvoiceItem(UUID invoiceId, UUID subscriptionId, DateTime startDate, DateTime endDate, String description, BigDecimal amount, BigDecimal rate, Currency currency) {
-        this(UUID.randomUUID(), invoiceId, subscriptionId, startDate, endDate, description, amount, rate, currency);
+    public DefaultInvoiceItem(UUID invoiceId, UUID subscriptionId, DateTime startDate, DateTime endDate,
+                              String description, BigDecimal recurringAmount, BigDecimal recurringRate,
+                              BigDecimal fixedAmount, Currency currency) {
+        this(UUID.randomUUID(), invoiceId, subscriptionId, startDate, endDate, description,
+             recurringAmount, recurringRate, fixedAmount, currency);
     }
 
-    public DefaultInvoiceItem(UUID id, UUID invoiceId, UUID subscriptionId, DateTime startDate, DateTime endDate, String description, BigDecimal amount, BigDecimal rate, Currency currency) {
+    public DefaultInvoiceItem(UUID id, UUID invoiceId, UUID subscriptionId, DateTime startDate, DateTime endDate,
+                              String description, BigDecimal recurringAmount, BigDecimal recurringRate,
+                              BigDecimal fixedAmount, Currency currency) {
         this.id = id;
         this.invoiceId = invoiceId;
         this.subscriptionId = subscriptionId;
         this.startDate = startDate;
         this.endDate = endDate;
         this.description = description;
-        this.amount = amount;
-        this.rate = rate;
+        this.recurringAmount = recurringAmount;
+        this.recurringRate = recurringRate;
+        this.fixedAmount = fixedAmount;
         this.currency = currency;
     }
 
@@ -57,14 +65,18 @@ public class DefaultInvoiceItem implements InvoiceItem {
         this.startDate = that.getStartDate();
         this.endDate = that.getEndDate();
         this.description = that.getDescription();
-        this.amount = that.getAmount();
-        this.rate = that.getRate();
+        this.recurringAmount = that.getRecurringAmount();
+        this.recurringRate = that.getRecurringRate();
+        this.fixedAmount = that.getFixedAmount();
         this.currency = that.getCurrency();
     }
 
     @Override
     public InvoiceItem asCredit(UUID invoiceId) {
-        return new DefaultInvoiceItem(invoiceId, subscriptionId, startDate, endDate, description, amount.negate(), rate, currency);
+        BigDecimal recurringAmountNegated = recurringAmount == null ? null : recurringAmount.negate();
+        BigDecimal fixedAmountNegated = fixedAmount == null ? null : fixedAmount.negate();
+        return new DefaultInvoiceItem(invoiceId, subscriptionId, startDate, endDate, description,
+                                      recurringAmountNegated, recurringRate, fixedAmountNegated, currency);
     }
 
     @Override
@@ -98,13 +110,18 @@ public class DefaultInvoiceItem implements InvoiceItem {
     }
 
     @Override
-    public BigDecimal getAmount() {
-        return amount;
+    public BigDecimal getRecurringAmount() {
+        return recurringAmount;
     }
 
     @Override
-    public BigDecimal getRate() {
-        return rate;
+    public BigDecimal getRecurringRate() {
+        return recurringRate;
+    }
+
+    @Override
+    public BigDecimal getFixedAmount() {
+        return fixedAmount;
     }
 
     @Override
@@ -113,11 +130,20 @@ public class DefaultInvoiceItem implements InvoiceItem {
     }
 
     @Override
-    public int compareTo(InvoiceItem invoiceItem) {
-        int compareSubscriptions = getSubscriptionId().compareTo(invoiceItem.getSubscriptionId());
+    public int compareTo(InvoiceItem that) {
+        int compareSubscriptions = getSubscriptionId().compareTo(that.getSubscriptionId());
 
         if (compareSubscriptions == 0) {
-            return getStartDate().compareTo(invoiceItem.getStartDate());
+            // move null end dates to the end of the set
+            if ((this.endDate != null) && (that.getEndDate() == null)) {
+                return -1;
+            }
+
+            if ((this.endDate == null) && (that.getEndDate() != null)) {
+                return 1;
+            }
+
+            return getStartDate().compareTo(that.getStartDate());
         } else {
             return compareSubscriptions;
         }
@@ -128,44 +154,89 @@ public class DefaultInvoiceItem implements InvoiceItem {
     public void subtract(InvoiceItem that) {
         if (this.startDate.equals(that.getStartDate()) && this.endDate.equals(that.getEndDate())) {
             this.startDate = this.endDate;
-            this.amount = this.amount.subtract(that.getAmount());
+                this.recurringAmount = safeSubtract(this.recurringAmount, that.getRecurringAmount());
+                this.fixedAmount = safeSubtract(this.fixedAmount, that.getFixedAmount());
         } else {
             if (this.startDate.equals(that.getStartDate())) {
                 this.startDate = that.getEndDate();
-                this.amount = this.amount.subtract(that.getAmount());
+                this.recurringAmount = safeSubtract(this.recurringAmount, that.getRecurringAmount());
+                this.fixedAmount = safeSubtract(this.fixedAmount, that.getFixedAmount());
             }
 
             if (this.endDate.equals(that.getEndDate())) {
                 this.endDate = that.getStartDate();
-                this.amount = this.amount.subtract(that.getAmount());
+                this.recurringAmount = safeSubtract(this.recurringAmount, that.getRecurringAmount());
+                this.fixedAmount = safeSubtract(this.fixedAmount, that.getFixedAmount());
             }
         }
     }
 
+    private BigDecimal safeSubtract(BigDecimal minuend, BigDecimal subtrahend) {
+        // minuend - subtrahend == difference
+        if (minuend == null) {
+            if (subtrahend == null) {
+                return BigDecimal.ZERO;
+            } else {
+                return subtrahend.negate();
+            }
+        } else {
+            if (subtrahend == null) {
+                return minuend;
+            } else {
+                return minuend.subtract(subtrahend);
+            }
+        }
+
+    }
+
     @Override
     public boolean duplicates(InvoiceItem that) {
         if(!this.getSubscriptionId().equals(that.getSubscriptionId())) {return false;}
-        if(!this.getRate().equals(that.getRate())) {return false;}
+        
+        //if (!compareNullableBigDecimal(this.getRecurringAmount(), that.getRecurringAmount())) {return false;}
+        if (!compareNullableBigDecimal(this.getRecurringRate(), that.getRecurringRate())) {return false;}
+        if (!compareNullableBigDecimal(this.getFixedAmount(), that.getFixedAmount())) {return false;}
+
         if(!this.getCurrency().equals(that.getCurrency())) {return false;}
 
         DateRange thisDateRange = new DateRange(this.getStartDate(), this.getEndDate());
         return thisDateRange.contains(that.getStartDate()) && thisDateRange.contains(that.getEndDate());
     }
 
+    private boolean compareNullableBigDecimal(@Nullable BigDecimal value1, @Nullable BigDecimal value2) {
+        if ((value1 == null) && (value2 != null)) {return false;}
+        if ((value1 != null) && (value2 == null)) {return false;}
+
+        if ((value1 != null) && (value2 != null)) {
+            if (!value1.equals(value2)) {return false;}
+        }
+
+        return true;
+    }
+
     /**
      * indicates whether the supplied item is a cancelling item for this item
-     * @param that
-     * @return
+     * @param that  the InvoiceItem to be examined
+     * @return true if the two invoice items cancel each other out (same subscription, same date range, sum of amounts = 0)
      */
     @Override
     public boolean cancels(InvoiceItem that) {
         if(!this.getSubscriptionId().equals(that.getSubscriptionId())) {return false;}
         if(!this.getEndDate().equals(that.getEndDate())) {return false;}
         if(!this.getStartDate().equals(that.getStartDate())) {return false;}
-        if(!this.getAmount().equals(that.getAmount().negate())) {return false;}
-        if(!this.getRate().equals(that.getRate())) {return false;}
+
+        if (!safeCheckForZeroSum(this.getRecurringAmount(), that.getRecurringAmount())) {return false;}
+        if(!this.getRecurringRate().equals(that.getRecurringRate())) {return false;}
+
+        if (!safeCheckForZeroSum(this.getFixedAmount(), that.getFixedAmount())) {return false;}
         if(!this.getCurrency().equals(that.getCurrency())) {return false;}
 
         return true;
     }
+
+    private boolean safeCheckForZeroSum(final BigDecimal value1, final BigDecimal value2) {
+        if ((value1 == null) && (value2 == null)) {return true;}
+        if ((value1 == null) ^ (value2 == null)) {return false;}
+        return (value1.add(value2).compareTo(BigDecimal.ZERO) == 0);
+    }
 }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoicePayment.java b/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoicePayment.java
index ef1ed29..6760481 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoicePayment.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoicePayment.java
@@ -16,31 +16,56 @@
 
 package com.ning.billing.invoice.model;
 
-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.InvoicePayment;
-import com.ning.billing.util.entity.EntityBase;
+import org.joda.time.DateTime;
 
-public class DefaultInvoicePayment extends EntityBase<InvoicePayment> implements InvoicePayment {
+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);
+        this(UUID.randomUUID(), invoiceId, paymentDate, amount, currency, null, null);
     }
 
-    public DefaultInvoicePayment(final UUID id, final UUID invoiceId, final DateTime paymentDate,
+    public DefaultInvoicePayment(final UUID paymentAttemptId, final UUID invoiceId, final DateTime paymentDate,
                                  final BigDecimal amount, final Currency currency) {
-        super(id);
+        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
@@ -49,7 +74,7 @@ public class DefaultInvoicePayment extends EntityBase<InvoicePayment> implements
     }
 
     @Override
-    public DateTime getPaymentDate() {
+    public DateTime getPaymentAttemptDate() {
         return paymentDate;
     }
 
@@ -62,4 +87,14 @@ public class DefaultInvoicePayment extends EntityBase<InvoicePayment> implements
     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/InAdvanceBillingMode.java b/invoice/src/main/java/com/ning/billing/invoice/model/InAdvanceBillingMode.java
index 37b5820..3ad4f50 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
@@ -114,7 +114,12 @@ public class InAdvanceBillingMode extends BillingModeBase {
         DateTime nextBillingCycleDate = calculateBillingCycleDateOnOrAfter(startDate, billingCycleDay);
         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);
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..626993e 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,12 @@
 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 org.joda.time.DateTime;
 
+import javax.annotation.Nullable;
 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 InvoiceItemList items, DateTime targetDate, Currency targetCurrency);
 }
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 6ee713c..5a10928 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
@@ -18,41 +18,38 @@ package com.ning.billing.invoice.model;
 
 import java.math.BigDecimal;
 import java.util.ArrayList;
+import java.util.Iterator;
 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.getRoundingMethod();
 
     public InvoiceItemList() {
+        super();
     }
 
-    public InvoiceItemList(List<InvoiceItem> items) {
-        addAll(items);
+    public InvoiceItemList(final List<InvoiceItem> invoiceItems) {
+        super();
+        this.addAll(invoiceItems);
     }
 
     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());
-        }
+        // naive implementation, assumes all invoice items share the same currency
+        BigDecimal total = BigDecimal.ZERO.setScale(NUMBER_OF_DECIMALS, ROUNDING_METHOD);
 
-        return total.setScale(NUMBER_OF_DECIMALS);
-    }
-
-    public void removeZeroDollarItems() {
-        List<InvoiceItem> itemsToRemove = new ArrayList<InvoiceItem>();
+        for (final InvoiceItem item : this) {
+            if (item.getRecurringAmount() != null) {
+                total = total.add(item.getRecurringAmount());
+            }
 
-        for (InvoiceItem item : this) {
-            if (item.getAmount().compareTo(BigDecimal.ZERO) == 0) {
-                itemsToRemove.add(item);
+            if (item.getFixedAmount() != null) {
+                total = total.add(item.getFixedAmount());
             }
         }
 
-        this.removeAll(itemsToRemove);
+        return total.setScale(NUMBER_OF_DECIMALS, ROUNDING_METHOD);
     }
 
     public void removeCancellingPairs() {
@@ -71,4 +68,21 @@ public class InvoiceItemList extends ArrayList<InvoiceItem> {
 
         this.removeAll(itemsToRemove);
     }
+
+   /*
+    * removes 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();
+            Boolean hasZeroRecurringAmount = item.getRecurringAmount() == null || (item.getRecurringAmount().compareTo(BigDecimal.ZERO) == 0);
+            Boolean hasRecurringRate = item.getRecurringRate() == null || (item.getRecurringRate().compareTo(BigDecimal.ZERO) != 0);
+            Boolean hasZeroFixedAmount = item.getFixedAmount() == null || (item.getFixedAmount().compareTo(BigDecimal.ZERO) == 0);
+
+            if (hasZeroRecurringAmount && hasRecurringRate & hasZeroFixedAmount) {
+                iterator.remove();
+            }
+        }
+    }
 }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/notification/DefaultNextBillingDateNotifier.java b/invoice/src/main/java/com/ning/billing/invoice/notification/DefaultNextBillingDateNotifier.java
index 794426e..3fd03b2 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/notification/DefaultNextBillingDateNotifier.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/notification/DefaultNextBillingDateNotifier.java
@@ -18,6 +18,8 @@ package com.ning.billing.invoice.notification;
 
 import java.util.UUID;
 
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.engine.dao.EntitlementDao;
 import org.joda.time.DateTime;
 import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
 import org.slf4j.Logger;
@@ -26,16 +28,13 @@ import org.slf4j.LoggerFactory;
 import com.google.inject.Inject;
 import com.ning.billing.config.InvoiceConfig;
 import com.ning.billing.invoice.api.DefaultInvoiceService;
-import com.ning.billing.lifecycle.KillbillService;
-import com.ning.billing.lifecycle.LifecycleHandlerType;
-import com.ning.billing.lifecycle.LifecycleHandlerType.LifecycleLevel;
-import com.ning.billing.util.eventbus.Bus;
-import com.ning.billing.util.eventbus.Bus.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.NotificationKey;
 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 DefaultNextBillingDateNotifier implements  NextBillingDateNotifier {
@@ -47,17 +46,19 @@ public class DefaultNextBillingDateNotifier implements  NextBillingDateNotifier 
     private final Bus eventBus;
     private final NotificationQueueService notificationQueueService;
 	private final InvoiceConfig config;
+    private final EntitlementDao entitlementDao;
 
     private NotificationQueue nextBillingQueue;
 
     @Inject
-	public DefaultNextBillingDateNotifier(NotificationQueueService notificationQueueService, Bus eventBus, InvoiceConfig config){
+	public DefaultNextBillingDateNotifier(NotificationQueueService notificationQueueService, Bus eventBus,
+                                          InvoiceConfig config, EntitlementDao entitlementDao){
 		this.notificationQueueService = notificationQueueService;
 		this.config = config;
 		this.eventBus = eventBus;
+        this.entitlementDao = entitlementDao;
 	}
 
-
     @Override
     public void initialize() {
 		try {
@@ -68,13 +69,18 @@ public class DefaultNextBillingDateNotifier implements  NextBillingDateNotifier 
                 public void handleReadyNotification(String notificationKey) {
                 	UUID subscriptionId;
                 	try {
-                		subscriptionId = UUID.fromString(notificationKey);
+                 		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);
+                        }
                 	} catch (IllegalArgumentException e) {
-                		log.error("The key returned from the NextBillingNotificationQueue is not a valid UUID",e);
+                		log.error("The key returned from the NextBillingNotificationQueue is not a valid UUID", e);
                 		return;
                 	}
 
-                    processEvent(subscriptionId);
                 }
             },
             new NotificationConfig() {
@@ -95,7 +101,7 @@ public class DefaultNextBillingDateNotifier implements  NextBillingDateNotifier 
                     return config.getDaoMaxReadyEvents();
                 }
             });
-        } catch (NotficationQueueAlreadyExists e) {
+        } catch (NotificationQueueAlreadyExists e) {
             throw new RuntimeException(e);
         }
     }
@@ -121,12 +127,18 @@ public class DefaultNextBillingDateNotifier implements  NextBillingDateNotifier 
     }
 
     @Override
-    public void insertNextBillingNotification(Transmogrifier transactionalDao, final UUID subscriptionId, DateTime futureNotificationTime) {
-    	nextBillingQueue.recordFutureNotificationFromTransaction(transactionalDao, futureNotificationTime, new NotificationKey(){
-    		@Override
-            public String toString() {
-    			return subscriptionId.toString();
-    		}
-    	});
+    public void insertNextBillingNotification(final Transmogrifier transactionalDao, final UUID subscriptionId, final DateTime futureNotificationTime) {
+    	if (nextBillingQueue != null) {
+            log.info("Queuing next billing date notification. id: {}, timestamp: {}", subscriptionId.toString(), futureNotificationTime.toString());
+
+            nextBillingQueue.recordFutureNotificationFromTransaction(transactionalDao, futureNotificationTime, new NotificationKey(){
+                @Override
+                public String toString() {
+                    return subscriptionId.toString();
+                }
+    	    });
+        } else {
+            log.error("Attempting to put items on a non-existent queue (NextBillingDateNotifier).");
+        }
     }
 }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/notification/NextBillingDateEvent.java b/invoice/src/main/java/com/ning/billing/invoice/notification/NextBillingDateEvent.java
index 59ec8a2..659d9e9 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/notification/NextBillingDateEvent.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/notification/NextBillingDateEvent.java
@@ -18,7 +18,7 @@ package com.ning.billing.invoice.notification;
 
 import java.util.UUID;
 
-import com.ning.billing.util.eventbus.BusEvent;
+import com.ning.billing.util.bus.BusEvent;
 
 public class NextBillingDateEvent implements BusEvent{
 	private final UUID subscriptionId;
diff --git a/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoiceItemSqlDao.sql.stg b/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoiceItemSqlDao.sql.stg
index 4dc783f..39fcd24 100644
--- a/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoiceItemSqlDao.sql.stg
+++ b/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoiceItemSqlDao.sql.stg
@@ -1,39 +1,54 @@
 group InvoiceItemDao;
 
+invoiceItemFields(prefix) ::= <<
+  <prefix>id,
+  <prefix>invoice_id,
+  <prefix>subscription_id,
+  <prefix>start_date,
+  <prefix>end_date,
+  <prefix>description,
+  <prefix>recurring_amount,
+  <prefix>recurring_rate,
+  <prefix>fixed_amount,
+  <prefix>currency
+>>
+
 getById() ::= <<
-  SELECT id, invoice_id, subscription_id, start_date, end_date, description, amount, rate, currency
+  SELECT <invoiceItemFields()>
   FROM invoice_items
   WHERE id = :id;
 >>
 
 getInvoiceItemsByInvoice() ::= <<
-  SELECT id, invoice_id, subscription_id, start_date, end_date, description, amount, rate, currency
+  SELECT <invoiceItemFields()>
   FROM invoice_items
   WHERE invoice_id = :invoiceId;
 >>
 
 getInvoiceItemsByAccount() ::= <<
-  SELECT ii.id, ii.invoice_id, ii.subscription_id, ii.start_date, ii.end_date, ii.description, ii.amount, ii.rate, ii.currency
+  SELECT <invoiceItemFields("ii.")>
   FROM invoice_items ii
   INNER JOIN invoices i ON i.id = ii.invoice_id
   WHERE i.account_id = :accountId;
 >>
 
 getInvoiceItemsBySubscription() ::= <<
-  SELECT id, invoice_id, subscription_id, start_date, end_date, description, amount, rate, currency
+  SELECT <invoiceItemFields()>
   FROM invoice_items
   WHERE subscription_id = :subscriptionId;
 >>
 
 create() ::= <<
-  INSERT INTO invoice_items(id, invoice_id, subscription_id, start_date, end_date, description, amount, rate, currency)
-  VALUES(:id, :invoiceId, :subscriptionId, :startDate, :endDate, :description, :amount, :rate, :currency);
+  INSERT INTO invoice_items(<invoiceItemFields()>)
+  VALUES(:id, :invoiceId, :subscriptionId, :startDate, :endDate, :description,
+         :recurringAmount, :recurringRate, :fixedAmount, :currency);
 >>
 
 update() ::= <<
   UPDATE invoice_items
   SET invoice_id = :invoiceId, subscription_id = :subscriptionId, start_date = :startDate, end_date = :endDate,
-      description = :description, amount = :amount, rate = :rate, currency = :currency
+      description = :description, recurring_amount = :recurringAmount, recurring_rate = :recurringRate,
+      fixed_amount = :fixedAmount, currency = :currency
   WHERE id = :id;
 >>
 
diff --git a/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoicePaymentSqlDao.sql.stg b/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoicePaymentSqlDao.sql.stg
index ef91849..a635456 100644
--- a/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoicePaymentSqlDao.sql.stg
+++ b/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoicePaymentSqlDao.sql.stg
@@ -1,31 +1,52 @@
 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(invoice_id, payment_id, payment_date, amount, currency)
-  VALUES(:invoiceId, :paymentId, :paymentDate, :amount, :currency)
+  INSERT INTO invoice_payments(<invoicePaymentFields()>)
+  VALUES(:invoiceId, :paymentAttemptId, :paymentAttemptDate, :amount, :currency, :createdDate, :updatedDate);
 >>
 
 update() ::= <<
   UPDATE invoice_payments
-  SET payment_date = :paymentDate, amount = :amount, currency = :currency
-  WHERE invoice_id = :invoiceId, payment_id = :paymentId
+  SET payment_date = :paymentAttemptDate, amount = :amount, currency = :currency, created_date = :createdDate, updated_date = :updatedDate
+  WHERE invoice_id = :invoiceId, payment_attempt_id = :paymentAttemptId;
 >>
 
-getById() ::= <<
-  SELECT invoice_id, payment_id, payment_date, amount, currency
+getByPaymentAttemptId() ::= <<
+  SELECT <invoicePaymentFields()>
   FROM invoice_payments
-  WHERE payment_id = :id
+  WHERE payment_id = :paymentAttemptId;
 >>
 
 get() ::= <<
-  SELECT invoice_id, payment_id, payment_date, amount, currency
-  FROM invoice_payments
+  SELECT <invoicePaymentFields()>
+  FROM invoice_payments;
 >>
 
 getPaymentsForInvoice() ::= <<
-  SELECT invoice_id, payment_id, payment_date, amount, currency
+  SELECT <invoicePaymentFields()>
   FROM invoice_payments
-  WHERE invoice_id = :invoiceId
+  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() ::= <<
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 f0b8116..f483773 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,42 +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
-  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
-  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 i.id, i.account_id, i.invoice_date, i.target_date, i.currency, SUM(ii.amount) AS amount
-  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
-  AND i.target_date >= :fromDate
-  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 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
+  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;
+  GROUP BY <invoiceFields("i.")>;
 >>
 
 getInvoicesForPayment() ::= <<
@@ -47,37 +44,51 @@ getInvoicesForPayment() ::= <<
   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;
+  GROUP BY <invoiceFields("i.")>;
 >>
 
 getById() ::= <<
-  SELECT i.id, i.account_id, i.invoice_date, i.target_date, i.currency, SUM(ii.amount) AS amount
+  SELECT <invoiceFields()>
+  FROM invoices
+  WHERE id = :id;
+>>
+
+getAccountBalance() ::= <<
+  SELECT SUM(iis.total_amount) 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);
->>
-
-notifyFailedPayment() ::= <<
-  INSERT INTO invoice_payments(invoice_id, payment_id, payment_date)
-  VALUES(:invoiceId, :paymentId, :paymentAttemptDate);
+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.total_amount) > SUM(ips.total_paid)) OR (SUM(ips.total_paid) IS NULL)
+  ORDER BY i.target_date ASC;
 >>
 
 test() ::= <<
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..348f423 100644
--- a/invoice/src/main/resources/com/ning/billing/invoice/ddl.sql
+++ b/invoice/src/main/resources/com/ning/billing/invoice/ddl.sql
@@ -4,14 +4,14 @@ CREATE TABLE invoice_items (
   invoice_id char(36) NOT NULL,
   subscription_id char(36) NOT NULL,
   start_date datetime NOT NULL,
-  end_date datetime NOT NULL,
+  end_date datetime NULL,
   description varchar(100) NOT NULL,
-  amount numeric(10,4) NOT NULL,
-  rate numeric(10,4) NOT NULL,
+  recurring_amount numeric(10,4) NOT NULL,
+  recurring_rate numeric(10,4) NOT NULL,
+  fixed_amount numeric(10,4) NOT NULL,
   currency char(3) NOT NULL,
   PRIMARY KEY(id)
 ) ENGINE=innodb;
-
 CREATE INDEX invoice_items_subscription_id ON invoice_items(subscription_id ASC);
 
 DROP TABLE IF EXISTS invoices;
@@ -23,28 +23,29 @@ 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, SUM(amount) 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, sum(recurring_amount) + SUM(fixed_amount) AS total_amount
+from 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..e8f66b1
--- /dev/null
+++ b/invoice/src/test/java/com/ning/billing/invoice/api/MockInvoicePaymentApi.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.invoice.api;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import com.ning.billing.invoice.model.DefaultInvoicePayment;
+import org.joda.time.DateTime;
+
+import com.ning.billing.catalog.api.Currency;
+
+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 7d41ed8..4779495 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.BusService;
-
-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 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();
+            invoiceItemDao = module.getInvoiceItemSqlDao();
+
+            invoicePaymentDao = module.getInvoicePaymentSqlDao();
 
             BusService busService = injector.getInstance(BusService.class);
-            ((DefaultEventBusService) busService).startBus();
+            ((DefaultBusService) 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 2d7d4f2..b63c230 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,28 +16,45 @@
 
 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.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.invoice.api.Invoice;
 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.DefaultInvoiceGenerator;
 import com.ning.billing.invoice.model.DefaultInvoiceItem;
+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;
-import static org.testng.Assert.fail;
 
 @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() {
@@ -66,7 +83,7 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         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);
+        InvoiceItem invoiceItem = new DefaultInvoiceItem(invoiceId, subscriptionId, startDate, endDate, "test", new BigDecimal("21.00"), new BigDecimal("7.00"), null, Currency.USD);
         invoice.addInvoiceItem(invoiceItem);
         invoiceDao.create(invoice);
 
@@ -78,8 +95,9 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         assertEquals(savedInvoice.getInvoiceItems().size(), 1);
 
         BigDecimal paymentAmount = new BigDecimal("11.00");
-        UUID paymentId = UUID.randomUUID();
-        invoiceDao.notifySuccessfulPayment(invoiceId, paymentAmount, Currency.USD, paymentId, new DefaultClock().getUTCNow().plusDays(12));
+        UUID paymentAttemptId = UUID.randomUUID();
+
+        invoiceDao.notifyOfPaymentAttempt(new DefaultInvoicePayment(paymentAttemptId, invoiceId, clock.getUTCNow().plusDays(12), paymentAmount, Currency.USD));
 
         Invoice retrievedInvoice = invoiceDao.getById(invoiceId);
         assertNotNull(retrievedInvoice);
@@ -101,16 +119,16 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         DateTime targetDate = new DateTime(2011, 10, 6, 0, 0, 0, 0);
         Invoice invoice = new DefaultInvoice(accountId, targetDate, Currency.USD);
 
-        UUID paymentId = UUID.randomUUID();
+        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(), paymentAmount, Currency.USD, paymentId, paymentAttemptDate);
+        invoiceDao.notifyOfPaymentAttempt(new DefaultInvoicePayment(paymentAttemptId, invoice.getId(), paymentAttemptDate, paymentAmount, Currency.USD));
 
         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);
     }
 
@@ -123,21 +141,21 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         DateTime paymentAttemptDate = new DateTime(2011, 6, 24, 12, 14, 36, 0);
 
         invoiceDao.create(invoice);
-        invoiceDao.notifyFailedPayment(invoice.getId(), UUID.randomUUID(), paymentAttemptDate);
+        invoiceDao.notifyOfPaymentAttempt(new DefaultInvoicePayment(invoice.getId(), paymentAttemptDate));
 
         invoice = invoiceDao.getById(invoice.getId());
-        assertTrue(invoice.getLastPaymentAttempt().equals(paymentAttemptDate));
+        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, NUMBER_OF_DAY_BETWEEN_RETRIES);
         int existingInvoiceCount = invoices.size();
-        
+
         UUID accountId = UUID.randomUUID();
         Invoice invoice = new DefaultInvoice(accountId, targetDate, Currency.USD);
 
@@ -162,7 +180,7 @@ 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);
+        DefaultInvoiceItem item = new DefaultInvoiceItem(invoiceId, subscriptionId, targetDate, endDate, "test", amount, rate, null, Currency.USD);
         invoice.addInvoiceItem(item);
         invoiceDao.create(invoice);
 
@@ -175,7 +193,7 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
 
         // 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(), UUID.randomUUID(), notionalDate);
+        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);
@@ -188,7 +206,8 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         assertEquals(invoices.size(), count);
 
         // post successful partial payment; ensure that number of invoices for payment has decreased by 1
-        invoiceDao.notifySuccessfulPayment(invoiceId, new BigDecimal("22.0000"), Currency.USD, UUID.randomUUID(), notionalDate);
+        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);
@@ -205,7 +224,8 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         assertEquals(invoices.size(), count);
 
         // post completed payment; ensure that the number of invoices for payment has decreased by 1
-        invoiceDao.notifySuccessfulPayment(invoiceId, new BigDecimal("5.0000"), Currency.USD, UUID.randomUUID(), notionalDate);
+        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);
@@ -255,16 +275,16 @@ 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);
+        DefaultInvoiceItem item1 = new DefaultInvoiceItem(invoiceId1, subscriptionId1, startDate, endDate, "test A", rate1, rate1, null, Currency.USD);
         invoiceItemDao.create(item1);
 
-        DefaultInvoiceItem item2 = new DefaultInvoiceItem(invoiceId1, subscriptionId2, startDate, endDate, "test B", rate2, rate2, Currency.USD);
+        DefaultInvoiceItem item2 = new DefaultInvoiceItem(invoiceId1, subscriptionId2, startDate, endDate, "test B", rate2, rate2, null, Currency.USD);
         invoiceItemDao.create(item2);
 
-        DefaultInvoiceItem item3 = new DefaultInvoiceItem(invoiceId1, subscriptionId3, startDate, endDate, "test C", rate3, rate3, Currency.USD);
+        DefaultInvoiceItem item3 = new DefaultInvoiceItem(invoiceId1, subscriptionId3, startDate, endDate, "test C", rate3, rate3, null, Currency.USD);
         invoiceItemDao.create(item3);
 
-        DefaultInvoiceItem item4 = new DefaultInvoiceItem(invoiceId1, subscriptionId4, startDate, endDate, "test D", rate4, rate4, Currency.USD);
+        DefaultInvoiceItem item4 = new DefaultInvoiceItem(invoiceId1, subscriptionId4, startDate, endDate, "test D", rate4, rate4, null, Currency.USD);
         invoiceItemDao.create(item4);
 
         // create invoice 2 (subscriptions 1-3)
@@ -276,13 +296,13 @@ 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);
+        DefaultInvoiceItem item5 = new DefaultInvoiceItem(invoiceId2, subscriptionId1, startDate, endDate, "test A", rate1, rate1, null, Currency.USD);
         invoiceItemDao.create(item5);
 
-        DefaultInvoiceItem item6 = new DefaultInvoiceItem(invoiceId2, subscriptionId2, startDate, endDate, "test B", rate2, rate2, Currency.USD);
+        DefaultInvoiceItem item6 = new DefaultInvoiceItem(invoiceId2, subscriptionId2, startDate, endDate, "test B", rate2, rate2, null, Currency.USD);
         invoiceItemDao.create(item6);
 
-        DefaultInvoiceItem item7 = new DefaultInvoiceItem(invoiceId2, subscriptionId3, startDate, endDate, "test C", rate3, rate3, Currency.USD);
+        DefaultInvoiceItem item7 = new DefaultInvoiceItem(invoiceId2, subscriptionId3, startDate, endDate, "test C", rate3, rate3, null, Currency.USD);
         invoiceItemDao.create(item7);
 
         // check that each subscription returns the correct number of invoices
@@ -313,18 +333,225 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
 
         List<Invoice> invoices;
         invoices = invoiceDao.getInvoicesByAccount(accountId, new DateTime(2011, 1, 1, 0, 0, 0, 0));
-        //assertEquals(invoices.size(), 2);
+        assertEquals(invoices.size(), 2);
 
         invoices = invoiceDao.getInvoicesByAccount(accountId, new DateTime(2011, 10, 6, 0, 0, 0, 0));
-        //assertEquals(invoices.size(), 2);
+        assertEquals(invoices.size(), 2);
 
         invoices = invoiceDao.getInvoicesByAccount(accountId, new DateTime(2011, 10, 11, 0, 0, 0, 0));
-        //assertEquals(invoices.size(), 1);
+        assertEquals(invoices.size(), 1);
 
         invoices = invoiceDao.getInvoicesByAccount(accountId, new DateTime(2011, 12, 6, 0, 0, 0, 0));
-        //assertEquals(invoices.size(), 1);
+        assertEquals(invoices.size(), 1);
 
         invoices = invoiceDao.getInvoicesByAccount(accountId, new DateTime(2012, 1, 1, 0, 0, 0, 0));
-        //assertEquals(invoices.size(), 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);
+        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");
+
+        DefaultInvoiceItem item1 = new DefaultInvoiceItem(invoice1.getId(), UUID.randomUUID(), startDate, endDate, "test A", rate1, rate1, null, Currency.USD);
+        invoiceItemDao.create(item1);
+
+        DefaultInvoiceItem item2 = new DefaultInvoiceItem(invoice1.getId(), UUID.randomUUID(), startDate, endDate, "test B", rate2, rate2, null, Currency.USD);
+        invoiceItemDao.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);
+        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");
+
+        DefaultInvoiceItem item1 = new DefaultInvoiceItem(invoice1.getId(), UUID.randomUUID(), startDate, endDate, "test A", rate1, rate1, null, Currency.USD);
+        invoiceItemDao.create(item1);
+
+        DefaultInvoiceItem item2 = new DefaultInvoiceItem(invoice1.getId(), UUID.randomUUID(), startDate, endDate, "test B", rate2, rate2, null, Currency.USD);
+        invoiceItemDao.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);
+        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);
+        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");
+
+        DefaultInvoiceItem item1 = new DefaultInvoiceItem(invoice1.getId(), UUID.randomUUID(), startDate, endDate, "test A", rate1, rate1, null, Currency.USD);
+        invoiceItemDao.create(item1);
+
+        DefaultInvoiceItem item2 = new DefaultInvoiceItem(invoice1.getId(), UUID.randomUUID(), startDate, endDate, "test B", rate2, rate2, null, Currency.USD);
+        invoiceItemDao.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);
+        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");
+
+        DefaultInvoiceItem item3 = new DefaultInvoiceItem(invoice2.getId(), UUID.randomUUID(), startDate2, endDate2, "test C", rate3, rate3, null, Currency.USD);
+        invoiceItemDao.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() {
+        InvoiceGenerator generator = new DefaultInvoiceGenerator();
+
+        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");
+
+        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");
+        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() {
+        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");
+        BillingEventSet events = new BillingEventSet();
+        events.add(event);
+
+        DateTime targetDate = buildDateTime(2011, 1, 15);
+        InvoiceGenerator generator = new DefaultInvoiceGenerator();
+        Invoice invoice = generator.generateInvoice(UUID.randomUUID(), events, null, targetDate, Currency.USD);
+        assertEquals(invoice.getNumberOfItems(), 1);
+        assertEquals(invoice.getTotalAmount().compareTo(ZERO), 0);
+    }
+
+    @Test
+    public void testInvoiceForEmptyEventSet() {
+        InvoiceGenerator generator = new DefaultInvoiceGenerator();
+        BillingEventSet events = new BillingEventSet();
+        Invoice invoice = generator.generateInvoice(UUID.randomUUID(), events, null, new DateTime(), Currency.USD);
+        assertNull(invoice);
     }
 }
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..92a90b3 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
@@ -21,7 +21,7 @@ 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.glue.InvoiceModuleWithEmbeddedDb;
 import com.ning.billing.invoice.model.DefaultInvoice;
 import com.ning.billing.invoice.model.DefaultInvoiceItem;
 import org.apache.commons.io.IOUtils;
@@ -45,7 +45,7 @@ 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);
+        InvoiceItem item = new DefaultInvoiceItem(invoiceId, subscriptionId, startDate, endDate, "test", rate, rate, rate, Currency.USD);
         invoiceItemDao.create(item);
 
         InvoiceItem thisItem = invoiceItemDao.getById(item.getId().toString());
@@ -56,8 +56,9 @@ public class InvoiceItemDaoTests extends InvoiceDaoTestBase {
         assertEquals(thisItem.getStartDate(), item.getStartDate());
         assertEquals(thisItem.getEndDate(), item.getEndDate());
         assertEquals(thisItem.getDescription(), item.getDescription());
-        assertEquals(thisItem.getAmount().compareTo(item.getAmount()), 0);
-        assertEquals(thisItem.getRate().compareTo(item.getRate()), 0);
+        assertEquals(thisItem.getRecurringAmount().compareTo(item.getRecurringAmount()), 0);
+        assertEquals(thisItem.getRecurringRate().compareTo(item.getRecurringRate()), 0);
+        assertEquals(thisItem.getFixedAmount().compareTo(item.getFixedAmount()), 0);
         assertEquals(thisItem.getCurrency(), item.getCurrency());
     }
 
@@ -69,7 +70,7 @@ 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);
+            DefaultInvoiceItem item = new DefaultInvoiceItem(invoiceId, subscriptionId, startDate.plusMonths(i), startDate.plusMonths(i + 1), "test", rate, rate, null, Currency.USD);
             invoiceItemDao.create(item);
         }
 
@@ -86,7 +87,7 @@ 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);
+            DefaultInvoiceItem item = new DefaultInvoiceItem(invoiceId, subscriptionId, startDate, startDate.plusMonths(1), "test", amount, amount, amount, Currency.USD);
             invoiceItemDao.create(item);
         }
 
@@ -107,7 +108,7 @@ 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);
+        DefaultInvoiceItem item = new DefaultInvoiceItem(invoiceId, subscriptionId, startDate, startDate.plusMonths(1), "test", rate, rate, rate, Currency.USD);
         invoiceItemDao.create(item);
 
         List<InvoiceItem> items = invoiceItemDao.getInvoiceItemsByAccount(accountId.toString());
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..005a605
--- /dev/null
+++ b/invoice/src/test/java/com/ning/billing/invoice/dao/MockSubscription.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.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 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();
+    }
+}
\ 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..1d0c358
--- /dev/null
+++ b/invoice/src/test/java/com/ning/billing/invoice/glue/InvoiceModuleWithEmbeddedDb.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.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 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.invoice.dao.InvoiceItemSqlDao;
+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 InvoiceItemSqlDao getInvoiceItemSqlDao() {
+        return dbi.onDemand(InvoiceItemSqlDao.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());
+
+        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..60015c1
--- /dev/null
+++ b/invoice/src/test/java/com/ning/billing/invoice/glue/InvoiceModuleWithMocks.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.invoice.glue;
+
+import com.ning.billing.invoice.dao.InvoiceDao;
+import com.ning.billing.invoice.dao.MockInvoiceDao;
+
+public class InvoiceModuleWithMocks extends InvoiceModule {
+    @Override
+    protected void installInvoiceDao() {
+        bind(MockInvoiceDao.class).asEagerSingleton();
+        bind(InvoiceDao.class).to(MockInvoiceDao.class);
+    }
+
+    @Override
+    protected void installInvoiceListener() {
+
+    }
+
+    @Override
+    protected void installNotifier() {
+
+    }
+
+    @Override
+    protected void installInvoiceService() {
+
+    }
+}
diff --git a/invoice/src/test/java/com/ning/billing/invoice/notification/TestNextBillingDateNotifier.java b/invoice/src/test/java/com/ning/billing/invoice/notification/TestNextBillingDateNotifier.java
index 6036652..d2fcdee 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/notification/TestNextBillingDateNotifier.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/notification/TestNextBillingDateNotifier.java
@@ -16,24 +16,28 @@
 
 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.UUID;
 import java.util.concurrent.Callable;
 
+import com.ning.billing.catalog.DefaultCatalogService;
+import com.ning.billing.catalog.api.CatalogService;
+import com.ning.billing.config.CatalogConfig;
+import com.ning.billing.entitlement.engine.dao.EntitlementDao;
+import com.ning.billing.entitlement.engine.dao.EntitlementSqlDao;
+import com.ning.billing.util.bus.InMemoryBus;
 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.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.common.eventbus.Subscribe;
 import com.google.inject.AbstractModule;
 import com.google.inject.Guice;
@@ -44,58 +48,66 @@ import com.ning.billing.dbi.MysqlTestingHelper;
 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.Bus;
-import com.ning.billing.util.eventbus.MemoryEventBus;
+import com.ning.billing.util.bus.Bus;
 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 {
+import static com.jayway.awaitility.Awaitility.await;
+import static java.util.concurrent.TimeUnit.MINUTES;
 
+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;
-	
+
 	@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(MemoryEventBus.class).asEagerSingleton();
+				 bind(Bus.class).to(InMemoryBus.class).asEagerSingleton();
 				 bind(NotificationQueueService.class).to(DefaultNotificationQueueService.class).asEagerSingleton();
-				 final InvoiceConfig config = new ConfigurationObjectFactory(System.getProperties()).build(InvoiceConfig.class);
-				 bind(InvoiceConfig.class).toInstance(config);
-				 final MysqlTestingHelper helper = new MysqlTestingHelper();
+				 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);
-				 DBI dbi = helper.getDBI();
-				 bind(DBI.class).toInstance(dbi);
-
-			}  	
+				 IDBI dbi = helper.getDBI();
+				 bind(IDBI.class).toInstance(dbi);
+                 bind(EntitlementDao.class).to(EntitlementSqlDao.class).asEagerSingleton();
+			}
         });
 
         clock = g.getInstance(Clock.class);
-        DBI dbi = g.getInstance(DBI.class);
+        IDBI dbi = g.getInstance(IDBI.class);
         dao = dbi.onDemand(DummySqlTest.class);
         eventBus = g.getInstance(Bus.class);
         helper = g.getInstance(MysqlTestingHelper.class);
-        notifier = new DefaultNextBillingDateNotifier(g.getInstance(NotificationQueueService.class), eventBus, g.getInstance(InvoiceConfig.class));
+        notifier = new DefaultNextBillingDateNotifier(g.getInstance(NotificationQueueService.class), eventBus, g.getInstance(InvoiceConfig.class), g.getInstance(EntitlementDao.class));
         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);
 	}
 
 	public static class NextBillingEventListener {
 		private int eventCount=0;
+		private NextBillingDateEvent event;
 
 		public int getEventCount() {
 			return eventCount;
@@ -104,13 +116,18 @@ public class TestNextBillingDateNotifier {
 		@Subscribe
 		public synchronized void processEvent(NextBillingDateEvent event) {
 			eventCount++;
+			this.event = event;
 			//log.debug("Got event {} {}", event.name, event.value);
 		}
+		
+		public NextBillingDateEvent getLatestEvent() {
+			return event;
+		}
 	}
-	
-	@Test(enabled=true, groups="slow")
+
+	@Test(enabled=false, groups="slow")
 	public void test() throws Exception {
-		final UUID subscriptionId = new UUID(0L,1L);
+		final UUID subscriptionId = new UUID(0L,1000L);
 		final DateTime now = new DateTime();
 		final DateTime readyTime = now.plusMillis(2000);
 
@@ -118,7 +135,7 @@ public class TestNextBillingDateNotifier {
 		eventBus.start();
 		notifier.initialize();
 		notifier.start();
-		
+
         eventBus.register(listener);
 		dao.inTransaction(new Transaction<Void, DummySqlTest>() {
 			@Override
@@ -142,5 +159,6 @@ public class TestNextBillingDateNotifier {
 	        });
 
 		Assert.assertEquals(listener.getEventCount(), 1);
+		Assert.assertEquals(listener.getLatestEvent().getSubscriptionId(), 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 67de300..702801b 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,19 +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.Catalog;
 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;
@@ -37,27 +31,35 @@ 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.invoice.api.Invoice;
 import com.ning.billing.invoice.api.InvoiceItem;
+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.InvoiceGenerator;
 import com.ning.billing.invoice.model.InvoiceItemList;
+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;
+
+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 MockCatalog catalog = new MockCatalog();
 
     @Test
     public void testWithNullEventSetAndNullInvoiceSet() {
         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
@@ -68,9 +70,7 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
         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
@@ -79,14 +79,12 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
 
         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 rate1 = TEN;
+        PlanPhase phase = createMockMonthlyPlanPhase(rate1);
         
-        BillingEvent event = new DefaultBillingEvent(sub, startDate, plan, phase,
-                                                     new InternationalPriceMock(ZERO),
-                                                     new InternationalPriceMock(TEN), BillingPeriod.MONTHLY,
-                                                     1, BillingModeType.IN_ADVANCE, "Test");
+        BillingEvent event = createBillingEvent(sub.getId(), startDate, plan, phase, 1);
         events.add(event);
 
         InvoiceItemList existingInvoiceItems = new InvoiceItemList();
@@ -98,6 +96,7 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
         assertNotNull(invoice);
         assertEquals(invoice.getNumberOfItems(), 1);
         assertEquals(invoice.getTotalAmount(), TWENTY);
+        assertEquals(invoice.getInvoiceItems().get(0).getSubscriptionId(), sub.getId());
     }
 
     @Test
@@ -106,14 +105,11 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
 
         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();
@@ -135,23 +131,20 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
     public void testTwoMonthlySubscriptionsWithAlignedBillingDates() {
         BillingEventSet events = new BillingEventSet();
 
-        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();
@@ -161,29 +154,25 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
 
         assertNotNull(invoice);
         assertEquals(invoice.getNumberOfItems(), 2);
-        assertEquals(invoice.getTotalAmount(), FIVE.add(TEN).setScale(NUMBER_OF_DECIMALS));
+        assertEquals(invoice.getTotalAmount(), rate1.add(rate2).setScale(NUMBER_OF_DECIMALS));
     }
 
     @Test
     public void testOnePlan_TwoMonthlyPhases_ChangeImmediate() {
         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();
@@ -200,8 +189,8 @@ 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);
@@ -211,28 +200,22 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
     public void testOnePlan_ThreeMonthlyPhases_ChangeEOT() {
         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();
@@ -242,7 +225,7 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
 
         assertNotNull(invoice);
         assertEquals(invoice.getNumberOfItems(), 3);
-        assertEquals(invoice.getTotalAmount(), FIVE.add(TEN).add(TWO.multiply(THIRTY)).setScale(NUMBER_OF_DECIMALS));
+        assertEquals(invoice.getTotalAmount(), rate1.add(rate2).add(TWO.multiply(rate3)).setScale(NUMBER_OF_DECIMALS));
     }
 
     @Test
@@ -251,30 +234,24 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
 
         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
@@ -286,37 +263,44 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
         // plan 5: addon to plan 2, with bill cycle alignment to plan; immediate cancellation
 
         UUID subscriptionId1 = UUID.randomUUID();
-        Plan plan1 = catalog.getCurrentPlans()[0];
-        PlanPhase plan1Phase1 = plan1.getAllPhases()[0]; PlanPhase plan1Phase2 = plan1.getAllPhases()[0]; PlanPhase plan1Phase3 = plan1.getAllPhases()[0];
+        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();
-        Plan plan2 = catalog.getCurrentPlans()[1];
-        PlanPhase plan2Phase1 = plan2.getAllPhases()[0]; PlanPhase plan2Phase2 = plan2.getAllPhases()[0]; PlanPhase plan2Phase3 = plan2.getAllPhases()[0];
+        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();
-        Plan plan3 = catalog.getCurrentPlans()[2];
-        PlanPhase plan3Phase1 = plan3.getAllPhases()[0]; PlanPhase plan3Phase2 = plan3.getAllPhases()[0];
+        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();
-        Plan plan4a = catalog.getCurrentPlans()[0];
-        Plan plan4b = catalog.getCurrentPlans()[1];
-        PlanPhase plan4aPhase1 = plan4a.getAllPhases()[0];
-        PlanPhase plan4bPhase1 = plan4b.getAllPhases()[0];
+        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();
-        Plan plan5 = catalog.getCurrentPlans()[2];
-        PlanPhase plan5Phase1 = plan5.getAllPhases()[0]; PlanPhase plan5Phase2 = plan5.getAllPhases()[0];
+        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);
 
@@ -325,7 +309,7 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
         BillingEventSet events = new BillingEventSet();
 
         // on 1/5/2011, create subscription 1 (trial)
-        events.add(createBillingEvent(subscriptionId1, plan1StartDate, plan1, plan1Phase1, EIGHT, 5));
+        events.add(createBillingEvent(subscriptionId1, plan1StartDate, plan1, plan1Phase1, 5));
         expectedAmount = EIGHT;
         testInvoiceGeneration(events, invoiceItems, plan1StartDate, 1, expectedAmount);
 
@@ -338,12 +322,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, plan2, plan2Phase1, 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, plan1, plan1Phase2, TWELVE, 5));
+        events.add(createBillingEvent(subscriptionId1, plan1PhaseChangeDate, plan1, plan1Phase2, 5));
         expectedAmount = TWELVE;
         testInvoiceGeneration(events, invoiceItems, plan1PhaseChangeDate, 1, expectedAmount);
 
@@ -352,7 +336,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, plan1, 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);
 
@@ -361,17 +345,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, plan3, plan3Phase1, 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, plan4a, plan4aPhase1, 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, plan2, plan2Phase2, THIRTY, 10));
+        events.add(createBillingEvent(subscriptionId2, plan2PhaseChangeToDiscountDate, plan2, plan2Phase2, 10));
         expectedAmount = THIRTY;
         testInvoiceGeneration(events, invoiceItems, plan2PhaseChangeToDiscountDate, 1, expectedAmount);
 
@@ -380,7 +364,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, plan5, plan5Phase1, 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);
 
@@ -397,14 +381,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, plan3, plan3Phase2, 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, plan4b, plan4bPhase1, TWENTY_FOUR, 7));
+        events.add(createBillingEvent(subscriptionId4, plan4ChangeOfPlanDate, plan4b, plan4bPhase1, 7));
         expectedAmount = TWENTY_FOUR;
         testInvoiceGeneration(events, invoiceItems, plan4ChangeOfPlanDate, 1, expectedAmount);
 
@@ -417,12 +401,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, plan2, plan2Phase3, 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, plan5, plan5Phase2, 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);
 
@@ -431,24 +415,32 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
         testInvoiceGeneration(events, invoiceItems, buildDateTime(2011, 10, 10), 1, expectedAmount);
     }
 
-    private DefaultBillingEvent createBillingEvent(final UUID subscriptionId, final DateTime startDate,
-                                                   final Plan plan, final PlanPhase planPhase,
-                                                   final BigDecimal rate, final int billCycleDay) {
-        Subscription sub = new SubscriptionData(new SubscriptionBuilder().setId(subscriptionId));
+    private MockPlanPhase createMockMonthlyPlanPhase() {
+        return new MockPlanPhase(null, null, BillingPeriod.MONTHLY);
+    }
 
-        return new DefaultBillingEvent(sub, startDate, plan, planPhase,
-                                       new InternationalPriceMock(ZERO),
-                                       new InternationalPriceMock(rate), BillingPeriod.MONTHLY,
-                                       billCycleDay, BillingModeType.IN_ADVANCE,"Test");
+    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 createMockAnnualPlanPhase(final BigDecimal recurringRate, final PhaseType phaseType) {
+        return new MockPlanPhase(new MockInternationalPrice(new DefaultPrice(recurringRate, Currency.USD)),
+                                 null, BillingPeriod.ANNUAL, phaseType);
     }
 
-    private DefaultBillingEvent createAnnualBillingEvent(final UUID subscriptionId, final DateTime startDate,
-                                                         final Plan plan, final PlanPhase planPhase,
-                                                         final BigDecimal rate, final int billCycleDay) {
+    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,
-                                       new InternationalPriceMock(ZERO),
-                                       new InternationalPriceMock(rate), BillingPeriod.ANNUAL,
+                                       planPhase.getFixedPrice(),
+                                       planPhase.getRecurringPrice(), planPhase.getBillingPeriod(),
                                        billCycleDay, BillingModeType.IN_ADVANCE,"Test");
     }
 
@@ -458,11 +450,11 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
         Currency currency = Currency.USD;
         UUID accountId = UUID.randomUUID();
         Invoice invoice = generator.generateInvoice(accountId, events, existingInvoiceItems, targetDate, currency);
-        existingInvoiceItems.addAll(invoice.getInvoiceItems());
         assertNotNull(invoice);
         assertEquals(invoice.getNumberOfItems(), expectedNumberOfItems);
+
+        existingInvoiceItems.addAll(invoice.getInvoiceItems());
         assertEquals(invoice.getTotalAmount(), expectedAmount);
     }
-
     // TODO: Jeff C -- how do we ensure that an annual add-on is properly aligned *at the end* with the base plan?
-}
\ No newline at end of file
+}                      
\ No newline at end of file

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

diff --git a/payment/pom.xml b/payment/pom.xml
index 3d6356e..3e050f0 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.5-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..69e0dfe
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/api/DefaultPaymentApi.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF 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 com.ning.billing.invoice.api.InvoicePayment;
+import com.ning.billing.invoice.model.DefaultInvoicePayment;
+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.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);
+            }
+            else {
+                throw new IllegalArgumentException("Did not find account with accountKey " + accountKey);
+            }
+        }
+
+        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> addPaypalPaymentMethod(@Nullable String accountKey, PaypalPaymentMethodInfo paypalPaymentMethod) {
+        final PaymentProviderPlugin plugin = getPaymentProviderPlugin(accountKey);
+        return plugin.addPaypalPaymentMethod(accountKey, paypalPaymentMethod);
+    }
+
+    @Override
+    public Either<PaymentError, Void> deletePaymentMethod(String accountKey, String paymentMethodId) {
+        final PaymentProviderPlugin plugin = getPaymentProviderPlugin(accountKey);
+        return plugin.deletePaypalPaymentMethod(accountKey, paymentMethodId);
+    }
+
+    @Override
+    public Either<PaymentError, PaymentMethodInfo> updatePaymentMethod(String accountKey, PaymentMethodInfo paymentMethodInfo) {
+        final PaymentProviderPlugin plugin = getPaymentProviderPlugin(accountKey);
+        return plugin.updatePaypalPaymentMethod(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);
+
+                    if (paymentInfo.getPaymentId() != null) {
+                        paymentDao.updatePaymentAttemptWithPaymentId(paymentAttempt.getPaymentAttemptId(), paymentInfo.getPaymentId());
+                    }
+                }
+
+                invoicePaymentApi.notifyOfPaymentAttempt(new DefaultInvoicePayment(paymentAttempt.getPaymentAttemptId(),
+                                                                                   invoice.getId(),
+                                                                                   paymentAttempt.getPaymentAttemptDate(),
+                                                                                   paymentInfo == null ? null : paymentInfo.getAmount(),
+//                                                                                 paymentInfo.getRefundAmount(), TODO
+                                                                                   paymentInfo == null ? 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, PaymentProviderAccount> updatePaymentProviderAccount(Account account) {
+        final PaymentProviderPlugin plugin = getPaymentProviderPlugin(account);
+        return plugin.updatePaymentProviderAccount(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..ceba7a7
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/dao/DefaultPaymentDao.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.payment.dao;
+
+import 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);
+    }
+
+}
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..d40b264
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/dao/PaymentDao.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.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);
+
+}
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..7cd8adb
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/dao/PaymentSqlDao.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.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 payment_id);
+
+    @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()); //TODO: suppport partial payments
+            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("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");
+            DateTime createdDate = getDate(rs, "created_dt");
+            DateTime updatedDate = getDate(rs, "updated_dt");
+
+            return new PaymentAttempt(paymentAttemptId, invoiceId, accountId, amount, currency, invoiceDate, paymentAttemptDate, paymentId, 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("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");
+            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,
+                                   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..8d65393
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/PaymentAttempt.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.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;
+
+    public PaymentAttempt(UUID paymentAttemptId, Invoice invoice) {
+        this.paymentAttemptId = paymentAttemptId;
+        this.accountId = invoice.getAccountId();
+        this.invoiceId = invoice.getId();
+        this.paymentAttemptAmount = invoice.getBalance();
+        this.paymentAttemptDate = new DateTime(DateTimeZone.UTC);
+    }
+
+    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;
+    }
+
+    @Override
+    public String toString() {
+        return "PaymentAttempt [paymentAttemptId=" + paymentAttemptId + ", accountId=" + accountId + ", invoiceId=" + invoiceId + ", paymentAttemptAmount=" + paymentAttemptAmount + "]";
+    }
+
+}
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/PaymentProviderPlugin.java b/payment/src/main/java/com/ning/billing/payment/provider/PaymentProviderPlugin.java
new file mode 100644
index 0000000..460951d
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/provider/PaymentProviderPlugin.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.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;
+import com.ning.billing.payment.api.PaypalPaymentMethodInfo;
+
+public interface PaymentProviderPlugin {
+    Either<PaymentError, PaymentInfo> processInvoice(Account account, Invoice invoice);
+    Either<PaymentError, String> createPaymentProviderAccount(Account account);
+    Either<PaymentError, String> addPaypalPaymentMethod(String accountId, PaypalPaymentMethodInfo paypalPaymentMethod);
+    Either<PaymentError, PaymentProviderAccount> updatePaymentProviderAccount(Account account);
+
+    Either<PaymentError, PaymentInfo> getPaymentInfo(String paymentId);
+    Either<PaymentError, PaymentMethodInfo> getPaymentMethodInfo(String paymentMethodId);
+    Either<PaymentError, PaymentProviderAccount> getPaymentProviderAccount(String accountKey);
+    Either<PaymentError, List<PaymentMethodInfo>> getPaymentMethods(String accountKey);
+
+    Either<PaymentError, Void> updatePaymentGateway(String accountKey);
+    Either<PaymentError, Void> deletePaypalPaymentMethod(String accountKey, String paymentMethodId);
+    Either<PaymentError, PaymentMethodInfo> updatePaypalPaymentMethod(String accountKey, PaymentMethodInfo paymentMethodInfo);
+
+}
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..9700450
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/RequestProcessor.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.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()) {
+                    eventBus.post(new PaymentError("unknown", "No payment processed"));
+                }
+                else {
+                    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/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..3c05c13
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/setup/PaymentModule.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.payment.setup;
+
+import java.util.Properties;
+
+import org.skife.config.ConfigurationObjectFactory;
+
+import com.google.inject.AbstractModule;
+import com.ning.billing.payment.api.DefaultPaymentApi;
+import com.ning.billing.payment.api.PaymentApi;
+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();
+        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..5156b03
--- /dev/null
+++ b/payment/src/main/resources/com/ning/billing/payment/dao/PaymentSqlDao.sql.stg
@@ -0,0 +1,57 @@
+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>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>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, :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, :effective_dt, NOW(), NOW());
+>>
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..fd6a1ea
--- /dev/null
+++ b/payment/src/main/resources/com/ning/billing/payment/ddl.sql
@@ -0,0 +1,30 @@
+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,
+      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,
+      payment_type varchar(20) COLLATE utf8_bin,
+      reference_id varchar(36) 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..22d3c50
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/api/TestPaymentApi.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF 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.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.joda.time.DateTime;
+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.catalog.api.Currency;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.model.DefaultInvoiceItem;
+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(groups = "fast")
+    @Test
+    public void testCreatePayment() throws AccountApiException {
+        final DateTime now = new DateTime();
+        final Account account = testHelper.createTestAccount();
+        final Invoice invoice = testHelper.createTestInvoice(account, now, Currency.USD);
+        final BigDecimal amount = new BigDecimal("10.00");
+        final UUID subscriptionId = UUID.randomUUID();
+
+        invoice.addInvoiceItem(new DefaultInvoiceItem(invoice.getId(),
+                                           subscriptionId,
+                                           now,
+                                           now.plusMonths(1),
+                                           "Test",
+                                           amount,
+                                           new BigDecimal("1.0"),
+                                           null,
+                                           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());
+        assertEquals(paymentInfo.getAmount().doubleValue(), amount.doubleValue());
+    }
+}
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..74d8552
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/dao/MockPaymentDao.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.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;
+    }
+
+}
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..121db01
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/provider/MockPaymentProviderPlugin.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF 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 java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.commons.lang.math.RandomUtils;
+import org.joda.time.DateTime;
+
+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;
+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>();
+
+    @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) {
+            PaymentProviderAccount paymentProviderAccount = accounts.put(account.getExternalKey(),
+                                                                         new PaymentProviderAccount.Builder().setAccountNumber(String.valueOf(RandomUtils.nextInt(10)))
+                                                                                                             .setDefaultPaymentMethod(String.valueOf(RandomUtils.nextInt(10)))
+                                                                                                             .setId(String.valueOf(RandomUtils.nextInt(10)))
+                                                                                                             .build());
+
+            return Either.right(paymentProviderAccount.getId());
+        }
+        else {
+            return Either.left(new PaymentError("unknown", "Did not get account to create payment provider account"));
+        }
+    }
+
+    @Override
+    public Either<PaymentError, PaymentProviderAccount> updatePaymentProviderAccount(Account account) {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public Either<PaymentError, PaymentMethodInfo> getPaymentMethodInfo(String paymentMethodId) {
+        // TODO
+        return Either.left(new PaymentError("unknown", "Not implemented"));
+    }
+
+    @Override
+    public Either<PaymentError, List<PaymentMethodInfo>> getPaymentMethods(String accountId) {
+        // TODO
+        return Either.left(new PaymentError("unknown", "Not implemented"));
+    }
+
+    @Override
+    public Either<PaymentError, Void> updatePaymentGateway(String accountKey) {
+        // TODO
+        return Either.left(new PaymentError("unknown", "Not implemented"));
+    }
+
+    @Override
+    public Either<PaymentError, PaymentProviderAccount> getPaymentProviderAccount(String accountKey) {
+        // TODO
+        return Either.left(new PaymentError("unknown", "Not implemented"));
+    }
+
+    @Override
+    public Either<PaymentError, String> addPaypalPaymentMethod(String accountId, PaypalPaymentMethodInfo paypalPaymentMethod) {
+        // TODO
+        return Either.left(new PaymentError("unknown", "Not implemented"));
+    }
+
+    @Override
+    public Either<PaymentError, Void> deletePaypalPaymentMethod(String accountKey, String paymentMethodId) {
+        // TODO
+        return Either.left(new PaymentError("unknown", "Not implemented"));
+    }
+
+    @Override
+    public Either<PaymentError, PaymentMethodInfo> updatePaypalPaymentMethod(String accountKey, PaymentMethodInfo paymentMethodInfo) {
+        // TODO
+        return Either.left(new PaymentError("unknown", "Not implemented"));
+    }
+
+}
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..0d73691
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/setup/PaymentTestModuleWithMocks.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.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..b4df1ba
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/TestHelper.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.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.DefaultInvoiceItem;
+
+public class TestHelper {
+    protected final AccountDao accountDao;
+    protected final InvoiceDao invoiceDao;
+
+    @Inject
+    public TestHelper(AccountDao accountDao, InvoiceDao invoiceDao) {
+        this.accountDao = accountDao;
+        this.invoiceDao = invoiceDao;
+    }
+
+    public Account createTestAccount() throws AccountApiException {
+        final String name = "First" + RandomStringUtils.random(5) + " " + "Last" + RandomStringUtils.random(5);
+        final Account account = new AccountBuilder(UUID.randomUUID()).name(name)
+                                                                     .firstNameLength(name.length())
+                                                                     .externalKey("12345")
+                                                                     .phone("123-456-7890")
+                                                                     .email("user@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) {
+            invoice.addInvoiceItem(new DefaultInvoiceItem(invoice.getId(),
+                                               item.getSubscriptionId(),
+                                               item.getStartDate(),
+                                               item.getEndDate(),
+                                               item.getDescription(),
+                                               item.getRecurringAmount(),
+                                               item.getRecurringRate(),
+                                               item.getFixedAmount(),
+                                               item.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 DefaultInvoiceItem(null, subscriptionId, now, now.plusMonths(1), "Test", amount, new BigDecimal("1.0"), null, 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..1bd7695
--- /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.createTestAccount();
+        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.createTestAccount();
+        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..0533bdc
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/TestPaymentInvoiceIntegration.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.payment;
+
+import 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;
+
+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 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.createTestAccount();
+        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..d682e16
--- /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.createTestAccount();
+
+        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 79(+66 -13)

diff --git a/pom.xml b/pom.xml
index fb37e6b..5ec08b5 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.5-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,8 +78,9 @@
             </dependency>
             <dependency>
                 <groupId>com.ning.billing</groupId>
-                <artifactId>killbill-invoice</artifactId>
+                <artifactId>killbill-entitlement</artifactId>
                 <version>${project.version}</version>
+                <type>test-jar</type>
             </dependency>
             <dependency>
                 <groupId>com.ning.billing</groupId>
@@ -86,14 +92,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>
@@ -101,19 +110,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>
@@ -139,9 +154,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>
@@ -181,6 +202,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>
@@ -236,10 +262,10 @@
             <dependency>
                 <groupId>org.testng</groupId>
                 <artifactId>testng</artifactId>
-                <version>6.0</version>
+                <version>6.3.1</version>
                 <scope>test</scope>
             </dependency>
-             <dependency>
+            <dependency>
                 <groupId>com.jayway.awaitility</groupId>
                 <artifactId>awaitility</artifactId>
                 <version>1.3.3</version>
@@ -295,6 +321,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>
@@ -403,6 +441,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..b16ecfa 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.5-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/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/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..039ce2b 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(Clock.class).to(DefaultClock.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..a297581 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,7 +49,7 @@ 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);
@@ -75,8 +75,9 @@ public interface NotificationSqlDao extends Transactional<NotificationSqlDao>, C
             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());
         }
     }
@@ -95,12 +96,13 @@ public interface NotificationSqlDao extends Transactional<NotificationSqlDao>, C
 
             final UUID id = 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, 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..3c4c476 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
@@ -24,26 +24,28 @@ public class DefaultNotification implements Notification {
 
     private final UUID id;
     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(UUID id, String owner, String queueName, DateTime nextAvailableDate,
             NotificationLifecycleState lifecycleState,
             String notificationKey, DateTime effectiveDate) {
         super();
         this.id = id;
         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);
+    public DefaultNotification(String queueName, String notificationKey, DateTime effectiveDate) {
+        this(UUID.randomUUID(), null, queueName, null, NotificationLifecycleState.AVAILABLE, notificationKey, effectiveDate);
     }
 
     @Override
@@ -94,4 +96,9 @@ 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 2f18379..b6f9b09 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
@@ -20,13 +20,11 @@ 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.IDBI;
 import org.skife.jdbi.v2.Transaction;
 import org.skife.jdbi.v2.TransactionStatus;
 import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
-
 import com.ning.billing.util.clock.Clock;
 import com.ning.billing.util.notificationq.NotificationQueueService.NotificationQueueHandler;
 import com.ning.billing.util.notificationq.dao.NotificationSqlDao;
@@ -35,15 +33,13 @@ 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 void doProcessEvents(final int sequenceId) {
         List<Notification> notifications = getReadyNotifications(sequenceId);
         for (Notification cur : notifications) {
             nbProcessedEvents.incrementAndGet();
@@ -57,7 +53,7 @@ public class DefaultNotificationQueue extends NotificationQueueBase {
     public void recordFutureNotificationFromTransaction(final Transmogrifier transactionalDao,
             final DateTime futureNotificationTime, final NotificationKey notificationKey) {
         NotificationSqlDao transactionalNotificationDao =  transactionalDao.become(NotificationSqlDao.class);
-        Notification notification = new DefaultNotification(notificationKey.toString(), futureNotificationTime);
+        Notification notification = new DefaultNotification(getFullQName(), notificationKey.toString(), futureNotificationTime);
         transactionalNotificationDao.insertNotification(notification);
     }
 
@@ -96,7 +92,7 @@ public class DefaultNotificationQueue extends NotificationQueueBase {
                     TransactionStatus status) throws Exception {
 
                 List<Notification> claimedNotifications = new ArrayList<Notification>();
-                List<Notification> input = transactionalDao.getReadyNotifications(now, config.getDaoMaxReadyEvents());
+                List<Notification> input = transactionalDao.getReadyNotifications(now, config.getDaoMaxReadyEvents(), getFullQName());
                 for (Notification cur : input) {
                     final boolean claimed = (transactionalDao.claimNotification(hostname, nextAvailable, cur.getId().toString(), now) == 1);
                     if (claimed) {
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..91e7110 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;
     }
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..8749fa0 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
@@ -28,4 +28,8 @@ public interface Notification extends NotificationLifecycle {
     public String getNotificationKey();
 
     public DateTime getEffectiveDate();
+    
+    public String getQueueName();
+    
+
 }
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 c1feca1..4fb17b5 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,8 +16,6 @@
 
 package com.ning.billing.util.notificationq;
 
-import java.util.NoSuchElementException;
-
 
 public interface NotificationQueueService {
 
@@ -25,15 +23,15 @@ public interface NotificationQueueService {
         /**
          * Called for each notification ready
          *
-         * @param key the notification key associated to that notification entry
+         * @param notificationKey the notification key associated to that notification entry
          */
         public void handleReadyNotification(String notificationKey);
      }
 
-    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);
         }
     }
@@ -56,11 +54,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;
+        throws NotificationQueueAlreadyExists;
 
     /**
      * Retrieves an already created NotificationQueue by service and name if it exists
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..98a10b1 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
@@ -43,7 +43,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 +53,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);
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/TagBinder.java b/util/src/main/java/com/ning/billing/util/tag/dao/TagBinder.java
new file mode 100644
index 0000000..d56257d
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/tag/dao/TagBinder.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.tag.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 org.skife.jdbi.v2.SQLStatement;
+import org.skife.jdbi.v2.sqlobject.Binder;
+import org.skife.jdbi.v2.sqlobject.BinderFactory;
+import org.skife.jdbi.v2.sqlobject.BindingAnnotation;
+import com.ning.billing.util.tag.Tag;
+
+@BindingAnnotation(TagBinder.TagBinderFactory.class)
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.PARAMETER})
+public @interface TagBinder {
+    public static class TagBinderFactory implements BinderFactory {
+        public Binder build(Annotation annotation) {
+            return new Binder<TagBinder, Tag>() {
+                public void bind(SQLStatement q, TagBinder bind, Tag tag) {
+                    q.bind("id", tag.getId().toString());
+                    q.bind("tagDefinitionName", tag.getTagDefinitionName());
+                    q.bind("addedDate", tag.getAddedDate().toDate());
+                    q.bind("addedBy", tag.getAddedBy());
+                }
+            };
+        }
+    }
+}
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/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..883f61b 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,4 +1,4 @@
-group IFieldStoreDao;
+group FieldStoreDao;
 
 save() ::= <<
   INSERT INTO custom_fields(id, object_id, object_type, field_name, field_value)
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..cf2ceb2 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,7 +42,8 @@ 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)
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..efe7e4f 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
@@ -6,12 +6,14 @@ getReadyNotifications(now, max) ::= <<
     , 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
@@ -53,6 +55,7 @@ insertNotification() ::= <<
     , notification_key
       , created_dt
       , effective_dt
+      , queue_name
       , processing_owner
       , processing_available_dt
       , processing_state
@@ -61,6 +64,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..07ee518 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());
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..1e949a2 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,11 +31,8 @@ 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
@@ -73,7 +74,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()) {
@@ -113,7 +114,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);
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..d744caf 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);
 
@@ -159,6 +152,7 @@ public class TestNotificationSqlDao {
                 		", notification_key" +
                 		", created_dt" +
                 		", effective_dt" +
+                		", queue_name" +
                 		", processing_owner" +
                 		", processing_available_dt" +
                 		", processing_state" +
@@ -199,8 +193,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..0fe65ec 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,7 +52,7 @@ 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);
         }
@@ -75,7 +75,7 @@ public class MockNotificationQueue extends NotificationQueueBase implements Noti
             }
             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());
+                DefaultNotification processedNotification = new DefaultNotification(cur.getId(), hostname, "MockQueue", clock.getUTCNow().plus(config.getDaoClaimTimeMs()), NotificationLifecycleState.PROCESSED, cur.getNotificationKey(), cur.getEffectiveDate());
                 oldNotifications.add(cur);
                 processedNotifications.add(processedNotification);
 
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 207a78c..6db48d0 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,6 +16,8 @@
 
 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 java.io.IOException;
@@ -24,14 +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.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.Assert;
 import org.testng.annotations.BeforeSuite;
 import org.testng.annotations.BeforeTest;
 import org.testng.annotations.Guice;
@@ -49,8 +56,9 @@ import com.ning.billing.util.notificationq.dao.NotificationSqlDao;
 
 @Guice(modules = TestNotificationQueue.TestNotificationQueueModule.class)
 public class TestNotificationQueue {
+	Logger log = LoggerFactory.getLogger(TestNotificationQueue.class);
 	@Inject
-	private DBI dbi;
+	private IDBI dbi;
 
 	@Inject
 	MysqlTestingHelper helper;
@@ -59,6 +67,8 @@ public class TestNotificationQueue {
 	private Clock clock;
 
 	private DummySqlTest dao;
+	
+	private int eventsReceived;
 
 	// private NotificationQueue queue;
 
@@ -97,11 +107,10 @@ public class TestNotificationQueue {
 	/**
 	 * 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
+	 * @throws Exception 
 	 */
-	@Test
-	public void testSimpleNotification() throws InterruptedException {
+	@Test(groups={"fast"}, enabled = true)
+	public void testSimpleNotification() throws Exception {
 
 		final Map<String, Boolean> expectedNotifications = new TreeMap<String, Boolean>();
 
@@ -110,7 +119,9 @@ public class TestNotificationQueue {
 			@Override
 			public void handleReadyNotification(String notificationKey) {
 				synchronized (expectedNotifications) {
-					expectedNotifications.put(notificationKey, Boolean.TRUE);
+	            	log.info("Handler received key: " + notificationKey);
+
+					expectedNotifications.put(notificationKey.toString(), Boolean.TRUE);
 					expectedNotifications.notify();
 				}
 			}
@@ -142,6 +153,8 @@ public class TestNotificationQueue {
 				transactional.insertDummy(obj);
 				queue.recordFutureNotificationFromTransaction(transactional,
 						readyTime, notificationKey);
+            	log.info("Posted key: " + notificationKey);
+
 				return null;
 			}
 		});
@@ -150,7 +163,14 @@ public class TestNotificationQueue {
 		((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 Boolean call() throws Exception {
+                return expectedNotifications.get(notificationKey.toString());
+            }
+        });
+
+	Assert.assertTrue(expectedNotifications.get(notificationKey.toString()));
 	}
 
 	@Test
@@ -234,6 +254,126 @@ public class TestNotificationQueue {
 		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 false;
+            }
+            @Override
+            public long getNotificationSleepTimeMs() {
+                return 10;
+            }
+            @Override
+            public int getDaoMaxReadyEvents() {
+                return 1;
+            }
+            @Override
+            public long getDaoClaimTimeMs() {
+                return 60000;
+            }
+		};
+		
+		
+		final NotificationQueue queueFred = notificationQueueService.createNotificationQueue("UtilTest", "Fred", new NotificationQueueHandler() {
+                @Override
+                public void handleReadyNotification(String notificationKey) {
+                	log.info("Fred received key: " + notificationKey);
+                	expectedNotificationsFred.put(notificationKey, Boolean.TRUE);
+                	eventsReceived++;
+                }
+            },
+            config);
+		
+		final NotificationQueue queueBarney = notificationQueueService.createNotificationQueue("UtilTest", "Barney", new NotificationQueueHandler() {
+            @Override
+            public void handleReadyNotification(String notificationKey) {
+             	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) {
@@ -266,8 +406,8 @@ public class TestNotificationQueue {
 
 			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();
             final DbiConfig config = new ConfigurationObjectFactory(System.getProperties()).build(DbiConfig.class);
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..633095f
--- /dev/null
+++ b/util/src/test/java/com/ning/billing/util/tag/TestTagStore.java
@@ -0,0 +1,341 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF 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.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.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 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);
+            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();
+    }
+
+    @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);
+        dao.save(accountId.toString(), ACCOUNT_TYPE, 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();
+        tagStoreSqlDao.save(accountId.toString(), ACCOUNT_TYPE, 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();
+        tagStoreSqlDao.save(accountId.toString(), ACCOUNT_TYPE, 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();
+        tagStoreSqlDao.save(accountId.toString(), ACCOUNT_TYPE, 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);
+
+        tagStoreSqlDao.save(objectId.toString(), objectType, 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);
+
+        tagStoreSqlDao.save(objectId.toString(), objectType, 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);
+    }
+}