killbill-aplcache

Changes

payment/src/test/java/com/ning/billing/payment/setup/PaymentModuleTestBase.java 44(+0 -44)

util/pom.xml 16(+15 -1)

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

Details

diff --git a/account/src/main/java/com/ning/billing/account/api/DefaultAccount.java b/account/src/main/java/com/ning/billing/account/api/DefaultAccount.java
index 32bbd4b..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,7 +16,6 @@
 
 package com.ning.billing.account.api;
 
-import java.math.BigDecimal;
 import java.util.List;
 import java.util.UUID;
 
@@ -25,179 +24,246 @@ 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;
-    private final DateTime createdDate;
-    private final DateTime updatedDate;
-
-    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, null, null);
-    }
-
-    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, null, null);
-    }
-
-    public DefaultAccount(UUID id,
-                          String externalKey,
-                          String email,
-                          String name,
-                          int firstNameLength,
-                          String phone,
-                          Currency currency,
-                          int billCycleDay,
-                          String paymentProviderName,
-                          BigDecimal balance) {
-        this(id, externalKey, email, name, firstNameLength, phone, currency, billCycleDay, paymentProviderName, balance, null, null);
-    }
-
-    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, final DateTime createdDate, final DateTime updatedDate) {
-        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;
-        DateTime now = new DateTime(DateTimeZone.UTC);
-        this.createdDate = (createdDate == null) ? now : createdDate;
-        this.updatedDate = (updatedDate == null) ? now : updatedDate;
-
-        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 DateTime getCreatedDate() {
-        return createdDate;
-    }
-
-    @Override
-    public DateTime getUpdatedDate() {
-        return updatedDate;
-    }
-
-    @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;
-    }
-
-    @Override
-    public String toString() {
-        return "DefaultAccount [externalKey=" + externalKey + ", email=" + email + ", name=" + name + ", firstNameLength=" + firstNameLength + ", phone=" + phone + ", currency=" + currency + ", billCycleDay=" + billCycleDay + ", paymentProviderName=" + paymentProviderName + ", balance=" + balance + ", tags=" + tags + ", createdDate=" + createdDate + ", updatedDate=" + updatedDate + "]";
-    }
+	//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 c2866f7..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,10 +16,10 @@
 
 package com.ning.billing.account.api.user;
 
-import java.math.BigDecimal;
 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;
@@ -30,11 +30,19 @@ public class AccountBuilder {
     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;
 
@@ -42,52 +50,92 @@ public class 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;
     }
 
@@ -103,7 +151,10 @@ public class AccountBuilder {
 
     public DefaultAccount build() {
         return new DefaultAccount(id, externalKey, email, name, firstNameLength,
-                                  phone, currency, billingCycleDay, paymentProviderName,
-                                  balance, createdDate, updatedDate);
+                                  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 978872c..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
@@ -66,6 +66,10 @@ 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 {
@@ -81,19 +85,39 @@ public interface AccountSqlDao extends EntityDao<Account>, Transactional<Account
             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();
@@ -106,24 +130,34 @@ public interface AccountSqlDao extends EntityDao<Account>, Transactional<Account
     public @interface AccountBinder {
         public static class AccountBinderFactory implements BinderFactory {
             @Override
-            public Binder build(Annotation annotation) {
+            public Binder<AccountBinder, Account> build(Annotation annotation) {
                 return new Binder<AccountBinder, Account>() {
                     private Date getDate(DateTime dateTime) {
                         return dateTime == null ? null : dateTime.toDate();
                     }
 
                     @Override
-                    public void bind(SQLStatement q, AccountBinder bind, Account account) {
+                    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..2b787f0 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.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/resources/com/ning/billing/account/dao/AccountSqlDao.sql.stg b/account/src/main/resources/com/ning/billing/account/dao/AccountSqlDao.sql.stg
index 412bcbf..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
@@ -6,33 +6,51 @@ accountFields(prefix) ::= <<
     <prefix>email,
     <prefix>name,
     <prefix>first_name_length,
-    <prefix>phone,
     <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 (<accountFields()>)
-    VALUES (:id, :externalKey, :email, :name, :firstNameLength, :phone, :currency, :billingCycleDay, :paymentProviderName, :createdDate, :updatedDate);
+    INSERT INTO accounts
+      (<accountFields()>)
+    VALUES
+      (: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 <accountFields()>
     from accounts
diff --git a/account/src/main/resources/com/ning/billing/account/ddl.sql b/account/src/main/resources/com/ning/billing/account/ddl.sql
index fff193e..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,13 +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
index 2aef1ab..433a663 100644
--- a/account/src/test/java/com/ning/billing/account/api/MockAccountUserApi.java
+++ b/account/src/test/java/com/ning/billing/account/api/MockAccountUserApi.java
@@ -22,6 +22,8 @@ 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;
@@ -34,15 +36,28 @@ public class MockAccountUserApi implements AccountUserApi {
                                  String email,
                                  String name,
                                  int firstNameLength,
-                                 String phone,
                                  Currency currency,
                                  int billCycleDay,
                                  String paymentProviderName,
-                                 BigDecimal balance) {
-        Account result = new DefaultAccount(id, externalKey, email, name, firstNameLength, phone, currency, billCycleDay, paymentProviderName, balance);
-        accounts.add(result);
-        return result;
-    }
+                                 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 {
@@ -90,4 +105,15 @@ public class MockAccountUserApi implements AccountUserApi {
     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/MockAccountDao.java b/account/src/test/java/com/ning/billing/account/dao/MockAccountDao.java
index 27643c7..71a1793 100644
--- a/account/src/test/java/com/ning/billing/account/dao/MockAccountDao.java
+++ b/account/src/test/java/com/ning/billing/account/dao/MockAccountDao.java
@@ -24,6 +24,7 @@ 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;
@@ -95,4 +96,13 @@ public class MockAccountDao implements AccountDao {
             }
         }
     }
+
+	@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 c67b70e..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
@@ -19,6 +19,7 @@ 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;
@@ -34,23 +35,23 @@ import com.ning.billing.account.api.DefaultAccount;
 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.DefaultTagDescription;
+import com.ning.billing.util.tag.DefaultTagDefinition;
 import com.ning.billing.util.tag.Tag;
-import com.ning.billing.util.tag.TagDescription;
-import com.ning.billing.util.tag.dao.TagDescriptionDao;
+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);
 
@@ -61,12 +62,14 @@ public class TestSimpleAccountDao extends AccountDaoTestBase {
                                    .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);
@@ -105,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";
@@ -120,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);
 
@@ -136,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);
 
@@ -198,10 +207,52 @@ public class TestSimpleAccountDao extends AccountDaoTestBase {
             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());
@@ -209,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/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/test/java/com/ning/billing/analytics/api/TestAnalyticsService.java b/analytics/src/test/java/com/ning/billing/analytics/api/TestAnalyticsService.java
index 2feffda..79bf994 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
@@ -48,10 +48,10 @@ 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.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/MockAccount.java b/analytics/src/test/java/com/ning/billing/analytics/MockAccount.java
index 6fdfe50..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,20 +16,18 @@
 
 package com.ning.billing.analytics;
 
-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 sun.reflect.generics.reflectiveObjects.NotImplementedException;
-
 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 MockAccount implements Account
 {
@@ -90,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;
@@ -136,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();
     }
 
@@ -151,7 +194,7 @@ public class MockAccount implements Account
     }
 
     @Override
-    public void removeTag(TagDescription description) {
+    public void removeTag(TagDefinition definition) {
         throw new NotImplementedException();
     }
 
@@ -166,11 +209,6 @@ public class MockAccount implements Account
     }
 
     @Override
-    public BigDecimal getBalance() {
-        return BigDecimal.ZERO;
-    }
-
-    @Override
     public DateTime getCreatedDate() {
         return new DateTime(DateTimeZone.UTC);
     }
@@ -179,4 +217,5 @@ public class MockAccount implements Account
     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 916856d..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,7 +34,7 @@ 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);
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 5272630..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,15 +16,12 @@
 
 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();
 
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 e395706..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,6 +16,8 @@
 
 package com.ning.billing.account.api;
 
+import org.joda.time.DateTimeZone;
+
 import com.ning.billing.catalog.api.Currency;
 
 public interface AccountData {
@@ -28,12 +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..5a23528 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
@@ -19,13 +19,19 @@ package com.ning.billing.account.api;
 import java.util.List;
 import java.util.UUID;
 import com.ning.billing.util.customfield.CustomField;
+import com.ning.billing.util.eventbus.Bus;
 import com.ning.billing.util.tag.Tag;
 
 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 +39,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/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/ErrorCode.java b/api/src/main/java/com/ning/billing/ErrorCode.java
index c3238e2..d7bd49e 100644
--- a/api/src/main/java/com/ning/billing/ErrorCode.java
+++ b/api/src/main/java/com/ning/billing/ErrorCode.java
@@ -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/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();
 }
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 85da42a..aa8c9e5 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;
@@ -290,6 +291,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/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 8a2d47f..c212b75 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
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 47149a3..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
@@ -146,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/test/java/com/ning/billing/entitlement/api/billing/BrainDeadAccount.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/BrainDeadAccount.java
index da35883..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,7 +16,6 @@
 
 package com.ning.billing.entitlement.api.billing;
 
-import java.math.BigDecimal;
 import java.util.List;
 import java.util.UUID;
 
@@ -27,7 +26,7 @@ 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 {
 
@@ -70,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();
 	}
@@ -110,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();
 	}
@@ -127,7 +170,7 @@ public class BrainDeadAccount implements Account {
 	}
 
 	@Override
-	public void removeTag(TagDescription description) {
+	public void removeTag(TagDefinition definition) {
 		throw new UnsupportedOperationException();
 	}
 
@@ -140,12 +183,6 @@ 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();
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/TestApiBase.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/TestApiBase.java
index 75dd17f..4018002 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
@@ -27,6 +27,7 @@ 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;
@@ -314,6 +315,50 @@ public abstract class TestApiBase {
             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/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 9a353ff..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
@@ -17,7 +17,6 @@
 package com.ning.billing.invoice.api.user;
 
 import java.math.BigDecimal;
-import java.util.Collection;
 import java.util.List;
 import java.util.UUID;
 
@@ -58,9 +57,10 @@ public class DefaultInvoiceUserApi implements InvoiceUserApi {
     }
     
     @Override
-    public BigDecimal getAccountBalance(final UUID accountId) {
-        return dao.getAccountBalance(accountId);
-    }
+	public BigDecimal getAccountBalance(UUID accountId) {
+		BigDecimal result = dao.getAccountBalance(accountId);
+		return result == null ? BigDecimal.ZERO : result;
+	}
 
     @Override
     public List<InvoiceItem> getInvoiceItemsByAccount(final UUID accountId) {
@@ -73,7 +73,7 @@ public class DefaultInvoiceUserApi implements InvoiceUserApi {
     }
 
     @Override
-    public Collection<Invoice> getUnpaidInvoicesByAccountId(final UUID accountId, final DateTime upToDate) {
+    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/InvoiceSqlDao.java b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceSqlDao.java
index ccb8eff..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
@@ -142,5 +142,7 @@ public interface InvoiceSqlDao extends EntityDao<Invoice>, Transactional<Invoice
             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 e5bd1f4..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
@@ -51,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 7cff1ee..487f181 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/InvoiceListener.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/InvoiceListener.java
@@ -20,6 +20,7 @@ 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;
@@ -32,13 +33,10 @@ 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;
@@ -51,17 +49,15 @@ public class InvoiceListener {
     private final EntitlementBillingApi entitlementBillingApi;
     private final AccountUserApi accountUserApi;
     private final InvoiceDao invoiceDao;
-    private final Clock clock;
 
     @Inject
     public InvoiceListener(final InvoiceGenerator generator, final AccountUserApi accountUserApi,
                            final EntitlementBillingApi entitlementBillingApi,
-                           final InvoiceDao invoiceDao, final Clock clock) {
+                           final InvoiceDao invoiceDao) {
         this.generator = generator;
         this.entitlementBillingApi = entitlementBillingApi;
         this.accountUserApi = accountUserApi;
         this.invoiceDao = invoiceDao;
-        this.clock = clock;
     }
 
     @Subscribe
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 e21da4f..2dc2144 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
@@ -32,7 +32,6 @@ 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;
 
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..b4d81a7 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,7 +17,6 @@
 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;
 
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 f483773..ce7a429 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
@@ -91,6 +91,15 @@ getUnpaidInvoicesByAccountId() ::= <<
   ORDER BY i.target_date ASC;
 >>
 
+getAccountBalance() ::= <<
+  SELECT SUM(iis.total_amount) AS amount_invoiced, SUM(ips.total_paid) AS amount_paid
+  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
+  GROUP BY i.account_id;
+>>
+
 test() ::= <<
   SELECT 1
   FROM invoices;
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 c2995c7..da13062 100644
--- a/invoice/src/main/resources/com/ning/billing/invoice/ddl.sql
+++ b/invoice/src/main/resources/com/ning/billing/invoice/ddl.sql
@@ -11,7 +11,6 @@ CREATE TABLE invoice_items (
   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,7 +22,6 @@ 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;
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 fae8e44..8e54bb6 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,20 +16,21 @@
 
 package com.ning.billing.invoice.dao;
 
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
+
+import java.io.IOException;
+
+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.InvoiceModuleWithEmbeddedDb;
 import com.ning.billing.util.eventbus.BusService;
 import com.ning.billing.util.eventbus.DefaultEventBusService;
-import org.apache.commons.io.IOUtils;
-import org.testng.annotations.AfterClass;
-import org.testng.annotations.BeforeClass;
-
-import java.io.IOException;
-
-import static org.testng.Assert.assertTrue;
-import static org.testng.Assert.fail;
 
 public abstract class InvoiceDaoTestBase {
     protected InvoiceDao invoiceDao;
@@ -54,9 +55,9 @@ public abstract class InvoiceDaoTestBase {
             invoiceDao = injector.getInstance(InvoiceDao.class);
             invoiceDao.test();
 
-            invoiceItemDao = module.getInvoiceItemDao();
+            invoiceItemDao = module.getInvoiceItemSqlDao();
 
-            invoicePaymentDao = module.getIDBI().onDemand(InvoicePaymentSqlDao.class);
+            invoicePaymentDao = module.getInvoicePaymentSqlDao();
 
             BusService busService = injector.getInstance(BusService.class);
             ((DefaultEventBusService) busService).startBus();
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 8ce7a26..59f39ac 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
@@ -303,6 +303,13 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         List<Invoice> items4 = invoiceDao.getInvoicesBySubscription(subscriptionId4);
         assertEquals(items4.size(), 1);
     }
+    
+    @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);
 
     @Test
     public void testGetInvoicesForAccountAfterDate() {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/dao/MockInvoiceDao.java b/invoice/src/test/java/com/ning/billing/invoice/dao/MockInvoiceDao.java
index 522ef48..55b4ce2 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/dao/MockInvoiceDao.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/dao/MockInvoiceDao.java
@@ -60,44 +60,6 @@ public class MockInvoiceDao implements InvoiceDao {
         }
     }
 
-//    private Invoice munge(Invoice invoice) {
-//        if (invoice == null) {
-//            return null;
-//        }
-//
-//        DateTime lastPaymentDate = null;
-//        BigDecimal amountPaid = new BigDecimal("0");
-//
-//        for (InvoicePayment invoicePayment : invoicePayments.values()) {
-//            if (invoicePayment.getInvoiceId().equals(invoice.getId())) {
-//                if (lastPaymentDate == null || lastPaymentDate.isBefore(invoicePayment.getPaymentAttemptDate())) {
-//                    lastPaymentDate = invoicePayment.getPaymentAttemptDate();
-//                }
-//                if (invoicePayment.getAmount() != null) {
-//                    amountPaid = amountPaid.add(invoicePayment.getAmount());
-//                }
-//            }
-//        }
-//
-//        Invoice newInvoice = new DefaultInvoice(invoice.getId(),
-//                                                invoice.getAccountId(),
-//                                                invoice.getInvoiceDate(),
-//                                                invoice.getTargetDate(),
-//                                                invoice.getCurrency());
-//        newInvoice.addInvoiceItems(invoice.getInvoiceItems());
-//        newInvoice.addPayments(invoice.getPayments());
-//
-//        return newInvoice;
-//    }
-//
-//    private List<Invoice> munge(Collection<Invoice> invoices) {
-//        List<Invoice> result = new ArrayList<Invoice>();
-//        for (Invoice invoice : invoices) {
-//            result.add(munge(invoice));
-//        }
-//        return result;
-//    }
-
     @Override
     public Invoice getById(UUID id) {
         synchronized (monitor) {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/glue/InvoiceModuleWithEmbeddedDb.java b/invoice/src/test/java/com/ning/billing/invoice/glue/InvoiceModuleWithEmbeddedDb.java
index 53f08fb..fb29cc6 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/glue/InvoiceModuleWithEmbeddedDb.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/glue/InvoiceModuleWithEmbeddedDb.java
@@ -18,6 +18,7 @@ package com.ning.billing.invoice.glue;
 
 import java.io.IOException;
 
+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;
@@ -46,12 +47,12 @@ public class InvoiceModuleWithEmbeddedDb extends InvoiceModule {
         helper.stopMysql();
     }
 
-    public IDBI getIDBI() {
-        return dbi;
+    public InvoiceItemSqlDao getInvoiceItemSqlDao() {
+        return dbi.onDemand(InvoiceItemSqlDao.class);
     }
 
-    public InvoiceItemSqlDao getInvoiceItemDao() {
-        return dbi.onDemand(InvoiceItemSqlDao.class);
+    public InvoicePaymentSqlDao getInvoicePaymentSqlDao() {
+        return dbi.onDemand(InvoicePaymentSqlDao.class);
     }
 
     private void installNotificationQueue() {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/glue/InvoiceModuleWithMocks.java b/invoice/src/test/java/com/ning/billing/invoice/glue/InvoiceModuleWithMocks.java
index d01301b..60015c1 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/glue/InvoiceModuleWithMocks.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/glue/InvoiceModuleWithMocks.java
@@ -25,4 +25,19 @@ public class InvoiceModuleWithMocks extends InvoiceModule {
         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/tests/DefaultInvoiceGeneratorTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/DefaultInvoiceGeneratorTests.java
index 5e8168f..0952f28 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
@@ -27,9 +27,9 @@ 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;
diff --git a/payment/src/test/java/com/ning/billing/payment/api/TestPaymentApi.java b/payment/src/test/java/com/ning/billing/payment/api/TestPaymentApi.java
index 1113306..d2d8033 100644
--- a/payment/src/test/java/com/ning/billing/payment/api/TestPaymentApi.java
+++ b/payment/src/test/java/com/ning/billing/payment/api/TestPaymentApi.java
@@ -32,6 +32,7 @@ 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;
@@ -59,7 +60,7 @@ public abstract class TestPaymentApi {
 
 //    @Test(groups = "fast")
     @Test
-    public void testCreatePayment() {
+    public void testCreatePayment() throws AccountApiException {
         final DateTime now = new DateTime();
         final Account account = testHelper.createTestAccount();
         final Invoice invoice = testHelper.createTestInvoice(account, now, Currency.USD);
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
index 4546ed3..e051440 100644
--- a/payment/src/test/java/com/ning/billing/payment/setup/PaymentModuleWithMocks.java
+++ b/payment/src/test/java/com/ning/billing/payment/setup/PaymentModuleWithMocks.java
@@ -19,7 +19,7 @@ package com.ning.billing.payment.setup;
 import com.ning.billing.payment.dao.MockPaymentDao;
 import com.ning.billing.payment.dao.PaymentDao;
 
-public class PaymentModuleWithMocks extends PaymentModuleTestBase {
+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
index ca3f387..e8c723b 100644
--- a/payment/src/test/java/com/ning/billing/payment/setup/PaymentTestModuleWithEmbeddedDb.java
+++ b/payment/src/test/java/com/ning/billing/payment/setup/PaymentTestModuleWithEmbeddedDb.java
@@ -23,7 +23,7 @@ import com.google.common.collect.ImmutableMap;
 import com.ning.billing.payment.provider.MockPaymentProviderPluginModule;
 import com.ning.billing.util.eventbus.MemoryEventBus;
 
-public class PaymentTestModuleWithEmbeddedDb extends PaymentModuleTestBase {
+public class PaymentTestModuleWithEmbeddedDb extends PaymentModule {
     public PaymentTestModuleWithEmbeddedDb() {
         super(MapUtils.toProperties(ImmutableMap.of("killbill.payment.provider.default", "my-mock")));
     }
diff --git a/payment/src/test/java/com/ning/billing/payment/setup/PaymentTestModuleWithMocks.java b/payment/src/test/java/com/ning/billing/payment/setup/PaymentTestModuleWithMocks.java
index a4e6649..75c045d 100644
--- a/payment/src/test/java/com/ning/billing/payment/setup/PaymentTestModuleWithMocks.java
+++ b/payment/src/test/java/com/ning/billing/payment/setup/PaymentTestModuleWithMocks.java
@@ -29,7 +29,7 @@ import com.ning.billing.payment.provider.MockPaymentProviderPluginModule;
 import com.ning.billing.util.eventbus.Bus;
 import com.ning.billing.util.eventbus.MemoryEventBus;
 
-public class PaymentTestModuleWithMocks extends PaymentModuleTestBase {
+public class PaymentTestModuleWithMocks extends PaymentModule {
     public PaymentTestModuleWithMocks() {
         super(MapUtils.toProperties(ImmutableMap.of("killbill.payment.provider.default", "my-mock")));
     }
diff --git a/payment/src/test/java/com/ning/billing/payment/TestHelper.java b/payment/src/test/java/com/ning/billing/payment/TestHelper.java
index 2f4a7d1..c690352 100644
--- a/payment/src/test/java/com/ning/billing/payment/TestHelper.java
+++ b/payment/src/test/java/com/ning/billing/payment/TestHelper.java
@@ -25,6 +25,7 @@ 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;
@@ -44,7 +45,7 @@ public class TestHelper {
         this.invoiceDao = invoiceDao;
     }
 
-    public Account createTestAccount() {
+    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())
diff --git a/payment/src/test/java/com/ning/billing/payment/TestNotifyInvoicePaymentApi.java b/payment/src/test/java/com/ning/billing/payment/TestNotifyInvoicePaymentApi.java
index ef36ba3..13cf865 100644
--- a/payment/src/test/java/com/ning/billing/payment/TestNotifyInvoicePaymentApi.java
+++ b/payment/src/test/java/com/ning/billing/payment/TestNotifyInvoicePaymentApi.java
@@ -28,6 +28,7 @@ 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;
@@ -61,7 +62,7 @@ public class TestNotifyInvoicePaymentApi {
     }
 
     @Test
-    public void testNotifyPaymentSuccess() {
+    public void testNotifyPaymentSuccess() throws AccountApiException {
         final Account account = testHelper.createTestAccount();
         final Invoice invoice = testHelper.createTestInvoice(account);
 
@@ -79,7 +80,7 @@ public class TestNotifyInvoicePaymentApi {
     }
 
     @Test
-    public void testNotifyPaymentFailure() {
+    public void testNotifyPaymentFailure() throws AccountApiException {
         final Account account = testHelper.createTestAccount();
         final Invoice invoice = testHelper.createTestInvoice(account);
 
diff --git a/payment/src/test/java/com/ning/billing/payment/TestPaymentInvoiceIntegration.java b/payment/src/test/java/com/ning/billing/payment/TestPaymentInvoiceIntegration.java
index 9f70017..2c9c2f1 100644
--- a/payment/src/test/java/com/ning/billing/payment/TestPaymentInvoiceIntegration.java
+++ b/payment/src/test/java/com/ning/billing/payment/TestPaymentInvoiceIntegration.java
@@ -26,7 +26,9 @@ 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;
@@ -44,7 +46,6 @@ 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.invoice.glue.InvoiceModule;
 import com.ning.billing.payment.api.PaymentApi;
 import com.ning.billing.payment.api.PaymentAttempt;
 import com.ning.billing.payment.api.PaymentError;
@@ -95,7 +96,7 @@ public class TestPaymentInvoiceIntegration {
     public void setUp() throws EventBusException {
         Injector injector = Guice.createInjector(new PaymentTestModuleWithEmbeddedDb(),
                                                  new AccountModule(),
-                                                 new InvoiceModule(),
+                                                 new InvoiceModuleWithMocks(),
                                                  new AbstractModule() {
                                                     @Override
                                                     protected void configure() {
@@ -145,8 +146,12 @@ public class TestPaymentInvoiceIntegration {
         Assert.assertNotNull(invoiceForPayment);
         Assert.assertEquals(invoiceForPayment.getId(), invoice.getId());
         Assert.assertEquals(invoiceForPayment.getAccountId(), account.getId());
-        Assert.assertTrue(invoiceForPayment.getLastPaymentAttempt().isEqual(paymentAttempt.getPaymentAttemptDate()));
+
+        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.getBalance().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
index 2fb040e..bd175f4 100644
--- a/payment/src/test/java/com/ning/billing/payment/TestPaymentProvider.java
+++ b/payment/src/test/java/com/ning/billing/payment/TestPaymentProvider.java
@@ -58,6 +58,8 @@ public class TestPaymentProvider {
         eventBus.start();
         eventBus.register(invoiceProcessor);
         eventBus.register(paymentInfoReceiver);
+
+        assertTrue(true);
     }
 
     @AfterMethod(alwaysRun = true)
@@ -65,6 +67,8 @@ public class TestPaymentProvider {
         eventBus.unregister(invoiceProcessor);
         eventBus.unregister(paymentInfoReceiver);
         eventBus.stop();
+
+        assertTrue(true);
     }
 
     @Test

util/pom.xml 16(+15 -1)

diff --git a/util/pom.xml b/util/pom.xml
index 06b2135..2b23ebb 100644
--- a/util/pom.xml
+++ b/util/pom.xml
@@ -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>
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/eventbus/MemoryEventBus.java b/util/src/main/java/com/ning/billing/util/eventbus/MemoryEventBus.java
index 7312e5b..4b29f78 100644
--- a/util/src/main/java/com/ning/billing/util/eventbus/MemoryEventBus.java
+++ b/util/src/main/java/com/ning/billing/util/eventbus/MemoryEventBus.java
@@ -80,9 +80,9 @@ public class MemoryEventBus implements Bus {
     }
 
     @Override
-    public void register(Object handlerInstnace) throws EventBusException {
+    public void register(Object handlerInstance) throws EventBusException {
         checkInitialized("register");
-        delegate.register(handlerInstnace);
+        delegate.register(handlerInstance);
     }
 
     @Override
@@ -111,7 +111,6 @@ public class MemoryEventBus implements Bus {
         }
     }
 
-
     private void checkInitialized(String operation) throws EventBusException {
         if (!isInitialized.get()) {
             throw new EventBusException(String.format("Attempting operation %s on an non initialized eventbus",
@@ -124,7 +123,7 @@ public class MemoryEventBus implements Bus {
             log.info("MemoryEventBus stopping...");
             delegate.completeDispatch();
             delegate.stop();
-            log.info("MemoryEventBus stoped...");
+            log.info("MemoryEventBus stopped...");
         }
     }
 }
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/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..3dfc9be 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 (
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/util/tag/TagStoreModuleMock.java b/util/src/test/java/com/ning/billing/util/tag/TagStoreModuleMock.java
new file mode 100644
index 0000000..9fc4c78
--- /dev/null
+++ b/util/src/test/java/com/ning/billing/util/tag/TagStoreModuleMock.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.util.tag;
+
+import java.io.IOException;
+import org.skife.jdbi.v2.IDBI;
+import com.ning.billing.dbi.MysqlTestingHelper;
+import com.ning.billing.util.glue.TagStoreModule;
+
+public class TagStoreModuleMock extends TagStoreModule {
+    private final MysqlTestingHelper helper = new MysqlTestingHelper();
+
+    public void startDb() throws IOException {
+        helper.startMysql();
+    }
+
+    public void initDb(String ddl) throws IOException {
+        helper.initDb(ddl);
+    }
+
+    public void stopDb() {
+        helper.stopMysql();
+    }
+
+    @Override
+    protected void configure() {
+        bind(IDBI.class).toInstance(helper.getDBI());
+        super.configure();
+    }
+}
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);
+    }
+}