killbill-aplcache

Merge branch 'integration' into server NOT SO TRIVIAL MERGE....

4/14/2012 12:17:39 AM

Changes

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

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

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

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

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

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

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

pom.xml 5(+2 -3)

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

util/pom.xml 5(+2 -3)

Details

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

diff --git a/account/pom.xml b/account/pom.xml
index c48b8b2..de028b7 100644
--- a/account/pom.xml
+++ b/account/pom.xml
@@ -13,7 +13,7 @@
     <parent>
         <groupId>com.ning.billing</groupId>
         <artifactId>killbill</artifactId>
-        <version>0.1.8-SNAPSHOT</version>
+        <version>0.1.11-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-account</artifactId>
diff --git a/analytics/pom.xml b/analytics/pom.xml
index d5fff4c..a92d088 100644
--- a/analytics/pom.xml
+++ b/analytics/pom.xml
@@ -13,7 +13,7 @@
     <parent>
         <groupId>com.ning.billing</groupId>
         <artifactId>killbill</artifactId>
-        <version>0.1.8-SNAPSHOT</version>
+        <version>0.1.11-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-analytics</artifactId>

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

diff --git a/api/pom.xml b/api/pom.xml
index ac7f6c9..a9cec84 100644
--- a/api/pom.xml
+++ b/api/pom.xml
@@ -13,7 +13,7 @@
     <parent>
         <groupId>com.ning.billing</groupId>
         <artifactId>killbill</artifactId>
-        <version>0.1.8-SNAPSHOT</version>
+        <version>0.1.11-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-api</artifactId>

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

diff --git a/beatrix/pom.xml b/beatrix/pom.xml
index 28f478f..64726c8 100644
--- a/beatrix/pom.xml
+++ b/beatrix/pom.xml
@@ -13,7 +13,7 @@
     <parent>
         <groupId>com.ning.billing</groupId>
         <artifactId>killbill</artifactId>
-        <version>0.1.8-SNAPSHOT</version>
+        <version>0.1.11-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-beatrix</artifactId>
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegration.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegration.java
index 0b509e3..e334966 100644
--- a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegration.java
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegration.java
@@ -19,6 +19,7 @@ package com.ning.billing.beatrix.integration;
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertNotNull;
 import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
 
 import java.io.IOException;
 import java.math.BigDecimal;
@@ -33,6 +34,7 @@ import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
 
 import com.ning.billing.invoice.api.Invoice;
 import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.model.InvoicingConfiguration;
 import com.ning.billing.util.callcontext.CallContext;
 import com.ning.billing.util.callcontext.CallOrigin;
 import com.ning.billing.util.callcontext.UserType;
@@ -82,8 +84,8 @@ import com.ning.billing.util.bus.BusService;
 @Test(groups = "slow")
 @Guice(modules = {MockModule.class})
 public class TestIntegration {
-    private static final int NUMBER_OF_DECIMALS = 4;
-    private static final int ROUNDING_METHOD = BigDecimal.ROUND_HALF_EVEN;
+    private static final int NUMBER_OF_DECIMALS = InvoicingConfiguration.getNumberOfDecimals();
+    private static final int ROUNDING_METHOD = InvoicingConfiguration.getRoundingMode();
 
     private static final BigDecimal ONE = new BigDecimal("1.0000").setScale(NUMBER_OF_DECIMALS);
     private static final BigDecimal TWENTY_NINE = new BigDecimal("29.0000").setScale(NUMBER_OF_DECIMALS);
@@ -244,7 +246,9 @@ public class TestIntegration {
             }
         }
 
-        assertTrue(wasFound);
+        if (!wasFound) {
+            fail();
+        }
 
         DateTime ctd = subscription.getChargedThroughDate();
         assertNotNull(ctd);

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

diff --git a/catalog/pom.xml b/catalog/pom.xml
index dee138d..489aba0 100644
--- a/catalog/pom.xml
+++ b/catalog/pom.xml
@@ -13,7 +13,7 @@
     <parent>
         <groupId>com.ning.billing</groupId>
         <artifactId>killbill</artifactId>
-        <version>0.1.8-SNAPSHOT</version>
+        <version>0.1.11-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-catalog</artifactId>
diff --git a/entitlement/pom.xml b/entitlement/pom.xml
index cccf568..426d9f8 100644
--- a/entitlement/pom.xml
+++ b/entitlement/pom.xml
@@ -13,7 +13,7 @@
     <parent>
         <groupId>com.ning.billing</groupId>
         <artifactId>killbill</artifactId>
-        <version>0.1.8-SNAPSHOT</version>
+        <version>0.1.11-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-entitlement</artifactId>
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/BundleSqlDao.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/BundleSqlDao.java
index d9d9381..bccd559 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/BundleSqlDao.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/BundleSqlDao.java
@@ -18,6 +18,8 @@ package com.ning.billing.entitlement.engine.dao;
 
 import com.ning.billing.entitlement.api.user.SubscriptionBundle;
 import com.ning.billing.entitlement.api.user.SubscriptionBundleData;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.callcontext.CallContextBinder;
 import com.ning.billing.util.dao.BinderBase;
 import com.ning.billing.util.dao.MapperBase;
 import org.joda.time.DateTime;
@@ -43,7 +45,8 @@ import java.util.UUID;
 public interface BundleSqlDao extends Transactional<BundleSqlDao>, CloseMe, Transmogrifier {
 
     @SqlUpdate
-    public void insertBundle(@Bind(binder = SubscriptionBundleBinder.class) SubscriptionBundleData bundle);
+    public void insertBundle(@Bind(binder = SubscriptionBundleBinder.class) SubscriptionBundleData bundle,
+                             @CallContextBinder final CallContext context);
 
     @SqlQuery
     @Mapper(ISubscriptionBundleSqlMapper.class)
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EntitlementSqlDao.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EntitlementSqlDao.java
index 2dd8a8b..55238f5 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EntitlementSqlDao.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EntitlementSqlDao.java
@@ -73,542 +73,544 @@ import com.ning.billing.util.notificationq.NotificationQueueService.NoSuchNotifi
 import javax.annotation.Nullable;
 
 public class EntitlementSqlDao implements EntitlementDao {
-	private final static Logger log = LoggerFactory.getLogger(EntitlementSqlDao.class);
-	public static final String ENTITLEMENT_EVENTS_TABLE_NAME = "entitlement_events";
-	public static final String BUNDLES_TABLE_NAME = "bundles";
-	public static final String SUBSCRIPTIONS_TABLE_NAME = "subscriptions";
-
-	private final Clock clock;
-	private final SubscriptionSqlDao subscriptionsDao;
-	private final BundleSqlDao bundlesDao;
-	private final EventSqlDao eventsDao;
-	private final NotificationQueueService notificationQueueService;
-	private final AddonUtils addonUtils;
-	private final CustomFieldDao customFieldDao;
-
-	//
-	// We are not injecting SubscriptionFactory since that creates circular dependencies--
-	// Guice would still work, but this is playing with fire.
-	//
-	// Instead that factory passed through API top to bottom for the call where is it needed-- where we returned fully rehydrated Subscriptions
-	//
-	@Inject
-	public EntitlementSqlDao(final IDBI dbi, final Clock clock,
-			final AddonUtils addonUtils, final NotificationQueueService notificationQueueService,
-			final CustomFieldDao customFieldDao) {
-		this.clock = clock;
-		this.subscriptionsDao = dbi.onDemand(SubscriptionSqlDao.class);
-		this.eventsDao = dbi.onDemand(EventSqlDao.class);
-		this.bundlesDao = dbi.onDemand(BundleSqlDao.class);
-		this.notificationQueueService = notificationQueueService;
-		this.addonUtils = addonUtils;
-		this.customFieldDao = customFieldDao;
-	}
-
-	@Override
-	public SubscriptionBundle getSubscriptionBundleFromKey(final String bundleKey) {
-		return bundlesDao.getBundleFromKey(bundleKey);
-	}
-
-	@Override
-	public List<SubscriptionBundle> getSubscriptionBundleForAccount(
-			final UUID accountId) {
-		return bundlesDao.getBundleFromAccount(accountId.toString());
-	}
-
-	@Override
-	public SubscriptionBundle getSubscriptionBundleFromId(final UUID bundleId) {
-		return bundlesDao.getBundleFromId(bundleId.toString());
-	}
-
-	@Override
-	public SubscriptionBundle createSubscriptionBundle(final SubscriptionBundleData bundle, final CallContext context) {
-		return bundlesDao.inTransaction(new Transaction<SubscriptionBundle, BundleSqlDao>() {
-			@Override
-			public SubscriptionBundle inTransaction(BundleSqlDao bundlesDao, TransactionStatus status) {
-				bundlesDao.insertBundle(bundle);
-
-				AuditSqlDao auditSqlDao = bundlesDao.become(AuditSqlDao.class);
-				String bundleId = bundle.getId().toString();
-				auditSqlDao.insertAuditFromTransaction(BUNDLES_TABLE_NAME, bundleId, ChangeType.INSERT, context);
-
-				return bundle;
-			}
-		});
-	}
-
-	@Override
-	public UUID getAccountIdFromSubscriptionId(final UUID subscriptionId) {
-		Subscription subscription = subscriptionsDao.getSubscriptionFromId(subscriptionId.toString());
-		if (subscription == null) {
-			log.error(String.format(ErrorCode.ENT_INVALID_SUBSCRIPTION_ID.getFormat(), subscriptionId.toString()));
-			return null;
-		}
-
-		UUID bundleId = subscription.getBundleId();
-		if (bundleId == null) {
-			log.error(String.format(ErrorCode.ENT_GET_NO_BUNDLE_FOR_SUBSCRIPTION.getFormat(), subscriptionId.toString()));
-			return null;
-		}
-
-		SubscriptionBundle bundle = bundlesDao.getBundleFromId(bundleId.toString());
-		if (bundle == null) {
-			log.error(String.format(ErrorCode.ENT_GET_INVALID_BUNDLE_ID.getFormat(), bundleId.toString()));
-			return null;
-		}
-
-		return bundle.getAccountId();
-	}
-
-	@Override
-	public Subscription getBaseSubscription(final SubscriptionFactory factory, final UUID bundleId) {
-		return getBaseSubscription(factory, bundleId, true);
-	}
-
-	@Override
-	public Subscription getSubscriptionFromId(final SubscriptionFactory factory, final UUID subscriptionId) {
-		return buildSubscription(factory, subscriptionsDao.getSubscriptionFromId(subscriptionId.toString()));
-	}
-
-	@Override
-	public List<Subscription> getSubscriptions(final SubscriptionFactory factory, final UUID bundleId) {
-		return buildBundleSubscriptions(factory, subscriptionsDao.getSubscriptionsFromBundleId(bundleId.toString()));
-	}
-
-	@Override
-	public List<Subscription> getSubscriptionsForKey(final SubscriptionFactory factory, final String bundleKey) {
-		SubscriptionBundle bundle =  bundlesDao.getBundleFromKey(bundleKey);
-		if (bundle == null) {
-			return Collections.emptyList();
-		}
-		return getSubscriptions(factory, bundle.getId());
-	}
-
-	@Override
-	public void updateSubscription(final SubscriptionData subscription, final CallContext context) {
-
-		final Date ctd = (subscription.getChargedThroughDate() != null)  ? subscription.getChargedThroughDate().toDate() : null;
-		final Date ptd = (subscription.getPaidThroughDate() != null)  ? subscription.getPaidThroughDate().toDate() : null;
-
-		subscriptionsDao.inTransaction(new Transaction<Void, SubscriptionSqlDao>() {
-			@Override
-			public Void inTransaction(SubscriptionSqlDao transactionalDao,
-					TransactionStatus status) throws Exception {
-				transactionalDao.updateSubscription(subscription.getId().toString(), subscription.getActiveVersion(), ctd, ptd, context);
-
-				AuditSqlDao auditSqlDao = transactionalDao.become(AuditSqlDao.class);
-				String subscriptionId = subscription.getId().toString();
-				auditSqlDao.insertAuditFromTransaction(SUBSCRIPTIONS_TABLE_NAME, subscriptionId, ChangeType.UPDATE, context);
-				return null;
-			}
-		});
-	}
-
-	@Override
-	public void createNextPhaseEvent(final UUID subscriptionId, final EntitlementEvent nextPhase, final CallContext context) {
-		eventsDao.inTransaction(new Transaction<Void, EventSqlDao>() {
-			@Override
-			public Void inTransaction(EventSqlDao dao,
-					TransactionStatus status) throws Exception {
-				cancelNextPhaseEventFromTransaction(subscriptionId, dao, context);
-				dao.insertEvent(nextPhase);
-				AuditSqlDao auditSqlDao = dao.become(AuditSqlDao.class);
-				auditSqlDao.insertAuditFromTransaction(ENTITLEMENT_EVENTS_TABLE_NAME, nextPhase.getId().toString(), ChangeType.INSERT, context);
-
-				recordFutureNotificationFromTransaction(dao,
-						nextPhase.getEffectiveDate(),
-						new EntitlementNotificationKey(nextPhase.getId()));
-				return null;
-			}
-		});
-	}
-
-	@Override
-	public EntitlementEvent getEventById(UUID eventId) {
-		return eventsDao.getEventById(eventId.toString());
-	}
-
-	@Override
-	public List<EntitlementEvent> getEventsForSubscription(UUID subscriptionId) {
-		return eventsDao.getEventsForSubscription(subscriptionId.toString());
-	}
-
-	@Override
-	public List<EntitlementEvent> getPendingEventsForSubscription(UUID subscriptionId) {
-		Date now = clock.getUTCNow().toDate();
-		return eventsDao.getFutureActiveEventForSubscription(subscriptionId.toString(), now);
-	}
-
-	@Override
-	public void createSubscription(final SubscriptionData subscription,
-			final List<EntitlementEvent> initialEvents, final CallContext context) {
-
-		subscriptionsDao.inTransaction(new Transaction<Void, SubscriptionSqlDao>() {
-
-			@Override
-			public Void inTransaction(SubscriptionSqlDao dao,
-					TransactionStatus status) throws Exception {
-
-				dao.insertSubscription(subscription, context);
-				// STEPH batch as well
-				EventSqlDao eventsDaoFromSameTransaction = dao.become(EventSqlDao.class);
-				List<String> eventIds = new ArrayList<String>();
-
-				for (final EntitlementEvent cur : initialEvents) {
-					eventsDaoFromSameTransaction.insertEvent(cur);
-					eventIds.add(cur.getId().toString()); // collect ids for batch audit log insert
-					recordFutureNotificationFromTransaction(dao,
-							cur.getEffectiveDate(),
-							new EntitlementNotificationKey(cur.getId()));
-				}
-
-				AuditSqlDao auditSqlDao = dao.become(AuditSqlDao.class);
-				auditSqlDao.insertAuditFromTransaction(ENTITLEMENT_EVENTS_TABLE_NAME, eventIds, ChangeType.INSERT, context);
-				return null;
-			}
-		});
-	}
-
-	@Override
-	public void recreateSubscription(final UUID subscriptionId,
-			final List<EntitlementEvent> recreateEvents, final CallContext context) {
-
-		eventsDao.inTransaction(new Transaction<Void, EventSqlDao>() {
-			@Override
-			public Void inTransaction(EventSqlDao dao,
-					TransactionStatus status) throws Exception {
-
-				List<String> eventIds = new ArrayList<String>();
-				for (final EntitlementEvent cur : recreateEvents) {
-					dao.insertEvent(cur);
-					eventIds.add(cur.getId().toString()); // gather event ids for batch audit insert
-					recordFutureNotificationFromTransaction(dao,
-							cur.getEffectiveDate(),
-							new EntitlementNotificationKey(cur.getId()));
-				}
-
-				AuditSqlDao auditSqlDao = dao.become(AuditSqlDao.class);
-				auditSqlDao.insertAuditFromTransaction(ENTITLEMENT_EVENTS_TABLE_NAME, eventIds, ChangeType.INSERT, context);
-				return null;
-			}
-		});
-	}
-
-	@Override
-	public void cancelSubscription(final UUID subscriptionId, final EntitlementEvent cancelEvent, final CallContext context, final int seqId) {
-
-		eventsDao.inTransaction(new Transaction<Void, EventSqlDao>() {
-			@Override
-			public Void inTransaction(EventSqlDao dao,
-					TransactionStatus status) throws Exception {
-				cancelNextCancelEventFromTransaction(subscriptionId, dao, context);
-				cancelNextChangeEventFromTransaction(subscriptionId, dao, context);
-				cancelNextPhaseEventFromTransaction(subscriptionId, dao, context);
-				dao.insertEvent(cancelEvent);
-				AuditSqlDao auditSqlDao = dao.become(AuditSqlDao.class);
-				String cancelEventId = cancelEvent.getId().toString();
-				auditSqlDao.insertAuditFromTransaction(ENTITLEMENT_EVENTS_TABLE_NAME, cancelEventId, ChangeType.INSERT, context);
-
-				recordFutureNotificationFromTransaction(dao,
-						cancelEvent.getEffectiveDate(),
-						new EntitlementNotificationKey(cancelEvent.getId(), seqId));
-				return null;
-			}
-		});
-	}
-
-	@Override
-	public void uncancelSubscription(final UUID subscriptionId, final List<EntitlementEvent> uncancelEvents, final CallContext context) {
-
-		eventsDao.inTransaction(new Transaction<Void, EventSqlDao>() {
-
-			@Override
-			public Void inTransaction(EventSqlDao dao,
-					TransactionStatus status) throws Exception {
-
-				UUID existingCancelId = null;
-				Date now = clock.getUTCNow().toDate();
-				List<EntitlementEvent> events = dao.getFutureActiveEventForSubscription(subscriptionId.toString(), now);
-
-				for (EntitlementEvent cur : events) {
-					if (cur.getType() == EventType.API_USER && ((ApiEvent) cur).getEventType() == ApiEventType.CANCEL) {
-						if (existingCancelId != null) {
-							throw new EntitlementError(String.format("Found multiple cancel active events for subscriptions %s", subscriptionId.toString()));
-						}
-						existingCancelId = cur.getId();
-					}
-				}
-
-				if (existingCancelId != null) {
-					dao.unactiveEvent(existingCancelId.toString(), now);
-					String deactivatedEventId = existingCancelId.toString();
-
-					List<String> eventIds = new ArrayList<String>();
-					for (final EntitlementEvent cur : uncancelEvents) {
-						dao.insertEvent(cur);
-						eventIds.add(cur.getId().toString()); // gather event ids for batch insert into audit log
-						recordFutureNotificationFromTransaction(dao,
-								cur.getEffectiveDate(),
-								new EntitlementNotificationKey(cur.getId()));
-					}
-
-					AuditSqlDao auditSqlDao = dao.become(AuditSqlDao.class);
-					auditSqlDao.insertAuditFromTransaction(ENTITLEMENT_EVENTS_TABLE_NAME, deactivatedEventId, ChangeType.UPDATE, context);
-					auditSqlDao.insertAuditFromTransaction(ENTITLEMENT_EVENTS_TABLE_NAME, eventIds, ChangeType.INSERT, context);
-				}
-				return null;
-			}
-		});
-	}
-
-	@Override
-	public void changePlan(final UUID subscriptionId, final List<EntitlementEvent> changeEvents, final CallContext context) {
-		eventsDao.inTransaction(new Transaction<Void, EventSqlDao>() {
-			@Override
-			public Void inTransaction(EventSqlDao dao, TransactionStatus status) throws Exception {
-				cancelNextChangeEventFromTransaction(subscriptionId, dao, context);
-				cancelNextPhaseEventFromTransaction(subscriptionId, dao, context);
-
-				List<String> eventIds = new ArrayList<String>();
-				for (final EntitlementEvent cur : changeEvents) {
-					dao.insertEvent(cur);
-					eventIds.add(cur.getId().toString()); // gather event ids for batch audit log insert
-
-					recordFutureNotificationFromTransaction(dao,
-							cur.getEffectiveDate(),
-							new EntitlementNotificationKey(cur.getId()));
-				}
-
-				AuditSqlDao auditSqlDao = dao.become(AuditSqlDao.class);
-				auditSqlDao.insertAuditFromTransaction(ENTITLEMENT_EVENTS_TABLE_NAME, eventIds, ChangeType.INSERT, context);
-				return null;
-			}
-		});
-	}
-
-	private void cancelNextPhaseEventFromTransaction(final UUID subscriptionId, final EventSqlDao dao, final CallContext context) {
-		cancelFutureEventFromTransaction(subscriptionId, dao, EventType.PHASE, null, context);
-	}
-
-	private void cancelNextChangeEventFromTransaction(final UUID subscriptionId, final EventSqlDao dao, final CallContext context) {
-		cancelFutureEventFromTransaction(subscriptionId, dao, EventType.API_USER, ApiEventType.CHANGE, context);
-	}
-
-	private void cancelNextCancelEventFromTransaction(final UUID subscriptionId, final EventSqlDao dao, final CallContext context) {
-		cancelFutureEventFromTransaction(subscriptionId, dao, EventType.API_USER, ApiEventType.CANCEL, context);
-	}
-
-	private void cancelFutureEventFromTransaction(final UUID subscriptionId, final EventSqlDao dao,
-			final EventType type, @Nullable final ApiEventType apiType,
-			final CallContext context) {
-
-		UUID futureEventId = null;
-		Date now = clock.getUTCNow().toDate();
-		List<EntitlementEvent> events = dao.getFutureActiveEventForSubscription(subscriptionId.toString(), now);
-		for (EntitlementEvent cur : events) {
-			if (cur.getType() == type &&
-					(apiType == null || apiType == ((ApiEvent) cur).getEventType() )) {
-				if (futureEventId != null) {
-					throw new EntitlementError(
-							String.format("Found multiple future events for type %s for subscriptions %s",
-									type, subscriptionId.toString()));
-				}
-				futureEventId = cur.getId();
-			}
-		}
-
-		if (futureEventId != null) {
-			dao.unactiveEvent(futureEventId.toString(), now);
-
-			AuditSqlDao auditSqlDao = dao.become(AuditSqlDao.class);
-			auditSqlDao.insertAuditFromTransaction(ENTITLEMENT_EVENTS_TABLE_NAME, futureEventId.toString(), ChangeType.UPDATE, context);
-		}
-	}
-
-	private void updateCustomFieldsFromTransaction(final SubscriptionSqlDao transactionalDao,
-			final SubscriptionData subscription,
-			final CallContext context) {
-		customFieldDao.saveFields(transactionalDao, subscription.getId(), subscription.getObjectName(), subscription.getFieldList(), context);
-	}
-
-	private Subscription buildSubscription(final SubscriptionFactory factory, final Subscription input) {
-		if (input == null) {
-			return null;
-		}
-		List<Subscription> bundleInput = new ArrayList<Subscription>();
-		if (input.getCategory() == ProductCategory.ADD_ON) {
-			Subscription baseSubscription = getBaseSubscription(factory, input.getBundleId(), false);
-			bundleInput.add(baseSubscription);
-			bundleInput.add(input);
-		} else {
-			bundleInput.add(input);
-		}
-		List<Subscription> reloadedSubscriptions = buildBundleSubscriptions(factory, bundleInput);
-		for (Subscription cur : reloadedSubscriptions) {
-			if (cur.getId().equals(input.getId())) {
-				return cur;
-			}
-		}
-		throw new EntitlementError(String.format("Unexpected code path in buildSubscription"));
-	}
-
-	private List<Subscription> buildBundleSubscriptions(final SubscriptionFactory factory, final List<Subscription> input) {
-		// Make sure BasePlan -- if exists-- is first
-		Collections.sort(input, new Comparator<Subscription>() {
-			@Override
-			public int compare(Subscription o1, Subscription o2) {
-				if (o1.getCategory() == ProductCategory.BASE) {
-					return -1;
-				} else if (o2.getCategory() == ProductCategory.BASE) {
-					return 1;
-				} else {
-					return o1.getStartDate().compareTo(o2.getStartDate());
-				}
-			}
-		});
-
-		EntitlementEvent futureBaseEvent = null;
-		List<Subscription> result = new ArrayList<Subscription>(input.size());
-		for (Subscription cur : input) {
-
-			List<EntitlementEvent> events = eventsDao.getEventsForSubscription(cur.getId().toString());
-			Subscription reloaded = factory.createSubscription(new SubscriptionBuilder((SubscriptionData) cur), events);
-
-			switch (cur.getCategory()) {
-			case BASE:
-				Collection<EntitlementEvent> futureApiEvents = Collections2.filter(events, new Predicate<EntitlementEvent>() {
-					@Override
-					public boolean apply(EntitlementEvent input) {
-						return (input.getEffectiveDate().isAfter(clock.getUTCNow()) &&
-								((input instanceof ApiEventCancel) || (input instanceof ApiEventChange)));
-					}
-				});
-				futureBaseEvent = (futureApiEvents.size() == 0) ? null : futureApiEvents.iterator().next();
-				break;
-
-			case ADD_ON:
-				Plan targetAddOnPlan = reloaded.getCurrentPlan();
-				String baseProductName = (futureBaseEvent instanceof ApiEventChange) ?
-						((ApiEventChange) futureBaseEvent).getEventPlan() : null;
-
-						boolean createCancelEvent = (futureBaseEvent != null) &&
-						((futureBaseEvent instanceof ApiEventCancel) ||
-								((! addonUtils.isAddonAvailable(baseProductName, futureBaseEvent.getEffectiveDate(), targetAddOnPlan)) ||
-										(addonUtils.isAddonIncluded(baseProductName, futureBaseEvent.getEffectiveDate(), targetAddOnPlan))));
-
-						if (createCancelEvent) {
-							DateTime now = clock.getUTCNow();
-							EntitlementEvent addOnCancelEvent = new ApiEventCancel(new ApiEventBuilder()
-							.setSubscriptionId(reloaded.getId())
-							.setActiveVersion(((SubscriptionData) reloaded).getActiveVersion())
-							.setProcessedDate(now)
-							.setEffectiveDate(futureBaseEvent.getEffectiveDate())
-							.setRequestedDate(now)
-							// This event is only there to indicate the ADD_ON is future canceled, but it is not there
-							// on disk until the base plan cancellation becomes effective
-							.setFromDisk(false));
-
-							events.add(addOnCancelEvent);
-							// Finally reload subscription with full set of events
-							reloaded = factory.createSubscription(new SubscriptionBuilder((SubscriptionData) cur), events);
-						}
-						break;
-			default:
-				break;
-			}
-			loadCustomFields((SubscriptionData) reloaded);
-			result.add(reloaded);
-		}
-		return result;
-	}
-
-	@Override
-	public void migrate(final UUID accountId, final AccountMigrationData accountData, final CallContext context) {
-
-		eventsDao.inTransaction(new Transaction<Void, EventSqlDao>() {
-
-			@Override
-			public Void inTransaction(EventSqlDao transEventDao,
-					TransactionStatus status) throws Exception {
-
-				SubscriptionSqlDao transSubDao = transEventDao.become(SubscriptionSqlDao.class);
-				BundleSqlDao transBundleDao = transEventDao.become(BundleSqlDao.class);
-
-				List<String> bundleIds = new ArrayList<String>();
-				List<String> subscriptionIds = new ArrayList<String>();
-				List<String> eventIds = new ArrayList<String>();
-
-				for (BundleMigrationData curBundle : accountData.getData()) {
-					SubscriptionBundleData bundleData = curBundle.getData();
-
-					for (SubscriptionMigrationData curSubscription : curBundle.getSubscriptions()) {
-
-						SubscriptionData subData = curSubscription.getData();
-						for (final EntitlementEvent curEvent : curSubscription.getInitialEvents()) {
-							transEventDao.insertEvent(curEvent);
-							eventIds.add(curEvent.getId().toString()); // gather event ids for batch audit
-
-							recordFutureNotificationFromTransaction(transEventDao,
-									curEvent.getEffectiveDate(),
-									new EntitlementNotificationKey(curEvent.getId()));
-						}
-						transSubDao.insertSubscription(subData, context);
-						subscriptionIds.add(subData.getId().toString()); // gather subscription ids for batch audit
-					}
-					transBundleDao.insertBundle(bundleData);
-					bundleIds.add(bundleData.getId().toString()); // gather bundle ids for batch audit
-				}
-
-				// add audit records for bundles, subscriptions, and events
-				AuditSqlDao auditSqlDao = transBundleDao.become(AuditSqlDao.class);
-				auditSqlDao.insertAuditFromTransaction(SUBSCRIPTIONS_TABLE_NAME, subscriptionIds, ChangeType.INSERT, context);
-				auditSqlDao.insertAuditFromTransaction(BUNDLES_TABLE_NAME, bundleIds, ChangeType.INSERT, context);
-				auditSqlDao.insertAuditFromTransaction(ENTITLEMENT_EVENTS_TABLE_NAME, eventIds, ChangeType.INSERT, context);
-
-				return null;
-			}
-		});
-	}
-
-
-	private Subscription getBaseSubscription(final SubscriptionFactory factory, final UUID bundleId, boolean rebuildSubscription) {
-		List<Subscription> subscriptions = subscriptionsDao.getSubscriptionsFromBundleId(bundleId.toString());
-		for (Subscription cur : subscriptions) {
-			if (cur.getCategory() == ProductCategory.BASE) {
-				return  rebuildSubscription ? buildSubscription(factory, cur) : cur;
-			}
-		}
-		return null;
-	}
-
-	private void recordFutureNotificationFromTransaction(final Transmogrifier transactionalDao, final DateTime effectiveDate, final NotificationKey notificationKey) {
-		try {
-			NotificationQueue subscriptionEventQueue = notificationQueueService.getNotificationQueue(Engine.ENTITLEMENT_SERVICE_NAME,
-					Engine.NOTIFICATION_QUEUE_NAME);
-			subscriptionEventQueue.recordFutureNotificationFromTransaction(transactionalDao, effectiveDate, notificationKey);
-		} catch (NoSuchNotificationQueue e) {
-			throw new RuntimeException(e);
-		}
-	}
-
-	@Override
-	public void saveCustomFields(final SubscriptionData subscription, final CallContext context) {
-		subscriptionsDao.inTransaction(new Transaction<Void, SubscriptionSqlDao>() {
-			@Override
-			public Void inTransaction(SubscriptionSqlDao transactionalDao,
-					TransactionStatus status) throws Exception {
-				updateCustomFieldsFromTransaction(transactionalDao, subscription, context);
-				return null;
-			}
-		});
-	}
-
-	private void loadCustomFields(final SubscriptionData subscription) {
-		CustomFieldSqlDao customFieldSqlDao = subscriptionsDao.become(CustomFieldSqlDao.class);
-		List<CustomField> fields = customFieldSqlDao.load(subscription.getId().toString(), subscription.getObjectName());
-		subscription.clearFields();
-		if (fields != null) {
-			subscription.setFields(fields);
-		}
-	}
+
+    private final static Logger log = LoggerFactory.getLogger(EntitlementSqlDao.class);
+    public static final String ENTITLEMENT_EVENTS_TABLE_NAME = "entitlement_events";
+    public static final String BUNDLES_TABLE_NAME = "bundles";
+    public static final String SUBSCRIPTIONS_TABLE_NAME = "subscriptions";
+
+    private final Clock clock;
+    private final SubscriptionSqlDao subscriptionsDao;
+    private final BundleSqlDao bundlesDao;
+    private final EventSqlDao eventsDao;
+    private final NotificationQueueService notificationQueueService;
+    private final AddonUtils addonUtils;
+    private final CustomFieldDao customFieldDao;
+
+    //
+    // We are not injecting SubscriptionFactory since that creates circular dependencies--
+    // Guice would still work, but this is playing with fire.
+    //
+    // Instead that factory passed through API top to bottom for the call where is it needed-- where we returned fully rehydrated Subscriptions
+    //
+    @Inject
+    public EntitlementSqlDao(final IDBI dbi, final Clock clock,
+            final AddonUtils addonUtils, final NotificationQueueService notificationQueueService,
+            final CustomFieldDao customFieldDao) {
+        this.clock = clock;
+        this.subscriptionsDao = dbi.onDemand(SubscriptionSqlDao.class);
+        this.eventsDao = dbi.onDemand(EventSqlDao.class);
+        this.bundlesDao = dbi.onDemand(BundleSqlDao.class);
+        this.notificationQueueService = notificationQueueService;
+        this.addonUtils = addonUtils;
+        this.customFieldDao = customFieldDao;
+    }
+
+    @Override
+    public SubscriptionBundle getSubscriptionBundleFromKey(final String bundleKey) {
+        return bundlesDao.getBundleFromKey(bundleKey);
+    }
+
+    @Override
+    public List<SubscriptionBundle> getSubscriptionBundleForAccount(
+            final UUID accountId) {
+        return bundlesDao.getBundleFromAccount(accountId.toString());
+    }
+
+    @Override
+    public SubscriptionBundle getSubscriptionBundleFromId(final UUID bundleId) {
+        return bundlesDao.getBundleFromId(bundleId.toString());
+    }
+
+    @Override
+    public SubscriptionBundle createSubscriptionBundle(final SubscriptionBundleData bundle, final CallContext context) {
+        return bundlesDao.inTransaction(new Transaction<SubscriptionBundle, BundleSqlDao>() {
+            @Override
+            public SubscriptionBundle inTransaction(BundleSqlDao bundlesDao, TransactionStatus status) {
+                bundlesDao.insertBundle(bundle, context);
+
+                AuditSqlDao auditSqlDao = bundlesDao.become(AuditSqlDao.class);
+                String bundleId = bundle.getId().toString();
+                auditSqlDao.insertAuditFromTransaction(BUNDLES_TABLE_NAME, bundleId, ChangeType.INSERT, context);
+
+                return bundle;
+            }
+        });
+    }
+
+    @Override
+    public UUID getAccountIdFromSubscriptionId(final UUID subscriptionId) {
+        Subscription subscription = subscriptionsDao.getSubscriptionFromId(subscriptionId.toString());
+        if (subscription == null) {
+            log.error(String.format(ErrorCode.ENT_INVALID_SUBSCRIPTION_ID.getFormat(), subscriptionId.toString()));
+            return null;
+        }
+
+        UUID bundleId = subscription.getBundleId();
+        if (bundleId == null) {
+            log.error(String.format(ErrorCode.ENT_GET_NO_BUNDLE_FOR_SUBSCRIPTION.getFormat(), subscriptionId.toString()));
+            return null;
+        }
+
+        SubscriptionBundle bundle = bundlesDao.getBundleFromId(bundleId.toString());
+        if (bundle == null) {
+            log.error(String.format(ErrorCode.ENT_GET_INVALID_BUNDLE_ID.getFormat(), bundleId.toString()));
+            return null;
+        }
+
+        return bundle.getAccountId();
+    }
+
+    @Override
+    public Subscription getBaseSubscription(final SubscriptionFactory factory, final UUID bundleId) {
+        return getBaseSubscription(factory, bundleId, true);
+    }
+
+    @Override
+    public Subscription getSubscriptionFromId(final SubscriptionFactory factory, final UUID subscriptionId) {
+        return buildSubscription(factory, subscriptionsDao.getSubscriptionFromId(subscriptionId.toString()));
+    }
+
+    @Override
+    public List<Subscription> getSubscriptions(final SubscriptionFactory factory, final UUID bundleId) {
+        return buildBundleSubscriptions(factory, subscriptionsDao.getSubscriptionsFromBundleId(bundleId.toString()));
+    }
+
+    @Override
+    public List<Subscription> getSubscriptionsForKey(final SubscriptionFactory factory, final String bundleKey) {
+        SubscriptionBundle bundle =  bundlesDao.getBundleFromKey(bundleKey);
+        if (bundle == null) {
+            return Collections.emptyList();
+        }
+        return getSubscriptions(factory, bundle.getId());
+    }
+
+    @Override
+    public void updateSubscription(final SubscriptionData subscription, final CallContext context) {
+
+        final Date ctd = (subscription.getChargedThroughDate() != null)  ? subscription.getChargedThroughDate().toDate() : null;
+        final Date ptd = (subscription.getPaidThroughDate() != null)  ? subscription.getPaidThroughDate().toDate() : null;
+
+        subscriptionsDao.inTransaction(new Transaction<Void, SubscriptionSqlDao>() {
+            @Override
+            public Void inTransaction(SubscriptionSqlDao transactionalDao,
+                    TransactionStatus status) throws Exception {
+                transactionalDao.updateSubscription(subscription.getId().toString(), subscription.getActiveVersion(), ctd, ptd, context);
+
+                AuditSqlDao auditSqlDao = transactionalDao.become(AuditSqlDao.class);
+                String subscriptionId = subscription.getId().toString();
+                auditSqlDao.insertAuditFromTransaction(SUBSCRIPTIONS_TABLE_NAME, subscriptionId, ChangeType.UPDATE, context);
+                return null;
+            }
+        });
+    }
+
+    @Override
+    public void createNextPhaseEvent(final UUID subscriptionId, final EntitlementEvent nextPhase, final CallContext context) {
+        eventsDao.inTransaction(new Transaction<Void, EventSqlDao>() {
+            @Override
+            public Void inTransaction(EventSqlDao dao,
+                    TransactionStatus status) throws Exception {
+                cancelNextPhaseEventFromTransaction(subscriptionId, dao, context);
+                dao.insertEvent(nextPhase, context);
+                AuditSqlDao auditSqlDao = dao.become(AuditSqlDao.class);
+                auditSqlDao.insertAuditFromTransaction(ENTITLEMENT_EVENTS_TABLE_NAME, nextPhase.getId().toString(), ChangeType.INSERT, context);
+
+                recordFutureNotificationFromTransaction(dao,
+                        nextPhase.getEffectiveDate(),
+                        new EntitlementNotificationKey(nextPhase.getId()));
+                return null;
+            }
+        });
+    }
+
+    @Override
+    public EntitlementEvent getEventById(UUID eventId) {
+        return eventsDao.getEventById(eventId.toString());
+    }
+
+    @Override
+    public List<EntitlementEvent> getEventsForSubscription(UUID subscriptionId) {
+        return eventsDao.getEventsForSubscription(subscriptionId.toString());
+    }
+
+    @Override
+    public List<EntitlementEvent> getPendingEventsForSubscription(UUID subscriptionId) {
+        Date now = clock.getUTCNow().toDate();
+        return eventsDao.getFutureActiveEventForSubscription(subscriptionId.toString(), now);
+    }
+
+    @Override
+    public void createSubscription(final SubscriptionData subscription,
+            final List<EntitlementEvent> initialEvents, final CallContext context) {
+
+        subscriptionsDao.inTransaction(new Transaction<Void, SubscriptionSqlDao>() {
+
+            @Override
+            public Void inTransaction(SubscriptionSqlDao dao,
+                    TransactionStatus status) throws Exception {
+
+                dao.insertSubscription(subscription, context);
+                // STEPH batch as well
+                EventSqlDao eventsDaoFromSameTransaction = dao.become(EventSqlDao.class);
+                List<String> eventIds = new ArrayList<String>();
+
+                for (final EntitlementEvent cur : initialEvents) {
+                    eventsDaoFromSameTransaction.insertEvent(cur, context);
+                    eventIds.add(cur.getId().toString()); // collect ids for batch audit log insert
+                    recordFutureNotificationFromTransaction(dao,
+                            cur.getEffectiveDate(),
+                            new EntitlementNotificationKey(cur.getId()));
+                }
+
+                AuditSqlDao auditSqlDao = dao.become(AuditSqlDao.class);
+                auditSqlDao.insertAuditFromTransaction(ENTITLEMENT_EVENTS_TABLE_NAME, eventIds, ChangeType.INSERT, context);
+                return null;
+            }
+        });
+    }
+
+    @Override
+    public void recreateSubscription(final UUID subscriptionId,
+            final List<EntitlementEvent> recreateEvents, final CallContext context) {
+
+        eventsDao.inTransaction(new Transaction<Void, EventSqlDao>() {
+            @Override
+            public Void inTransaction(EventSqlDao dao,
+                    TransactionStatus status) throws Exception {
+
+                List<String> eventIds = new ArrayList<String>();
+                for (final EntitlementEvent cur : recreateEvents) {
+                    dao.insertEvent(cur, context);
+                    eventIds.add(cur.getId().toString()); // gather event ids for batch audit insert
+                    recordFutureNotificationFromTransaction(dao,
+                            cur.getEffectiveDate(),
+                            new EntitlementNotificationKey(cur.getId()));
+
+                }
+
+                AuditSqlDao auditSqlDao = dao.become(AuditSqlDao.class);
+                auditSqlDao.insertAuditFromTransaction(ENTITLEMENT_EVENTS_TABLE_NAME, eventIds, ChangeType.INSERT, context);
+                return null;
+            }
+        });
+    }
+
+    @Override
+    public void cancelSubscription(final UUID subscriptionId, final EntitlementEvent cancelEvent, final CallContext context, final int seqId) {
+
+        eventsDao.inTransaction(new Transaction<Void, EventSqlDao>() {
+            @Override
+            public Void inTransaction(EventSqlDao dao,
+                    TransactionStatus status) throws Exception {
+                cancelNextCancelEventFromTransaction(subscriptionId, dao, context);
+                cancelNextChangeEventFromTransaction(subscriptionId, dao, context);
+                cancelNextPhaseEventFromTransaction(subscriptionId, dao, context);
+                dao.insertEvent(cancelEvent, context);
+                AuditSqlDao auditSqlDao = dao.become(AuditSqlDao.class);
+                String cancelEventId = cancelEvent.getId().toString();
+                auditSqlDao.insertAuditFromTransaction(ENTITLEMENT_EVENTS_TABLE_NAME, cancelEventId, ChangeType.INSERT, context);
+
+                recordFutureNotificationFromTransaction(dao,
+                        cancelEvent.getEffectiveDate(),
+                        new EntitlementNotificationKey(cancelEvent.getId(), seqId));
+                return null;
+            }
+        });
+    }
+
+    @Override
+    public void uncancelSubscription(final UUID subscriptionId, final List<EntitlementEvent> uncancelEvents, final CallContext context) {
+
+        eventsDao.inTransaction(new Transaction<Void, EventSqlDao>() {
+
+            @Override
+            public Void inTransaction(EventSqlDao dao,
+                    TransactionStatus status) throws Exception {
+
+                UUID existingCancelId = null;
+                Date now = clock.getUTCNow().toDate();
+                List<EntitlementEvent> events = dao.getFutureActiveEventForSubscription(subscriptionId.toString(), now);
+
+                for (EntitlementEvent cur : events) {
+                    if (cur.getType() == EventType.API_USER && ((ApiEvent) cur).getEventType() == ApiEventType.CANCEL) {
+                        if (existingCancelId != null) {
+                            throw new EntitlementError(String.format("Found multiple cancel active events for subscriptions %s", subscriptionId.toString()));
+                        }
+                        existingCancelId = cur.getId();
+                    }
+                }
+
+                if (existingCancelId != null) {
+                    dao.unactiveEvent(existingCancelId.toString(), context);
+                    String deactivatedEventId = existingCancelId.toString();
+
+                    List<String> eventIds = new ArrayList<String>();
+                    for (final EntitlementEvent cur : uncancelEvents) {
+                        dao.insertEvent(cur, context);
+                        eventIds.add(cur.getId().toString()); // gather event ids for batch insert into audit log
+                        recordFutureNotificationFromTransaction(dao,
+                                cur.getEffectiveDate(),
+                                new EntitlementNotificationKey(cur.getId()));
+                    }
+
+                    AuditSqlDao auditSqlDao = dao.become(AuditSqlDao.class);
+                    auditSqlDao.insertAuditFromTransaction(ENTITLEMENT_EVENTS_TABLE_NAME, deactivatedEventId, ChangeType.UPDATE, context);
+                    auditSqlDao.insertAuditFromTransaction(ENTITLEMENT_EVENTS_TABLE_NAME, eventIds, ChangeType.INSERT, context);
+                }
+                return null;
+            }
+        });
+    }
+
+    @Override
+    public void changePlan(final UUID subscriptionId, final List<EntitlementEvent> changeEvents, final CallContext context) {
+        eventsDao.inTransaction(new Transaction<Void, EventSqlDao>() {
+            @Override
+            public Void inTransaction(EventSqlDao dao, TransactionStatus status) throws Exception {
+                cancelNextChangeEventFromTransaction(subscriptionId, dao, context);
+                cancelNextPhaseEventFromTransaction(subscriptionId, dao, context);
+
+                List<String> eventIds = new ArrayList<String>();
+                for (final EntitlementEvent cur : changeEvents) {
+                    dao.insertEvent(cur, context);
+                    eventIds.add(cur.getId().toString()); // gather event ids for batch audit log insert
+
+                    recordFutureNotificationFromTransaction(dao,
+                            cur.getEffectiveDate(),
+                            new EntitlementNotificationKey(cur.getId()));
+                }
+
+                AuditSqlDao auditSqlDao = dao.become(AuditSqlDao.class);
+                auditSqlDao.insertAuditFromTransaction(ENTITLEMENT_EVENTS_TABLE_NAME, eventIds, ChangeType.INSERT, context);
+                return null;
+            }
+        });
+    }
+
+    private void cancelNextPhaseEventFromTransaction(final UUID subscriptionId, final EventSqlDao dao, final CallContext context) {
+        cancelFutureEventFromTransaction(subscriptionId, dao, EventType.PHASE, null, context);
+    }
+
+    private void cancelNextChangeEventFromTransaction(final UUID subscriptionId, final EventSqlDao dao, final CallContext context) {
+        cancelFutureEventFromTransaction(subscriptionId, dao, EventType.API_USER, ApiEventType.CHANGE, context);
+    }
+
+    private void cancelNextCancelEventFromTransaction(final UUID subscriptionId, final EventSqlDao dao, final CallContext context) {
+        cancelFutureEventFromTransaction(subscriptionId, dao, EventType.API_USER, ApiEventType.CANCEL, context);
+    }
+
+    private void cancelFutureEventFromTransaction(final UUID subscriptionId, final EventSqlDao dao,
+            final EventType type, @Nullable final ApiEventType apiType,
+            final CallContext context) {
+
+        UUID futureEventId = null;
+        Date now = clock.getUTCNow().toDate();
+        List<EntitlementEvent> events = dao.getFutureActiveEventForSubscription(subscriptionId.toString(), now);
+        for (EntitlementEvent cur : events) {
+            if (cur.getType() == type &&
+                    (apiType == null || apiType == ((ApiEvent) cur).getEventType() )) {
+                if (futureEventId != null) {
+                    throw new EntitlementError(
+                            String.format("Found multiple future events for type %s for subscriptions %s",
+                                    type, subscriptionId.toString()));
+                }
+                futureEventId = cur.getId();
+            }
+        }
+
+        if (futureEventId != null) {
+            dao.unactiveEvent(futureEventId.toString(), context);
+
+            AuditSqlDao auditSqlDao = dao.become(AuditSqlDao.class);
+            auditSqlDao.insertAuditFromTransaction(ENTITLEMENT_EVENTS_TABLE_NAME, futureEventId.toString(), ChangeType.UPDATE, context);
+        }
+    }
+
+    private void updateCustomFieldsFromTransaction(final SubscriptionSqlDao transactionalDao,
+            final SubscriptionData subscription,
+            final CallContext context) {
+        customFieldDao.saveFields(transactionalDao, subscription.getId(), subscription.getObjectName(), subscription.getFieldList(), context);
+    }
+
+    private Subscription buildSubscription(final SubscriptionFactory factory, final Subscription input) {
+        if (input == null) {
+            return null;
+        }
+        List<Subscription> bundleInput = new ArrayList<Subscription>();
+        if (input.getCategory() == ProductCategory.ADD_ON) {
+            Subscription baseSubscription = getBaseSubscription(factory, input.getBundleId(), false);
+            bundleInput.add(baseSubscription);
+            bundleInput.add(input);
+        } else {
+            bundleInput.add(input);
+        }
+        List<Subscription> reloadedSubscriptions = buildBundleSubscriptions(factory, bundleInput);
+        for (Subscription cur : reloadedSubscriptions) {
+            if (cur.getId().equals(input.getId())) {
+                return cur;
+            }
+        }
+        throw new EntitlementError(String.format("Unexpected code path in buildSubscription"));
+    }
+
+    private List<Subscription> buildBundleSubscriptions(final SubscriptionFactory factory, final List<Subscription> input) {
+        // Make sure BasePlan -- if exists-- is first
+        Collections.sort(input, new Comparator<Subscription>() {
+            @Override
+            public int compare(Subscription o1, Subscription o2) {
+                if (o1.getCategory() == ProductCategory.BASE) {
+                    return -1;
+                } else if (o2.getCategory() == ProductCategory.BASE) {
+                    return 1;
+                } else {
+                    return o1.getStartDate().compareTo(o2.getStartDate());
+                }
+            }
+        });
+
+        EntitlementEvent futureBaseEvent = null;
+        List<Subscription> result = new ArrayList<Subscription>(input.size());
+        for (Subscription cur : input) {
+
+            List<EntitlementEvent> events = eventsDao.getEventsForSubscription(cur.getId().toString());
+            Subscription reloaded = factory.createSubscription(new SubscriptionBuilder((SubscriptionData) cur), events);
+
+            switch (cur.getCategory()) {
+            case BASE:
+                Collection<EntitlementEvent> futureApiEvents = Collections2.filter(events, new Predicate<EntitlementEvent>() {
+                    @Override
+                    public boolean apply(EntitlementEvent input) {
+                        return (input.getEffectiveDate().isAfter(clock.getUTCNow()) &&
+                                ((input instanceof ApiEventCancel) || (input instanceof ApiEventChange)));
+                    }
+                });
+                futureBaseEvent = (futureApiEvents.size() == 0) ? null : futureApiEvents.iterator().next();
+                break;
+
+            case ADD_ON:
+                Plan targetAddOnPlan = reloaded.getCurrentPlan();
+                String baseProductName = (futureBaseEvent instanceof ApiEventChange) ?
+                        ((ApiEventChange) futureBaseEvent).getEventPlan() : null;
+
+                        boolean createCancelEvent = (futureBaseEvent != null) &&
+                        ((futureBaseEvent instanceof ApiEventCancel) ||
+                                ((! addonUtils.isAddonAvailable(baseProductName, futureBaseEvent.getEffectiveDate(), targetAddOnPlan)) ||
+                                        (addonUtils.isAddonIncluded(baseProductName, futureBaseEvent.getEffectiveDate(), targetAddOnPlan))));
+
+                        if (createCancelEvent) {
+                            DateTime now = clock.getUTCNow();
+                            EntitlementEvent addOnCancelEvent = new ApiEventCancel(new ApiEventBuilder()
+                            .setSubscriptionId(reloaded.getId())
+                            .setActiveVersion(((SubscriptionData) reloaded).getActiveVersion())
+                            .setProcessedDate(now)
+                            .setEffectiveDate(futureBaseEvent.getEffectiveDate())
+                            .setRequestedDate(now)
+                            // This event is only there to indicate the ADD_ON is future canceled, but it is not there
+                            // on disk until the base plan cancellation becomes effective
+                            .setFromDisk(false));
+
+                            events.add(addOnCancelEvent);
+                            // Finally reload subscription with full set of events
+                            reloaded = factory.createSubscription(new SubscriptionBuilder((SubscriptionData) cur), events);
+                        }
+                        break;
+            default:
+                break;
+            }
+            loadCustomFields((SubscriptionData) reloaded);
+            result.add(reloaded);
+        }
+        return result;
+    }
+
+    @Override
+    public void migrate(final UUID accountId, final AccountMigrationData accountData, final CallContext context) {
+
+        eventsDao.inTransaction(new Transaction<Void, EventSqlDao>() {
+
+            @Override
+            public Void inTransaction(EventSqlDao transEventDao,
+                    TransactionStatus status) throws Exception {
+
+                SubscriptionSqlDao transSubDao = transEventDao.become(SubscriptionSqlDao.class);
+                BundleSqlDao transBundleDao = transEventDao.become(BundleSqlDao.class);
+
+                List<String> bundleIds = new ArrayList<String>();
+                List<String> subscriptionIds = new ArrayList<String>();
+                List<String> eventIds = new ArrayList<String>();
+
+                for (BundleMigrationData curBundle : accountData.getData()) {
+                    SubscriptionBundleData bundleData = curBundle.getData();
+
+                    for (SubscriptionMigrationData curSubscription : curBundle.getSubscriptions()) {
+
+                        SubscriptionData subData = curSubscription.getData();
+                        for (final EntitlementEvent curEvent : curSubscription.getInitialEvents()) {
+                            transEventDao.insertEvent(curEvent, context);
+                            eventIds.add(curEvent.getId().toString()); // gather event ids for batch audit
+
+                            recordFutureNotificationFromTransaction(transEventDao,
+                                    curEvent.getEffectiveDate(),
+                                    new EntitlementNotificationKey(curEvent.getId()));
+                        }
+                        transSubDao.insertSubscription(subData, context);
+                        subscriptionIds.add(subData.getId().toString()); // gather subscription ids for batch audit
+                    }
+                    transBundleDao.insertBundle(bundleData, context);
+                    bundleIds.add(bundleData.getId().toString()); // gather bundle ids for batch audit
+                }
+
+                // add audit records for bundles, subscriptions, and events
+                AuditSqlDao auditSqlDao = transBundleDao.become(AuditSqlDao.class);
+                auditSqlDao.insertAuditFromTransaction(SUBSCRIPTIONS_TABLE_NAME, subscriptionIds, ChangeType.INSERT, context);
+                auditSqlDao.insertAuditFromTransaction(BUNDLES_TABLE_NAME, bundleIds, ChangeType.INSERT, context);
+                auditSqlDao.insertAuditFromTransaction(ENTITLEMENT_EVENTS_TABLE_NAME, eventIds, ChangeType.INSERT, context);
+
+                return null;
+            }
+        });
+    }
+
+
+    private Subscription getBaseSubscription(final SubscriptionFactory factory, final UUID bundleId, boolean rebuildSubscription) {
+        List<Subscription> subscriptions = subscriptionsDao.getSubscriptionsFromBundleId(bundleId.toString());
+        for (Subscription cur : subscriptions) {
+            if (cur.getCategory() == ProductCategory.BASE) {
+                return  rebuildSubscription ? buildSubscription(factory, cur) : cur;
+            }
+        }
+        return null;
+    }
+
+    private void recordFutureNotificationFromTransaction(final Transmogrifier transactionalDao, final DateTime effectiveDate, final NotificationKey notificationKey) {
+        try {
+            NotificationQueue subscriptionEventQueue = notificationQueueService.getNotificationQueue(Engine.ENTITLEMENT_SERVICE_NAME,
+                    Engine.NOTIFICATION_QUEUE_NAME);
+            subscriptionEventQueue.recordFutureNotificationFromTransaction(transactionalDao, effectiveDate, notificationKey);
+        } catch (NoSuchNotificationQueue e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public void saveCustomFields(final SubscriptionData subscription, final CallContext context) {
+        subscriptionsDao.inTransaction(new Transaction<Void, SubscriptionSqlDao>() {
+            @Override
+            public Void inTransaction(SubscriptionSqlDao transactionalDao,
+                    TransactionStatus status) throws Exception {
+                updateCustomFieldsFromTransaction(transactionalDao, subscription, context);
+                return null;
+            }
+        });
+    }
+
+    private void loadCustomFields(final SubscriptionData subscription) {
+        CustomFieldSqlDao customFieldSqlDao = subscriptionsDao.become(CustomFieldSqlDao.class);
+        List<CustomField> fields = customFieldSqlDao.load(subscription.getId().toString(), subscription.getObjectName());
+        subscription.clearFields();
+        if (fields != null) {
+            subscription.setFields(fields);
+        }
+    }
 }
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EventSqlDao.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EventSqlDao.java
index 5254517..89baed8 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EventSqlDao.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EventSqlDao.java
@@ -24,6 +24,8 @@ import com.ning.billing.entitlement.events.phase.PhaseEventBuilder;
 import com.ning.billing.entitlement.events.phase.PhaseEventData;
 import com.ning.billing.entitlement.events.user.*;
 import com.ning.billing.entitlement.exceptions.EntitlementError;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.callcontext.CallContextBinder;
 import com.ning.billing.util.dao.BinderBase;
 import com.ning.billing.util.dao.MapperBase;
 import org.joda.time.DateTime;
@@ -53,13 +55,16 @@ public interface EventSqlDao extends Transactional<EventSqlDao>, CloseMe, Transm
     public EntitlementEvent getEventById(@Bind("event_id") String eventId);
 
     @SqlUpdate
-    public void insertEvent(@Bind(binder = EventSqlDaoBinder.class) EntitlementEvent evt);
+    public void insertEvent(@Bind(binder = EventSqlDaoBinder.class) EntitlementEvent evt,
+                            @CallContextBinder final CallContext context);
 
     @SqlUpdate
-    public void unactiveEvent(@Bind("event_id")String eventId, @Bind("now") Date now);
+    public void unactiveEvent(@Bind("event_id")String eventId,
+                              @CallContextBinder final CallContext context);
 
     @SqlUpdate
-    public void reactiveEvent(@Bind("event_id")String eventId, @Bind("now") Date now);
+    public void reactiveEvent(@Bind("event_id")String eventId,
+                              @CallContextBinder final CallContext context);
 
     @SqlQuery
     @Mapper(EventSqlMapper.class)
@@ -77,22 +82,18 @@ public interface EventSqlDao extends Transactional<EventSqlDao>, CloseMe, Transm
         	String phaseName = null;
         	String priceListName = null;
         	String userType = null;
-        	String userToken = null;
         	if (evt.getType() == EventType.API_USER) {
         		ApiEvent userEvent = (ApiEvent) evt;
             	planName = userEvent.getEventPlan();
             	phaseName = userEvent.getEventPlanPhase();
             	priceListName = userEvent.getPriceList();
             	userType = userEvent.getEventType().toString();
-            	userToken = (userEvent.getUserToken() != null) ? userEvent.getUserToken().toString() : null; 
         	} else {
         		phaseName = ((PhaseEvent) evt).getPhase();
         	}
             stmt.bind("event_id", evt.getId().toString());
             stmt.bind("event_type", evt.getType().toString());
             stmt.bind("user_type", userType);
-            stmt.bind("created_dt", getDate(evt.getProcessedDate()));
-            stmt.bind("updated_dt", getDate(evt.getProcessedDate()));
             stmt.bind("requested_dt", getDate(evt.getRequestedDate()));
             stmt.bind("effective_dt", getDate(evt.getEffectiveDate()));
             stmt.bind("subscription_id", evt.getSubscriptionId().toString());
@@ -101,7 +102,6 @@ public interface EventSqlDao extends Transactional<EventSqlDao>, CloseMe, Transm
             stmt.bind("plist_name", priceListName);
             stmt.bind("current_version", evt.getActiveVersion());
             stmt.bind("is_active", evt.isActive());
-            stmt.bind("user_token", userToken);
         }
     }
 
@@ -114,7 +114,7 @@ public interface EventSqlDao extends Transactional<EventSqlDao>, CloseMe, Transm
             UUID id = UUID.fromString(r.getString("event_id"));
             EventType eventType = EventType.valueOf(r.getString("event_type"));
             ApiEventType userType = (eventType == EventType.API_USER) ? ApiEventType.valueOf(r.getString("user_type")) : null;
-            DateTime createdDate = getDate(r, "created_dt");
+            DateTime createdDate = getDate(r, "created_date");
             DateTime requestedDate = getDate(r, "requested_dt");
             DateTime effectiveDate = getDate(r, "effective_dt");
             UUID subscriptionId = UUID.fromString(r.getString("subscription_id"));
diff --git a/entitlement/src/main/resources/com/ning/billing/entitlement/ddl.sql b/entitlement/src/main/resources/com/ning/billing/entitlement/ddl.sql
index 0a64176..d2bb5b7 100644
--- a/entitlement/src/main/resources/com/ning/billing/entitlement/ddl.sql
+++ b/entitlement/src/main/resources/com/ning/billing/entitlement/ddl.sql
@@ -5,8 +5,6 @@ CREATE TABLE entitlement_events (
     event_id char(36) NOT NULL,
     event_type varchar(9) NOT NULL,
     user_type varchar(25) DEFAULT NULL,
-    created_dt datetime NOT NULL,
-    updated_dt datetime NOT NULL,
     requested_dt datetime NOT NULL,
     effective_dt datetime NOT NULL,
     subscription_id char(36) NOT NULL,
@@ -16,11 +14,14 @@ CREATE TABLE entitlement_events (
     user_token char(36),
     current_version int(11) DEFAULT 1,
     is_active bool DEFAULT 1,
+    created_by varchar(50) NOT NULL,
+    created_date datetime NOT NULL,
+    updated_by varchar(50) NOT NULL,
+    updated_date datetime NOT NULL,
     PRIMARY KEY(id)
 ) ENGINE=innodb;
 CREATE INDEX idx_ent_1 ON entitlement_events(subscription_id,is_active,effective_dt);
-CREATE INDEX idx_ent_2 ON entitlement_events(subscription_id,effective_dt,created_dt,requested_dt,id);
-
+CREATE INDEX idx_ent_2 ON entitlement_events(subscription_id,effective_dt,created_date,requested_dt,id);
 
 DROP TABLE IF EXISTS subscriptions;
 CREATE TABLE subscriptions (
@@ -32,6 +33,10 @@ CREATE TABLE subscriptions (
     active_version int(11) DEFAULT 1,
     ctd_dt datetime DEFAULT NULL,
     ptd_dt datetime DEFAULT NULL,
+    created_by varchar(50) NOT NULL,
+    created_date datetime NOT NULL,
+    updated_by varchar(50) NOT NULL,
+    updated_date datetime NOT NULL,
     PRIMARY KEY(id)
 ) ENGINE=innodb;
 
diff --git a/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/EventSqlDao.sql.stg b/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/EventSqlDao.sql.stg
index a1aae54..cd5afbf 100644
--- a/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/EventSqlDao.sql.stg
+++ b/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/EventSqlDao.sql.stg
@@ -1,22 +1,27 @@
 group EventSqlDao;
 
-getEventById(event_id) ::= <<
-  select
-      id
-      , event_id
-      , event_type
-      , user_type
-      , created_dt
-      , updated_dt
-      , requested_dt
-      , effective_dt
-      , subscription_id
-      , plan_name
-      , phase_name
-      , plist_name
-      , current_version
-      , user_token
-      , is_active  
+fields(prefix) ::= <<
+    <prefix>event_id,
+    <prefix>event_type,
+    <prefix>user_type,
+    <prefix>requested_dt,
+    <prefix>effective_dt,
+    <prefix>subscription_id,
+    <prefix>plan_name,
+    <prefix>phase_name,
+    <prefix>plist_name,
+    <prefix>user_token,
+    <prefix>current_version,
+    <prefix>is_active,
+    <prefix>created_by,
+    <prefix>created_date,
+    <prefix>updated_by,
+    <prefix>updated_date
+>>
+
+
+getEventById() ::= <<
+  select id, <fields()>
   from entitlement_events
   where
       event_id = :event_id
@@ -24,76 +29,52 @@ getEventById(event_id) ::= <<
 >>
 
 insertEvent() ::= <<
-    insert into entitlement_events (
-      event_id
-      , event_type
-      , user_type
-      , created_dt
-      , updated_dt
-      , requested_dt
-      , effective_dt
-      , subscription_id
-      , plan_name
-      , phase_name
-      , plist_name
-      , current_version
-      , user_token
-      , is_active
-    ) values (
-      :event_id
-      , :event_type
-      , :user_type
-      , :created_dt
-      , :updated_dt
-      , :requested_dt
-      , :effective_dt
-      , :subscription_id
-      , :plan_name
-      , :phase_name
-      , :plist_name
-      , :current_version
-      , :user_token
-      , :is_active
+    insert into entitlement_events (<fields()>)
+    values (
+    :event_id
+    , :event_type
+    , :user_type
+    , :requested_dt
+    , :effective_dt
+    , :subscription_id
+    , :plan_name
+    , :phase_name
+    , :plist_name
+    , :userToken
+    , :current_version
+    , :is_active
+    , :userName
+    , :createdDate
+    , :userName
+    , :updatedDate
     );   
 >>
 
-unactiveEvent(event_id, now) ::= <<
+unactiveEvent() ::= <<
     update entitlement_events
     set
       is_active = 0
-      , updated_dt = :now
+      , updated_by = :userName
+      , updated_date = :updatedDate
     where
       event_id = :event_id
     ;
 >>
 
-reactiveEvent(event_id, now) ::= <<
+reactiveEvent() ::= <<
     update entitlement_events
     set
       is_active = 1
-      , updated_dt = :now
+      , updated_by = :userName
+      , updated_date = :updatedDate
     where
       event_id = :event_id
     ;
 >>
 
-getFutureActiveEventForSubscription(subscription_id, now) ::= <<
-    select 
-      id
-      , event_id
-      , event_type
-      , user_type
-      , created_dt
-      , updated_dt
-      , requested_dt
-      , effective_dt
-      , subscription_id
-      , plan_name
-      , phase_name
-      , plist_name
-      , current_version
-      , user_token
-      , is_active
+
+getFutureActiveEventForSubscription() ::= <<
+    select id, <fields()>
     from entitlement_events
     where
       subscription_id = :subscription_id
@@ -101,35 +82,20 @@ getFutureActiveEventForSubscription(subscription_id, now) ::= <<
       and effective_dt > :now
     order by
       effective_dt asc
-      , created_dt asc
+      , created_date asc
       , requested_dt asc
       , id asc
     ;
 >> 
 
 getEventsForSubscription(subscription_id) ::= <<
-    select
-       id
-      , event_id
-      , event_type
-      , user_type
-      , created_dt
-      , updated_dt
-      , requested_dt
-      , effective_dt
-      , subscription_id
-      , plan_name
-      , phase_name
-      , plist_name
-      , current_version
-      , user_token
-      , is_active
+    select id, <fields()>
     from entitlement_events
     where
       subscription_id = :subscription_id
     order by
       effective_dt asc
-      , created_dt asc
+      , created_date asc
       , requested_dt asc
       , id asc
     ;      
diff --git a/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/SubscriptionSqlDao.sql.stg b/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/SubscriptionSqlDao.sql.stg
index 094bdf1..780c06a 100644
--- a/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/SubscriptionSqlDao.sql.stg
+++ b/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/SubscriptionSqlDao.sql.stg
@@ -10,6 +10,10 @@ insertSubscription() ::= <<
       , active_version
       , ctd_dt
       , ptd_dt
+      , created_by
+      , created_date
+      , updated_by
+      , updated_date
     ) values (
         :id
       , :bundle_id
@@ -18,7 +22,11 @@ insertSubscription() ::= <<
       , :bundle_start_dt
       , :active_version
       , :ctd_dt
-      , :ptd_dt 
+      , :ptd_dt
+      , :userName
+      , :createdDate
+      , :userName
+      , :updatedDate
     );
 >>
 
@@ -58,6 +66,8 @@ updateSubscription(id, active_version, ctd_dt, ptd_dt) ::= <<
       active_version = :active_version
       , ctd_dt = :ctd_dt
       , ptd_dt = :ptd_dt
+      , updated_by = :userName
+      , updated_date = :updatedDate
     where id = :id
     ;
 >>
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 808e457..0a65693 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
@@ -189,10 +189,13 @@ public abstract class TestApiBase {
 
         log.warn("RESET TEST FRAMEWORK\n\n");
 
-        testListener.reset();
+        if (testListener != null) {
+            testListener.reset();
+        }
 
         clock.resetDeltaFromReality();
         ((MockEntitlementDao) dao).reset();
+
         try {
             busService.getBus().register(testListener);
             UUID accountId = UUID.randomUUID();

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

diff --git a/invoice/pom.xml b/invoice/pom.xml
index ccef93e..8db3258 100644
--- a/invoice/pom.xml
+++ b/invoice/pom.xml
@@ -13,7 +13,7 @@
     <parent>
         <groupId>com.ning.billing</groupId>
         <artifactId>killbill</artifactId>
-        <version>0.1.8-SNAPSHOT</version>
+        <version>0.1.11-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-invoice</artifactId>
@@ -39,13 +39,21 @@
             <type>test-jar</type>
             <scope>test</scope>
         </dependency>
+            <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-entitlement</artifactId>
+            <scope>test</scope>
+        </dependency>
         <dependency>
             <groupId>com.ning.billing</groupId>
             <artifactId>killbill-entitlement</artifactId>
+            <type>test-jar</type>
+            <scope>test</scope>
         </dependency>
         <dependency>
             <groupId>com.ning.billing</groupId>
             <artifactId>killbill-catalog</artifactId>
+            <scope>test</scope>
         </dependency>
         <dependency>
             <groupId>com.ning.billing</groupId>
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/InvoicingConfiguration.java b/invoice/src/main/java/com/ning/billing/invoice/model/InvoicingConfiguration.java
index 554dc62..7741f00 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/InvoicingConfiguration.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/InvoicingConfiguration.java
@@ -20,7 +20,7 @@ import java.math.BigDecimal;
 
 public class InvoicingConfiguration {
     private final static int roundingMethod = BigDecimal.ROUND_HALF_UP;
-    private final static int numberOfDecimals = 4;
+    private final static int numberOfDecimals = 2;
 
     public static int getRoundingMode() {
         return roundingMethod;

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

diff --git a/jaxrs/pom.xml b/jaxrs/pom.xml
index 0701aa2..f8472a9 100644
--- a/jaxrs/pom.xml
+++ b/jaxrs/pom.xml
@@ -14,7 +14,7 @@
     <parent>
         <groupId>com.ning.billing</groupId>
         <artifactId>killbill</artifactId>
-        <version>0.1.8-SNAPSHOT</version>
+        <version>0.1.11-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-jaxrs</artifactId>

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

diff --git a/payment/pom.xml b/payment/pom.xml
index fc660f7..d55d8d9 100644
--- a/payment/pom.xml
+++ b/payment/pom.xml
@@ -13,7 +13,7 @@
     <parent>
         <groupId>com.ning.billing</groupId>
         <artifactId>killbill</artifactId>
-        <version>0.1.8-SNAPSHOT</version>
+        <version>0.1.11-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-payment</artifactId>
diff --git a/payment/src/main/java/com/ning/billing/payment/api/DefaultPaymentApi.java b/payment/src/main/java/com/ning/billing/payment/api/DefaultPaymentApi.java
index e565ccc..0d9c19a 100644
--- a/payment/src/main/java/com/ning/billing/payment/api/DefaultPaymentApi.java
+++ b/payment/src/main/java/com/ning/billing/payment/api/DefaultPaymentApi.java
@@ -23,7 +23,6 @@ import java.util.UUID;
 
 import javax.annotation.Nullable;
 
-import com.ning.billing.util.callcontext.CallContext;
 import org.apache.commons.lang.StringUtils;
 import org.joda.time.DateTime;
 import org.slf4j.Logger;
@@ -40,6 +39,7 @@ import com.ning.billing.payment.RetryService;
 import com.ning.billing.payment.dao.PaymentDao;
 import com.ning.billing.payment.provider.PaymentProviderPlugin;
 import com.ning.billing.payment.provider.PaymentProviderPluginRegistry;
+import com.ning.billing.util.callcontext.CallContext;
 
 public class DefaultPaymentApi implements PaymentApi {
     private final PaymentProviderPluginRegistry pluginRegistry;
@@ -184,14 +184,7 @@ public class DefaultPaymentApi implements PaymentApi {
             Invoice invoice = invoicePaymentApi.getInvoice(UUID.fromString(invoiceId));
 
             if (invoice.getBalance().compareTo(BigDecimal.ZERO) <= 0 ) {
-                // TODO: send a notification that invoice was ignored?
-                log.info("Received invoice for payment with balance of 0 {} ", invoice);
-                Either<PaymentErrorEvent, PaymentInfoEvent> result = Either.left((PaymentErrorEvent) new DefaultPaymentError("invoice_balance_0",
-                                                                                        "Invoice balance was 0 or less",
-                                                                                        account.getId(),
-                                                                                        UUID.fromString(invoiceId),
-                                                                                        context.getUserToken()));
-                processedPaymentsOrErrors.add(result);
+                log.debug("Received invoice for payment with balance of 0 {} ", invoice);
             }
             else if (invoice.isMigrationInvoice()) {
             	log.info("Received invoice for payment that is a migration invoice - don't know how to handle those yet: {}", invoice);
@@ -312,9 +305,11 @@ public class DefaultPaymentApi implements PaymentApi {
     }
 
     @Override
-    public List<Either<PaymentErrorEvent, PaymentInfoEvent>> createRefund(Account account, List<String> invoiceIds, CallContext context) {
-        //TODO
-        throw new UnsupportedOperationException();
+    public List<Either<PaymentErrorEvent, PaymentInfoEvent>> createRefund(Account account,
+                                                                List<String> invoiceIds,
+                                                                CallContext context) {
+        final PaymentProviderPlugin plugin = getPaymentProviderPlugin(account);
+        return plugin.processRefund(account);
     }
 
     @Override
diff --git a/payment/src/main/java/com/ning/billing/payment/provider/NoOpPaymentProviderPlugin.java b/payment/src/main/java/com/ning/billing/payment/provider/NoOpPaymentProviderPlugin.java
index 23dfe51..8e28b80 100644
--- a/payment/src/main/java/com/ning/billing/payment/provider/NoOpPaymentProviderPlugin.java
+++ b/payment/src/main/java/com/ning/billing/payment/provider/NoOpPaymentProviderPlugin.java
@@ -110,4 +110,10 @@ public class NoOpPaymentProviderPlugin implements PaymentProviderPlugin {
         return Either.right(null);
     }
 
+    @Override
+    public List<Either<PaymentErrorEvent, PaymentInfoEvent>> processRefund(Account account) {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
 }
diff --git a/payment/src/main/java/com/ning/billing/payment/provider/PaymentProviderPlugin.java b/payment/src/main/java/com/ning/billing/payment/provider/PaymentProviderPlugin.java
index c46d24d..2daa160 100644
--- a/payment/src/main/java/com/ning/billing/payment/provider/PaymentProviderPlugin.java
+++ b/payment/src/main/java/com/ning/billing/payment/provider/PaymentProviderPlugin.java
@@ -40,7 +40,8 @@ public interface PaymentProviderPlugin {
     Either<PaymentErrorEvent, PaymentMethodInfo> updatePaymentMethod(String accountKey, PaymentMethodInfo paymentMethodInfo);
     Either<PaymentErrorEvent, Void> deletePaymentMethod(String accountKey, String paymentMethodId);
 
+
     Either<PaymentErrorEvent, Void> updatePaymentProviderAccountExistingContact(Account account);
     Either<PaymentErrorEvent, Void> updatePaymentProviderAccountWithNewContact(Account account);
-
+    List<Either<PaymentErrorEvent, PaymentInfoEvent>> processRefund(Account account);
 }
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 c4fd34a..ac2b9f3 100644
--- a/payment/src/test/java/com/ning/billing/payment/api/TestPaymentApi.java
+++ b/payment/src/test/java/com/ning/billing/payment/api/TestPaymentApi.java
@@ -22,6 +22,7 @@ import static org.testng.Assert.assertNotNull;
 import static org.testng.Assert.assertTrue;
 
 import java.math.BigDecimal;
+import java.math.RoundingMode;
 import java.util.Arrays;
 import java.util.List;
 import java.util.UUID;
@@ -80,7 +81,7 @@ public abstract class TestPaymentApi {
         final DateTime now = new DateTime(DateTimeZone.UTC);
         final Account account = testHelper.createTestCreditCardAccount();
         final Invoice invoice = testHelper.createTestInvoice(account, now, Currency.USD);
-        final BigDecimal amount = new BigDecimal("10.00");
+        final BigDecimal amount = new BigDecimal("10.0011");
         final UUID subscriptionId = UUID.randomUUID();
 
         invoice.addInvoiceItem(new RecurringInvoiceItem(invoice.getId(), account.getId(),
@@ -100,7 +101,7 @@ public abstract class TestPaymentApi {
         PaymentInfoEvent paymentInfo = results.get(0).getRight();
 
         assertNotNull(paymentInfo.getPaymentId());
-        assertTrue(paymentInfo.getAmount().compareTo(amount) == 0);
+        assertTrue(paymentInfo.getAmount().compareTo(amount.setScale(2, RoundingMode.HALF_EVEN)) == 0);
         assertNotNull(paymentInfo.getPaymentNumber());
         assertFalse(paymentInfo.getStatus().equals("Error"));
 
diff --git a/payment/src/test/java/com/ning/billing/payment/provider/MockPaymentProviderPlugin.java b/payment/src/test/java/com/ning/billing/payment/provider/MockPaymentProviderPlugin.java
index 074676b..c027985 100644
--- a/payment/src/test/java/com/ning/billing/payment/provider/MockPaymentProviderPlugin.java
+++ b/payment/src/test/java/com/ning/billing/payment/provider/MockPaymentProviderPlugin.java
@@ -24,13 +24,11 @@ import java.util.UUID;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicBoolean;
 
-import com.google.inject.Inject;
-import com.ning.billing.util.clock.Clock;
 import org.apache.commons.lang.RandomStringUtils;
-import org.joda.time.DateTime;
 
 import com.google.common.base.Predicate;
 import com.google.common.collect.Collections2;
+import com.google.inject.Inject;
 import com.ning.billing.account.api.Account;
 import com.ning.billing.invoice.api.Invoice;
 import com.ning.billing.payment.api.CreditCardPaymentMethodInfo;
@@ -42,6 +40,7 @@ import com.ning.billing.payment.api.PaymentInfoEvent;
 import com.ning.billing.payment.api.PaymentMethodInfo;
 import com.ning.billing.payment.api.PaymentProviderAccount;
 import com.ning.billing.payment.api.PaypalPaymentMethodInfo;
+import com.ning.billing.util.clock.Clock;
 
 public class MockPaymentProviderPlugin implements PaymentProviderPlugin {
     private final AtomicBoolean makeNextInvoiceFail = new AtomicBoolean(false);
@@ -268,8 +267,11 @@ public class MockPaymentProviderPlugin implements PaymentProviderPlugin {
 
     @Override
     public Either<PaymentErrorEvent, Void> updatePaymentProviderAccountWithNewContact(Account account) {
-        // nothing to do here
         return Either.right(null);
     }
 
+    @Override
+    public List<Either<PaymentErrorEvent, PaymentInfoEvent>> processRefund(Account account) {
+        return null;
+    }
 }

pom.xml 5(+2 -3)

diff --git a/pom.xml b/pom.xml
index b50bc5f..e0b602f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -7,8 +7,7 @@
     OR CONDITIONS OF ANY KIND, either express or implied. See the ~ License for 
     the specific language governing permissions and limitations ~ under the License. -->
 
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
     <parent>
         <groupId>org.sonatype.oss</groupId>
         <artifactId>oss-parent</artifactId>
@@ -18,7 +17,7 @@
     <groupId>com.ning.billing</groupId>
     <artifactId>killbill</artifactId>
     <packaging>pom</packaging>
-    <version>0.1.8-SNAPSHOT</version>
+    <version>0.1.11-SNAPSHOT</version>
     <name>killbill</name>
     <description>Library for managing recurring subscriptions and the associated billing</description>
     <url>http://github.com/ning/killbill</url>

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

diff --git a/server/pom.xml b/server/pom.xml
index 9cf0a9b..ea39345 100644
--- a/server/pom.xml
+++ b/server/pom.xml
@@ -14,7 +14,7 @@
 	<parent>
 		<groupId>com.ning.billing</groupId>
 		<artifactId>killbill</artifactId>
-		<version>0.1.8-SNAPSHOT</version>
+		<version>0.1.11-SNAPSHOT</version>
 		<relativePath>../pom.xml</relativePath>
 	</parent>
 	<artifactId>killbill-server</artifactId>

util/pom.xml 5(+2 -3)

diff --git a/util/pom.xml b/util/pom.xml
index dc5c251..e0d256c 100644
--- a/util/pom.xml
+++ b/util/pom.xml
@@ -8,13 +8,12 @@
     OR CONDITIONS OF ANY KIND, either express or implied. See the ~ License for 
     the specific language governing permissions and limitations ~ under the License. -->
 
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
     <modelVersion>4.0.0</modelVersion>
     <parent>
         <groupId>com.ning.billing</groupId>
         <artifactId>killbill</artifactId>
-        <version>0.1.8-SNAPSHOT</version>
+        <version>0.1.11-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-util</artifactId>