killbill-memoizeit

Changes

Details

diff --git a/analytics/src/main/java/com/ning/billing/analytics/BusinessInvoiceRecorder.java b/analytics/src/main/java/com/ning/billing/analytics/BusinessInvoiceRecorder.java
index 554ee50..4dfd681 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/BusinessInvoiceRecorder.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/BusinessInvoiceRecorder.java
@@ -47,7 +47,10 @@ import com.ning.billing.invoice.api.InvoiceItem;
 import com.ning.billing.invoice.api.InvoiceUserApi;
 import com.ning.billing.util.clock.Clock;
 
+import com.google.common.annotations.VisibleForTesting;
+
 public class BusinessInvoiceRecorder {
+
     private static final Logger log = LoggerFactory.getLogger(BusinessInvoiceRecorder.class);
 
     private final AccountUserApi accountApi;
@@ -160,42 +163,35 @@ public class BusinessInvoiceRecorder {
         accountSqlDao.saveAccount(account);
     }
 
-    private BusinessInvoiceItem createBusinessInvoiceItem(final InvoiceItem invoiceItem) {
-        final String externalKey;
-        try {
-            final SubscriptionBundle bundle = entitlementApi.getBundleFromId(invoiceItem.getBundleId());
-            externalKey = bundle.getKey();
-        } catch (EntitlementUserApiException e) {
-            log.warn("Ignoring invoice item {} for bundle {} (bundle does not exist)",
-                     invoiceItem.getId().toString(),
-                     invoiceItem.getBundleId().toString());
-            return null;
-        }
-
-        final Subscription subscription;
-        try {
-            subscription = entitlementApi.getSubscriptionFromId(invoiceItem.getSubscriptionId());
-        } catch (EntitlementUserApiException e) {
-            log.warn("Ignoring invoice item {} for subscription {} (subscription does not exist)",
-                     invoiceItem.getId().toString(),
-                     invoiceItem.getSubscriptionId().toString());
-            return null;
-        }
-
-        final Plan plan = subscription.getCurrentPlan();
-        if (plan == null) {
-            log.warn("Ignoring invoice item {} for subscription {} (null plan)",
-                     invoiceItem.getId().toString(),
-                     invoiceItem.getSubscriptionId().toString());
-            return null;
+    @VisibleForTesting
+    BusinessInvoiceItem createBusinessInvoiceItem(final InvoiceItem invoiceItem) {
+        String externalKey = null;
+        Plan plan = null;
+        PlanPhase planPhase = null;
+
+        // Subscription and bundle could be null for e.g. credits or adjustments
+        if (invoiceItem.getBundleId() != null) {
+            try {
+                final SubscriptionBundle bundle = entitlementApi.getBundleFromId(invoiceItem.getBundleId());
+                externalKey = bundle.getKey();
+            } catch (EntitlementUserApiException e) {
+                log.warn("Ignoring subscription fields for invoice item {} for bundle {} (bundle does not exist)",
+                         invoiceItem.getId().toString(),
+                         invoiceItem.getBundleId().toString());
+            }
         }
 
-        final PlanPhase planPhase = subscription.getCurrentPhase();
-        if (planPhase == null) {
-            log.warn("Ignoring invoice item {} for subscription {} (null phase)",
-                     invoiceItem.getId().toString(),
-                     invoiceItem.getSubscriptionId().toString());
-            return null;
+        if (invoiceItem.getSubscriptionId() != null) {
+            final Subscription subscription;
+            try {
+                subscription = entitlementApi.getSubscriptionFromId(invoiceItem.getSubscriptionId());
+                plan = subscription.getCurrentPlan();
+                planPhase = subscription.getCurrentPhase();
+            } catch (EntitlementUserApiException e) {
+                log.warn("Ignoring subscription fields for invoice item {} for subscription {} (subscription does not exist)",
+                         invoiceItem.getId().toString(),
+                         invoiceItem.getSubscriptionId().toString());
+            }
         }
 
         return new BusinessInvoiceItem(externalKey, invoiceItem, plan, planPhase);
diff --git a/analytics/src/main/java/com/ning/billing/analytics/model/BusinessInvoiceItem.java b/analytics/src/main/java/com/ning/billing/analytics/model/BusinessInvoiceItem.java
index 518227b..bdbd945 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/model/BusinessInvoiceItem.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/model/BusinessInvoiceItem.java
@@ -19,6 +19,8 @@ package com.ning.billing.analytics.model;
 import java.math.BigDecimal;
 import java.util.UUID;
 
+import javax.annotation.Nullable;
+
 import org.joda.time.DateTime;
 import org.joda.time.DateTimeZone;
 import org.joda.time.LocalDate;
@@ -30,6 +32,7 @@ import com.ning.billing.catalog.api.PlanPhase;
 import com.ning.billing.invoice.api.InvoiceItem;
 
 public class BusinessInvoiceItem {
+
     private final UUID itemId;
     private final DateTime createdDate;
     private final DateTime updatedDate;
@@ -47,11 +50,11 @@ public class BusinessInvoiceItem {
     private final BigDecimal amount;
     private final Currency currency;
 
-    public BusinessInvoiceItem(final BigDecimal amount, final String billingPeriod, final DateTime createdDate,
+    public BusinessInvoiceItem(final BigDecimal amount, @Nullable final String billingPeriod, final DateTime createdDate,
                                final Currency currency, final LocalDate endDate, final String externalKey,
-                               final UUID invoiceId, final UUID itemId, final String itemType, final String phase,
-                               final String productCategory, final String productName, final String productType,
-                               final String slug, final LocalDate startDate, final DateTime updatedDate) {
+                               final UUID invoiceId, final UUID itemId, final String itemType, @Nullable final String phase,
+                               @Nullable final String productCategory, @Nullable final String productName, @Nullable final String productType,
+                               @Nullable final String slug, final LocalDate startDate, final DateTime updatedDate) {
         this.amount = amount;
         this.billingPeriod = billingPeriod;
         this.createdDate = createdDate;
@@ -70,11 +73,12 @@ public class BusinessInvoiceItem {
         this.updatedDate = updatedDate;
     }
 
-    public BusinessInvoiceItem(final String externalKey, final InvoiceItem invoiceItem, final Plan plan, final PlanPhase planPhase) {
-        this(invoiceItem.getAmount(), planPhase.getBillingPeriod().toString(), new DateTime(DateTimeZone.UTC), invoiceItem.getCurrency(), invoiceItem.getEndDate(),
-             externalKey, invoiceItem.getInvoiceId(), invoiceItem.getId(), invoiceItem.getInvoiceItemType().toString(),
-             planPhase.getPhaseType().toString(), plan.getProduct().getCategory().toString(), plan.getProduct().getName(), plan.getProduct().getCatalogName(),
-             planPhase.getName(), invoiceItem.getStartDate(), new DateTime(DateTimeZone.UTC));
+    public BusinessInvoiceItem(@Nullable final String externalKey, final InvoiceItem invoiceItem, @Nullable final Plan plan, @Nullable final PlanPhase planPhase) {
+        this(invoiceItem.getAmount(), planPhase != null ? planPhase.getBillingPeriod().toString() : null, new DateTime(DateTimeZone.UTC), invoiceItem.getCurrency(),
+             invoiceItem.getEndDate(), externalKey, invoiceItem.getInvoiceId(), invoiceItem.getId(), invoiceItem.getInvoiceItemType().toString(),
+             planPhase != null ? planPhase.getPhaseType().toString() : null, plan != null ? plan.getProduct().getCategory().toString() : null,
+             plan != null ? plan.getProduct().getName() : null, plan != null ? plan.getProduct().getCatalogName() : null,
+             planPhase != null ? planPhase.getName() : null, invoiceItem.getStartDate(), new DateTime(DateTimeZone.UTC));
     }
 
     public DateTime getCreatedDate() {
diff --git a/analytics/src/main/resources/com/ning/billing/analytics/ddl.sql b/analytics/src/main/resources/com/ning/billing/analytics/ddl.sql
index 3c0a2c6..1bfa1cb 100644
--- a/analytics/src/main/resources/com/ning/billing/analytics/ddl.sql
+++ b/analytics/src/main/resources/com/ning/billing/analytics/ddl.sql
@@ -91,11 +91,11 @@ create table bii (
 , updated_date bigint not null
 , invoice_id char(36) not null
 , item_type char(50) not null comment 'e.g. FIXED or RECURRING'
-, external_key varchar(50) not null comment 'Bundle external key'
+, external_key varchar(50) default null comment 'Bundle external key (could be null for certain items)'
 , product_name varchar(50) default null
 , product_type varchar(50) default null
 , product_category varchar(50) default null
-, slug varchar(50) default null comment 'foo'
+, slug varchar(50) default null
 , phase varchar(50) default null
 , billing_period varchar(50) default null
 , start_date date default null
diff --git a/analytics/src/test/java/com/ning/billing/analytics/TestBusinessInvoiceRecorder.java b/analytics/src/test/java/com/ning/billing/analytics/TestBusinessInvoiceRecorder.java
new file mode 100644
index 0000000..31e76e3
--- /dev/null
+++ b/analytics/src/test/java/com/ning/billing/analytics/TestBusinessInvoiceRecorder.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2010-2012 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.analytics;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import org.joda.time.LocalDate;
+import org.mockito.Mockito;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.ning.billing.account.api.AccountUserApi;
+import com.ning.billing.analytics.dao.BusinessInvoiceSqlDao;
+import com.ning.billing.analytics.model.BusinessInvoiceItem;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.entitlement.api.user.EntitlementUserApi;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.api.InvoiceItemType;
+import com.ning.billing.invoice.api.InvoiceUserApi;
+import com.ning.billing.util.clock.Clock;
+
+public class TestBusinessInvoiceRecorder {
+
+    private final AccountUserApi accountApi = Mockito.mock(AccountUserApi.class);
+    private final EntitlementUserApi entitlementApi = Mockito.mock(EntitlementUserApi.class);
+    private final InvoiceUserApi invoiceApi = Mockito.mock(InvoiceUserApi.class);
+    private final BusinessInvoiceSqlDao sqlDao = Mockito.mock(BusinessInvoiceSqlDao.class);
+    private final Clock clock = Mockito.mock(Clock.class);
+
+    @Test(groups = "fast")
+    public void testShouldBeAbleToHandleNullFieldsInInvoiceItem() throws Exception {
+        final BusinessInvoiceRecorder recorder = new BusinessInvoiceRecorder(accountApi, entitlementApi, invoiceApi, sqlDao, clock);
+
+        final InvoiceItem invoiceItem = Mockito.mock(InvoiceItem.class);
+        Mockito.when(invoiceItem.getAmount()).thenReturn(BigDecimal.TEN);
+        Mockito.when(invoiceItem.getCurrency()).thenReturn(Currency.AUD);
+        Mockito.when(invoiceItem.getEndDate()).thenReturn(new LocalDate(1200, 1, 12));
+        final UUID invoiceId = UUID.randomUUID();
+        Mockito.when(invoiceItem.getInvoiceId()).thenReturn(invoiceId);
+        final UUID id = UUID.randomUUID();
+        Mockito.when(invoiceItem.getId()).thenReturn(id);
+        Mockito.when(invoiceItem.getStartDate()).thenReturn(new LocalDate(1985, 9, 10));
+        Mockito.when(invoiceItem.getInvoiceItemType()).thenReturn(InvoiceItemType.CREDIT_ADJ);
+
+        final BusinessInvoiceItem bii = recorder.createBusinessInvoiceItem(invoiceItem);
+        Assert.assertNotNull(bii);
+        Assert.assertEquals(bii.getAmount(), invoiceItem.getAmount());
+        Assert.assertEquals(bii.getCurrency(), invoiceItem.getCurrency());
+        Assert.assertEquals(bii.getEndDate(), invoiceItem.getEndDate());
+        Assert.assertEquals(bii.getInvoiceId(), invoiceItem.getInvoiceId());
+        Assert.assertEquals(bii.getItemId(), invoiceItem.getId());
+        Assert.assertEquals(bii.getStartDate(), invoiceItem.getStartDate());
+        Assert.assertEquals(bii.getItemType(), invoiceItem.getInvoiceItemType().toString());
+        Assert.assertNull(bii.getBillingPeriod());
+        Assert.assertNull(bii.getPhase());
+        Assert.assertNull(bii.getProductCategory());
+        Assert.assertNull(bii.getProductName());
+        Assert.assertNull(bii.getProductType());
+        Assert.assertNull(bii.getSlug());
+        Assert.assertNull(bii.getExternalKey());
+    }
+}
diff --git a/api/src/main/java/com/ning/billing/payment/api/Refund.java b/api/src/main/java/com/ning/billing/payment/api/Refund.java
index ed2e15c..d6bb3ea 100644
--- a/api/src/main/java/com/ning/billing/payment/api/Refund.java
+++ b/api/src/main/java/com/ning/billing/payment/api/Refund.java
@@ -13,17 +13,27 @@
  * License for the specific language governing permissions and limitations
  * under the License.
  */
+
 package com.ning.billing.payment.api;
 
 import java.math.BigDecimal;
 import java.util.UUID;
 
+import org.joda.time.DateTime;
+
 import com.ning.billing.catalog.api.Currency;
 
 public interface Refund {
+
     public UUID getId();
+
     public UUID getPaymentId();
+
     public boolean isAdjusted();
+
     public BigDecimal getRefundAmount();
+
     public Currency getCurrency();
+
+    public DateTime getEffectiveDate();
 }
diff --git a/api/src/main/java/com/ning/billing/util/tag/ControlTagType.java b/api/src/main/java/com/ning/billing/util/tag/ControlTagType.java
index 709f34b..361f9bf 100644
--- a/api/src/main/java/com/ning/billing/util/tag/ControlTagType.java
+++ b/api/src/main/java/com/ning/billing/util/tag/ControlTagType.java
@@ -23,7 +23,8 @@ public enum ControlTagType {
     AUTO_PAY_OFF(new UUID(0, 1), "Suspends payments until removed.", true, false),
     AUTO_INVOICING_OFF(new UUID(0, 2), "Suspends invoicing until removed.", false, true),
     OVERDUE_ENFORCEMENT_OFF(new UUID(0, 3), "Suspends overdue enforcement behaviour until removed.", false, false),
-    WRITTEN_OFF(new UUID(0, 4), "Indicated that an invoice is written off. No billing or payment effect.", false, false);
+    WRITTEN_OFF(new UUID(0, 4), "Indicates that an invoice is written off. No billing or payment effect.", false, false),
+    MANUAL_PAY(new UUID(0, 5), "Indicates that Killbill doesn't process payments for that account (external payments only)", true, false);
 
     private final UUID id;
     private final String description;
diff --git a/api/src/main/java/com/ning/billing/util/template/translation/TranslatorConfig.java b/api/src/main/java/com/ning/billing/util/template/translation/TranslatorConfig.java
index 8d4e06e..5a4bf02 100644
--- a/api/src/main/java/com/ning/billing/util/template/translation/TranslatorConfig.java
+++ b/api/src/main/java/com/ning/billing/util/template/translation/TranslatorConfig.java
@@ -18,6 +18,7 @@ package com.ning.billing.util.template.translation;
 
 import org.skife.config.Config;
 import org.skife.config.Default;
+import org.skife.config.Description;
 
 import com.ning.billing.invoice.api.formatters.InvoiceFormatterFactory;
 
@@ -44,6 +45,11 @@ public interface TranslatorConfig {
     @Default("com/ning/billing/util/email/templates/HtmlInvoiceTemplate.mustache")
     String getTemplateName();
 
+    @Config("killbill.manualPayTemplate.name")
+    @Default("com/ning/billing/util/email/templates/HtmlInvoiceTemplate.mustache")
+    @Description("Invoice template for accounts with MANUAL_PAY tag")
+    String getManualPayTemplateName();
+
     @Config("killbill.template.invoiceFormatterFactoryClass")
     @Default("com.ning.billing.invoice.template.formatters.DefaultInvoiceFormatterFactory")
     Class<? extends InvoiceFormatterFactory> getInvoiceFormatterFactoryClass();
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 5ee9262..ea8e17a 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/api/user/DefaultInvoiceUserApi.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/api/user/DefaultInvoiceUserApi.java
@@ -19,6 +19,7 @@ package com.ning.billing.invoice.api.user;
 import java.io.IOException;
 import java.math.BigDecimal;
 import java.util.List;
+import java.util.Map;
 import java.util.UUID;
 
 import org.joda.time.DateTime;
@@ -39,7 +40,11 @@ import com.ning.billing.invoice.dao.InvoiceDao;
 import com.ning.billing.invoice.model.CreditAdjInvoiceItem;
 import com.ning.billing.invoice.template.HtmlInvoiceGenerator;
 import com.ning.billing.util.api.TagApiException;
+import com.ning.billing.util.api.TagUserApi;
 import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.dao.ObjectType;
+import com.ning.billing.util.tag.ControlTagType;
+import com.ning.billing.util.tag.Tag;
 
 import com.google.inject.Inject;
 
@@ -48,13 +53,16 @@ public class DefaultInvoiceUserApi implements InvoiceUserApi {
     private final InvoiceDao dao;
     private final InvoiceDispatcher dispatcher;
     private final AccountUserApi accountUserApi;
+    private final TagUserApi tagUserApi;
     private final HtmlInvoiceGenerator generator;
 
     @Inject
-    public DefaultInvoiceUserApi(final InvoiceDao dao, final InvoiceDispatcher dispatcher, final AccountUserApi accountUserApi, final HtmlInvoiceGenerator generator) {
+    public DefaultInvoiceUserApi(final InvoiceDao dao, final InvoiceDispatcher dispatcher, final AccountUserApi accountUserApi,
+                                 final TagUserApi tagUserApi, final HtmlInvoiceGenerator generator) {
         this.dao = dao;
         this.dispatcher = dispatcher;
         this.accountUserApi = accountUserApi;
+        this.tagUserApi = tagUserApi;
         this.generator = generator;
     }
 
@@ -155,7 +163,18 @@ public class DefaultInvoiceUserApi implements InvoiceUserApi {
         }
 
         final Account account = accountUserApi.getAccountById(invoice.getAccountId());
-        return generator.generateInvoice(account, invoice);
+
+        // Check if this account has the MANUAL_PAY system tag
+        boolean manualPay = false;
+        final Map<String, Tag> accountTags = tagUserApi.getTags(account.getId(), ObjectType.ACCOUNT);
+        for (final Tag tag : accountTags.values()) {
+            if (ControlTagType.MANUAL_PAY.getId().equals(tag.getTagDefinitionId())) {
+                manualPay = true;
+                break;
+            }
+        }
+
+        return generator.generateInvoice(account, invoice, manualPay);
     }
 
 }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/notification/EmailInvoiceNotifier.java b/invoice/src/main/java/com/ning/billing/invoice/notification/EmailInvoiceNotifier.java
index 27b9541..10d7202 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/notification/EmailInvoiceNotifier.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/notification/EmailInvoiceNotifier.java
@@ -19,6 +19,7 @@ package com.ning.billing.invoice.notification;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 
 import com.google.inject.Inject;
 import com.ning.billing.ErrorCode;
@@ -29,19 +30,26 @@ import com.ning.billing.invoice.api.Invoice;
 import com.ning.billing.invoice.api.InvoiceApiException;
 import com.ning.billing.invoice.api.InvoiceNotifier;
 import com.ning.billing.invoice.template.HtmlInvoiceGenerator;
+import com.ning.billing.util.api.TagUserApi;
+import com.ning.billing.util.dao.ObjectType;
 import com.ning.billing.util.email.DefaultEmailSender;
 import com.ning.billing.util.email.EmailApiException;
 import com.ning.billing.util.email.EmailConfig;
 import com.ning.billing.util.email.EmailSender;
+import com.ning.billing.util.tag.ControlTagType;
+import com.ning.billing.util.tag.Tag;
 
 public class EmailInvoiceNotifier implements InvoiceNotifier {
     private final AccountUserApi accountUserApi;
+    private final TagUserApi tagUserApi;
     private final HtmlInvoiceGenerator generator;
     private final EmailConfig config;
 
     @Inject
-    public EmailInvoiceNotifier(final AccountUserApi accountUserApi, final HtmlInvoiceGenerator generator, final EmailConfig config) {
+    public EmailInvoiceNotifier(final AccountUserApi accountUserApi, final TagUserApi tagUserApi,
+                                final HtmlInvoiceGenerator generator, final EmailConfig config) {
         this.accountUserApi = accountUserApi;
+        this.tagUserApi = tagUserApi;
         this.generator = generator;
         this.config = config;
     }
@@ -57,9 +65,19 @@ public class EmailInvoiceNotifier implements InvoiceNotifier {
             cc.add(email.getEmail());
         }
 
+        // Check if this account has the MANUAL_PAY system tag
+        boolean manualPay = false;
+        final Map<String, Tag> accountTags = tagUserApi.getTags(account.getId(), ObjectType.ACCOUNT);
+        for (final Tag tag : accountTags.values()) {
+            if (ControlTagType.MANUAL_PAY.getId().equals(tag.getTagDefinitionId())) {
+                manualPay = true;
+                break;
+            }
+        }
+
         final String htmlBody;
         try {
-            htmlBody = generator.generateInvoice(account, invoice);
+            htmlBody = generator.generateInvoice(account, invoice, manualPay);
         } catch (IOException e) {
             throw new InvoiceApiException(e, ErrorCode.EMAIL_SENDING_FAILED);
         }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/template/HtmlInvoiceGenerator.java b/invoice/src/main/java/com/ning/billing/invoice/template/HtmlInvoiceGenerator.java
index 04c5075..7f55be3 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/template/HtmlInvoiceGenerator.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/template/HtmlInvoiceGenerator.java
@@ -43,7 +43,7 @@ public class HtmlInvoiceGenerator {
         this.config = config;
     }
 
-    public String generateInvoice(final Account account, @Nullable final Invoice invoice) throws IOException {
+    public String generateInvoice(final Account account, @Nullable final Invoice invoice, final boolean manualPay) throws IOException {
         // Don't do anything if the invoice is null
         if (invoice == null) {
             return null;
@@ -59,6 +59,10 @@ public class HtmlInvoiceGenerator {
         final InvoiceFormatter formattedInvoice = factory.createInvoiceFormatter(config, invoice, locale);
         data.put("invoice", formattedInvoice);
 
-        return templateEngine.executeTemplate(config.getTemplateName(), data);
+        if (manualPay) {
+            return templateEngine.executeTemplate(config.getManualPayTemplateName(), data);
+        } else {
+            return templateEngine.executeTemplate(config.getTemplateName(), data);
+        }
     }
 }
diff --git a/invoice/src/test/java/com/ning/billing/invoice/notification/TestNextBillingDateNotifier.java b/invoice/src/test/java/com/ning/billing/invoice/notification/TestNextBillingDateNotifier.java
index e126ba2..5fb9e88 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/notification/TestNextBillingDateNotifier.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/notification/TestNextBillingDateNotifier.java
@@ -61,6 +61,7 @@ import com.ning.billing.util.email.templates.TemplateModule;
 import com.ning.billing.util.glue.BusModule;
 import com.ning.billing.util.glue.BusModule.BusType;
 import com.ning.billing.util.glue.NotificationQueueModule;
+import com.ning.billing.util.glue.TagStoreModule;
 import com.ning.billing.util.notificationq.DummySqlTest;
 import com.ning.billing.util.notificationq.NotificationQueueService;
 
@@ -113,6 +114,7 @@ public class TestNextBillingDateNotifier extends InvoiceTestSuiteWithEmbeddedDB 
                 install(new MockCatalogModule());
                 install(new NotificationQueueModule());
                 install(new TemplateModule());
+                install(new TagStoreModule());
 
                 final MysqlTestingHelper helper = KillbillTestSuiteWithEmbeddedDB.getMysqlTestingHelper();
                 bind(MysqlTestingHelper.class).toInstance(helper);
diff --git a/invoice/src/test/java/com/ning/billing/invoice/TestHtmlInvoiceGenerator.java b/invoice/src/test/java/com/ning/billing/invoice/TestHtmlInvoiceGenerator.java
index c3fc3d4..5d7cb5b 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/TestHtmlInvoiceGenerator.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/TestHtmlInvoiceGenerator.java
@@ -54,20 +54,20 @@ public class TestHtmlInvoiceGenerator extends InvoiceTestSuite {
 
     @Test(groups = "fast")
     public void testGenerateInvoice() throws Exception {
-        final String output = g.generateInvoice(createAccount(), createInvoice());
+        final String output = g.generateInvoice(createAccount(), createInvoice(), false);
         Assert.assertNotNull(output);
     }
 
     @Test(groups = "fast")
     public void testGenerateEmptyInvoice() throws Exception {
         final Invoice invoice = Mockito.mock(Invoice.class);
-        final String output = g.generateInvoice(createAccount(), invoice);
+        final String output = g.generateInvoice(createAccount(), invoice, false);
         Assert.assertNotNull(output);
     }
 
     @Test(groups = "fast")
     public void testGenerateNullInvoice() throws Exception {
-        final String output = g.generateInvoice(createAccount(), null);
+        final String output = g.generateInvoice(createAccount(), null, false);
         Assert.assertNull(output);
     }
 
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/RefundJson.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/RefundJson.java
index f65f6d7..ff1547a 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/RefundJson.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/RefundJson.java
@@ -18,10 +18,12 @@ package com.ning.billing.jaxrs.json;
 
 import java.math.BigDecimal;
 
-import com.fasterxml.jackson.annotation.JsonCreator;
-import com.fasterxml.jackson.annotation.JsonProperty;
+import org.joda.time.DateTime;
+
 import com.ning.billing.payment.api.Refund;
 
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
 
 public class RefundJson {
 
@@ -29,24 +31,27 @@ public class RefundJson {
     private final String paymentId;
     private final BigDecimal refundAmount;
     private final Boolean isAdjusted;
+    private final DateTime requestedDate;
+    private final DateTime effectiveDate;
 
-    public RefundJson(Refund input) {
-        this(input.getId().toString(), input.getPaymentId().toString(), input.getRefundAmount(), input.isAdjusted());
+    public RefundJson(final Refund input) {
+        this(input.getId().toString(), input.getPaymentId().toString(), input.getRefundAmount(), input.isAdjusted(),
+             input.getEffectiveDate(), input.getEffectiveDate());
     }
 
     @JsonCreator
     public RefundJson(@JsonProperty("refund_id") final String refundId,
-            @JsonProperty("paymentId") String paymentId,
-            @JsonProperty("refundAmount") BigDecimal refundAmount,
-            @JsonProperty("adjusted") final Boolean isAdjusted) {
+                      @JsonProperty("paymentId") final String paymentId,
+                      @JsonProperty("refundAmount") final BigDecimal refundAmount,
+                      @JsonProperty("adjusted") final Boolean isAdjusted,
+                      @JsonProperty("requestedDate") final DateTime requestedDate,
+                      @JsonProperty("effectiveDate") final DateTime effectiveDate) {
         this.refundId = refundId;
         this.paymentId = paymentId;
         this.refundAmount = refundAmount;
         this.isAdjusted = isAdjusted;
-    }
-
-    public RefundJson() {
-        this(null, null, null, null);
+        this.requestedDate = requestedDate;
+        this.effectiveDate = effectiveDate;
     }
 
     public String getRefundId() {
@@ -65,61 +70,111 @@ public class RefundJson {
         return isAdjusted;
     }
 
+    public DateTime getRequestedDate() {
+        return requestedDate;
+    }
+
+    public DateTime getEffectiveDate() {
+        return effectiveDate;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder();
+        sb.append("RefundJson");
+        sb.append("{refundId='").append(refundId).append('\'');
+        sb.append(", paymentId='").append(paymentId).append('\'');
+        sb.append(", refundAmount=").append(refundAmount);
+        sb.append(", isAdjusted=").append(isAdjusted);
+        sb.append(", requestedDate=").append(requestedDate);
+        sb.append(", effectiveDate=").append(effectiveDate);
+        sb.append('}');
+        return sb.toString();
+    }
+
     @Override
     public int hashCode() {
-        final int prime = 31;
-        int result = 1;
-        result = prime * result
-                + ((isAdjusted == null) ? 0 : isAdjusted.hashCode());
-        result = prime * result
-                + ((paymentId == null) ? 0 : paymentId.hashCode());
-        result = prime * result
-                + ((refundAmount == null) ? 0 : refundAmount.hashCode());
-        result = prime * result
-                + ((refundId == null) ? 0 : refundId.hashCode());
+        int result = refundId != null ? refundId.hashCode() : 0;
+        result = 31 * result + (paymentId != null ? paymentId.hashCode() : 0);
+        result = 31 * result + (refundAmount != null ? refundAmount.hashCode() : 0);
+        result = 31 * result + (isAdjusted != null ? isAdjusted.hashCode() : 0);
+        result = 31 * result + (requestedDate != null ? requestedDate.hashCode() : 0);
+        result = 31 * result + (effectiveDate != null ? effectiveDate.hashCode() : 0);
         return result;
     }
 
     @Override
     public boolean equals(final Object obj) {
-        if (! this.equalsNoId(obj)) {
+        if (!this.equalsNoIdNoDates(obj)) {
             return false;
         } else {
-            RefundJson other = (RefundJson) obj;
-            if (getRefundId() == null) {
-                return other.getRefundId() == null;
-            } else {
-                return getRefundId().equals(other.getRefundId());
+            final RefundJson other = (RefundJson) obj;
+            if (refundId == null) {
+                if (other.getRefundId() != null) {
+                    return false;
+                }
+            } else if (!refundId.equals(other.getRefundId())) {
+                return false;
             }
+
+            if (requestedDate == null) {
+                if (other.getRequestedDate() != null) {
+                    return false;
+                }
+            } else if (requestedDate.compareTo(other.getRequestedDate()) != 0) {
+                return false;
+            }
+
+            if (effectiveDate == null) {
+                if (other.getEffectiveDate() != null) {
+                    return false;
+                }
+            } else if (effectiveDate.compareTo(other.getEffectiveDate()) != 0) {
+                return false;
+            }
+
+            return true;
         }
     }
 
-    public boolean equalsNoId(final Object obj) {
-        if (this == obj)
+    public boolean equalsNoIdNoDates(final Object obj) {
+        if (this == obj) {
             return true;
-        if (obj == null)
+        }
+
+        if (obj == null) {
             return false;
-        if (getClass() != obj.getClass())
+        }
+
+        if (getClass() != obj.getClass()) {
             return false;
-        RefundJson other = (RefundJson) obj;
+        }
+
+        final RefundJson other = (RefundJson) obj;
         if (isAdjusted == null) {
-            if (other.isAdjusted != null)
+            if (other.isAdjusted != null) {
                 return false;
-        } else if (!isAdjusted.equals(other.isAdjusted))
+            }
+        } else if (!isAdjusted.equals(other.isAdjusted)) {
             return false;
+        }
+
         if (paymentId == null) {
-            if (other.paymentId != null)
+            if (other.paymentId != null) {
                 return false;
-        } else if (!paymentId.equals(other.paymentId))
+            }
+        } else if (!paymentId.equals(other.paymentId)) {
             return false;
+        }
+
         if (refundAmount == null) {
-            if (other.refundAmount != null)
+            if (other.refundAmount != null) {
                 return false;
+            }
         } else if (!refundAmount.equals(other.refundAmount)) {
             return false;
         }
+
         return true;
     }
-
-
 }
diff --git a/payment/src/main/java/com/ning/billing/payment/api/DefaultRefund.java b/payment/src/main/java/com/ning/billing/payment/api/DefaultRefund.java
index 729e701..81c8257 100644
--- a/payment/src/main/java/com/ning/billing/payment/api/DefaultRefund.java
+++ b/payment/src/main/java/com/ning/billing/payment/api/DefaultRefund.java
@@ -13,11 +13,14 @@
  * License for the specific language governing permissions and limitations
  * under the License.
  */
+
 package com.ning.billing.payment.api;
 
 import java.math.BigDecimal;
 import java.util.UUID;
 
+import org.joda.time.DateTime;
+
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.util.entity.EntityBase;
 
@@ -27,14 +30,16 @@ public class DefaultRefund extends EntityBase implements Refund {
     private final BigDecimal amount;
     private final Currency currency;
     private final boolean isAdjusted;
+    private final DateTime effectiveDate;
 
     public DefaultRefund(final UUID id, final UUID paymentId, final BigDecimal amount,
-            final Currency currency, final boolean isAdjusted) {
+                         final Currency currency, final boolean isAdjusted, final DateTime effectiveDate) {
         super(id);
         this.paymentId = paymentId;
         this.amount = amount;
         this.currency = currency;
         this.isAdjusted = isAdjusted;
+        this.effectiveDate = effectiveDate;
     }
 
     @Override
@@ -56,4 +61,62 @@ public class DefaultRefund extends EntityBase implements Refund {
     public boolean isAdjusted() {
         return isAdjusted;
     }
+
+    @Override
+    public DateTime getEffectiveDate() {
+        return effectiveDate;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder();
+        sb.append("DefaultRefund");
+        sb.append("{paymentId=").append(paymentId);
+        sb.append(", amount=").append(amount);
+        sb.append(", currency=").append(currency);
+        sb.append(", isAdjusted=").append(isAdjusted);
+        sb.append(", effectiveDate=").append(effectiveDate);
+        sb.append('}');
+        return sb.toString();
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        final DefaultRefund that = (DefaultRefund) o;
+
+        if (isAdjusted != that.isAdjusted) {
+            return false;
+        }
+        if (amount != null ? !amount.equals(that.amount) : that.amount != null) {
+            return false;
+        }
+        if (effectiveDate != null ? !effectiveDate.equals(that.effectiveDate) : that.effectiveDate != null) {
+            return false;
+        }
+        if (currency != that.currency) {
+            return false;
+        }
+        if (paymentId != null ? !paymentId.equals(that.paymentId) : that.paymentId != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = paymentId != null ? paymentId.hashCode() : 0;
+        result = 31 * result + (amount != null ? amount.hashCode() : 0);
+        result = 31 * result + (currency != null ? currency.hashCode() : 0);
+        result = 31 * result + (isAdjusted ? 1 : 0);
+        result = 31 * result + (effectiveDate != null ? effectiveDate.hashCode() : 0);
+        return result;
+    }
 }
diff --git a/payment/src/main/java/com/ning/billing/payment/core/RefundProcessor.java b/payment/src/main/java/com/ning/billing/payment/core/RefundProcessor.java
index d5ca7e9..69fbb4b 100644
--- a/payment/src/main/java/com/ning/billing/payment/core/RefundProcessor.java
+++ b/payment/src/main/java/com/ning/billing/payment/core/RefundProcessor.java
@@ -147,7 +147,8 @@ public class RefundProcessor extends ProcessorBase {
 
                     paymentDao.updateRefundStatus(refundInfo.getId(), RefundStatus.COMPLETED, context);
 
-                    return new DefaultRefund(refundInfo.getId(), paymentId, refundInfo.getAmount(), account.getCurrency(), isAdjusted);
+                    return new DefaultRefund(refundInfo.getId(), paymentId, refundInfo.getAmount(), account.getCurrency(),
+                                             isAdjusted, refundInfo.getCreatedDate());
                 } catch (PaymentPluginApiException e) {
                     throw new PaymentApiException(ErrorCode.PAYMENT_CREATE_REFUND, account.getId(), e.getMessage());
                 } catch (InvoiceApiException e) {
@@ -172,7 +173,8 @@ public class RefundProcessor extends ProcessorBase {
         if (completePluginCompletedRefund(filteredInput)) {
             result = paymentDao.getRefund(refundId);
         }
-        return new DefaultRefund(result.getId(), result.getPaymentId(), result.getAmount(), result.getCurrency(), result.isAdjsuted());
+        return new DefaultRefund(result.getId(), result.getPaymentId(), result.getAmount(), result.getCurrency(),
+                                 result.isAdjsuted(), result.getCreatedDate());
     }
 
 
@@ -200,7 +202,8 @@ public class RefundProcessor extends ProcessorBase {
         return new ArrayList<Refund>(Collections2.transform(in, new Function<RefundModelDao, Refund>() {
             @Override
             public Refund apply(RefundModelDao cur) {
-                return new DefaultRefund(cur.getId(), cur.getPaymentId(), cur.getAmount(), cur.getCurrency(), cur.isAdjsuted());
+                return new DefaultRefund(cur.getId(), cur.getPaymentId(), cur.getAmount(), cur.getCurrency(),
+                                         cur.isAdjsuted(), cur.getCreatedDate());
             }
         }));
     }
diff --git a/payment/src/main/java/com/ning/billing/payment/dao/RefundModelDao.java b/payment/src/main/java/com/ning/billing/payment/dao/RefundModelDao.java
index 286fe51..5c5c4a1 100644
--- a/payment/src/main/java/com/ning/billing/payment/dao/RefundModelDao.java
+++ b/payment/src/main/java/com/ning/billing/payment/dao/RefundModelDao.java
@@ -13,15 +13,19 @@
  * License for the specific language governing permissions and limitations
  * under the License.
  */
+
 package com.ning.billing.payment.dao;
 
 import java.math.BigDecimal;
 import java.util.UUID;
 
+import javax.annotation.Nullable;
+
+import org.joda.time.DateTime;
+
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.util.entity.EntityBase;
 
-
 public class RefundModelDao extends EntityBase {
 
     private final UUID accountId;
@@ -31,14 +35,15 @@ public class RefundModelDao extends EntityBase {
     private final boolean isAdjusted;
     private final RefundStatus refundStatus;
 
-    public RefundModelDao(final UUID accountId, final UUID paymentId,
-            final BigDecimal amount, final Currency currency, final boolean isAdjusted) {
-        this(UUID.randomUUID(), accountId, paymentId, amount, currency, isAdjusted, RefundStatus.CREATED);
+    public RefundModelDao(final UUID accountId, final UUID paymentId, final BigDecimal amount,
+                          final Currency currency, final boolean isAdjusted) {
+        this(UUID.randomUUID(), accountId, paymentId, amount, currency, isAdjusted, RefundStatus.CREATED, null, null);
     }
 
-    public RefundModelDao(final UUID id, final UUID accountId, final UUID paymentId,
-            final BigDecimal amount, final Currency currency, final boolean isAdjusted, final RefundStatus refundStatus) {
-        super(id);
+    public RefundModelDao(final UUID id, final UUID accountId, final UUID paymentId, final BigDecimal amount,
+                          final Currency currency, final boolean isAdjusted, final RefundStatus refundStatus,
+                          @Nullable final DateTime createdDate, @Nullable final DateTime updatedDate) {
+        super(id, createdDate, updatedDate);
         this.accountId = accountId;
         this.paymentId = paymentId;
         this.amount = amount;
@@ -76,4 +81,72 @@ public class RefundModelDao extends EntityBase {
         PLUGIN_COMPLETED,
         COMPLETED,
     }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder();
+        sb.append("RefundModelDao");
+        sb.append("{accountId=").append(accountId);
+        sb.append(", paymentId=").append(paymentId);
+        sb.append(", amount=").append(amount);
+        sb.append(", currency=").append(currency);
+        sb.append(", isAdjusted=").append(isAdjusted);
+        sb.append(", refundStatus=").append(refundStatus);
+        sb.append(", createdDate=").append(createdDate);
+        sb.append(", updatedDate=").append(updatedDate);
+        sb.append('}');
+        return sb.toString();
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        final RefundModelDao that = (RefundModelDao) o;
+
+        if (isAdjusted != that.isAdjusted) {
+            return false;
+        }
+        if (accountId != null ? !accountId.equals(that.accountId) : that.accountId != null) {
+            return false;
+        }
+        if (amount != null ? !amount.equals(that.amount) : that.amount != null) {
+            return false;
+        }
+        if (createdDate != null ? !createdDate.equals(that.createdDate) : that.createdDate != null) {
+            return false;
+        }
+        if (currency != that.currency) {
+            return false;
+        }
+        if (paymentId != null ? !paymentId.equals(that.paymentId) : that.paymentId != null) {
+            return false;
+        }
+        if (refundStatus != that.refundStatus) {
+            return false;
+        }
+        if (updatedDate != null ? !updatedDate.equals(that.updatedDate) : that.updatedDate != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = accountId != null ? accountId.hashCode() : 0;
+        result = 31 * result + (paymentId != null ? paymentId.hashCode() : 0);
+        result = 31 * result + (amount != null ? amount.hashCode() : 0);
+        result = 31 * result + (currency != null ? currency.hashCode() : 0);
+        result = 31 * result + (isAdjusted ? 1 : 0);
+        result = 31 * result + (refundStatus != null ? refundStatus.hashCode() : 0);
+        result = 31 * result + (createdDate != null ? createdDate.hashCode() : 0);
+        result = 31 * result + (updatedDate != null ? updatedDate.hashCode() : 0);
+        return result;
+    }
 }
diff --git a/payment/src/main/java/com/ning/billing/payment/dao/RefundSqlDao.java b/payment/src/main/java/com/ning/billing/payment/dao/RefundSqlDao.java
index e6bea41..7d1ff1a 100644
--- a/payment/src/main/java/com/ning/billing/payment/dao/RefundSqlDao.java
+++ b/payment/src/main/java/com/ning/billing/payment/dao/RefundSqlDao.java
@@ -13,6 +13,7 @@
  * License for the specific language governing permissions and limitations
  * under the License.
  */
+
 package com.ning.billing.payment.dao;
 
 import java.math.BigDecimal;
@@ -21,6 +22,7 @@ import java.sql.SQLException;
 import java.util.List;
 import java.util.UUID;
 
+import org.joda.time.DateTime;
 import org.skife.jdbi.v2.SQLStatement;
 import org.skife.jdbi.v2.StatementContext;
 import org.skife.jdbi.v2.sqlobject.Bind;
@@ -43,15 +45,13 @@ import com.ning.billing.util.dao.EntityHistory;
 import com.ning.billing.util.dao.MapperBase;
 import com.ning.billing.util.entity.dao.UpdatableEntitySqlDao;
 
-
 @ExternalizedSqlViaStringTemplate3()
 @RegisterMapper(RefundSqlDao.RefundModelDaoMapper.class)
 public interface RefundSqlDao extends Transactional<RefundSqlDao>, UpdatableEntitySqlDao<RefundModelDao>, Transmogrifier, CloseMe {
 
-
     @SqlUpdate
     void insertRefund(@Bind(binder = RefundModelDaoBinder.class) final RefundModelDao refundInfo,
-                       @CallContextBinder final CallContext context);
+                      @CallContextBinder final CallContext context);
 
     @SqlUpdate
     void updateStatus(@Bind("id") final String refundId, @Bind("refundStatus") final String status);
@@ -70,10 +70,10 @@ public interface RefundSqlDao extends Transactional<RefundSqlDao>, UpdatableEnti
     public void insertHistoryFromTransaction(@RefundHistoryBinder final EntityHistory<RefundModelDao> payment,
                                              @CallContextBinder final CallContext context);
 
-
     public static final class RefundModelDaoBinder extends BinderBase implements Binder<Bind, RefundModelDao> {
+
         @Override
-        public void bind(@SuppressWarnings("rawtypes") final SQLStatement stmt, final Bind bind, final RefundModelDao refund) {
+        public void bind(final SQLStatement stmt, final Bind bind, final RefundModelDao refund) {
             stmt.bind("id", refund.getId().toString());
             stmt.bind("accountId", refund.getAccountId().toString());
             stmt.bind("paymentId", refund.getPaymentId().toString());
@@ -81,6 +81,7 @@ public interface RefundSqlDao extends Transactional<RefundSqlDao>, UpdatableEnti
             stmt.bind("currency", refund.getCurrency().toString());
             stmt.bind("isAdjusted", refund.isAdjsuted());
             stmt.bind("refundStatus", refund.getRefundStatus().toString());
+            // createdDate and updatedDate are populated by the @CallContextBinder
         }
     }
 
@@ -96,7 +97,9 @@ public interface RefundSqlDao extends Transactional<RefundSqlDao>, UpdatableEnti
             final boolean isAdjusted = rs.getBoolean("is_adjusted");
             final Currency currency = Currency.valueOf(rs.getString("currency"));
             final RefundStatus refundStatus = RefundStatus.valueOf(rs.getString("refund_status"));
-            return new RefundModelDao(id, accountId, paymentId, amount, currency, isAdjusted, refundStatus);
+            final DateTime createdDate = getDateTime(rs, "created_date");
+            final DateTime updatedDate = getDateTime(rs, "updated_date");
+            return new RefundModelDao(id, accountId, paymentId, amount, currency, isAdjusted, refundStatus, createdDate, updatedDate);
         }
     }
 }
diff --git a/server/src/test/java/com/ning/billing/jaxrs/TestPayment.java b/server/src/test/java/com/ning/billing/jaxrs/TestPayment.java
index dc4f2f4..0afc6b8 100644
--- a/server/src/test/java/com/ning/billing/jaxrs/TestPayment.java
+++ b/server/src/test/java/com/ning/billing/jaxrs/TestPayment.java
@@ -92,7 +92,7 @@ public class TestPayment extends TestJaxrsBase {
 
         // Issue the refund
 
-        final RefundJson refundJson = new RefundJson(null, paymentId, paymentAmount, false);
+        final RefundJson refundJson = new RefundJson(null, paymentId, paymentAmount, false, null, null);
         baseJson = mapper.writeValueAsString(refundJson);
         response = doPost(uri, baseJson, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
         assertEquals(response.getStatusCode(), Status.CREATED.getStatusCode());
@@ -105,7 +105,13 @@ public class TestPayment extends TestJaxrsBase {
         Assert.assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
         baseJson = response.getResponseBody();
         final RefundJson refundJsonCheck = mapper.readValue(baseJson, RefundJson.class);
-        Assert.assertTrue(refundJsonCheck.equalsNoId(refundJson));
+        Assert.assertTrue(refundJsonCheck.equalsNoIdNoDates(refundJson));
+        Assert.assertEquals(refundJsonCheck.getEffectiveDate().getYear(), clock.getUTCNow().getYear());
+        Assert.assertEquals(refundJsonCheck.getEffectiveDate().getMonthOfYear(), clock.getUTCNow().getMonthOfYear());
+        Assert.assertEquals(refundJsonCheck.getEffectiveDate().getDayOfMonth(), clock.getUTCNow().getDayOfMonth());
+        Assert.assertEquals(refundJsonCheck.getRequestedDate().getYear(), clock.getUTCNow().getYear());
+        Assert.assertEquals(refundJsonCheck.getRequestedDate().getMonthOfYear(), clock.getUTCNow().getMonthOfYear());
+        Assert.assertEquals(refundJsonCheck.getRequestedDate().getDayOfMonth(), clock.getUTCNow().getDayOfMonth());
 
         uri = JaxrsResource.PAYMENTS_PATH + "/" + paymentId + "/" + JaxrsResource.REFUNDS;
         response = doGet(uri, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
diff --git a/util/src/main/java/com/ning/billing/util/entity/EntityBase.java b/util/src/main/java/com/ning/billing/util/entity/EntityBase.java
index 3cf0be4..1ac2fd0 100644
--- a/util/src/main/java/com/ning/billing/util/entity/EntityBase.java
+++ b/util/src/main/java/com/ning/billing/util/entity/EntityBase.java
@@ -18,21 +18,42 @@ package com.ning.billing.util.entity;
 
 import java.util.UUID;
 
+import org.joda.time.DateTime;
+
 public abstract class EntityBase implements Entity {
+
     protected final UUID id;
+    protected final DateTime createdDate;
+    protected final DateTime updatedDate;
 
     // used to hydrate objects
     public EntityBase(final UUID id) {
-        this.id = id;
+        this(id, null, null);
     }
 
     // used to create new objects
     public EntityBase() {
-        this.id = UUID.randomUUID();
+        this(UUID.randomUUID(), null, null);
+    }
+
+    public EntityBase(final UUID id, final DateTime createdDate, final DateTime updatedDate) {
+        this.id = id;
+        this.createdDate = createdDate;
+        this.updatedDate = updatedDate;
     }
 
     @Override
     public UUID getId() {
         return id;
     }
+
+    // TODO surface it in Entity
+    public DateTime getCreatedDate() {
+        return createdDate;
+    }
+
+    // TODO surface it in Entity
+    public DateTime getUpdatedDate() {
+        return updatedDate;
+    }
 }
diff --git a/util/src/test/java/com/ning/billing/util/tag/dao/TestAuditedTagDao.java b/util/src/test/java/com/ning/billing/util/tag/dao/TestAuditedTagDao.java
index e1f4105..4f1f8ed 100644
--- a/util/src/test/java/com/ning/billing/util/tag/dao/TestAuditedTagDao.java
+++ b/util/src/test/java/com/ning/billing/util/tag/dao/TestAuditedTagDao.java
@@ -16,8 +16,6 @@
 
 package com.ning.billing.util.tag.dao;
 
-import static org.testng.Assert.assertEquals;
-
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
@@ -31,8 +29,6 @@ import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Guice;
 import org.testng.annotations.Test;
 
-import com.google.common.eventbus.Subscribe;
-import com.google.inject.Inject;
 import com.ning.billing.dbi.MysqlTestingHelper;
 import com.ning.billing.util.UtilTestSuiteWithEmbeddedDB;
 import com.ning.billing.util.api.TagDefinitionApiException;
@@ -44,14 +40,17 @@ import com.ning.billing.util.callcontext.DefaultCallContextFactory;
 import com.ning.billing.util.callcontext.UserType;
 import com.ning.billing.util.clock.Clock;
 import com.ning.billing.util.dao.ObjectType;
-import com.ning.billing.util.io.IOUtils;
 import com.ning.billing.util.tag.ControlTagType;
 import com.ning.billing.util.tag.MockTagStoreModuleSql;
 import com.ning.billing.util.tag.Tag;
 import com.ning.billing.util.tag.TagDefinition;
-import com.ning.billing.util.tag.TestTagStore;
 import com.ning.billing.util.tag.api.TagEvent;
 
+import com.google.common.eventbus.Subscribe;
+import com.google.inject.Inject;
+
+import static org.testng.Assert.assertEquals;
+
 @Guice(modules = MockTagStoreModuleSql.class)
 public class TestAuditedTagDao extends UtilTestSuiteWithEmbeddedDB {
     @Inject
@@ -89,21 +88,19 @@ public class TestAuditedTagDao extends UtilTestSuiteWithEmbeddedDB {
         bus.stop();
     }
 
-
-    @Test(groups= {"slow"})
+    @Test(groups= "slow")
     public void testGetByIds() throws TagDefinitionApiException {
-
         final List<UUID> uuids = new ArrayList<UUID>();
 
-        // Check with a empty Collecion first
+        // Check with a empty Collection first
         List<TagDefinition> result = tagDefinitionDao.getByIds(uuids);
         assertEquals(result.size(), 0);
 
-        TagDefinition defYo = tagDefinitionDao.create("yo", "defintion yo", context);
+        final TagDefinition defYo = tagDefinitionDao.create("yo", "defintion yo", context);
         uuids.add(defYo.getId());
-        TagDefinition defBah = tagDefinitionDao.create("bah", "defintion bah", context);
+        final TagDefinition defBah = tagDefinitionDao.create("bah", "defintion bah", context);
         uuids.add(defBah.getId());
-        TagDefinition defZoo = tagDefinitionDao.create("zoo", "defintion zoo", context);
+        final TagDefinition defZoo = tagDefinitionDao.create("zoo", "defintion zoo", context);
         uuids.add(defZoo.getId());
 
         result = tagDefinitionDao.getByIds(uuids);
@@ -115,10 +112,10 @@ public class TestAuditedTagDao extends UtilTestSuiteWithEmbeddedDB {
         assertEquals(result.size(), 4);
 
         result = tagDefinitionDao.getTagDefinitions();
-        assertEquals(result.size(), 7);
+        assertEquals(result.size(), 3 + ControlTagType.values().length);
     }
 
-    @Test(groups= {"slow"})
+    @Test(groups= "slow")
     public void testGetById() throws TagDefinitionApiException {
 
         // User Tag
@@ -138,7 +135,7 @@ public class TestAuditedTagDao extends UtilTestSuiteWithEmbeddedDB {
         assertEquals(resdef_AUTO_INVOICING_OFF.getDescription(), ControlTagType.AUTO_INVOICING_OFF.getDescription());
     }
 
-    @Test(groups= {"slow"})
+    @Test(groups= "slow")
     public void testGetByName() throws TagDefinitionApiException {
 
         // User Tag