killbill-memoizeit

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

7/3/2012 7:36:46 PM

Changes

analytics/src/test/java/com/ning/billing/analytics/MockSubscription.java 148(+0 -148)

analytics/src/test/java/com/ning/billing/analytics/TestAnalyticsListener.java 244(+0 -244)

Details

diff --git a/analytics/pom.xml b/analytics/pom.xml
index ecd27bf..c95818d 100644
--- a/analytics/pom.xml
+++ b/analytics/pom.xml
@@ -117,7 +117,6 @@
         <dependency>
             <groupId>com.ning.billing</groupId>
             <artifactId>killbill-util</artifactId>
-            <scope>test</scope>
         </dependency>
         <dependency>
             <groupId>com.ning.billing</groupId>
diff --git a/analytics/src/main/java/com/ning/billing/analytics/AnalyticsListener.java b/analytics/src/main/java/com/ning/billing/analytics/AnalyticsListener.java
index 87a7060..bcddd64 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/AnalyticsListener.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/AnalyticsListener.java
@@ -24,6 +24,7 @@ import com.ning.billing.account.api.AccountCreationEvent;
 import com.ning.billing.entitlement.api.timeline.RepairEntitlementEvent;
 import com.ning.billing.entitlement.api.user.EffectiveSubscriptionEvent;
 import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
+import com.ning.billing.entitlement.api.user.RequestedSubscriptionEvent;
 import com.ning.billing.invoice.api.EmptyInvoiceEvent;
 import com.ning.billing.invoice.api.InvoiceCreationEvent;
 import com.ning.billing.payment.api.PaymentErrorEvent;
@@ -55,32 +56,19 @@ public class AnalyticsListener {
     }
 
     @Subscribe
-    public void handleSubscriptionTransitionChange(final EffectiveSubscriptionEvent eventEffective) throws AccountApiException, EntitlementUserApiException {
-        switch (eventEffective.getTransitionType()) {
-            // A subscription enters either through migration or as newly created subscription
-            case MIGRATE_ENTITLEMENT:
-            case CREATE:
-                bstRecorder.subscriptionCreated(eventEffective);
-                break;
-            case RE_CREATE:
-                bstRecorder.subscriptionRecreated(eventEffective);
-                break;
-            case MIGRATE_BILLING:
-                break;
-            case CANCEL:
-                bstRecorder.subscriptionCancelled(eventEffective);
-                break;
-            case CHANGE:
-                bstRecorder.subscriptionChanged(eventEffective);
-                break;
-            case UNCANCEL:
-                break;
-            case PHASE:
-                bstRecorder.subscriptionPhaseChanged(eventEffective);
-                break;
-            default:
-                throw new RuntimeException("Unexpected event type " + eventEffective.getTransitionType());
-        }
+    public void handleEffectiveSubscriptionTransitionChange(final EffectiveSubscriptionEvent eventEffective) throws AccountApiException, EntitlementUserApiException {
+        bstRecorder.rebuildTransitionsForBundle(eventEffective.getBundleId());
+    }
+
+    @Subscribe
+    public void handleRequestedSubscriptionTransitionChange(final RequestedSubscriptionEvent eventRequested) throws AccountApiException, EntitlementUserApiException {
+        bstRecorder.rebuildTransitionsForBundle(eventRequested.getBundleId());
+    }
+
+    @Subscribe
+    public void handleRepairEntitlement(final RepairEntitlementEvent event) {
+        // In case of repair, just rebuild all transitions
+        bstRecorder.rebuildTransitionsForBundle(event.getBundleId());
     }
 
     @Subscribe
@@ -99,6 +87,7 @@ public class AnalyticsListener {
 
     @Subscribe
     public void handleInvoiceCreation(final InvoiceCreationEvent event) {
+        // TODO - follow same logic as entitlements to support repair
         invoiceRecorder.invoiceCreated(event.getInvoiceId());
     }
 
@@ -156,9 +145,4 @@ public class AnalyticsListener {
     public void handleUserTagDefinitionDeletion(final UserTagDefinitionDeletionEvent event) {
         // Ignored for now
     }
-
-    @Subscribe
-    public void handleRepairEntitlement(final RepairEntitlementEvent event) {
-        // Ignored for now
-    }
 }
diff --git a/analytics/src/main/java/com/ning/billing/analytics/BusinessSubscriptionTransitionRecorder.java b/analytics/src/main/java/com/ning/billing/analytics/BusinessSubscriptionTransitionRecorder.java
index 49d6a9d..a4c9ff3 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/BusinessSubscriptionTransitionRecorder.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/BusinessSubscriptionTransitionRecorder.java
@@ -16,9 +16,13 @@
 
 package com.ning.billing.analytics;
 
+import java.util.ArrayList;
 import java.util.List;
+import java.util.UUID;
 
 import org.joda.time.DateTime;
+import org.skife.jdbi.v2.Transaction;
+import org.skife.jdbi.v2.TransactionStatus;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -32,10 +36,14 @@ import com.ning.billing.analytics.model.BusinessSubscriptionEvent;
 import com.ning.billing.analytics.model.BusinessSubscriptionTransition;
 import com.ning.billing.catalog.api.CatalogService;
 import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
 import com.ning.billing.entitlement.api.user.EffectiveSubscriptionEvent;
 import com.ning.billing.entitlement.api.user.EntitlementUserApi;
 import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
+import com.ning.billing.entitlement.api.user.Subscription;
 import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.entitlement.api.user.SubscriptionEvent;
+import com.ning.billing.util.clock.Clock;
 
 public class BusinessSubscriptionTransitionRecorder {
     private static final Logger log = LoggerFactory.getLogger(BusinessSubscriptionTransitionRecorder.class);
@@ -44,94 +52,194 @@ public class BusinessSubscriptionTransitionRecorder {
     private final EntitlementUserApi entitlementApi;
     private final AccountUserApi accountApi;
     private final CatalogService catalogService;
+    private final Clock clock;
 
     @Inject
-    public BusinessSubscriptionTransitionRecorder(final BusinessSubscriptionTransitionSqlDao sqlDao, final CatalogService catalogService, final EntitlementUserApi entitlementApi, final AccountUserApi accountApi) {
+    public BusinessSubscriptionTransitionRecorder(final BusinessSubscriptionTransitionSqlDao sqlDao,
+                                                  final CatalogService catalogService,
+                                                  final EntitlementUserApi entitlementApi,
+                                                  final AccountUserApi accountApi,
+                                                  final Clock clock) {
         this.sqlDao = sqlDao;
         this.catalogService = catalogService;
         this.entitlementApi = entitlementApi;
         this.accountApi = accountApi;
+        this.clock = clock;
     }
 
+    public void rebuildTransitionsForBundle(final UUID bundleId) {
+        final SubscriptionBundle bundle;
+        try {
+            bundle = entitlementApi.getBundleFromId(bundleId);
+        } catch (EntitlementUserApiException e) {
+            log.warn("Ignoring update for bundle {}: bundle does not exist", bundleId);
+            return;
+        }
+
+        final Account account;
+        try {
+            account = accountApi.getAccountById(bundle.getAccountId());
+        } catch (AccountApiException e) {
+            log.warn("Ignoring update for bundle {}: account {} does not exist", bundleId, bundle.getAccountId());
+            return;
+        }
+
+        final List<Subscription> subscriptions = entitlementApi.getSubscriptionsForBundle(bundleId);
+
+        final String externalKey = bundle.getKey();
+        final String accountKey = account.getExternalKey();
+        final Currency currency = account.getCurrency();
+
+        sqlDao.inTransaction(new Transaction<Void, BusinessSubscriptionTransitionSqlDao>() {
+            @Override
+            public Void inTransaction(final BusinessSubscriptionTransitionSqlDao transactional, final TransactionStatus status) throws Exception {
+                log.info("Started rebuilding transitions for bundle {}", externalKey);
+                transactional.deleteTransitionsForBundle(externalKey);
+
+                final ArrayList<BusinessSubscriptionTransition> transitions = new ArrayList<BusinessSubscriptionTransition>();
+                for (final Subscription subscription : subscriptions) {
+                    for (final EffectiveSubscriptionEvent event : subscription.getAllTransitions()) {
+                        final BusinessSubscriptionEvent businessEvent = getBusinessSubscriptionFromEvent(event);
+                        if (businessEvent == null) {
+                            continue;
+                        }
+
+                        final BusinessSubscription prevSubscription = createPreviousBusinessSubscription(event, businessEvent, transitions, currency);
+                        final BusinessSubscription nextSubscription = createNextBusinessSubscription(event, businessEvent, currency);
+                        final BusinessSubscriptionTransition transition = new BusinessSubscriptionTransition(
+                                event.getTotalOrdering(),
+                                externalKey,
+                                accountKey,
+                                event.getRequestedTransitionTime(),
+                                businessEvent,
+                                prevSubscription,
+                                nextSubscription
+                        );
 
-    public void subscriptionCreated(final EffectiveSubscriptionEvent created) throws AccountApiException, EntitlementUserApiException {
-        final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionCreated(created.getNextPlan(), catalogService.getFullCatalog(), created.getEffectiveTransitionTime(), created.getSubscriptionStartDate());
-        recordTransition(event, created);
+                        transactional.createTransition(transition);
+                        transitions.add(transition);
+                        log.info("Adding transition {}", transition);
+
+                        // We need to manually add the system cancel event
+                        if (SubscriptionTransitionType.CANCEL.equals(event.getTransitionType()) &&
+                                clock.getUTCNow().isAfter(event.getEffectiveTransitionTime())) {
+                            final BusinessSubscriptionTransition systemCancelTransition = new BusinessSubscriptionTransition(
+                                    event.getTotalOrdering(),
+                                    externalKey,
+                                    accountKey,
+                                    event.getRequestedTransitionTime(),
+                                    new BusinessSubscriptionEvent(BusinessSubscriptionEvent.EventType.SYSTEM_CANCEL, businessEvent.getCategory()),
+                                    prevSubscription,
+                                    nextSubscription
+                            );
+                            transactional.createTransition(systemCancelTransition);
+                            transitions.add(systemCancelTransition);
+                            log.info("Adding transition {}", systemCancelTransition);
+                        }
+                    }
+                }
+
+                log.info("Finished rebuilding transitions for bundle {}", externalKey);
+                return null;
+            }
+        });
     }
 
+    private BusinessSubscriptionEvent getBusinessSubscriptionFromEvent(final SubscriptionEvent event) throws AccountApiException, EntitlementUserApiException {
+        switch (event.getTransitionType()) {
+            // A subscription enters either through migration or as newly created subscription
+            case MIGRATE_ENTITLEMENT:
+            case CREATE:
+                return subscriptionCreated(event);
+            case RE_CREATE:
+                return subscriptionRecreated(event);
+            case CANCEL:
+                return subscriptionCancelled(event);
+            case CHANGE:
+                return subscriptionChanged(event);
+            case PHASE:
+                return subscriptionPhaseChanged(event);
+            // TODO - should we really ignore these?
+            case MIGRATE_BILLING:
+            case UNCANCEL:
+                return null;
+            default:
+                log.warn("Unexpected event type " + event.getTransitionType());
+                return null;
+        }
+    }
 
-    public void subscriptionRecreated(final EffectiveSubscriptionEvent recreated) throws AccountApiException, EntitlementUserApiException {
-        final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionRecreated(recreated.getNextPlan(), catalogService.getFullCatalog(), recreated.getEffectiveTransitionTime(), recreated.getSubscriptionStartDate());
-        recordTransition(event, recreated);
+    private BusinessSubscriptionEvent subscriptionCreated(final SubscriptionEvent created) throws AccountApiException, EntitlementUserApiException {
+        return BusinessSubscriptionEvent.subscriptionCreated(created.getNextPlan(), catalogService.getFullCatalog(), created.getEffectiveTransitionTime(), created.getSubscriptionStartDate());
     }
 
+    private BusinessSubscriptionEvent subscriptionRecreated(final SubscriptionEvent recreated) throws AccountApiException, EntitlementUserApiException {
+        return BusinessSubscriptionEvent.subscriptionRecreated(recreated.getNextPlan(), catalogService.getFullCatalog(), recreated.getEffectiveTransitionTime(), recreated.getSubscriptionStartDate());
+    }
 
-    public void subscriptionCancelled(final EffectiveSubscriptionEvent cancelled) throws AccountApiException, EntitlementUserApiException {
+    private BusinessSubscriptionEvent subscriptionCancelled(final SubscriptionEvent cancelled) throws AccountApiException, EntitlementUserApiException {
         // cancelled.getNextPlan() is null here - need to look at the previous one to create the correct event name
-        final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionCancelled(cancelled.getPreviousPlan(), catalogService.getFullCatalog(), cancelled.getEffectiveTransitionTime(), cancelled.getSubscriptionStartDate());
-        recordTransition(event, cancelled);
+        return BusinessSubscriptionEvent.subscriptionCancelled(cancelled.getPreviousPlan(), catalogService.getFullCatalog(), cancelled.getEffectiveTransitionTime(), cancelled.getSubscriptionStartDate());
     }
 
+    private BusinessSubscriptionEvent subscriptionChanged(final SubscriptionEvent changed) throws AccountApiException, EntitlementUserApiException {
+        return BusinessSubscriptionEvent.subscriptionChanged(changed.getNextPlan(), catalogService.getFullCatalog(), changed.getEffectiveTransitionTime(), changed.getSubscriptionStartDate());
+    }
 
-    public void subscriptionChanged(final EffectiveSubscriptionEvent changed) throws AccountApiException, EntitlementUserApiException {
-        final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionChanged(changed.getNextPlan(), catalogService.getFullCatalog(), changed.getEffectiveTransitionTime(), changed.getSubscriptionStartDate());
-        recordTransition(event, changed);
+    private BusinessSubscriptionEvent subscriptionPhaseChanged(final SubscriptionEvent phaseChanged) throws AccountApiException, EntitlementUserApiException {
+        return BusinessSubscriptionEvent.subscriptionPhaseChanged(phaseChanged.getNextPlan(), phaseChanged.getNextState(), catalogService.getFullCatalog(), phaseChanged.getEffectiveTransitionTime(), phaseChanged.getSubscriptionStartDate());
     }
 
-    public void subscriptionPhaseChanged(final EffectiveSubscriptionEvent phaseChanged) throws AccountApiException, EntitlementUserApiException {
-        final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionPhaseChanged(phaseChanged.getNextPlan(), phaseChanged.getNextState(), catalogService.getFullCatalog(), phaseChanged.getEffectiveTransitionTime(), phaseChanged.getSubscriptionStartDate());
-        recordTransition(event, phaseChanged);
+    private BusinessSubscription createNextBusinessSubscription(final EffectiveSubscriptionEvent event, final BusinessSubscriptionEvent businessEvent, final Currency currency) {
+        final BusinessSubscription nextSubscription;
+        if (BusinessSubscriptionEvent.EventType.CANCEL.equals(businessEvent.getEventType()) ||
+                BusinessSubscriptionEvent.EventType.SYSTEM_CANCEL.equals(businessEvent.getEventType())) {
+            nextSubscription = null;
+        } else {
+            nextSubscription = new BusinessSubscription(event.getNextPriceList(), event.getNextPlan(), event.getNextPhase(),
+                                                        currency, event.getEffectiveTransitionTime(), event.getNextState(),
+                                                        event.getSubscriptionId(), event.getBundleId(), catalogService.getFullCatalog());
+        }
+
+        return nextSubscription;
     }
 
-    void recordTransition(final BusinessSubscriptionEvent event, final EffectiveSubscriptionEvent transition)
-            throws AccountApiException, EntitlementUserApiException {
-        Currency currency = null;
-        String externalKey = null;
-        String accountKey = null;
-
-        // Retrieve key and currency via the bundle
-        final SubscriptionBundle bundle = entitlementApi.getBundleFromId(transition.getBundleId());
-        if (bundle != null) {
-            externalKey = bundle.getKey();
-
-            final Account account = accountApi.getAccountById(bundle.getAccountId());
-            if (account != null) {
-                accountKey = account.getExternalKey();
-                currency = account.getCurrency();
-            }
+    private BusinessSubscription createPreviousBusinessSubscription(final EffectiveSubscriptionEvent event,
+                                                                    final BusinessSubscriptionEvent businessEvent,
+                                                                    final ArrayList<BusinessSubscriptionTransition> transitions,
+                                                                    final Currency currency) {
+        if (BusinessSubscriptionEvent.EventType.ADD.equals(businessEvent.getEventType()) ||
+                BusinessSubscriptionEvent.EventType.RE_ADD.equals(businessEvent.getEventType())) {
+            return null;
         }
 
-        // The ISubscriptionTransition interface gives us all the prev/next information we need but the start date
-        // of the previous plan. We need to retrieve it from our own transitions table
-        DateTime previousEffectiveTransitionTime = null;
-        // For creation events, the prev subscription will always be null
-        if (event.getEventType() != BusinessSubscriptionEvent.EventType.ADD) {
-            final List<BusinessSubscriptionTransition> transitions = sqlDao.getTransitions(externalKey);
-            if (transitions != null && transitions.size() > 0) {
-                final BusinessSubscriptionTransition lastTransition = transitions.get(transitions.size() - 1);
-                if (lastTransition != null && lastTransition.getNextSubscription() != null) {
-                    previousEffectiveTransitionTime = lastTransition.getNextSubscription().getStartDate();
-                }
+        final BusinessSubscriptionTransition prevTransition = getPreviousBusinessSubscriptionTransitionForEvent(event, transitions);
+        return new BusinessSubscription(event.getPreviousPriceList(), event.getPreviousPlan(), event.getPreviousPhase(),
+                                        currency, prevTransition.getNextSubscription().getStartDate(), event.getPreviousState(),
+                                        event.getSubscriptionId(), event.getBundleId(), catalogService.getFullCatalog());
+    }
+
+    private BusinessSubscriptionTransition getPreviousBusinessSubscriptionTransitionForEvent(final EffectiveSubscriptionEvent event,
+                                                                                             final ArrayList<BusinessSubscriptionTransition> transitions) {
+        BusinessSubscriptionTransition transition = null;
+        for (final BusinessSubscriptionTransition candidate : transitions) {
+            final BusinessSubscription nextSubscription = candidate.getNextSubscription();
+            if (nextSubscription == null || !nextSubscription.getStartDate().isBefore(event.getEffectiveTransitionTime())) {
+                continue;
             }
-        }
 
-        // TODO Support currency changes
-        final BusinessSubscription prevSubscription;
-        if (previousEffectiveTransitionTime == null) {
-            prevSubscription = null;
-        } else {
-            prevSubscription = new BusinessSubscription(transition.getPreviousPriceList(), transition.getPreviousPlan(), transition.getPreviousPhase(), currency, previousEffectiveTransitionTime, transition.getPreviousState(), transition.getSubscriptionId(), transition.getBundleId(), catalogService.getFullCatalog());
+            if (nextSubscription.getSubscriptionId().equals(event.getSubscriptionId())) {
+                transition = candidate;
+            }
         }
-        final BusinessSubscription nextSubscription;
 
-        // next plan is null for CANCEL events
-        if (transition.getNextPlan() == null) {
-            nextSubscription = null;
-        } else {
-            nextSubscription = new BusinessSubscription(transition.getNextPriceList(), transition.getNextPlan(), transition.getNextPhase(), currency, transition.getEffectiveTransitionTime(), transition.getNextState(), transition.getSubscriptionId(), transition.getBundleId(), catalogService.getFullCatalog());
+        if (transition == null) {
+            log.error("Unable to retrieve the previous transition - THIS SHOULD NEVER HAPPEN");
+            // Fall back to the latest one?
+            transition = transitions.get(transitions.size() - 1);
         }
 
-        record(transition.getTotalOrdering(), externalKey, accountKey, transition.getRequestedTransitionTime(), event, prevSubscription, nextSubscription);
+        return transition;
     }
 
     // Public for internal reasons
@@ -145,8 +253,6 @@ public class BusinessSubscriptionTransitionRecorder {
                 prevSubscription,
                 nextSubscription
         );
-
-        log.info(transition.getEvent() + " " + transition);
         sqlDao.createTransition(transition);
     }
 }
diff --git a/analytics/src/main/java/com/ning/billing/analytics/BusinessTagRecorder.java b/analytics/src/main/java/com/ning/billing/analytics/BusinessTagRecorder.java
index 8aa4343..49c4b2b 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/BusinessTagRecorder.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/BusinessTagRecorder.java
@@ -122,13 +122,22 @@ public class BusinessTagRecorder {
             return;
         }
 
+        final Account account;
+        try {
+            account = accountApi.getAccountById(bundle.getAccountId());
+        } catch (AccountApiException e) {
+            log.warn("Ignoring tag addition of {} for bundle id {} and account id {} (account does not exist)", new Object[]{name, objectId.toString(), bundle.getAccountId()});
+            return;
+        }
+
         /*
          * Note: we store tags associated to bundles, not to subscriptions.
          * Subscriptions are in the core of killbill and not exposed in Analytics to avoid a hard dependency
          * (i.e. dashboards should not rely on killbill ids).
          */
+        final String accountKey = account.getExternalKey();
         final String externalKey = bundle.getKey();
-        subscriptionTransitionTagSqlDao.addTag(externalKey, name);
+        subscriptionTransitionTagSqlDao.addTag(accountKey, externalKey, name);
     }
 
     private void tagRemovedForBundle(final UUID objectId, final String name) {
diff --git a/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessOverdueStatusBinder.java b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessOverdueStatusBinder.java
index eb1bd9d..1e0575e 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessOverdueStatusBinder.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessOverdueStatusBinder.java
@@ -38,6 +38,7 @@ public @interface BusinessOverdueStatusBinder {
         public Binder build(final Annotation annotation) {
             return new Binder<BusinessOverdueStatusBinder, BusinessOverdueStatus>() {
                 public void bind(final SQLStatement q, final BusinessOverdueStatusBinder bind, final BusinessOverdueStatus overdueStatus) {
+                    q.bind("account_key", overdueStatus.getAccountKey());
                     q.bind("external_key", overdueStatus.getExternalKey());
                     q.bind("status", overdueStatus.getStatus());
 
diff --git a/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessOverdueStatusMapper.java b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessOverdueStatusMapper.java
index dcd6bf2..819c9ab 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessOverdueStatusMapper.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessOverdueStatusMapper.java
@@ -30,10 +30,11 @@ public class BusinessOverdueStatusMapper implements ResultSetMapper<BusinessOver
     @Override
     public BusinessOverdueStatus map(final int index, final ResultSet r, final StatementContext ctx) throws SQLException {
         final String externalKey = r.getString(1);
-        final String status = r.getString(2);
-        final DateTime startDate = new DateTime(r.getLong(3), DateTimeZone.UTC);
-        final DateTime endDate = new DateTime(r.getLong(4), DateTimeZone.UTC);
+        final String accountKey = r.getString(2);
+        final String status = r.getString(3);
+        final DateTime startDate = new DateTime(r.getLong(4), DateTimeZone.UTC);
+        final DateTime endDate = new DateTime(r.getLong(5), DateTimeZone.UTC);
 
-        return new BusinessOverdueStatus(endDate, externalKey, startDate, status);
+        return new BusinessOverdueStatus(accountKey, endDate, externalKey, startDate, status);
     }
 }
diff --git a/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionFieldMapper.java b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionFieldMapper.java
index 68ef0e2..53cfef7 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionFieldMapper.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionFieldMapper.java
@@ -27,6 +27,10 @@ import com.ning.billing.analytics.model.BusinessSubscriptionTransitionField;
 public class BusinessSubscriptionTransitionFieldMapper implements ResultSetMapper<BusinessSubscriptionTransitionField> {
     @Override
     public BusinessSubscriptionTransitionField map(final int index, final ResultSet r, final StatementContext ctx) throws SQLException {
-        return new BusinessSubscriptionTransitionField(r.getString(1), r.getString(2), r.getString(3));
+        final String externalKey = r.getString(1);
+        final String accountKey = r.getString(2);
+        final String name = r.getString(3);
+        final String value = r.getString(4);
+        return new BusinessSubscriptionTransitionField(accountKey, externalKey, name, value);
     }
 }
diff --git a/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionFieldSqlDao.java b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionFieldSqlDao.java
index d22076e..a316939 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionFieldSqlDao.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionFieldSqlDao.java
@@ -33,7 +33,8 @@ public interface BusinessSubscriptionTransitionFieldSqlDao {
     List<BusinessSubscriptionTransitionField> getFieldsForBusinessSubscriptionTransition(@Bind("external_key") final String externalKey);
 
     @SqlUpdate
-    int addField(@Bind("external_key") final String externalKey, @Bind("name") final String name, @Bind("value") final String value);
+    int addField(@Bind("account_key") final String accountKey, @Bind("external_key") final String externalKey,
+                 @Bind("name") final String name, @Bind("value") final String value);
 
     @SqlUpdate
     int removeField(@Bind("external_key") final String externalKey, @Bind("name") final String name);
diff --git a/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionSqlDao.java b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionSqlDao.java
index fe90321..2092d47 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionSqlDao.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionSqlDao.java
@@ -22,19 +22,29 @@ import org.skife.jdbi.v2.sqlobject.Bind;
 import org.skife.jdbi.v2.sqlobject.SqlQuery;
 import org.skife.jdbi.v2.sqlobject.SqlUpdate;
 import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
+import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
 import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
 
 import com.ning.billing.analytics.model.BusinessSubscriptionTransition;
 
 @ExternalizedSqlViaStringTemplate3()
 @RegisterMapper(BusinessSubscriptionTransitionMapper.class)
-public interface BusinessSubscriptionTransitionSqlDao {
+public interface BusinessSubscriptionTransitionSqlDao extends Transactional<BusinessSubscriptionTransitionSqlDao> {
     @SqlQuery
     List<BusinessSubscriptionTransition> getTransitions(@Bind("external_key") final String externalKey);
 
+    @SqlQuery
+    List<BusinessSubscriptionTransition> getTransitionForSubscription(@Bind("subscription_id") final String subscriptionId);
+
     @SqlUpdate
     int createTransition(@BusinessSubscriptionTransitionBinder final BusinessSubscriptionTransition transition);
 
     @SqlUpdate
+    void updateTransition(@Bind("total_ordering") long totalOrdering, @BusinessSubscriptionTransitionBinder BusinessSubscriptionTransition updatedFirstTransition);
+
+    @SqlUpdate
+    void deleteTransitionsForBundle(@Bind("external_key") final String externalKey);
+
+    @SqlUpdate
     void test();
 }
diff --git a/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionTagMapper.java b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionTagMapper.java
index 36f7631..47a7be9 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionTagMapper.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionTagMapper.java
@@ -27,6 +27,9 @@ import com.ning.billing.analytics.model.BusinessSubscriptionTransitionTag;
 public class BusinessSubscriptionTransitionTagMapper implements ResultSetMapper<BusinessSubscriptionTransitionTag> {
     @Override
     public BusinessSubscriptionTransitionTag map(final int index, final ResultSet r, final StatementContext ctx) throws SQLException {
-        return new BusinessSubscriptionTransitionTag(r.getString(1), r.getString(2));
+        final String externalKey = r.getString(1);
+        final String accountKey = r.getString(2);
+        final String name = r.getString(3);
+        return new BusinessSubscriptionTransitionTag(accountKey, externalKey, name);
     }
 }
diff --git a/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionTagSqlDao.java b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionTagSqlDao.java
index c073a53..094af9a 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionTagSqlDao.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionTagSqlDao.java
@@ -33,7 +33,7 @@ public interface BusinessSubscriptionTransitionTagSqlDao {
     List<BusinessSubscriptionTransitionTag> getTagsForBusinessSubscriptionTransition(@Bind("external_key") final String externalKey);
 
     @SqlUpdate
-    int addTag(@Bind("external_key") final String externalKey, @Bind("name") final String name);
+    int addTag(@Bind("account_key") final String accountKey, @Bind("external_key") final String externalKey, @Bind("name") final String name);
 
     @SqlUpdate
     int removeTag(@Bind("external_key") final String externalKey, @Bind("name") final String name);
diff --git a/analytics/src/main/java/com/ning/billing/analytics/model/BusinessAccountField.java b/analytics/src/main/java/com/ning/billing/analytics/model/BusinessAccountField.java
index b990125..3d9df23 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/model/BusinessAccountField.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/model/BusinessAccountField.java
@@ -33,8 +33,8 @@ public class BusinessAccountField extends BusinessField {
         final StringBuilder sb = new StringBuilder();
         sb.append("BusinessAccountField");
         sb.append("{accountKey='").append(accountKey).append('\'');
-        sb.append(", name='").append(name).append('\'');
-        sb.append(", value='").append(value).append('\'');
+        sb.append(", name='").append(getName()).append('\'');
+        sb.append(", value='").append(getValue()).append('\'');
         sb.append('}');
         return sb.toString();
     }
@@ -53,10 +53,10 @@ public class BusinessAccountField extends BusinessField {
         if (accountKey != null ? !accountKey.equals(that.accountKey) : that.accountKey != null) {
             return false;
         }
-        if (name != null ? !name.equals(that.name) : that.name != null) {
+        if (getName() != null ? !getName().equals(that.getName()) : that.getName() != null) {
             return false;
         }
-        if (value != null ? !value.equals(that.value) : that.value != null) {
+        if (getValue() != null ? !getValue().equals(that.getValue()) : that.getValue() != null) {
             return false;
         }
 
@@ -66,8 +66,8 @@ public class BusinessAccountField extends BusinessField {
     @Override
     public int hashCode() {
         int result = accountKey != null ? accountKey.hashCode() : 0;
-        result = 31 * result + (name != null ? name.hashCode() : 0);
-        result = 31 * result + (value != null ? value.hashCode() : 0);
+        result = 31 * result + (getName() != null ? getName().hashCode() : 0);
+        result = 31 * result + (getValue() != null ? getValue().hashCode() : 0);
         return result;
     }
 }
diff --git a/analytics/src/main/java/com/ning/billing/analytics/model/BusinessAccountTag.java b/analytics/src/main/java/com/ning/billing/analytics/model/BusinessAccountTag.java
index 3a8de99..1461c6d 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/model/BusinessAccountTag.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/model/BusinessAccountTag.java
@@ -33,7 +33,7 @@ public class BusinessAccountTag extends BusinessTag {
         final StringBuilder sb = new StringBuilder();
         sb.append("BusinessAccountTag");
         sb.append("{accountKey='").append(accountKey).append('\'');
-        sb.append(", name='").append(name).append('\'');
+        sb.append(", name='").append(getName()).append('\'');
         sb.append('}');
         return sb.toString();
     }
@@ -52,7 +52,7 @@ public class BusinessAccountTag extends BusinessTag {
         if (accountKey != null ? !accountKey.equals(that.accountKey) : that.accountKey != null) {
             return false;
         }
-        if (name != null ? !name.equals(that.name) : that.name != null) {
+        if (getName() != null ? !getName().equals(that.getName()) : that.getName() != null) {
             return false;
         }
 
@@ -62,7 +62,7 @@ public class BusinessAccountTag extends BusinessTag {
     @Override
     public int hashCode() {
         int result = accountKey != null ? accountKey.hashCode() : 0;
-        result = 31 * result + (name != null ? name.hashCode() : 0);
+        result = 31 * result + (getName() != null ? getName().hashCode() : 0);
         return result;
     }
 }
diff --git a/analytics/src/main/java/com/ning/billing/analytics/model/BusinessField.java b/analytics/src/main/java/com/ning/billing/analytics/model/BusinessField.java
index 68552d0..5d5e727 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/model/BusinessField.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/model/BusinessField.java
@@ -17,8 +17,8 @@
 package com.ning.billing.analytics.model;
 
 public abstract class BusinessField {
-    protected final String name;
-    protected final String value;
+    private final String name;
+    private final String value;
 
     public BusinessField(final String name, final String value) {
         this.name = name;
diff --git a/analytics/src/main/java/com/ning/billing/analytics/model/BusinessInvoiceField.java b/analytics/src/main/java/com/ning/billing/analytics/model/BusinessInvoiceField.java
index cf69497..d4597f4 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/model/BusinessInvoiceField.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/model/BusinessInvoiceField.java
@@ -35,8 +35,8 @@ public class BusinessInvoiceField extends BusinessField {
         final StringBuilder sb = new StringBuilder();
         sb.append("BusinessInvoiceField");
         sb.append("{invoiceId='").append(invoiceId).append('\'');
-        sb.append(", name='").append(name).append('\'');
-        sb.append(", value='").append(value).append('\'');
+        sb.append(", name='").append(getName()).append('\'');
+        sb.append(", value='").append(getValue()).append('\'');
         sb.append('}');
         return sb.toString();
     }
@@ -55,10 +55,10 @@ public class BusinessInvoiceField extends BusinessField {
         if (invoiceId != null ? !invoiceId.equals(that.invoiceId) : that.invoiceId != null) {
             return false;
         }
-        if (name != null ? !name.equals(that.name) : that.name != null) {
+        if (getName() != null ? !getName().equals(that.getName()) : that.getName() != null) {
             return false;
         }
-        if (value != null ? !value.equals(that.value) : that.value != null) {
+        if (getValue() != null ? !getValue().equals(that.getValue()) : that.getValue() != null) {
             return false;
         }
 
@@ -68,8 +68,8 @@ public class BusinessInvoiceField extends BusinessField {
     @Override
     public int hashCode() {
         int result = invoiceId != null ? invoiceId.hashCode() : 0;
-        result = 31 * result + (name != null ? name.hashCode() : 0);
-        result = 31 * result + (value != null ? value.hashCode() : 0);
+        result = 31 * result + (getName() != null ? getName().hashCode() : 0);
+        result = 31 * result + (getValue() != null ? getValue().hashCode() : 0);
         return result;
     }
 }
diff --git a/analytics/src/main/java/com/ning/billing/analytics/model/BusinessInvoicePaymentField.java b/analytics/src/main/java/com/ning/billing/analytics/model/BusinessInvoicePaymentField.java
index bcfc85b..c79b2b1 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/model/BusinessInvoicePaymentField.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/model/BusinessInvoicePaymentField.java
@@ -35,8 +35,8 @@ public class BusinessInvoicePaymentField extends BusinessField {
         final StringBuilder sb = new StringBuilder();
         sb.append("BusinessPaymentField");
         sb.append("{paymentId='").append(paymentId).append('\'');
-        sb.append(", name='").append(name).append('\'');
-        sb.append(", value='").append(value).append('\'');
+        sb.append(", name='").append(getName()).append('\'');
+        sb.append(", value='").append(getValue()).append('\'');
         sb.append('}');
         return sb.toString();
     }
@@ -55,10 +55,10 @@ public class BusinessInvoicePaymentField extends BusinessField {
         if (paymentId != null ? !paymentId.equals(that.paymentId) : that.paymentId != null) {
             return false;
         }
-        if (name != null ? !name.equals(that.name) : that.name != null) {
+        if (getName() != null ? !getName().equals(that.getName()) : that.getName() != null) {
             return false;
         }
-        if (value != null ? !value.equals(that.value) : that.value != null) {
+        if (getValue() != null ? !getValue().equals(that.getValue()) : that.getValue() != null) {
             return false;
         }
 
@@ -68,8 +68,8 @@ public class BusinessInvoicePaymentField extends BusinessField {
     @Override
     public int hashCode() {
         int result = paymentId != null ? paymentId.hashCode() : 0;
-        result = 31 * result + (name != null ? name.hashCode() : 0);
-        result = 31 * result + (value != null ? value.hashCode() : 0);
+        result = 31 * result + (getName() != null ? getName().hashCode() : 0);
+        result = 31 * result + (getValue() != null ? getValue().hashCode() : 0);
         return result;
     }
 }
diff --git a/analytics/src/main/java/com/ning/billing/analytics/model/BusinessInvoicePaymentTag.java b/analytics/src/main/java/com/ning/billing/analytics/model/BusinessInvoicePaymentTag.java
index fd48b1c..e1cbbd5 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/model/BusinessInvoicePaymentTag.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/model/BusinessInvoicePaymentTag.java
@@ -35,7 +35,7 @@ public class BusinessInvoicePaymentTag extends BusinessTag {
         final StringBuilder sb = new StringBuilder();
         sb.append("BusinessPaymentTag");
         sb.append("{paymentId='").append(paymentId).append('\'');
-        sb.append(", name='").append(name).append('\'');
+        sb.append(", name='").append(getName()).append('\'');
         sb.append('}');
         return sb.toString();
     }
@@ -54,7 +54,7 @@ public class BusinessInvoicePaymentTag extends BusinessTag {
         if (paymentId != null ? !paymentId.equals(that.paymentId) : that.paymentId != null) {
             return false;
         }
-        if (name != null ? !name.equals(that.name) : that.name != null) {
+        if (getName() != null ? !getName().equals(that.getName()) : that.getName() != null) {
             return false;
         }
 
@@ -64,7 +64,7 @@ public class BusinessInvoicePaymentTag extends BusinessTag {
     @Override
     public int hashCode() {
         int result = paymentId != null ? paymentId.hashCode() : 0;
-        result = 31 * result + (name != null ? name.hashCode() : 0);
+        result = 31 * result + (getName() != null ? getName().hashCode() : 0);
         return result;
     }
 }
diff --git a/analytics/src/main/java/com/ning/billing/analytics/model/BusinessInvoiceTag.java b/analytics/src/main/java/com/ning/billing/analytics/model/BusinessInvoiceTag.java
index e9efe5a..41b7d27 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/model/BusinessInvoiceTag.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/model/BusinessInvoiceTag.java
@@ -35,7 +35,7 @@ public class BusinessInvoiceTag extends BusinessTag {
         final StringBuilder sb = new StringBuilder();
         sb.append("BusinessInvoiceTag");
         sb.append("{paymentId='").append(invoiceId).append('\'');
-        sb.append(", name='").append(name).append('\'');
+        sb.append(", name='").append(getName()).append('\'');
         sb.append('}');
         return sb.toString();
     }
@@ -54,7 +54,7 @@ public class BusinessInvoiceTag extends BusinessTag {
         if (invoiceId != null ? !invoiceId.equals(that.invoiceId) : that.invoiceId != null) {
             return false;
         }
-        if (name != null ? !name.equals(that.name) : that.name != null) {
+        if (getName() != null ? !getName().equals(that.getName()) : that.getName() != null) {
             return false;
         }
 
@@ -64,7 +64,7 @@ public class BusinessInvoiceTag extends BusinessTag {
     @Override
     public int hashCode() {
         int result = invoiceId != null ? invoiceId.hashCode() : 0;
-        result = 31 * result + (name != null ? name.hashCode() : 0);
+        result = 31 * result + (getName() != null ? getName().hashCode() : 0);
         return result;
     }
 }
diff --git a/analytics/src/main/java/com/ning/billing/analytics/model/BusinessOverdueStatus.java b/analytics/src/main/java/com/ning/billing/analytics/model/BusinessOverdueStatus.java
index dd8a310..e76a02e 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/model/BusinessOverdueStatus.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/model/BusinessOverdueStatus.java
@@ -19,18 +19,25 @@ package com.ning.billing.analytics.model;
 import org.joda.time.DateTime;
 
 public class BusinessOverdueStatus {
+    private final String accountKey;
     private final String externalKey;
     private final String status;
     private final DateTime startDate;
     private final DateTime endDate;
 
-    public BusinessOverdueStatus(final DateTime endDate, final String externalKey, final DateTime startDate, final String status) {
+    public BusinessOverdueStatus(final String accountKey, final DateTime endDate, final String externalKey,
+                                 final DateTime startDate, final String status) {
+        this.accountKey = accountKey;
         this.endDate = endDate;
         this.externalKey = externalKey;
         this.startDate = startDate;
         this.status = status;
     }
 
+    public String getAccountKey() {
+        return accountKey;
+    }
+
     public DateTime getEndDate() {
         return endDate;
     }
@@ -51,7 +58,8 @@ public class BusinessOverdueStatus {
     public String toString() {
         final StringBuilder sb = new StringBuilder();
         sb.append("BusinessOverdueStatus");
-        sb.append("{endDate=").append(endDate);
+        sb.append("{accountKey=").append(accountKey);
+        sb.append(", endDate='").append(endDate).append('\'');
         sb.append(", externalKey='").append(externalKey).append('\'');
         sb.append(", status='").append(status).append('\'');
         sb.append(", startDate=").append(startDate);
@@ -70,6 +78,9 @@ public class BusinessOverdueStatus {
 
         final BusinessOverdueStatus that = (BusinessOverdueStatus) o;
 
+        if (accountKey != null ? !accountKey.equals(that.accountKey) : that.accountKey != null) {
+            return false;
+        }
         if (endDate != null ? !endDate.equals(that.endDate) : that.endDate != null) {
             return false;
         }
@@ -88,7 +99,8 @@ public class BusinessOverdueStatus {
 
     @Override
     public int hashCode() {
-        int result = externalKey != null ? externalKey.hashCode() : 0;
+        int result = accountKey != null ? accountKey.hashCode() : 0;
+        result = 31 * result + (externalKey != null ? externalKey.hashCode() : 0);
         result = 31 * result + (status != null ? status.hashCode() : 0);
         result = 31 * result + (startDate != null ? startDate.hashCode() : 0);
         result = 31 * result + (endDate != null ? endDate.hashCode() : 0);
diff --git a/analytics/src/main/java/com/ning/billing/analytics/model/BusinessSubscriptionTransition.java b/analytics/src/main/java/com/ning/billing/analytics/model/BusinessSubscriptionTransition.java
index 3618b7d..2e2a6ce 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/model/BusinessSubscriptionTransition.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/model/BusinessSubscriptionTransition.java
@@ -112,15 +112,28 @@ public class BusinessSubscriptionTransition {
 
         final BusinessSubscriptionTransition that = (BusinessSubscriptionTransition) o;
 
+        return totalOrdering == that.totalOrdering && isDuplicateOf(that);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = (int) (totalOrdering ^ (totalOrdering >>> 32));
+        result = 31 * result + (externalKey != null ? externalKey.hashCode() : 0);
+        result = 31 * result + (accountKey != null ? accountKey.hashCode() : 0);
+        result = 31 * result + (requestedTimestamp != null ? requestedTimestamp.hashCode() : 0);
+        result = 31 * result + (event != null ? event.hashCode() : 0);
+        result = 31 * result + (previousSubscription != null ? previousSubscription.hashCode() : 0);
+        result = 31 * result + (nextSubscription != null ? nextSubscription.hashCode() : 0);
+        return result;
+    }
+
+    public boolean isDuplicateOf(final BusinessSubscriptionTransition that) {
         if (accountKey != null ? !accountKey.equals(that.accountKey) : that.accountKey != null) {
             return false;
         }
         if (event != null ? !event.equals(that.event) : that.event != null) {
             return false;
         }
-        if (totalOrdering != that.totalOrdering) {
-            return false;
-        }
         if (externalKey != null ? !externalKey.equals(that.externalKey) : that.externalKey != null) {
             return false;
         }
@@ -136,16 +149,4 @@ public class BusinessSubscriptionTransition {
 
         return true;
     }
-
-    @Override
-    public int hashCode() {
-        int result = (int) (totalOrdering ^ (totalOrdering >>> 32));
-        result = 31 * result + (externalKey != null ? externalKey.hashCode() : 0);
-        result = 31 * result + (accountKey != null ? accountKey.hashCode() : 0);
-        result = 31 * result + (requestedTimestamp != null ? requestedTimestamp.hashCode() : 0);
-        result = 31 * result + (event != null ? event.hashCode() : 0);
-        result = 31 * result + (previousSubscription != null ? previousSubscription.hashCode() : 0);
-        result = 31 * result + (nextSubscription != null ? nextSubscription.hashCode() : 0);
-        return result;
-    }
 }
diff --git a/analytics/src/main/java/com/ning/billing/analytics/model/BusinessSubscriptionTransitionField.java b/analytics/src/main/java/com/ning/billing/analytics/model/BusinessSubscriptionTransitionField.java
index bab1259..f54e39e 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/model/BusinessSubscriptionTransitionField.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/model/BusinessSubscriptionTransitionField.java
@@ -17,13 +17,19 @@
 package com.ning.billing.analytics.model;
 
 public class BusinessSubscriptionTransitionField extends BusinessField {
+    private final String accountKey;
     private final String externalKey;
 
-    public BusinessSubscriptionTransitionField(final String externalKey, final String name, final String value) {
+    public BusinessSubscriptionTransitionField(final String accountKey, final String externalKey, final String name, final String value) {
         super(name, value);
+        this.accountKey = accountKey;
         this.externalKey = externalKey;
     }
 
+    public String getAccountKey() {
+        return accountKey;
+    }
+
     public String getExternalKey() {
         return externalKey;
     }
@@ -32,9 +38,10 @@ public class BusinessSubscriptionTransitionField extends BusinessField {
     public String toString() {
         final StringBuilder sb = new StringBuilder();
         sb.append("BusinessSubscriptionTransitionField");
-        sb.append("{externalKey='").append(externalKey).append('\'');
-        sb.append(", name='").append(name).append('\'');
-        sb.append(", value='").append(value).append('\'');
+        sb.append("{accountKey='").append(accountKey).append('\'');
+        sb.append(", externalKey='").append(externalKey).append('\'');
+        sb.append(", name='").append(getName()).append('\'');
+        sb.append(", value='").append(getValue()).append('\'');
         sb.append('}');
         return sb.toString();
     }
@@ -50,13 +57,16 @@ public class BusinessSubscriptionTransitionField extends BusinessField {
 
         final BusinessSubscriptionTransitionField that = (BusinessSubscriptionTransitionField) o;
 
+        if (accountKey != null ? !accountKey.equals(that.accountKey) : that.accountKey != null) {
+            return false;
+        }
         if (externalKey != null ? !externalKey.equals(that.externalKey) : that.externalKey != null) {
             return false;
         }
-        if (name != null ? !name.equals(that.name) : that.name != null) {
+        if (getName() != null ? !getName().equals(that.getName()) : that.getName() != null) {
             return false;
         }
-        if (value != null ? !value.equals(that.value) : that.value != null) {
+        if (getValue() != null ? !getValue().equals(that.getValue()) : that.getValue() != null) {
             return false;
         }
 
@@ -65,9 +75,10 @@ public class BusinessSubscriptionTransitionField extends BusinessField {
 
     @Override
     public int hashCode() {
-        int result = externalKey != null ? externalKey.hashCode() : 0;
-        result = 31 * result + (name != null ? name.hashCode() : 0);
-        result = 31 * result + (value != null ? value.hashCode() : 0);
+        int result = accountKey != null ? accountKey.hashCode() : 0;
+        result = 31 * result + (externalKey != null ? externalKey.hashCode() : 0);
+        result = 31 * result + (getName() != null ? getName().hashCode() : 0);
+        result = 31 * result + (getValue() != null ? getValue().hashCode() : 0);
         return result;
     }
 }
diff --git a/analytics/src/main/java/com/ning/billing/analytics/model/BusinessSubscriptionTransitionTag.java b/analytics/src/main/java/com/ning/billing/analytics/model/BusinessSubscriptionTransitionTag.java
index 522194a..0539461 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/model/BusinessSubscriptionTransitionTag.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/model/BusinessSubscriptionTransitionTag.java
@@ -17,13 +17,19 @@
 package com.ning.billing.analytics.model;
 
 public class BusinessSubscriptionTransitionTag extends BusinessTag {
+    private final String accountKey;
     private final String externalKey;
 
-    public BusinessSubscriptionTransitionTag(final String externalKey, final String name) {
+    public BusinessSubscriptionTransitionTag(final String accountKey, final String externalKey, final String name) {
         super(name);
+        this.accountKey = accountKey;
         this.externalKey = externalKey;
     }
 
+    public String getAccountKey() {
+        return accountKey;
+    }
+
     public String getExternalKey() {
         return externalKey;
     }
@@ -32,8 +38,9 @@ public class BusinessSubscriptionTransitionTag extends BusinessTag {
     public String toString() {
         final StringBuilder sb = new StringBuilder();
         sb.append("BusinessSubscriptionTransitionTag");
-        sb.append("{externalKey='").append(externalKey).append('\'');
-        sb.append(", name='").append(name).append('\'');
+        sb.append("{accountKey='").append(accountKey).append('\'');
+        sb.append(", externalKey='").append(externalKey).append('\'');
+        sb.append(", name='").append(getName()).append('\'');
         sb.append('}');
         return sb.toString();
     }
@@ -49,10 +56,13 @@ public class BusinessSubscriptionTransitionTag extends BusinessTag {
 
         final BusinessSubscriptionTransitionTag that = (BusinessSubscriptionTransitionTag) o;
 
+        if (accountKey != null ? !accountKey.equals(that.accountKey) : that.accountKey != null) {
+            return false;
+        }
         if (externalKey != null ? !externalKey.equals(that.externalKey) : that.externalKey != null) {
             return false;
         }
-        if (name != null ? !name.equals(that.name) : that.name != null) {
+        if (getName() != null ? !getName().equals(that.getName()) : that.getName() != null) {
             return false;
         }
 
@@ -61,8 +71,9 @@ public class BusinessSubscriptionTransitionTag extends BusinessTag {
 
     @Override
     public int hashCode() {
-        int result = externalKey != null ? externalKey.hashCode() : 0;
-        result = 31 * result + (name != null ? name.hashCode() : 0);
+        int result = accountKey != null ? accountKey.hashCode() : 0;
+        result = 31 * result + (externalKey != null ? externalKey.hashCode() : 0);
+        result = 31 * result + (getName() != null ? getName().hashCode() : 0);
         return result;
     }
 }
diff --git a/analytics/src/main/java/com/ning/billing/analytics/model/BusinessTag.java b/analytics/src/main/java/com/ning/billing/analytics/model/BusinessTag.java
index 80343e1..64ddaa9 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/model/BusinessTag.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/model/BusinessTag.java
@@ -17,7 +17,7 @@
 package com.ning.billing.analytics.model;
 
 public abstract class BusinessTag {
-    protected final String name;
+    private final String name;
 
     public BusinessTag(final String name) {
         this.name = name;
diff --git a/analytics/src/main/resources/com/ning/billing/analytics/dao/BusinessOverdueStatusSqlDao.sql.stg b/analytics/src/main/resources/com/ning/billing/analytics/dao/BusinessOverdueStatusSqlDao.sql.stg
index d412125..c1c7140 100644
--- a/analytics/src/main/resources/com/ning/billing/analytics/dao/BusinessOverdueStatusSqlDao.sql.stg
+++ b/analytics/src/main/resources/com/ning/billing/analytics/dao/BusinessOverdueStatusSqlDao.sql.stg
@@ -3,6 +3,7 @@ group BusinessOverdueStatus;
 getOverdueStatusesForBundle(external_key) ::= <<
 select
   external_key
+, account_key
 , status
 , start_date
 , end_date
@@ -15,11 +16,13 @@ order by start_date asc
 createOverdueStatus() ::= <<
 insert into bos (
   external_key
+, account_key
 , status
 , start_date
 , end_date
 ) values (
   :external_key
+, :account_key
 , :status
 , :start_date
 , :end_date
@@ -28,4 +31,4 @@ insert into bos (
 
 test() ::= <<
 select 1 from bos;
->>
\ No newline at end of file
+>>
diff --git a/analytics/src/main/resources/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionFieldSqlDao.sql.stg b/analytics/src/main/resources/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionFieldSqlDao.sql.stg
index 522e6d7..0330ac0 100644
--- a/analytics/src/main/resources/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionFieldSqlDao.sql.stg
+++ b/analytics/src/main/resources/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionFieldSqlDao.sql.stg
@@ -3,6 +3,7 @@ group BusinessSubscriptionTransitionField;
 getFieldsForBusinessSubscriptionTransition(external_key) ::=<<
 select
   external_key
+, account_key
 , name
 , value
 from bst_fields
@@ -13,10 +14,12 @@ where external_key = :external_key
 addField(external_key, name, value) ::=<<
 insert into bst_fields (
   external_key
+, account_key
 , name
 , value
 ) values (
   :external_key
+, :account_key
 , :name
 , :value
 );
@@ -28,4 +31,4 @@ delete from bst_fields where external_key = :external_key and name = :name;
 
 test() ::= <<
 select 1 from bst_fields;
->>
\ No newline at end of file
+>>
diff --git a/analytics/src/main/resources/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionSqlDao.sql.stg b/analytics/src/main/resources/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionSqlDao.sql.stg
index c80b6b5..7192e5f 100644
--- a/analytics/src/main/resources/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionSqlDao.sql.stg
+++ b/analytics/src/main/resources/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionSqlDao.sql.stg
@@ -41,6 +41,47 @@ getTransitions(external_key) ::= <<
   ;
 >>
 
+getTransitionForSubscription(subscription_id) ::= <<
+  select
+    total_ordering
+  , external_key
+  , account_key
+  , requested_timestamp
+  , event
+  , prev_product_name
+  , prev_product_type
+  , prev_product_category
+  , prev_slug
+  , prev_phase
+  , prev_billing_period
+  , prev_price
+  , prev_price_list
+  , prev_mrr
+  , prev_currency
+  , prev_start_date
+  , prev_state
+  , prev_subscription_id
+  , prev_bundle_id
+  , next_product_name
+  , next_product_type
+  , next_product_category
+  , next_slug
+  , next_phase
+  , next_billing_period
+  , next_price
+  , next_price_list
+  , next_mrr
+  , next_currency
+  , next_start_date
+  , next_state
+  , next_subscription_id
+  , next_bundle_id
+  from bst
+  where prev_subscription_id = :subscription_id or next_subscription_id = :subscription_id
+  order by requested_timestamp asc
+  ;
+>>
+
 createTransition() ::= <<
   insert ignore into bst(
     total_ordering
@@ -113,6 +154,50 @@ createTransition() ::= <<
   );
 >>
 
+updateTransition() ::= <<
+  update bst set
+    total_ordering = :total_ordering
+  , external_key = :external_key
+  , account_key = :account_key
+  , requested_timestamp = :requested_timestamp
+  , event = :event
+  , prev_product_name = :prev_product_name
+  , prev_product_type = :prev_product_type
+  , prev_product_category = :prev_product_category
+  , prev_slug = :prev_slug
+  , prev_phase = :prev_phase
+  , prev_billing_period = :prev_billing_period
+  , prev_price = :prev_price
+  , prev_price_list = :prev_price_list
+  , prev_mrr = :prev_mrr
+  , prev_currency = :prev_currency
+  , prev_start_date = :prev_start_date
+  , prev_state = :prev_state
+  , prev_subscription_id = :prev_subscription_id
+  , prev_bundle_id = :prev_bundle_id
+  , next_product_name = :next_product_name
+  , next_product_type = :next_product_type
+  , next_product_category = :next_product_category
+  , next_slug = :next_slug
+  , next_phase = :next_phase
+  , next_billing_period = :next_billing_period
+  , next_price = :next_price
+  , next_price_list = :next_price_list
+  , next_mrr = :next_mrr
+  , next_currency = :next_currency
+  , next_start_date = :next_start_date
+  , next_state = :next_state
+  , next_subscription_id = :next_subscription_id
+  , next_bundle_id = :next_bundle_id
+  where total_ordering = :total_ordering
+>>
+
+deleteTransitionsForBundle(external_key) ::= <<
+  delete from bst
+  where external_key=:external_key
+  ;
+>>
+
 test() ::= <<
   select 1 from bst;
 >>
diff --git a/analytics/src/main/resources/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionTagSqlDao.sql.stg b/analytics/src/main/resources/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionTagSqlDao.sql.stg
index 3d2255f..dcd99aa 100644
--- a/analytics/src/main/resources/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionTagSqlDao.sql.stg
+++ b/analytics/src/main/resources/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionTagSqlDao.sql.stg
@@ -3,6 +3,7 @@ group BusinessSubscriptionTransitionTag;
 getTagsForBusinessSubscriptionTransition(external_key) ::=<<
 select
   external_key
+, account_key
 , name
 from bst_tags
 where external_key = :external_key
@@ -12,9 +13,11 @@ where external_key = :external_key
 addTag(external_key, name) ::=<<
 insert into bst_tags (
   external_key
+, account_key
 , name
 ) values (
   :external_key
+, :account_key
 , :name
 );
 >>
@@ -25,4 +28,4 @@ delete from bst_tags where external_key = :external_key and name = :name;
 
 test() ::= <<
 select 1 from bst_tags;
->>
\ No newline at end of file
+>>
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 43aaad8..c7f9d1b 100644
--- a/analytics/src/main/resources/com/ning/billing/analytics/ddl.sql
+++ b/analytics/src/main/resources/com/ning/billing/analytics/ddl.sql
@@ -1,6 +1,7 @@
 drop table if exists bst;
 create table bst (
-  total_ordering bigint default 0
+  record_id int(11) unsigned not null auto_increment
+, total_ordering bigint default 0
 , external_key varchar(50) not null comment 'Bundle external key'
 , account_key varchar(50) not null comment 'Account external key'
 , requested_timestamp bigint not null
@@ -33,13 +34,14 @@ create table bst (
 , next_state varchar(32) default null
 , next_subscription_id varchar(100) default null
 , next_bundle_id varchar(100) default null
-, primary key(total_ordering)
+, primary key(record_id)
 ) engine=innodb comment 'Business Subscription Transitions, track bundles lifecycle';
 create index bst_key_index on bst (external_key, requested_timestamp asc);
 
 drop table if exists bac;
 create table bac (
-  account_key varchar(50) not null
+  record_id int(11) unsigned not null auto_increment
+, account_key varchar(50) not null
 , name varchar(100) not null
 , created_date bigint not null
 , updated_date bigint not null
@@ -51,12 +53,14 @@ create table bac (
 , credit_card_type varchar(32) default null
 , billing_address_country varchar(100) default null
 , currency char(3) default null
+, primary key(record_id)
 ) engine=innodb comment 'Business ACcounts, keep a record of all accounts';
 create unique index bac_key_index on bac (account_key);
 
 drop table if exists bin;
 create table bin (
-  invoice_id char(36) not null
+  record_id int(11) unsigned not null auto_increment
+, invoice_id char(36) not null
 , created_date bigint not null
 , updated_date bigint not null
 , account_key varchar(50) not null
@@ -67,12 +71,14 @@ create table bin (
 , amount_paid numeric(10, 4) default 0 comment 'Sums of the successful payments made for this invoice minus the refunds associated with this invoice'
 , amount_charged numeric(10, 4) default 0 comment 'Sums of the invoice items amount'
 , amount_credited numeric(10, 4) default 0 comment 'Sums of the credit items'
+, primary key(record_id)
 ) engine=innodb comment 'Business INvoices, keep a record of generated invoices';
 create unique index bin_key_index on bin (invoice_id);
 
 drop table if exists bii;
 create table bii (
-  item_id char(36) not null
+  record_id int(11) unsigned not null auto_increment
+, item_id char(36) not null
 , created_date bigint not null
 , updated_date bigint not null
 , invoice_id char(36) not null
@@ -88,12 +94,14 @@ create table bii (
 , end_date bigint default null
 , amount numeric(10, 4) default 0
 , currency char(3) default null
+, primary key(record_id)
 ) engine=innodb comment 'Business Invoice Items, keep a record of all invoice items';
 create unique index bii_key_index on bii (item_id);
 
 drop table if exists bip;
 create table bip (
-  payment_id char(36) not null
+  record_id int(11) unsigned not null auto_increment
+, payment_id char(36) not null
 , created_date bigint not null
 , updated_date bigint not null
 , attempt_id char(36) not null
@@ -110,66 +118,88 @@ create table bip (
 , payment_method varchar(20) default null
 , card_type varchar(20) default null
 , card_country varchar(20) default null
+, primary key(record_id)
 ) engine=innodb comment 'Business Invoice Payments, track all payment attempts';
 create unique index bip_key_index on bip (attempt_id);
 
 drop table if exists bos;
 create table bos (
-  external_key varchar(50) not null comment 'Bundle external key'
+  record_id int(11) unsigned not null auto_increment
+, external_key varchar(50) not null comment 'Bundle external key'
+, account_key varchar(50) not null comment 'Account external key'
 , status varchar(50) not null
 , start_date bigint default null
 , end_date bigint default null
+, primary key(record_id)
 ) engine=innodb comment 'Business Overdue Status, historical bundles overdue status';
 create unique index bos_key_index on bos (external_key, status);
 
 drop table if exists bac_tags;
 create table bac_tags (
-  account_key varchar(50) not null comment 'Account external key'
+  record_id int(11) unsigned not null auto_increment
+, account_key varchar(50) not null comment 'Account external key'
 , name varchar(20) not null
+, primary key(record_id)
 ) engine=innodb comment 'Tags associated to accounts';
 
 drop table if exists bac_fields;
 create table bac_fields (
-  account_key varchar(50) not null comment 'Account external key'
+  record_id int(11) unsigned not null auto_increment
+, account_key varchar(50) not null comment 'Account external key'
 , name varchar(30) not null
 , value varchar(255) default null
+, primary key(record_id)
 ) engine=innodb comment 'Custom fields associated to accounts';
 
 drop table if exists bst_tags;
 create table bst_tags (
-  external_key varchar(50) not null comment 'Bundle external key'
+  record_id int(11) unsigned not null auto_increment
+, external_key varchar(50) not null comment 'Bundle external key'
+, account_key varchar(50) not null comment 'Account external key'
 , name varchar(20) not null
+, primary key(record_id)
 ) engine=innodb comment 'Tags associated to bundles';
 
 drop table if exists bst_fields;
 create table bst_fields (
-  external_key varchar(50) not null comment 'Bundle external key'
+  record_id int(11) unsigned not null auto_increment
+, external_key varchar(50) not null comment 'Bundle external key'
+, account_key varchar(50) not null comment 'Account external key'
 , name varchar(30) not null
 , value varchar(255) default null
+, primary key(record_id)
 ) engine=innodb comment 'Custom fields associated to bundles';
 
 drop table if exists bin_tags;
 create table bin_tags (
-  invoice_id char(36) not null
+  record_id int(11) unsigned not null auto_increment
+, invoice_id char(36) not null
 , name varchar(20) not null
+, primary key(record_id)
 ) engine=innodb comment 'Tags associated to invoices';
 
 drop table if exists bin_fields;
 create table bin_fields (
-  invoice_id char(36) not null
+  record_id int(11) unsigned not null auto_increment
+, invoice_id char(36) not null
 , name varchar(30) not null
 , value varchar(255) default null
+, primary key(record_id)
 ) engine=innodb comment 'Custom fields associated to invoices';
 
 drop table if exists bip_tags;
 create table bip_tags (
-  payment_id char(36) not null
+  record_id int(11) unsigned not null auto_increment
+, payment_id char(36) not null
 , name varchar(20) not null
+, primary key(record_id)
 ) engine=innodb comment 'Tags associated to payments';
 
 drop table if exists bip_fields;
 create table bip_fields (
-  payment_id char(36) not null
+  record_id int(11) unsigned not null auto_increment
+, payment_id char(36) not null
 , name varchar(30) not null
 , value varchar(255) default null
+, primary key(record_id)
 ) engine=innodb comment 'Custom fields associated to payments';
diff --git a/analytics/src/test/java/com/ning/billing/analytics/api/TestAnalyticsService.java b/analytics/src/test/java/com/ning/billing/analytics/api/TestAnalyticsService.java
index ad54c53..e82b61c 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/api/TestAnalyticsService.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/api/TestAnalyticsService.java
@@ -38,7 +38,6 @@ import com.ning.billing.account.api.user.DefaultAccountCreationEvent;
 import com.ning.billing.analytics.AnalyticsTestModule;
 import com.ning.billing.analytics.MockDuration;
 import com.ning.billing.analytics.MockPhase;
-import com.ning.billing.analytics.MockPlan;
 import com.ning.billing.analytics.MockProduct;
 import com.ning.billing.analytics.TestWithEmbeddedDB;
 import com.ning.billing.analytics.dao.BusinessAccountSqlDao;
@@ -73,6 +72,7 @@ import com.ning.billing.invoice.dao.InvoiceDao;
 import com.ning.billing.invoice.model.DefaultInvoice;
 import com.ning.billing.invoice.model.FixedPriceInvoiceItem;
 import com.ning.billing.mock.MockAccountBuilder;
+import com.ning.billing.mock.MockPlan;
 import com.ning.billing.payment.api.DefaultPaymentInfoEvent;
 import com.ning.billing.payment.api.PaymentInfoEvent;
 import com.ning.billing.payment.api.PaymentStatus;
@@ -180,7 +180,6 @@ public class TestAnalyticsService extends TestWithEmbeddedDB {
         final DateTime requestedTransitionTime = clock.getUTCNow();
         final PriceList priceList = new MockPriceList().setName("something");
 
-
         transition = new DefaultEffectiveSubscriptionEvent(new SubscriptionTransitionData(
                 UUID.randomUUID(),
                 subscriptionId,
diff --git a/analytics/src/test/java/com/ning/billing/analytics/dao/TestAnalyticsDao.java b/analytics/src/test/java/com/ning/billing/analytics/dao/TestAnalyticsDao.java
index 5d6b3a6..a953ed2 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/dao/TestAnalyticsDao.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/dao/TestAnalyticsDao.java
@@ -36,7 +36,6 @@ import com.ning.billing.analytics.model.BusinessSubscriptionEvent;
 import com.ning.billing.analytics.model.BusinessSubscriptionTransition;
 import com.ning.billing.analytics.MockDuration;
 import com.ning.billing.analytics.MockPhase;
-import com.ning.billing.analytics.MockPlan;
 import com.ning.billing.analytics.MockProduct;
 import com.ning.billing.analytics.TestWithEmbeddedDB;
 import com.ning.billing.analytics.utils.Rounder;
@@ -50,6 +49,7 @@ import com.ning.billing.catalog.api.PlanPhase;
 import com.ning.billing.catalog.api.Product;
 import com.ning.billing.catalog.api.ProductCategory;
 import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.mock.MockPlan;
 
 public class TestAnalyticsDao extends TestWithEmbeddedDB {
     private static final Long TOTAL_ORDERING = 1L;
@@ -113,45 +113,6 @@ public class TestAnalyticsDao extends TestWithEmbeddedDB {
     }
 
     @Test(groups = "slow")
-    public void testHandleDuplicatedEvents() {
-        final BusinessSubscriptionTransition transitionWithNullPrev = new BusinessSubscriptionTransition(
-                transition.getTotalOrdering(),
-                transition.getExternalKey(),
-                transition.getAccountKey(),
-                transition.getRequestedTimestamp(),
-                transition.getEvent(),
-                null,
-                transition.getNextSubscription()
-        );
-
-        businessSubscriptionTransitionSqlDao.createTransition(transitionWithNullPrev);
-        List<BusinessSubscriptionTransition> transitions = businessSubscriptionTransitionSqlDao.getTransitions(EXTERNAL_KEY);
-        Assert.assertEquals(transitions.size(), 1);
-        Assert.assertEquals(transitions.get(0), transitionWithNullPrev);
-        // Try to add the same transition, with the same UUID - we should only store one though
-        businessSubscriptionTransitionSqlDao.createTransition(transitionWithNullPrev);
-        transitions = businessSubscriptionTransitionSqlDao.getTransitions(EXTERNAL_KEY);
-        Assert.assertEquals(transitions.size(), 1);
-        Assert.assertEquals(transitions.get(0), transitionWithNullPrev);
-
-        // Try now to store a look-alike transition (same fields except UUID) - we should store it this time
-        final BusinessSubscriptionTransition secondTransitionWithNullPrev = new BusinessSubscriptionTransition(
-                12L,
-                transition.getExternalKey(),
-                transition.getAccountKey(),
-                transition.getRequestedTimestamp(),
-                transition.getEvent(),
-                null,
-                transition.getNextSubscription()
-        );
-        businessSubscriptionTransitionSqlDao.createTransition(secondTransitionWithNullPrev);
-        transitions = businessSubscriptionTransitionSqlDao.getTransitions(EXTERNAL_KEY);
-        Assert.assertEquals(transitions.size(), 2);
-        Assert.assertTrue(transitions.contains(transitionWithNullPrev));
-        Assert.assertTrue(transitions.contains(secondTransitionWithNullPrev));
-    }
-
-    @Test(groups = "slow")
     public void testTransitionsWithNullPrevSubscription() {
         final BusinessSubscriptionTransition transitionWithNullPrev = new BusinessSubscriptionTransition(
                 transition.getTotalOrdering(),
diff --git a/analytics/src/test/java/com/ning/billing/analytics/dao/TestBusinessOverdueStatusSqlDao.java b/analytics/src/test/java/com/ning/billing/analytics/dao/TestBusinessOverdueStatusSqlDao.java
index c16ab3c..f21d3af 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/dao/TestBusinessOverdueStatusSqlDao.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/dao/TestBusinessOverdueStatusSqlDao.java
@@ -39,8 +39,9 @@ public class TestBusinessOverdueStatusSqlDao extends TestWithEmbeddedDB {
 
     @Test(groups = "slow")
     public void testCreate() throws Exception {
+        final String accountKey = UUID.randomUUID().toString();
         final String externalKey = UUID.randomUUID().toString();
-        final BusinessOverdueStatus firstOverdueStatus = createOverdueStatus(externalKey);
+        final BusinessOverdueStatus firstOverdueStatus = createOverdueStatus(accountKey, externalKey);
 
         // Verify initial state
         Assert.assertEquals(overdueStatusSqlDao.getOverdueStatusesForBundle(externalKey).size(), 0);
@@ -53,7 +54,7 @@ public class TestBusinessOverdueStatusSqlDao extends TestWithEmbeddedDB {
         Assert.assertEquals(overdueStatusSqlDao.getOverdueStatusesForBundle(externalKey).get(0), firstOverdueStatus);
 
         // Add a second one
-        final BusinessOverdueStatus secondOverdueStatus = createOverdueStatus(externalKey);
+        final BusinessOverdueStatus secondOverdueStatus = createOverdueStatus(accountKey, externalKey);
         Assert.assertEquals(overdueStatusSqlDao.createOverdueStatus(secondOverdueStatus), 1);
 
         // Retrieve both
@@ -72,11 +73,11 @@ public class TestBusinessOverdueStatusSqlDao extends TestWithEmbeddedDB {
         }
     }
 
-    private BusinessOverdueStatus createOverdueStatus(final String externalKey) {
+    private BusinessOverdueStatus createOverdueStatus(final String accountKey, final String externalKey) {
         final DateTime endDate = new DateTime(DateTimeZone.UTC);
         final DateTime startDate = new DateTime(DateTimeZone.UTC);
         final String status = UUID.randomUUID().toString();
 
-        return new BusinessOverdueStatus(endDate, externalKey, startDate, status);
+        return new BusinessOverdueStatus(accountKey, endDate, externalKey, startDate, status);
     }
 }
diff --git a/analytics/src/test/java/com/ning/billing/analytics/dao/TestBusinessSubscriptionTransitionFieldSqlDao.java b/analytics/src/test/java/com/ning/billing/analytics/dao/TestBusinessSubscriptionTransitionFieldSqlDao.java
index 5233229..eb0d374 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/dao/TestBusinessSubscriptionTransitionFieldSqlDao.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/dao/TestBusinessSubscriptionTransitionFieldSqlDao.java
@@ -38,6 +38,7 @@ public class TestBusinessSubscriptionTransitionFieldSqlDao extends TestWithEmbed
 
     @Test(groups = "slow")
     public void testCRUD() throws Exception {
+        final String accountKey = UUID.randomUUID().toString();
         final String externalKey = UUID.randomUUID().toString();
         final String name = UUID.randomUUID().toString().substring(0, 30);
         final String value = UUID.randomUUID().toString();
@@ -47,7 +48,7 @@ public class TestBusinessSubscriptionTransitionFieldSqlDao extends TestWithEmbed
         Assert.assertEquals(subscriptionTransitionFieldSqlDao.removeField(externalKey, name), 0);
 
         // Add an entry
-        Assert.assertEquals(subscriptionTransitionFieldSqlDao.addField(externalKey, name, value), 1);
+        Assert.assertEquals(subscriptionTransitionFieldSqlDao.addField(accountKey, externalKey, name, value), 1);
         final List<BusinessSubscriptionTransitionField> fieldsForBusinessSubscriptionTransition = subscriptionTransitionFieldSqlDao.getFieldsForBusinessSubscriptionTransition(externalKey);
         Assert.assertEquals(fieldsForBusinessSubscriptionTransition.size(), 1);
 
@@ -64,14 +65,15 @@ public class TestBusinessSubscriptionTransitionFieldSqlDao extends TestWithEmbed
 
     @Test(groups = "slow")
     public void testSegmentation() throws Exception {
+        final String accountKey = UUID.randomUUID().toString();
         final String externalKey1 = UUID.randomUUID().toString();
         final String name1 = UUID.randomUUID().toString().substring(0, 30);
         final String externalKey2 = UUID.randomUUID().toString();
         final String name2 = UUID.randomUUID().toString().substring(0, 30);
 
         // Add a field to both transitions
-        Assert.assertEquals(subscriptionTransitionFieldSqlDao.addField(externalKey1, name1, UUID.randomUUID().toString()), 1);
-        Assert.assertEquals(subscriptionTransitionFieldSqlDao.addField(externalKey2, name2, UUID.randomUUID().toString()), 1);
+        Assert.assertEquals(subscriptionTransitionFieldSqlDao.addField(accountKey, externalKey1, name1, UUID.randomUUID().toString()), 1);
+        Assert.assertEquals(subscriptionTransitionFieldSqlDao.addField(accountKey, externalKey2, name2, UUID.randomUUID().toString()), 1);
 
         Assert.assertEquals(subscriptionTransitionFieldSqlDao.getFieldsForBusinessSubscriptionTransition(externalKey1).size(), 1);
         Assert.assertEquals(subscriptionTransitionFieldSqlDao.getFieldsForBusinessSubscriptionTransition(externalKey2).size(), 1);
diff --git a/analytics/src/test/java/com/ning/billing/analytics/dao/TestBusinessSubscriptionTransitionTagSqlDao.java b/analytics/src/test/java/com/ning/billing/analytics/dao/TestBusinessSubscriptionTransitionTagSqlDao.java
index 8b4740f..7b8f1db 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/dao/TestBusinessSubscriptionTransitionTagSqlDao.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/dao/TestBusinessSubscriptionTransitionTagSqlDao.java
@@ -38,6 +38,7 @@ public class TestBusinessSubscriptionTransitionTagSqlDao extends TestWithEmbedde
 
     @Test(groups = "slow")
     public void testCRUD() throws Exception {
+        final String accountKey = UUID.randomUUID().toString();
         final String externalKey = UUID.randomUUID().toString();
         final String name = UUID.randomUUID().toString().substring(0, 20);
 
@@ -46,7 +47,7 @@ public class TestBusinessSubscriptionTransitionTagSqlDao extends TestWithEmbedde
         Assert.assertEquals(subscriptionTransitionTagSqlDao.removeTag(externalKey, name), 0);
 
         // Add an entry
-        Assert.assertEquals(subscriptionTransitionTagSqlDao.addTag(externalKey, name), 1);
+        Assert.assertEquals(subscriptionTransitionTagSqlDao.addTag(accountKey, externalKey, name), 1);
         final List<BusinessSubscriptionTransitionTag> tagsForBusinessSubscriptionTransition = subscriptionTransitionTagSqlDao.getTagsForBusinessSubscriptionTransition(externalKey);
         Assert.assertEquals(tagsForBusinessSubscriptionTransition.size(), 1);
 
@@ -62,14 +63,15 @@ public class TestBusinessSubscriptionTransitionTagSqlDao extends TestWithEmbedde
 
     @Test(groups = "slow")
     public void testSegmentation() throws Exception {
+        final String accountKey = UUID.randomUUID().toString();
         final String externalKey1 = UUID.randomUUID().toString();
         final String name1 = UUID.randomUUID().toString().substring(0, 20);
         final String externalKey2 = UUID.randomUUID().toString();
         final String name2 = UUID.randomUUID().toString().substring(0, 20);
 
         // Add a tag to both transitions
-        Assert.assertEquals(subscriptionTransitionTagSqlDao.addTag(externalKey1, name1), 1);
-        Assert.assertEquals(subscriptionTransitionTagSqlDao.addTag(externalKey2, name2), 1);
+        Assert.assertEquals(subscriptionTransitionTagSqlDao.addTag(accountKey, externalKey1, name1), 1);
+        Assert.assertEquals(subscriptionTransitionTagSqlDao.addTag(accountKey, externalKey2, name2), 1);
 
         Assert.assertEquals(subscriptionTransitionTagSqlDao.getTagsForBusinessSubscriptionTransition(externalKey1).size(), 1);
         Assert.assertEquals(subscriptionTransitionTagSqlDao.getTagsForBusinessSubscriptionTransition(externalKey2).size(), 1);
diff --git a/analytics/src/test/java/com/ning/billing/analytics/MockBusinessSubscriptionTransitionSqlDao.java b/analytics/src/test/java/com/ning/billing/analytics/MockBusinessSubscriptionTransitionSqlDao.java
index a442f2a..b54d7ce 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/MockBusinessSubscriptionTransitionSqlDao.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/MockBusinessSubscriptionTransitionSqlDao.java
@@ -21,8 +21,11 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
+import org.skife.jdbi.v2.Transaction;
 import org.skife.jdbi.v2.sqlobject.Bind;
+import org.testng.Assert;
 
+import com.google.common.collect.ImmutableList;
 import com.ning.billing.analytics.dao.BusinessSubscriptionTransitionBinder;
 import com.ning.billing.analytics.dao.BusinessSubscriptionTransitionSqlDao;
 import com.ning.billing.analytics.model.BusinessSubscriptionTransition;
@@ -36,6 +39,11 @@ public class MockBusinessSubscriptionTransitionSqlDao implements BusinessSubscri
     }
 
     @Override
+    public List<BusinessSubscriptionTransition> getTransitionForSubscription(@Bind("subscription_id") final String subscriptionId) {
+        return ImmutableList.<BusinessSubscriptionTransition>of();
+    }
+
+    @Override
     public int createTransition(@BusinessSubscriptionTransitionBinder final BusinessSubscriptionTransition transition) {
         if (content.get(transition.getExternalKey()) == null) {
             content.put(transition.getExternalKey(), new ArrayList<BusinessSubscriptionTransition>());
@@ -45,6 +53,50 @@ public class MockBusinessSubscriptionTransitionSqlDao implements BusinessSubscri
     }
 
     @Override
+    public void updateTransition(@Bind("total_ordering") final long totalOrdering, @BusinessSubscriptionTransitionBinder final BusinessSubscriptionTransition updatedFirstTransition) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void deleteTransitionsForBundle(@Bind("external_key") final String externalKey) {
+        content.put(externalKey, new ArrayList<BusinessSubscriptionTransition>());
+    }
+
+    @Override
     public void test() {
     }
+
+    @Override
+    public void begin() {
+    }
+
+    @Override
+    public void commit() {
+    }
+
+    @Override
+    public void rollback() {
+    }
+
+    @Override
+    public void checkpoint(final String name) {
+    }
+
+    @Override
+    public void release(final String name) {
+    }
+
+    @Override
+    public void rollback(final String name) {
+    }
+
+    @Override
+    public <ReturnType> ReturnType inTransaction(final Transaction<ReturnType, BusinessSubscriptionTransitionSqlDao> func) {
+        try {
+            return func.inTransaction(this, null);
+        } catch (Exception e) {
+            Assert.fail(e.toString());
+            return null;
+        }
+    }
 }
diff --git a/analytics/src/test/java/com/ning/billing/analytics/model/TestBusinessOverdueStatus.java b/analytics/src/test/java/com/ning/billing/analytics/model/TestBusinessOverdueStatus.java
index b92ee12..6edcbf9 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/model/TestBusinessOverdueStatus.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/model/TestBusinessOverdueStatus.java
@@ -28,21 +28,26 @@ import com.ning.billing.analytics.AnalyticsTestSuite;
 public class TestBusinessOverdueStatus extends AnalyticsTestSuite {
     @Test(groups = "fast")
     public void testEquals() throws Exception {
+        final String accountKey = UUID.randomUUID().toString();
         final DateTime endDate = new DateTime(DateTimeZone.UTC);
         final String externalKey = UUID.randomUUID().toString();
         final DateTime startDate = new DateTime(DateTimeZone.UTC);
         final String status = UUID.randomUUID().toString();
-        final BusinessOverdueStatus overdueStatus = new BusinessOverdueStatus(endDate, externalKey, startDate, status);
+        final BusinessOverdueStatus overdueStatus = new BusinessOverdueStatus(accountKey, endDate, externalKey, startDate, status);
         Assert.assertSame(overdueStatus, overdueStatus);
         Assert.assertEquals(overdueStatus, overdueStatus);
         Assert.assertTrue(overdueStatus.equals(overdueStatus));
+        Assert.assertEquals(overdueStatus.getAccountKey(), accountKey);
         Assert.assertEquals(overdueStatus.getEndDate(), endDate);
         Assert.assertEquals(overdueStatus.getExternalKey(), externalKey);
         Assert.assertEquals(overdueStatus.getStartDate(), startDate);
         Assert.assertEquals(overdueStatus.getStatus(), status);
 
-        final BusinessOverdueStatus otherOverdueStatus = new BusinessOverdueStatus(new DateTime(DateTimeZone.UTC), UUID.randomUUID().toString(),
-                                                                                   new DateTime(DateTimeZone.UTC), UUID.randomUUID().toString());
+        final BusinessOverdueStatus otherOverdueStatus = new BusinessOverdueStatus(UUID.randomUUID().toString(),
+                                                                                   new DateTime(DateTimeZone.UTC),
+                                                                                   UUID.randomUUID().toString(),
+                                                                                   new DateTime(DateTimeZone.UTC),
+                                                                                   UUID.randomUUID().toString());
         Assert.assertFalse(overdueStatus.equals(otherOverdueStatus));
     }
 }
diff --git a/analytics/src/test/java/com/ning/billing/analytics/model/TestBusinessSubscription.java b/analytics/src/test/java/com/ning/billing/analytics/model/TestBusinessSubscription.java
index c102d35..7a2cbd7 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/model/TestBusinessSubscription.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/model/TestBusinessSubscription.java
@@ -27,10 +27,7 @@ import org.testng.annotations.Test;
 import com.ning.billing.analytics.AnalyticsTestSuite;
 import com.ning.billing.analytics.MockDuration;
 import com.ning.billing.analytics.MockPhase;
-import com.ning.billing.analytics.MockPlan;
 import com.ning.billing.analytics.MockProduct;
-import com.ning.billing.analytics.MockSubscription;
-import com.ning.billing.analytics.model.BusinessSubscription;
 import com.ning.billing.catalog.api.Catalog;
 import com.ning.billing.catalog.api.CatalogService;
 import com.ning.billing.catalog.api.Duration;
@@ -40,6 +37,8 @@ import com.ning.billing.catalog.api.PlanPhase;
 import com.ning.billing.catalog.api.Product;
 import com.ning.billing.catalog.api.ProductCategory;
 import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.mock.MockPlan;
+import com.ning.billing.mock.MockSubscription;
 
 import static com.ning.billing.catalog.api.Currency.USD;
 
diff --git a/analytics/src/test/java/com/ning/billing/analytics/model/TestBusinessSubscriptionEvent.java b/analytics/src/test/java/com/ning/billing/analytics/model/TestBusinessSubscriptionEvent.java
index 11c6215..de713a8 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/model/TestBusinessSubscriptionEvent.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/model/TestBusinessSubscriptionEvent.java
@@ -25,10 +25,7 @@ import org.testng.annotations.Test;
 import com.ning.billing.analytics.AnalyticsTestSuite;
 import com.ning.billing.analytics.MockDuration;
 import com.ning.billing.analytics.MockPhase;
-import com.ning.billing.analytics.MockPlan;
 import com.ning.billing.analytics.MockProduct;
-import com.ning.billing.analytics.MockSubscription;
-import com.ning.billing.analytics.model.BusinessSubscriptionEvent;
 import com.ning.billing.catalog.api.Catalog;
 import com.ning.billing.catalog.api.CatalogService;
 import com.ning.billing.catalog.api.PhaseType;
@@ -37,6 +34,8 @@ import com.ning.billing.catalog.api.PlanPhase;
 import com.ning.billing.catalog.api.Product;
 import com.ning.billing.catalog.api.ProductCategory;
 import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.mock.MockPlan;
+import com.ning.billing.mock.MockSubscription;
 
 public class TestBusinessSubscriptionEvent extends AnalyticsTestSuite {
     private Product product;
diff --git a/analytics/src/test/java/com/ning/billing/analytics/model/TestBusinessSubscriptionTransition.java b/analytics/src/test/java/com/ning/billing/analytics/model/TestBusinessSubscriptionTransition.java
index 4c401a6..1e10acf 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/model/TestBusinessSubscriptionTransition.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/model/TestBusinessSubscriptionTransition.java
@@ -26,12 +26,7 @@ import org.testng.annotations.Test;
 import com.ning.billing.analytics.AnalyticsTestSuite;
 import com.ning.billing.analytics.MockDuration;
 import com.ning.billing.analytics.MockPhase;
-import com.ning.billing.analytics.MockPlan;
 import com.ning.billing.analytics.MockProduct;
-import com.ning.billing.analytics.MockSubscription;
-import com.ning.billing.analytics.model.BusinessSubscription;
-import com.ning.billing.analytics.model.BusinessSubscriptionEvent;
-import com.ning.billing.analytics.model.BusinessSubscriptionTransition;
 import com.ning.billing.catalog.api.Catalog;
 import com.ning.billing.catalog.api.CatalogService;
 import com.ning.billing.catalog.api.PhaseType;
@@ -40,6 +35,8 @@ import com.ning.billing.catalog.api.PlanPhase;
 import com.ning.billing.catalog.api.Product;
 import com.ning.billing.catalog.api.ProductCategory;
 import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.mock.MockPlan;
+import com.ning.billing.mock.MockSubscription;
 
 import static com.ning.billing.catalog.api.Currency.USD;
 
diff --git a/analytics/src/test/java/com/ning/billing/analytics/model/TestBusinessSubscriptionTransitionField.java b/analytics/src/test/java/com/ning/billing/analytics/model/TestBusinessSubscriptionTransitionField.java
index 5a1e31b..af9122e 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/model/TestBusinessSubscriptionTransitionField.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/model/TestBusinessSubscriptionTransitionField.java
@@ -26,21 +26,25 @@ import com.ning.billing.analytics.AnalyticsTestSuite;
 public class TestBusinessSubscriptionTransitionField extends AnalyticsTestSuite {
     @Test(groups = "fast")
     public void testEquals() throws Exception {
+        final String accountKey = UUID.randomUUID().toString();
         final String externalKey = UUID.randomUUID().toString();
         final String name = UUID.randomUUID().toString();
         final String value = UUID.randomUUID().toString();
-        final BusinessSubscriptionTransitionField subscriptionTransitionField = new BusinessSubscriptionTransitionField(externalKey,
+        final BusinessSubscriptionTransitionField subscriptionTransitionField = new BusinessSubscriptionTransitionField(accountKey,
+                                                                                                                        externalKey,
                                                                                                                         name,
                                                                                                                         value);
         Assert.assertSame(subscriptionTransitionField, subscriptionTransitionField);
         Assert.assertEquals(subscriptionTransitionField, subscriptionTransitionField);
         Assert.assertTrue(subscriptionTransitionField.equals(subscriptionTransitionField));
+        Assert.assertEquals(subscriptionTransitionField.getAccountKey(), accountKey);
         Assert.assertEquals(subscriptionTransitionField.getExternalKey(), externalKey);
         Assert.assertEquals(subscriptionTransitionField.getName(), name);
         Assert.assertEquals(subscriptionTransitionField.getValue(), value);
 
         final BusinessSubscriptionTransitionField otherSubscriptionField = new BusinessSubscriptionTransitionField(UUID.randomUUID().toString(),
                                                                                                                    UUID.randomUUID().toString(),
+                                                                                                                   UUID.randomUUID().toString(),
                                                                                                                    UUID.randomUUID().toString());
         Assert.assertFalse(subscriptionTransitionField.equals(otherSubscriptionField));
     }
diff --git a/analytics/src/test/java/com/ning/billing/analytics/model/TestBusinessSubscriptionTransitionTag.java b/analytics/src/test/java/com/ning/billing/analytics/model/TestBusinessSubscriptionTransitionTag.java
index b5a40d1..9d079b4 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/model/TestBusinessSubscriptionTransitionTag.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/model/TestBusinessSubscriptionTransitionTag.java
@@ -26,16 +26,22 @@ import com.ning.billing.analytics.AnalyticsTestSuite;
 public class TestBusinessSubscriptionTransitionTag extends AnalyticsTestSuite {
     @Test(groups = "fast")
     public void testEquals() throws Exception {
+        final String accountKey = UUID.randomUUID().toString();
         final String externalKey = UUID.randomUUID().toString();
         final String name = UUID.randomUUID().toString();
-        final BusinessSubscriptionTransitionTag subscriptionTransitionTag = new BusinessSubscriptionTransitionTag(externalKey, name);
+        final BusinessSubscriptionTransitionTag subscriptionTransitionTag = new BusinessSubscriptionTransitionTag(accountKey,
+                                                                                                                  externalKey,
+                                                                                                                  name);
         Assert.assertSame(subscriptionTransitionTag, subscriptionTransitionTag);
         Assert.assertEquals(subscriptionTransitionTag, subscriptionTransitionTag);
         Assert.assertTrue(subscriptionTransitionTag.equals(subscriptionTransitionTag));
+        Assert.assertEquals(subscriptionTransitionTag.getAccountKey(), accountKey);
         Assert.assertEquals(subscriptionTransitionTag.getExternalKey(), externalKey);
         Assert.assertEquals(subscriptionTransitionTag.getName(), name);
 
-        final BusinessSubscriptionTransitionTag otherTransitionTag = new BusinessSubscriptionTransitionTag(UUID.randomUUID().toString(), UUID.randomUUID().toString());
+        final BusinessSubscriptionTransitionTag otherTransitionTag = new BusinessSubscriptionTransitionTag(UUID.randomUUID().toString(),
+                                                                                                           UUID.randomUUID().toString(),
+                                                                                                           UUID.randomUUID().toString());
         Assert.assertFalse(subscriptionTransitionTag.equals(otherTransitionTag));
     }
 }
diff --git a/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscriptionTransitionRecorder.java b/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscriptionTransitionRecorder.java
index 75c3ebc..48cefb4 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscriptionTransitionRecorder.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscriptionTransitionRecorder.java
@@ -24,23 +24,24 @@ import org.mockito.Mockito;
 import org.testng.Assert;
 import org.testng.annotations.Test;
 
+import com.google.common.collect.ImmutableList;
 import com.ning.billing.account.api.Account;
 import com.ning.billing.account.api.AccountUserApi;
 import com.ning.billing.analytics.dao.BusinessSubscriptionTransitionSqlDao;
-import com.ning.billing.analytics.model.BusinessSubscription;
-import com.ning.billing.analytics.model.BusinessSubscriptionEvent;
 import com.ning.billing.analytics.model.BusinessSubscriptionTransition;
 import com.ning.billing.catalog.api.Catalog;
 import com.ning.billing.catalog.api.CatalogService;
-import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
 import com.ning.billing.entitlement.api.user.EffectiveSubscriptionEvent;
 import com.ning.billing.entitlement.api.user.EntitlementUserApi;
 import com.ning.billing.entitlement.api.user.Subscription;
 import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.util.clock.DefaultClock;
 
 public class TestBusinessSubscriptionTransitionRecorder extends AnalyticsTestSuite {
     @Test(groups = "fast")
     public void testCreateAddOn() throws Exception {
+        final UUID bundleId = UUID.randomUUID();
         final UUID externalKey = UUID.randomUUID();
 
         // Setup the catalog
@@ -49,26 +50,10 @@ public class TestBusinessSubscriptionTransitionRecorder extends AnalyticsTestSui
 
         // Setup the dao
         final BusinessSubscriptionTransitionSqlDao sqlDao = new MockBusinessSubscriptionTransitionSqlDao();
-        // Add a previous subscription to make sure it doesn't impact the addon
-        final BusinessSubscription nextPrevSubscription = new BusinessSubscription(UUID.randomUUID().toString(),
-                                                                                   UUID.randomUUID().toString(),
-                                                                                   UUID.randomUUID().toString(),
-                                                                                   Currency.USD,
-                                                                                   new DateTime(DateTimeZone.UTC),
-                                                                                   Subscription.SubscriptionState.ACTIVE,
-                                                                                   UUID.randomUUID(),
-                                                                                   UUID.randomUUID(),
-                                                                                   catalogService.getFullCatalog());
-        sqlDao.createTransition(new BusinessSubscriptionTransition(10L,
-                                                                externalKey.toString(),
-                                                                UUID.randomUUID().toString(),
-                                                                new DateTime(DateTimeZone.UTC),
-                                                                BusinessSubscriptionEvent.valueOf("ADD_MISC"),
-                                                                null,
-                                                                nextPrevSubscription));
 
         // Setup the entitlement API
         final SubscriptionBundle bundle = Mockito.mock(SubscriptionBundle.class);
+        Mockito.when(bundle.getId()).thenReturn(bundleId);
         Mockito.when(bundle.getKey()).thenReturn(externalKey.toString());
         final EntitlementUserApi entitlementApi = Mockito.mock(EntitlementUserApi.class);
         Mockito.when(entitlementApi.getBundleFromId(Mockito.<UUID>any())).thenReturn(bundle);
@@ -79,19 +64,25 @@ public class TestBusinessSubscriptionTransitionRecorder extends AnalyticsTestSui
         final AccountUserApi accountApi = Mockito.mock(AccountUserApi.class);
         Mockito.when(accountApi.getAccountById(bundle.getAccountId())).thenReturn(account);
 
-        final BusinessSubscriptionTransitionRecorder recorder = new BusinessSubscriptionTransitionRecorder(sqlDao, catalogService, entitlementApi, accountApi);
-
         // Create an new subscription event
         final EffectiveSubscriptionEvent eventEffective = Mockito.mock(EffectiveSubscriptionEvent.class);
         Mockito.when(eventEffective.getId()).thenReturn(UUID.randomUUID());
+        Mockito.when(eventEffective.getTransitionType()).thenReturn(SubscriptionTransitionType.CREATE);
+        Mockito.when(eventEffective.getSubscriptionId()).thenReturn(UUID.randomUUID());
         Mockito.when(eventEffective.getRequestedTransitionTime()).thenReturn(new DateTime(DateTimeZone.UTC));
         Mockito.when(eventEffective.getNextPlan()).thenReturn(UUID.randomUUID().toString());
         Mockito.when(eventEffective.getEffectiveTransitionTime()).thenReturn(new DateTime(DateTimeZone.UTC));
         Mockito.when(eventEffective.getSubscriptionStartDate()).thenReturn(new DateTime(DateTimeZone.UTC));
-        recorder.subscriptionCreated(eventEffective);
 
-        Assert.assertEquals(sqlDao.getTransitions(externalKey.toString()).size(), 2);
-        final BusinessSubscriptionTransition transition = sqlDao.getTransitions(externalKey.toString()).get(1);
+        final Subscription subscription = Mockito.mock(Subscription.class);
+        Mockito.when(subscription.getAllTransitions()).thenReturn(ImmutableList.<EffectiveSubscriptionEvent>of(eventEffective));
+        Mockito.when(entitlementApi.getSubscriptionsForBundle(Mockito.<UUID>any())).thenReturn(ImmutableList.<Subscription>of(subscription));
+
+        final BusinessSubscriptionTransitionRecorder recorder = new BusinessSubscriptionTransitionRecorder(sqlDao, catalogService, entitlementApi, accountApi, new DefaultClock());
+        recorder.rebuildTransitionsForBundle(bundle.getId());
+
+        Assert.assertEquals(sqlDao.getTransitions(externalKey.toString()).size(), 1);
+        final BusinessSubscriptionTransition transition = sqlDao.getTransitions(externalKey.toString()).get(0);
         Assert.assertEquals(transition.getTotalOrdering(), (long) eventEffective.getTotalOrdering());
         Assert.assertEquals(transition.getAccountKey(), externalKey.toString());
         // Make sure all the prev_ columns are null
diff --git a/analytics/src/test/java/com/ning/billing/analytics/TestBusinessTagRecorder.java b/analytics/src/test/java/com/ning/billing/analytics/TestBusinessTagRecorder.java
index 6f18739..79e762f 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/TestBusinessTagRecorder.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/TestBusinessTagRecorder.java
@@ -85,7 +85,7 @@ public class TestBusinessTagRecorder extends TestWithEmbeddedDB {
         final CatalogService catalogService = new DefaultCatalogService(Mockito.mock(CatalogConfig.class), Mockito.mock(VersionedCatalogLoader.class));
         final AddonUtils addonUtils = new AddonUtils(catalogService);
         final DefaultNotificationQueueService notificationQueueService = new DefaultNotificationQueueService(dbi, clock);
-        final EntitlementDao entitlementDao = new AuditedEntitlementDao(dbi, clock, addonUtils, notificationQueueService, eventBus);
+        final EntitlementDao entitlementDao = new AuditedEntitlementDao(dbi, clock, addonUtils, notificationQueueService, eventBus, catalogService);
         final PlanAligner planAligner = new PlanAligner(catalogService);
         final DefaultSubscriptionApiService apiService = new DefaultSubscriptionApiService(clock, entitlementDao, catalogService, planAligner);
         final DefaultSubscriptionFactory subscriptionFactory = new DefaultSubscriptionFactory(apiService, clock, catalogService);
diff --git a/api/src/main/java/com/ning/billing/entitlement/api/user/Subscription.java b/api/src/main/java/com/ning/billing/entitlement/api/user/Subscription.java
index ac17237..e75ea1b 100644
--- a/api/src/main/java/com/ning/billing/entitlement/api/user/Subscription.java
+++ b/api/src/main/java/com/ning/billing/entitlement/api/user/Subscription.java
@@ -31,7 +31,6 @@ import com.ning.billing.junction.api.Blockable;
 import com.ning.billing.util.callcontext.CallContext;
 import com.ning.billing.util.entity.Entity;
 
-
 public interface Subscription extends Entity, Blockable {
 
     public boolean cancel(DateTime requestedDate, boolean eot, CallContext context)
@@ -40,7 +39,7 @@ public interface Subscription extends Entity, Blockable {
     public boolean uncancel(CallContext context)
             throws EntitlementUserApiException;
 
-    public boolean changePlan(String productName, BillingPeriod term, String planSet, DateTime requestedDate, CallContext context)
+    public boolean changePlan(String productName, BillingPeriod term, String priceList, DateTime requestedDate, CallContext context)
             throws EntitlementUserApiException;
 
     public boolean recreate(PlanPhaseSpecifier spec, DateTime requestedDate, CallContext context)
@@ -76,4 +75,6 @@ public interface Subscription extends Entity, Blockable {
     public EffectiveSubscriptionEvent getPreviousTransition();
 
     public List<EffectiveSubscriptionEvent> getBillingTransitions();
+
+    public List<EffectiveSubscriptionEvent> getAllTransitions();
 }
diff --git a/api/src/main/java/com/ning/billing/ErrorCode.java b/api/src/main/java/com/ning/billing/ErrorCode.java
index bc20699..c3b55a1 100644
--- a/api/src/main/java/com/ning/billing/ErrorCode.java
+++ b/api/src/main/java/com/ning/billing/ErrorCode.java
@@ -244,6 +244,9 @@ public enum ErrorCode {
     PAYMENT_NO_SUCH_PAYMENT(7020, "Payment %s does not exist"),
     PAYMENT_NO_DEFAULT_PAYMENT_METHOD(7021, "Account %s does not have a default payment method set"),
     PAYMENT_DEL_DEFAULT_PAYMENT_METHOD(7022, "Cannot delete default payment method for account %s"),
+    PAYMENT_NO_SUCH_REFUND(7023, "Refund %s does not exist"),
+    PAYMENT_NO_SUCH_SUCCESS_PAYMENT(7024, "Payment %s did not succeed"),
+    PAYMENT_REFUND_AMOUNT_TOO_LARGE(7025, "Refund amount if larger than payment"),
 
     PAYMENT_PLUGIN_TIMEOUT(7100, "Plugin timeout for account %s and invoice %s"),
     PAYMENT_PLUGIN_ACCOUNT_INIT(7101, "Account initialization for account %s and plugin % s failed: %s"),
diff --git a/api/src/main/java/com/ning/billing/invoice/api/InvoicePayment.java b/api/src/main/java/com/ning/billing/invoice/api/InvoicePayment.java
index 880fc2b..102eb64 100644
--- a/api/src/main/java/com/ning/billing/invoice/api/InvoicePayment.java
+++ b/api/src/main/java/com/ning/billing/invoice/api/InvoicePayment.java
@@ -39,6 +39,8 @@ public interface InvoicePayment extends Entity {
 
     UUID getLinkedInvoicePaymentId();
 
+    UUID getPaymentCookieId();
+
     public enum InvoicePaymentType {
         ATTEMPT,
         CHARGED_BACK,
diff --git a/api/src/main/java/com/ning/billing/invoice/api/InvoicePaymentApi.java b/api/src/main/java/com/ning/billing/invoice/api/InvoicePaymentApi.java
index a71783a..54e03bf 100644
--- a/api/src/main/java/com/ning/billing/invoice/api/InvoicePaymentApi.java
+++ b/api/src/main/java/com/ning/billing/invoice/api/InvoicePaymentApi.java
@@ -42,7 +42,7 @@ public interface InvoicePaymentApi {
 
     public void notifyOfPaymentAttempt(UUID invoiceId, BigDecimal amountOutstanding, Currency currency, UUID paymentAttemptId, DateTime paymentAttemptDate, CallContext context);
 
-    public InvoicePayment createRefund(UUID paymentAttemptId, BigDecimal amount, boolean isInvoiceAdjusted, CallContext context) throws InvoiceApiException;
+    public InvoicePayment createRefund(UUID paymentAttemptId, BigDecimal amount, boolean isInvoiceAdjusted, UUID paymentCookieId, CallContext context) throws InvoiceApiException;
 
     public InvoicePayment createChargeback(UUID invoicePaymentId, BigDecimal amount, CallContext context) throws InvoiceApiException;
 
diff --git a/api/src/main/java/com/ning/billing/payment/api/PaymentApi.java b/api/src/main/java/com/ning/billing/payment/api/PaymentApi.java
index c8f9b2f..479966a 100644
--- a/api/src/main/java/com/ning/billing/payment/api/PaymentApi.java
+++ b/api/src/main/java/com/ning/billing/payment/api/PaymentApi.java
@@ -1,4 +1,4 @@
-/* 
+/*
  * Copyright 2010-2011 Ning, Inc.
  *
  * Ning licenses this file to you under the Apache License, version 2.0
@@ -31,8 +31,17 @@ public interface PaymentApi {
     public Payment createPayment(final Account account, final UUID invoiceId, final BigDecimal amount, final CallContext context)
             throws PaymentApiException;
 
-    public Refund createRefund(final Account account, final UUID paymentId, final CallContext context)
-            throws PaymentApiException;
+    public Refund getRefund(final UUID refundId)
+    throws PaymentApiException;
+
+    public Refund createRefund(final Account account, final UUID paymentId, final BigDecimal refundAmount, final boolean isAdjusted, final CallContext context)
+    throws PaymentApiException;
+
+    public List<Refund> getAccountRefunds(final Account account)
+    throws PaymentApiException;
+
+    public List<Refund> getPaymentRefunds(final UUID paymentId)
+    throws PaymentApiException;
 
     public List<Payment> getInvoicePayments(final UUID invoiceId)
             throws PaymentApiException;
diff --git a/api/src/main/java/com/ning/billing/payment/api/PaymentApiException.java b/api/src/main/java/com/ning/billing/payment/api/PaymentApiException.java
index 2ef846d..aa69bd0 100644
--- a/api/src/main/java/com/ning/billing/payment/api/PaymentApiException.java
+++ b/api/src/main/java/com/ning/billing/payment/api/PaymentApiException.java
@@ -1,4 +1,4 @@
-/* 
+/*
  * Copyright 2010-2011 Ning, Inc.
  *
  * Ning licenses this file to you under the Apache License, version 2.0
@@ -18,11 +18,16 @@ package com.ning.billing.payment.api;
 import com.ning.billing.BillingExceptionBase;
 import com.ning.billing.ErrorCode;
 import com.ning.billing.account.api.AccountApiException;
+import com.ning.billing.invoice.api.InvoiceApiException;
 
 public class PaymentApiException extends BillingExceptionBase {
 
     private static final long serialVersionUID = 39445033L;
 
+    public PaymentApiException(final InvoiceApiException e) {
+        super(e, e.getCode(), e.getMessage());
+    }
+
     public PaymentApiException(final AccountApiException e) {
         super(e, e.getCode(), e.getMessage());
     }
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 e769e54..ed2e15c 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
@@ -1,4 +1,4 @@
-/* 
+/*
  * Copyright 2010-2011 Ning, Inc.
  *
  * Ning licenses this file to you under the Apache License, version 2.0
@@ -15,6 +15,15 @@
  */
 package com.ning.billing.payment.api;
 
-public interface Refund {
+import java.math.BigDecimal;
+import java.util.UUID;
+
+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();
 }
diff --git a/api/src/main/java/com/ning/billing/payment/plugin/api/PaymentPluginApi.java b/api/src/main/java/com/ning/billing/payment/plugin/api/PaymentPluginApi.java
index 10b329e..5f8fe2d 100644
--- a/api/src/main/java/com/ning/billing/payment/plugin/api/PaymentPluginApi.java
+++ b/api/src/main/java/com/ning/billing/payment/plugin/api/PaymentPluginApi.java
@@ -33,8 +33,11 @@ public interface PaymentPluginApi {
     public PaymentInfoPlugin getPaymentInfo(UUID paymentId)
             throws PaymentPluginApiException;
 
-    public List<PaymentInfoPlugin> processRefund(Account account)
-            throws PaymentPluginApiException;
+    public void processRefund(Account account, UUID paymentId, BigDecimal refundAmout)
+    throws PaymentPluginApiException;
+
+    public int getNbRefundForPaymentAmount(final Account account, final UUID paymentId, final BigDecimal refundAmount)
+        throws PaymentPluginApiException;
 
     public String createPaymentProviderAccount(Account account)
             throws PaymentPluginApiException;
diff --git a/api/src/main/java/com/ning/billing/util/dao/ObjectType.java b/api/src/main/java/com/ning/billing/util/dao/ObjectType.java
index a158e77..b67e847 100644
--- a/api/src/main/java/com/ning/billing/util/dao/ObjectType.java
+++ b/api/src/main/java/com/ning/billing/util/dao/ObjectType.java
@@ -22,9 +22,10 @@ public enum ObjectType {
     BUNDLE("subscription bundle"),
     INVOICE("invoice"),
     PAYMENT("payment"),
-    RECURRING_INVOICE_ITEM("recurring_invoice_item"),
+    INVOICE_ITEM("invoice item"),
     SUBSCRIPTION("subscription"),
-    PAYMENT_METHOD("payment method");
+    PAYMENT_METHOD("payment method"),
+    REFUND("refund");
 
     private final String objectName;
 
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestAnalytics.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestAnalytics.java
index 9dadb9e..0c7eeec 100644
--- a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestAnalytics.java
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestAnalytics.java
@@ -38,11 +38,14 @@ import com.ning.billing.analytics.model.BusinessInvoiceItem;
 import com.ning.billing.analytics.model.BusinessSubscriptionEvent;
 import com.ning.billing.analytics.model.BusinessSubscriptionTransition;
 import com.ning.billing.analytics.utils.Rounder;
+import com.ning.billing.api.TestApiListener;
 import com.ning.billing.catalog.api.BillingPeriod;
 import com.ning.billing.catalog.api.CatalogApiException;
 import com.ning.billing.catalog.api.PhaseType;
+import com.ning.billing.catalog.api.Plan;
 import com.ning.billing.catalog.api.PlanPhaseSpecifier;
 import com.ning.billing.catalog.api.PriceListSet;
+import com.ning.billing.catalog.api.Product;
 import com.ning.billing.catalog.api.ProductCategory;
 import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
 import com.ning.billing.entitlement.api.user.Subscription;
@@ -52,8 +55,12 @@ import com.ning.billing.util.api.TagDefinitionApiException;
 import com.ning.billing.util.dao.ObjectType;
 import com.ning.billing.util.tag.TagDefinition;
 
+import static org.testng.Assert.assertTrue;
+
 @Guice(modules = BeatrixModule.class)
 public class TestAnalytics extends TestIntegrationBase {
+    private Plan subscriptionPlan;
+
     @BeforeMethod(groups = "slow")
     public void setUpAnalyticsHandler() throws Exception {
         busService.getBus().register(analyticsListener);
@@ -65,7 +72,46 @@ public class TestAnalytics extends TestIntegrationBase {
     }
 
     @Test(groups = "slow")
-    public void testAnalyticsEvents() throws Exception {
+    public void testCreateAndCancelSubscription() throws Exception {
+        // Create an account
+        final Account account = verifyAccountCreation();
+
+        // Create a bundle
+        final SubscriptionBundle bundle = verifyFirstBundle(account);
+
+        // Add a subscription
+        Subscription subscription = verifyFirstSubscription(account, bundle);
+
+        // Move after trial
+        clock.addDeltaFromReality(AT_LEAST_ONE_MONTH_MS);
+        busHandler.pushExpectedEvent(TestApiListener.NextEvent.PHASE);
+        busHandler.pushExpectedEvent(TestApiListener.NextEvent.INVOICE);
+        busHandler.pushExpectedEvent(TestApiListener.NextEvent.PAYMENT);
+        Assert.assertTrue(busHandler.isCompleted(DELAY));
+
+        // Check BST - nothing should have changed
+        verifyBSTWithTrialAndEvergreenPhases(account, bundle, subscription);
+
+        // Cancel end of term - refetch the subscription to have the CTD set
+        // (otherwise, cancellation would be immediate)
+        subscription = entitlementUserApi.getSubscriptionFromId(subscription.getId());
+        subscription.cancel(clock.getUTCNow(), true, context);
+
+        waitALittle();
+
+        verifyBSTWithTrialAndEvergreenPhasesAndCancellation(account, bundle, subscription);
+
+        // Move after cancel date
+        clock.addDeltaFromReality(AT_LEAST_ONE_MONTH_MS + 100);
+        assertTrue(busHandler.isCompleted(DELAY));
+        waitALittle();
+
+        // Check BST received the system cancel event
+        verifyBSTWithTrialAndEvergreenPhasesAndCancellationAndSystemCancellation(account, bundle, subscription);
+    }
+
+    @Test(groups = "slow")
+    public void testCreateAndUpdateSubscription() throws Exception {
         // Create an account
         final Account account = verifyAccountCreation();
 
@@ -192,35 +238,12 @@ public class TestAnalytics extends TestIntegrationBase {
         final String planSetName = PriceListSet.DEFAULT_PRICELIST_NAME;
         final PlanPhaseSpecifier phaseSpecifier = new PlanPhaseSpecifier(productName, ProductCategory.BASE, term, planSetName, null);
         final Subscription subscription = entitlementUserApi.createSubscription(bundle.getId(), phaseSpecifier, null, context);
+        subscriptionPlan = subscription.getCurrentPlan();
 
         waitALittle();
 
-        // BST should have one transition
-        final List<BusinessSubscriptionTransition> transitions = analyticsUserApi.getTransitionsForBundle(bundle.getKey());
-        Assert.assertEquals(transitions.size(), 1);
-        final BusinessSubscriptionTransition transition = transitions.get(0);
-        Assert.assertEquals(transition.getExternalKey(), bundle.getKey());
-        Assert.assertEquals(transition.getAccountKey(), account.getExternalKey());
-        Assert.assertEquals(transition.getEvent().getCategory(), phaseSpecifier.getProductCategory());
-        Assert.assertEquals(transition.getEvent().getEventType(), BusinessSubscriptionEvent.EventType.ADD);
-
-        // This is the first transition
-        Assert.assertNull(transition.getPreviousSubscription());
-
-        Assert.assertEquals(transition.getNextSubscription().getBillingPeriod(), subscription.getCurrentPhase().getBillingPeriod().toString());
-        Assert.assertEquals(transition.getNextSubscription().getBundleId(), subscription.getBundleId());
-        Assert.assertEquals(transition.getNextSubscription().getCurrency(), account.getCurrency().toString());
-        Assert.assertEquals(transition.getNextSubscription().getPhase(), subscription.getCurrentPhase().getPhaseType().toString());
-        // Trial: fixed price of zero
-        Assert.assertEquals(transition.getNextSubscription().getPrice().doubleValue(), subscription.getCurrentPhase().getFixedPrice().getPrice(account.getCurrency()).doubleValue());
-        Assert.assertEquals(transition.getNextSubscription().getPriceList(), subscription.getCurrentPriceList().getName());
-        Assert.assertEquals(transition.getNextSubscription().getProductCategory(), subscription.getCurrentPlan().getProduct().getCategory());
-        Assert.assertEquals(transition.getNextSubscription().getProductName(), subscription.getCurrentPlan().getProduct().getName());
-        Assert.assertEquals(transition.getNextSubscription().getProductType(), subscription.getCurrentPlan().getProduct().getCatalogName());
-        Assert.assertEquals(transition.getNextSubscription().getSlug(), subscription.getCurrentPhase().getName());
-        Assert.assertEquals(transition.getNextSubscription().getStartDate(), subscription.getStartDate());
-        Assert.assertEquals(transition.getNextSubscription().getState(), subscription.getState());
-        Assert.assertEquals(transition.getNextSubscription().getSubscriptionId(), subscription.getId());
+        // Verify BST
+        verifyBSTWithTrialAndEvergreenPhases(account, bundle, subscription);
 
         // Make sure the account balance is still zero
         final BusinessAccount businessAccount = analyticsUserApi.getAccountByKey(account.getExternalKey());
@@ -260,6 +283,118 @@ public class TestAnalytics extends TestIntegrationBase {
         return subscription;
     }
 
+    private void verifyBSTWithTrialAndEvergreenPhases(final Account account, final SubscriptionBundle bundle, final Subscription subscription) throws CatalogApiException {
+        // BST should have two transitions
+        final List<BusinessSubscriptionTransition> transitions = analyticsUserApi.getTransitionsForBundle(bundle.getKey());
+        Assert.assertEquals(transitions.size(), 2);
+
+        verifyTrialAndEvergreenPhases(account, bundle, subscription);
+    }
+
+    private void verifyBSTWithTrialAndEvergreenPhasesAndCancellation(final Account account, final SubscriptionBundle bundle, final Subscription subscription) throws CatalogApiException {
+        // BST should have three transitions
+        final List<BusinessSubscriptionTransition> transitions = analyticsUserApi.getTransitionsForBundle(bundle.getKey());
+        Assert.assertEquals(transitions.size(), 3);
+
+        verifyTrialAndEvergreenPhases(account, bundle, subscription);
+        verifyCancellationTransition(account, bundle);
+    }
+
+    private void verifyBSTWithTrialAndEvergreenPhasesAndCancellationAndSystemCancellation(final Account account, final SubscriptionBundle bundle, final Subscription subscription) throws CatalogApiException {
+        // BST should have four transitions
+        final List<BusinessSubscriptionTransition> transitions = analyticsUserApi.getTransitionsForBundle(bundle.getKey());
+        Assert.assertEquals(transitions.size(), 4);
+
+        verifyTrialAndEvergreenPhases(account, bundle, subscription);
+        verifyCancellationTransition(account, bundle);
+        verifySystemCancellationTransition(account, bundle);
+    }
+
+    private void verifyTrialAndEvergreenPhases(final Account account, final SubscriptionBundle bundle, final Subscription subscription) throws CatalogApiException {
+        final Product currentProduct = subscriptionPlan.getProduct();
+        final List<BusinessSubscriptionTransition> transitions = analyticsUserApi.getTransitionsForBundle(bundle.getKey());
+
+        // Check the first transition (into trial phase)
+        final BusinessSubscriptionTransition initialTransition = transitions.get(0);
+        Assert.assertEquals(initialTransition.getExternalKey(), bundle.getKey());
+        Assert.assertEquals(initialTransition.getAccountKey(), account.getExternalKey());
+        Assert.assertEquals(initialTransition.getEvent().getCategory(), currentProduct.getCategory());
+        Assert.assertEquals(initialTransition.getEvent().getEventType(), BusinessSubscriptionEvent.EventType.ADD);
+
+        // This is the first transition
+        Assert.assertNull(initialTransition.getPreviousSubscription());
+
+        Assert.assertEquals(initialTransition.getNextSubscription().getBillingPeriod(), BillingPeriod.NO_BILLING_PERIOD.toString());
+        Assert.assertEquals(initialTransition.getNextSubscription().getBundleId(), subscription.getBundleId());
+        Assert.assertEquals(initialTransition.getNextSubscription().getCurrency(), account.getCurrency().toString());
+        Assert.assertEquals(initialTransition.getNextSubscription().getPhase(), PhaseType.TRIAL.toString());
+        // Trial: fixed price of zero
+        Assert.assertEquals(initialTransition.getNextSubscription().getPrice().doubleValue(), (double) 0);
+        Assert.assertEquals(initialTransition.getNextSubscription().getPriceList(), subscription.getCurrentPriceList().getName());
+        Assert.assertEquals(initialTransition.getNextSubscription().getProductCategory(), currentProduct.getCategory());
+        Assert.assertEquals(initialTransition.getNextSubscription().getProductName(), currentProduct.getName());
+        Assert.assertEquals(initialTransition.getNextSubscription().getProductType(), currentProduct.getCatalogName());
+        Assert.assertEquals(initialTransition.getNextSubscription().getSlug(), currentProduct.getName().toLowerCase() + "-monthly-trial");
+        Assert.assertEquals(initialTransition.getNextSubscription().getStartDate(), subscription.getStartDate());
+        Assert.assertEquals(initialTransition.getNextSubscription().getState(), Subscription.SubscriptionState.ACTIVE);
+        Assert.assertEquals(initialTransition.getNextSubscription().getSubscriptionId(), subscription.getId());
+
+        // Check the second transition (from trial to evergreen)
+        final BusinessSubscriptionTransition futureTransition = transitions.get(1);
+        Assert.assertEquals(futureTransition.getExternalKey(), bundle.getKey());
+        Assert.assertEquals(futureTransition.getAccountKey(), account.getExternalKey());
+        Assert.assertEquals(futureTransition.getEvent().getCategory(), currentProduct.getCategory());
+        Assert.assertEquals(futureTransition.getEvent().getEventType(), BusinessSubscriptionEvent.EventType.SYSTEM_CHANGE);
+
+        Assert.assertEquals(futureTransition.getPreviousSubscription(), initialTransition.getNextSubscription());
+
+        // The billing period should have changed (NO_BILLING_PERIOD for the trial period)
+        Assert.assertEquals(futureTransition.getNextSubscription().getBillingPeriod(), BillingPeriod.MONTHLY.toString());
+        Assert.assertEquals(futureTransition.getNextSubscription().getBundleId(), subscription.getBundleId());
+        Assert.assertEquals(initialTransition.getNextSubscription().getCurrency(), account.getCurrency().toString());
+        // From trial to evergreen
+        Assert.assertEquals(futureTransition.getNextSubscription().getPhase(), PhaseType.EVERGREEN.toString());
+        Assert.assertTrue(futureTransition.getNextSubscription().getPrice().doubleValue() > 0);
+        Assert.assertEquals(futureTransition.getNextSubscription().getPriceList(), subscription.getCurrentPriceList().getName());
+        Assert.assertEquals(futureTransition.getNextSubscription().getProductCategory(), currentProduct.getCategory());
+        Assert.assertEquals(futureTransition.getNextSubscription().getProductName(), currentProduct.getName());
+        Assert.assertEquals(futureTransition.getNextSubscription().getProductType(), currentProduct.getCatalogName());
+        Assert.assertEquals(futureTransition.getNextSubscription().getSlug(), currentProduct.getName().toLowerCase() + "-monthly-evergreen");
+        // 30 days trial
+        Assert.assertEquals(futureTransition.getNextSubscription().getStartDate(), subscription.getStartDate().plusDays(30));
+        Assert.assertEquals(futureTransition.getNextSubscription().getState(), Subscription.SubscriptionState.ACTIVE);
+        Assert.assertEquals(futureTransition.getNextSubscription().getSubscriptionId(), subscription.getId());
+    }
+
+    private void verifyCancellationTransition(final Account account, final SubscriptionBundle bundle) throws CatalogApiException {
+        final Product currentProduct = subscriptionPlan.getProduct();
+        final List<BusinessSubscriptionTransition> transitions = analyticsUserApi.getTransitionsForBundle(bundle.getKey());
+
+        final BusinessSubscriptionTransition cancellationRequest = transitions.get(2);
+        Assert.assertEquals(cancellationRequest.getExternalKey(), bundle.getKey());
+        Assert.assertEquals(cancellationRequest.getAccountKey(), account.getExternalKey());
+        Assert.assertEquals(cancellationRequest.getEvent().getCategory(), currentProduct.getCategory());
+        Assert.assertEquals(cancellationRequest.getEvent().getEventType(), BusinessSubscriptionEvent.EventType.CANCEL);
+
+        Assert.assertNull(cancellationRequest.getNextSubscription());
+        // The actual content has already been checked in verifyTrialAndEvergreenPhases
+        Assert.assertEquals(cancellationRequest.getPreviousSubscription(), transitions.get(1).getNextSubscription());
+    }
+
+    private void verifySystemCancellationTransition(final Account account, final SubscriptionBundle bundle) throws CatalogApiException {
+        final List<BusinessSubscriptionTransition> transitions = analyticsUserApi.getTransitionsForBundle(bundle.getKey());
+
+        final BusinessSubscriptionTransition systemCancellation = transitions.get(3);
+        Assert.assertEquals(systemCancellation.getExternalKey(), bundle.getKey());
+        Assert.assertEquals(systemCancellation.getAccountKey(), account.getExternalKey());
+        Assert.assertEquals(systemCancellation.getEvent().getCategory(), ProductCategory.BASE);
+        Assert.assertEquals(systemCancellation.getEvent().getEventType(), BusinessSubscriptionEvent.EventType.SYSTEM_CANCEL);
+
+        Assert.assertNull(systemCancellation.getNextSubscription());
+        // The actual content has already been checked in verifyTrialAndEvergreenPhases
+        Assert.assertEquals(systemCancellation.getPreviousSubscription(), transitions.get(1).getNextSubscription());
+    }
+
     private void verifyChangePlan(final Account account, final SubscriptionBundle bundle, final Subscription subscription) throws EntitlementUserApiException, InterruptedException {
         final String newProductName = "Assault-Rifle";
         final BillingPeriod newTerm = BillingPeriod.MONTHLY;
@@ -269,9 +404,9 @@ public class TestAnalytics extends TestIntegrationBase {
 
         waitALittle();
 
-        // BST should have two transitions
+        // BST should have three transitions (a ADD_BASE, CHANGE_BASE and SYSTEM_CHANGE_BASE)
         final List<BusinessSubscriptionTransition> transitions = analyticsUserApi.getTransitionsForBundle(bundle.getKey());
-        Assert.assertEquals(transitions.size(), 2);
+        Assert.assertEquals(transitions.size(), 3);
         final BusinessSubscriptionTransition previousTransition = transitions.get(0);
         final BusinessSubscriptionTransition transition = transitions.get(1);
         Assert.assertEquals(transition.getExternalKey(), bundle.getKey());
@@ -317,6 +452,6 @@ public class TestAnalytics extends TestIntegrationBase {
 
     private void waitALittle() throws InterruptedException {
         // We especially need to wait for entitlement events
-        Thread.sleep(2000);
+        Thread.sleep(4000);
     }
 }
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/DefaultEntitlementTimelineApi.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/DefaultEntitlementTimelineApi.java
index 368dbb5..220c2ad 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/DefaultEntitlementTimelineApi.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/DefaultEntitlementTimelineApi.java
@@ -37,6 +37,7 @@ import com.ning.billing.entitlement.api.SubscriptionFactory;
 import com.ning.billing.entitlement.api.SubscriptionTransitionType;
 import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline.NewEvent;
 import com.ning.billing.entitlement.api.user.DefaultSubscriptionFactory.SubscriptionBuilder;
+import com.ning.billing.entitlement.api.user.EffectiveSubscriptionEvent;
 import com.ning.billing.entitlement.api.user.Subscription;
 import com.ning.billing.entitlement.api.user.SubscriptionBundle;
 import com.ning.billing.entitlement.api.user.SubscriptionBundleData;
@@ -323,7 +324,7 @@ public class DefaultEntitlementTimelineApi implements EntitlementTimelineApi {
         if (nbDeleted != deletedEvents.size()) {
             for (final SubscriptionTimeline.DeletedEvent d : deletedEvents) {
                 boolean found = false;
-                for (final SubscriptionTransitionData cur : data.getAllTransitions()) {
+                for (final EffectiveSubscriptionEvent cur : data.getAllTransitions()) {
                     if (cur.getId().equals(d.getEventId())) {
                         found = true;
                     }
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/SubscriptionDataRepair.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/SubscriptionDataRepair.java
index cdc6797..66cf27c 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/SubscriptionDataRepair.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/SubscriptionDataRepair.java
@@ -165,7 +165,7 @@ public class SubscriptionDataRepair extends SubscriptionData {
                                                                                 .setRequestedDate(now)
                                                                                 .setUserToken(context.getUserToken())
                                                                                 .setFromDisk(true));
-                repairDao.cancelSubscription(cur.getId(), cancelEvent, context, 0);
+                repairDao.cancelSubscription(cur, cancelEvent, context, 0);
                 cur.rebuildTransitions(repairDao.getEventsForSubscription(cur.getId()), catalogService.getFullCatalog());
             }
         }
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/DefaultRequestedSubscriptionEvent.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/DefaultRequestedSubscriptionEvent.java
index f03ba35..38b6b98 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/DefaultRequestedSubscriptionEvent.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/DefaultRequestedSubscriptionEvent.java
@@ -23,6 +23,7 @@ import org.joda.time.DateTime;
 import com.fasterxml.jackson.annotation.JsonCreator;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import com.ning.billing.entitlement.api.SubscriptionTransitionType;
+import com.ning.billing.entitlement.events.EntitlementEvent;
 
 public class DefaultRequestedSubscriptionEvent extends DefaultSubscriptionEvent implements RequestedSubscriptionEvent {
     public DefaultRequestedSubscriptionEvent(final SubscriptionTransitionData in, final DateTime startDate) {
@@ -52,4 +53,9 @@ public class DefaultRequestedSubscriptionEvent extends DefaultSubscriptionEvent 
               previousPhase, previousPriceList, nextState, nextPlan, nextPhase, nextPriceList, totalOrdering, userToken,
               transitionType, remainingEventsForUserOperation, startDate);
     }
+
+    public DefaultRequestedSubscriptionEvent(final SubscriptionData subscription, final EntitlementEvent nextEvent) {
+        this(nextEvent.getId(), nextEvent.getSubscriptionId(), subscription.getBundleId(), nextEvent.getRequestedDate(), nextEvent.getEffectiveDate(),
+             null, null, null, null, null, null, null, null, nextEvent.getTotalOrdering(), null, null, 0, null);
+    }
 }
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/DefaultSubscriptionApiService.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/DefaultSubscriptionApiService.java
index 4469661..99e3e58 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/DefaultSubscriptionApiService.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/DefaultSubscriptionApiService.java
@@ -139,7 +139,7 @@ public class DefaultSubscriptionApiService implements SubscriptionApiService {
                 events.add(nextPhaseEvent);
             }
             if (reCreate) {
-                dao.recreateSubscription(subscription.getId(), events, context);
+                dao.recreateSubscription(subscription, events, context);
             } else {
                 dao.createSubscription(subscription, events, context);
             }
@@ -179,7 +179,7 @@ public class DefaultSubscriptionApiService implements SubscriptionApiService {
                                                                             .setUserToken(context.getUserToken())
                                                                             .setFromDisk(true));
 
-            dao.cancelSubscription(subscription.getId(), cancelEvent, context, 0);
+            dao.cancelSubscription(subscription, cancelEvent, context, 0);
             subscription.rebuildTransitions(dao.getEventsForSubscription(subscription.getId()), catalogService.getFullCatalog());
             return (policy == ActionPolicy.IMMEDIATE);
         } catch (CatalogApiException e) {
@@ -213,7 +213,7 @@ public class DefaultSubscriptionApiService implements SubscriptionApiService {
             uncancelEvents.add(nextPhaseEvent);
         }
 
-        dao.uncancelSubscription(subscription.getId(), uncancelEvents, context);
+        dao.uncancelSubscription(subscription, uncancelEvents, context);
         subscription.rebuildTransitions(dao.getEventsForSubscription(subscription.getId()), catalogService.getFullCatalog());
 
         return true;
@@ -285,7 +285,7 @@ public class DefaultSubscriptionApiService implements SubscriptionApiService {
                 changeEvents.add(nextPhaseEvent);
             }
             changeEvents.add(changeEvent);
-            dao.changePlan(subscription.getId(), changeEvents, context);
+            dao.changePlan(subscription, changeEvents, context);
             subscription.rebuildTransitions(dao.getEventsForSubscription(subscription.getId()), catalogService.getFullCatalog());
             return (policy == ActionPolicy.IMMEDIATE);
         } catch (CatalogApiException e) {
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionData.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionData.java
index 409bc8d..d5586e9 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionData.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionData.java
@@ -303,8 +303,19 @@ public class SubscriptionData extends EntityBase implements Subscription {
         return activeVersion;
     }
 
-    public List<SubscriptionTransitionData> getAllTransitions() {
-        return transitions;
+    @Override
+    public List<EffectiveSubscriptionEvent> getAllTransitions() {
+        if (transitions == null) {
+            return Collections.emptyList();
+        }
+
+        final List<EffectiveSubscriptionEvent> result = new ArrayList<EffectiveSubscriptionEvent>();
+        final SubscriptionTransitionDataIterator it = new SubscriptionTransitionDataIterator(clock, transitions, Order.ASC_FROM_PAST, Kind.ALL, Visibility.ALL, TimeLimit.ALL);
+        while (it.hasNext()) {
+            result.add(new DefaultEffectiveSubscriptionEvent(it.next(), startDate));
+        }
+
+        return result;
     }
 
     public SubscriptionTransitionData getInitialTransitionForCurrentPlan() {
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransitionData.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransitionData.java
index ac54ea3..d3707fa 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransitionData.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransitionData.java
@@ -30,8 +30,6 @@ import com.ning.billing.entitlement.events.user.ApiEventType;
 import com.ning.billing.entitlement.exceptions.EntitlementError;
 
 public class SubscriptionTransitionData /* implements SubscriptionEvent */ {
-
-
     private final Long totalOrdering;
     private final UUID subscriptionId;
     private final UUID bundleId;
@@ -52,7 +50,6 @@ public class SubscriptionTransitionData /* implements SubscriptionEvent */ {
     private final Integer remainingEventsForUserOperation;
     private final UUID userToken;
 
-
     public SubscriptionTransitionData(final UUID eventId,
                                       final UUID subscriptionId,
                                       final UUID bundleId,
@@ -71,7 +68,6 @@ public class SubscriptionTransitionData /* implements SubscriptionEvent */ {
                                       final Long totalOrdering,
                                       final UUID userToken,
                                       final Boolean isFromDisk) {
-        super();
         this.eventId = eventId;
         this.subscriptionId = subscriptionId;
         this.bundleId = bundleId;
@@ -116,7 +112,6 @@ public class SubscriptionTransitionData /* implements SubscriptionEvent */ {
         this.remainingEventsForUserOperation = remainingEventsForUserOperation;
     }
 
-
     public UUID getId() {
         return eventId;
     }
@@ -153,7 +148,6 @@ public class SubscriptionTransitionData /* implements SubscriptionEvent */ {
         return nextState;
     }
 
-
     public PriceList getPreviousPriceList() {
         return previousPriceList;
     }
@@ -170,7 +164,6 @@ public class SubscriptionTransitionData /* implements SubscriptionEvent */ {
         return remainingEventsForUserOperation;
     }
 
-
     public SubscriptionTransitionType getTransitionType() {
         return toSubscriptionTransitionType(eventType, apiEventType);
     }
@@ -194,7 +187,6 @@ public class SubscriptionTransitionData /* implements SubscriptionEvent */ {
         return effectiveTransitionTime;
     }
 
-
     public Long getTotalOrdering() {
         return totalOrdering;
     }
@@ -211,21 +203,126 @@ public class SubscriptionTransitionData /* implements SubscriptionEvent */ {
         return eventType;
     }
 
-
     @Override
     public String toString() {
-        return "SubscriptionTransition [eventId=" + eventId
-                + ", subscriptionId=" + subscriptionId
-                + ", eventType=" + eventType + ", apiEventType="
-                + apiEventType + ", requestedTransitionTime=" + requestedTransitionTime
-                + ", effectiveTransitionTime=" + effectiveTransitionTime
-                + ", previousState=" + previousState + ", previousPlan="
-                + ((previousPlan != null) ? previousPlan.getName() : null)
-                + ", previousPhase=" + ((previousPhase != null) ? previousPhase.getName() : null)
-                + ", previousPriceList " + previousPriceList
-                + ", nextState=" + nextState
-                + ", nextPlan=" + ((nextPlan != null) ? nextPlan.getName() : null)
-                + ", nextPriceList " + nextPriceList
-                + ", nextPhase=" + ((nextPhase != null) ? nextPhase.getName() : null) + "]";
+        final StringBuilder sb = new StringBuilder();
+        sb.append("SubscriptionTransitionData");
+        sb.append("{apiEventType=").append(apiEventType);
+        sb.append(", totalOrdering=").append(totalOrdering);
+        sb.append(", subscriptionId=").append(subscriptionId);
+        sb.append(", bundleId=").append(bundleId);
+        sb.append(", eventId=").append(eventId);
+        sb.append(", eventType=").append(eventType);
+        sb.append(", requestedTransitionTime=").append(requestedTransitionTime);
+        sb.append(", effectiveTransitionTime=").append(effectiveTransitionTime);
+        sb.append(", previousState=").append(previousState);
+        sb.append(", previousPriceList=").append(previousPriceList);
+        sb.append(", previousPlan=").append(previousPlan);
+        sb.append(", previousPhase=").append(previousPhase);
+        sb.append(", nextState=").append(nextState);
+        sb.append(", nextPriceList=").append(nextPriceList);
+        sb.append(", nextPlan=").append(nextPlan);
+        sb.append(", nextPhase=").append(nextPhase);
+        sb.append(", isFromDisk=").append(isFromDisk);
+        sb.append(", remainingEventsForUserOperation=").append(remainingEventsForUserOperation);
+        sb.append(", userToken=").append(userToken);
+        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 SubscriptionTransitionData that = (SubscriptionTransitionData) o;
+
+        if (apiEventType != that.apiEventType) {
+            return false;
+        }
+        if (bundleId != null ? !bundleId.equals(that.bundleId) : that.bundleId != null) {
+            return false;
+        }
+        if (effectiveTransitionTime != null ? effectiveTransitionTime.compareTo(that.effectiveTransitionTime) != 0 : that.effectiveTransitionTime != null) {
+            return false;
+        }
+        if (eventId != null ? !eventId.equals(that.eventId) : that.eventId != null) {
+            return false;
+        }
+        if (eventType != that.eventType) {
+            return false;
+        }
+        if (isFromDisk != null ? !isFromDisk.equals(that.isFromDisk) : that.isFromDisk != null) {
+            return false;
+        }
+        if (nextPhase != null ? !nextPhase.equals(that.nextPhase) : that.nextPhase != null) {
+            return false;
+        }
+        if (nextPlan != null ? !nextPlan.equals(that.nextPlan) : that.nextPlan != null) {
+            return false;
+        }
+        if (nextPriceList != null ? !nextPriceList.equals(that.nextPriceList) : that.nextPriceList != null) {
+            return false;
+        }
+        if (nextState != that.nextState) {
+            return false;
+        }
+        if (previousPhase != null ? !previousPhase.equals(that.previousPhase) : that.previousPhase != null) {
+            return false;
+        }
+        if (previousPlan != null ? !previousPlan.equals(that.previousPlan) : that.previousPlan != null) {
+            return false;
+        }
+        if (previousPriceList != null ? !previousPriceList.equals(that.previousPriceList) : that.previousPriceList != null) {
+            return false;
+        }
+        if (previousState != that.previousState) {
+            return false;
+        }
+        if (remainingEventsForUserOperation != null ? !remainingEventsForUserOperation.equals(that.remainingEventsForUserOperation) : that.remainingEventsForUserOperation != null) {
+            return false;
+        }
+        if (requestedTransitionTime != null ? requestedTransitionTime.compareTo(that.requestedTransitionTime) != 0 : that.requestedTransitionTime != null) {
+            return false;
+        }
+        if (subscriptionId != null ? !subscriptionId.equals(that.subscriptionId) : that.subscriptionId != null) {
+            return false;
+        }
+        if (totalOrdering != null ? !totalOrdering.equals(that.totalOrdering) : that.totalOrdering != null) {
+            return false;
+        }
+        if (userToken != null ? !userToken.equals(that.userToken) : that.userToken != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = totalOrdering != null ? totalOrdering.hashCode() : 0;
+        result = 31 * result + (subscriptionId != null ? subscriptionId.hashCode() : 0);
+        result = 31 * result + (bundleId != null ? bundleId.hashCode() : 0);
+        result = 31 * result + (eventId != null ? eventId.hashCode() : 0);
+        result = 31 * result + (eventType != null ? eventType.hashCode() : 0);
+        result = 31 * result + (apiEventType != null ? apiEventType.hashCode() : 0);
+        result = 31 * result + (requestedTransitionTime != null ? requestedTransitionTime.hashCode() : 0);
+        result = 31 * result + (effectiveTransitionTime != null ? effectiveTransitionTime.hashCode() : 0);
+        result = 31 * result + (previousState != null ? previousState.hashCode() : 0);
+        result = 31 * result + (previousPriceList != null ? previousPriceList.hashCode() : 0);
+        result = 31 * result + (previousPlan != null ? previousPlan.hashCode() : 0);
+        result = 31 * result + (previousPhase != null ? previousPhase.hashCode() : 0);
+        result = 31 * result + (nextState != null ? nextState.hashCode() : 0);
+        result = 31 * result + (nextPriceList != null ? nextPriceList.hashCode() : 0);
+        result = 31 * result + (nextPlan != null ? nextPlan.hashCode() : 0);
+        result = 31 * result + (nextPhase != null ? nextPhase.hashCode() : 0);
+        result = 31 * result + (isFromDisk != null ? isFromDisk.hashCode() : 0);
+        result = 31 * result + (remainingEventsForUserOperation != null ? remainingEventsForUserOperation.hashCode() : 0);
+        result = 31 * result + (userToken != null ? userToken.hashCode() : 0);
+        return result;
     }
 }
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/Engine.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/Engine.java
index a4a926e..57b21d3 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/Engine.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/Engine.java
@@ -16,15 +16,11 @@
 
 package com.ning.billing.entitlement.engine.core;
 
-
 import java.util.HashMap;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.UUID;
 
-import javax.swing.text.html.HTMLDocument.HTMLReader.IsindexAction;
-
 import org.joda.time.DateTime;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -69,8 +65,6 @@ import com.ning.billing.util.notificationq.NotificationQueueService.Notification
 import com.ning.billing.util.notificationq.NotificationQueueService.NotificationQueueHandler;
 
 public class Engine implements EventListener, EntitlementService {
-
-
     public static final String NOTIFICATION_QUEUE_NAME = "subscription-events";
     public static final String ENTITLEMENT_SERVICE_NAME = "entitlement-service";
 
@@ -81,21 +75,20 @@ public class Engine implements EventListener, EntitlementService {
     private final PlanAligner planAligner;
     private final AddonUtils addonUtils;
     private final Bus eventBus;
-
     private final EntitlementConfig config;
     private final NotificationQueueService notificationQueueService;
     private final CallContextFactory factory;
     private final SubscriptionFactory subscriptionFactory;
+
     private NotificationQueue subscriptionEventQueue;
 
     @Inject
     public Engine(final Clock clock, final EntitlementDao dao, final PlanAligner planAligner,
-            final EntitlementConfig config,
-            final AddonUtils addonUtils, final Bus eventBus,
-            final NotificationQueueService notificationQueueService,
-            final SubscriptionFactory subscriptionFactory,
-            final CallContextFactory factory) {
-        super();
+                  final EntitlementConfig config,
+                  final AddonUtils addonUtils, final Bus eventBus,
+                  final NotificationQueueService notificationQueueService,
+                  final SubscriptionFactory subscriptionFactory,
+                  final CallContextFactory factory) {
         this.clock = clock;
         this.dao = dao;
         this.planAligner = planAligner;
@@ -114,32 +107,29 @@ public class Engine implements EventListener, EntitlementService {
 
     @LifecycleHandlerType(LifecycleLevel.INIT_SERVICE)
     public void initialize() {
-
         try {
-            subscriptionEventQueue = notificationQueueService.createNotificationQueue(ENTITLEMENT_SERVICE_NAME,
-                    NOTIFICATION_QUEUE_NAME,
-                    new NotificationQueueHandler() {
+            final NotificationQueueHandler queueHandler = new NotificationQueueHandler() {
                 @Override
                 public void handleReadyNotification(final NotificationKey inputKey, final DateTime eventDateTime) {
-
-                    if (! (inputKey instanceof EntitlementNotificationKey)) {
+                    if (!(inputKey instanceof EntitlementNotificationKey)) {
                         log.error("Entitlement service received an unexpected event type {}" + inputKey.getClass().getName());
                         return;
                     }
-                    EntitlementNotificationKey key = (EntitlementNotificationKey) inputKey;
-                    
-                     final EntitlementEvent event = dao.getEventById(key.getEventId());
+
+                    final EntitlementNotificationKey key = (EntitlementNotificationKey) inputKey;
+                    final EntitlementEvent event = dao.getEventById(key.getEventId());
                     if (event == null) {
                         log.warn("Failed to extract event for notification key {}", inputKey);
                         return;
                     }
+
                     final UUID userToken = (event.getType() == EventType.API_USER) ? ((ApiEvent) event).getUserToken() : null;
                     final CallContext context = factory.createCallContext("SubscriptionEventQueue", CallOrigin.INTERNAL, UserType.SYSTEM, userToken);
                     processEventReady(event, key.getSeqId(), context);
                 }
-            },
-            new NotificationConfig() {
+            };
 
+            final NotificationConfig notificationConfig = new NotificationConfig() {
                 @Override
                 public long getSleepTimeMs() {
                     return config.getSleepTimeMs();
@@ -149,8 +139,12 @@ public class Engine implements EventListener, EntitlementService {
                 public boolean isNotificationProcessingOff() {
                     return config.isNotificationProcessingOff();
                 }
-            }
-            );
+            };
+
+            subscriptionEventQueue = notificationQueueService.createNotificationQueue(ENTITLEMENT_SERVICE_NAME,
+                                                                                      NOTIFICATION_QUEUE_NAME,
+                                                                                      queueHandler,
+                                                                                      notificationConfig);
         } catch (NotificationQueueAlreadyExists e) {
             throw new RuntimeException(e);
         }
@@ -169,12 +163,12 @@ public class Engine implements EventListener, EntitlementService {
         }
     }
 
-
     @Override
     public void processEventReady(final EntitlementEvent event, final int seqId, final CallContext context) {
         if (!event.isActive()) {
             return;
         }
+
         final SubscriptionData subscription = (SubscriptionData) dao.getSubscriptionFromId(subscriptionFactory, event.getSubscriptionId());
         if (subscription == null) {
             log.warn("Failed to retrieve subscription for id %s", event.getSubscriptionId());
@@ -192,10 +186,10 @@ public class Engine implements EventListener, EntitlementService {
         int theRealSeqId = seqId;
         if (event.getType() == EventType.PHASE) {
             onPhaseEvent(subscription, context);
-        } else if (event.getType() == EventType.API_USER &&
-                subscription.getCategory() == ProductCategory.BASE) {
+        } else if (event.getType() == EventType.API_USER && subscription.getCategory() == ProductCategory.BASE) {
             theRealSeqId = onBasePlanEvent(subscription, (ApiEvent) event, context);
         }
+
         try {
             eventBus.post(subscription.getTransitionFromEvent(event, theRealSeqId));
         } catch (EventBusException e) {
@@ -203,41 +197,36 @@ public class Engine implements EventListener, EntitlementService {
         }
     }
 
-
     private void onPhaseEvent(final SubscriptionData subscription, final CallContext context) {
         try {
             final DateTime now = clock.getUTCNow();
             final TimedPhase nextTimedPhase = planAligner.getNextTimedPhase(subscription, now, now);
             final PhaseEvent nextPhaseEvent = (nextTimedPhase != null) ?
                     PhaseEventData.createNextPhaseEvent(nextTimedPhase.getPhase().getName(), subscription, now, nextTimedPhase.getStartPhase()) :
-                        null;
-                    if (nextPhaseEvent != null) {
-                        dao.createNextPhaseEvent(subscription.getId(), nextPhaseEvent, context);
-                    }
+                    null;
+            if (nextPhaseEvent != null) {
+                dao.createNextPhaseEvent(subscription, nextPhaseEvent, context);
+            }
         } catch (EntitlementError e) {
             log.error(String.format("Failed to insert next phase for subscription %s", subscription.getId()), e);
         }
     }
 
     private int onBasePlanEvent(final SubscriptionData baseSubscription, final ApiEvent event, final CallContext context) {
-
         final DateTime now = clock.getUTCNow();
-
-        final Product baseProduct = (baseSubscription.getState() == SubscriptionState.CANCELLED) ?
-                null : baseSubscription.getCurrentPlan().getProduct();
+        final Product baseProduct = (baseSubscription.getState() == SubscriptionState.CANCELLED) ? null : baseSubscription.getCurrentPlan().getProduct();
 
         final List<Subscription> subscriptions = dao.getSubscriptions(subscriptionFactory, baseSubscription.getBundleId());
 
-
         final Map<UUID, EntitlementEvent> addOnCancellations = new HashMap<UUID, EntitlementEvent>();
-
-        final Iterator<Subscription> it = subscriptions.iterator();
-        while (it.hasNext()) {
-            final SubscriptionData cur = (SubscriptionData) it.next();
+        final Map<UUID, SubscriptionData> addOnCancellationSubscriptions = new HashMap<UUID, SubscriptionData>();
+        for (final Subscription subscription : subscriptions) {
+            final SubscriptionData cur = (SubscriptionData) subscription;
             if (cur.getState() == SubscriptionState.CANCELLED ||
                     cur.getCategory() != ProductCategory.ADD_ON) {
                 continue;
             }
+
             final Plan addonCurrentPlan = cur.getCurrentPlan();
             if (baseProduct == null ||
                     addonUtils.isAddonIncluded(baseProduct, addonCurrentPlan) ||
@@ -246,23 +235,26 @@ public class Engine implements EventListener, EntitlementService {
                 // Perform AO cancellation using the effectiveDate of the BP
                 //
                 final EntitlementEvent cancelEvent = new ApiEventCancel(new ApiEventBuilder()
-                .setSubscriptionId(cur.getId())
-                .setActiveVersion(cur.getActiveVersion())
-                .setProcessedDate(now)
-                .setEffectiveDate(event.getEffectiveDate())
-                .setRequestedDate(now)
-                .setUserToken(context.getUserToken())
-                .setFromDisk(true));
+                                                                                .setSubscriptionId(cur.getId())
+                                                                                .setActiveVersion(cur.getActiveVersion())
+                                                                                .setProcessedDate(now)
+                                                                                .setEffectiveDate(event.getEffectiveDate())
+                                                                                .setRequestedDate(now)
+                                                                                .setUserToken(context.getUserToken())
+                                                                                .setFromDisk(true));
 
                 addOnCancellations.put(cur.getId(), cancelEvent);
+                addOnCancellationSubscriptions.put(cur.getId(), cur);
             }
         }
+
         final int addOnSize = addOnCancellations.size();
         int cancelSeq = addOnSize - 1;
         for (final UUID key : addOnCancellations.keySet()) {
-            dao.cancelSubscription(key, addOnCancellations.get(key), context, cancelSeq);
+            dao.cancelSubscription(addOnCancellationSubscriptions.get(key), addOnCancellations.get(key), context, cancelSeq);
             cancelSeq--;
         }
+
         return addOnSize;
     }
 }
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/AuditedEntitlementDao.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/AuditedEntitlementDao.java
index 2d7f212..24c7cca 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/AuditedEntitlementDao.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/AuditedEntitlementDao.java
@@ -40,6 +40,7 @@ import com.google.common.base.Predicate;
 import com.google.common.collect.Collections2;
 import com.google.inject.Inject;
 import com.ning.billing.ErrorCode;
+import com.ning.billing.catalog.api.CatalogService;
 import com.ning.billing.catalog.api.Plan;
 import com.ning.billing.catalog.api.ProductCategory;
 import com.ning.billing.entitlement.api.SubscriptionFactory;
@@ -49,6 +50,7 @@ import com.ning.billing.entitlement.api.migration.AccountMigrationData.Subscript
 import com.ning.billing.entitlement.api.timeline.DefaultRepairEntitlementEvent;
 import com.ning.billing.entitlement.api.timeline.RepairEntitlementEvent;
 import com.ning.billing.entitlement.api.timeline.SubscriptionDataRepair;
+import com.ning.billing.entitlement.api.user.DefaultRequestedSubscriptionEvent;
 import com.ning.billing.entitlement.api.user.DefaultSubscriptionFactory.SubscriptionBuilder;
 import com.ning.billing.entitlement.api.user.Subscription;
 import com.ning.billing.entitlement.api.user.SubscriptionBundle;
@@ -87,10 +89,11 @@ public class AuditedEntitlementDao implements EntitlementDao {
     private final NotificationQueueService notificationQueueService;
     private final AddonUtils addonUtils;
     private final Bus eventBus;
+    private final CatalogService catalogService;
 
     @Inject
     public AuditedEntitlementDao(final IDBI dbi, final Clock clock, final AddonUtils addonUtils,
-                                 final NotificationQueueService notificationQueueService, final Bus eventBus) {
+                                 final NotificationQueueService notificationQueueService, final Bus eventBus, final CatalogService catalogService) {
         this.clock = clock;
         this.subscriptionsDao = dbi.onDemand(SubscriptionSqlDao.class);
         this.eventsDao = dbi.onDemand(EntitlementEventSqlDao.class);
@@ -98,6 +101,7 @@ public class AuditedEntitlementDao implements EntitlementDao {
         this.notificationQueueService = notificationQueueService;
         this.addonUtils = addonUtils;
         this.eventBus = eventBus;
+        this.catalogService = catalogService;
     }
 
     @Override
@@ -206,10 +210,11 @@ public class AuditedEntitlementDao implements EntitlementDao {
     }
 
     @Override
-    public void createNextPhaseEvent(final UUID subscriptionId, final EntitlementEvent nextPhase, final CallContext context) {
+    public void createNextPhaseEvent(final SubscriptionData subscription, final EntitlementEvent nextPhase, final CallContext context) {
         eventsDao.inTransaction(new Transaction<Void, EntitlementEventSqlDao>() {
             @Override
             public Void inTransaction(final EntitlementEventSqlDao transactional, final TransactionStatus status) throws Exception {
+                final UUID subscriptionId = subscription.getId();
                 cancelNextPhaseEventFromTransaction(subscriptionId, transactional, context);
                 transactional.insertEvent(nextPhase, context);
 
@@ -221,6 +226,9 @@ public class AuditedEntitlementDao implements EntitlementDao {
                                                         nextPhase.getEffectiveDate(),
                                                         new EntitlementNotificationKey(nextPhase.getId()));
 
+                // Notify the Bus of the requested change
+                notifyBusOfRequestedChange(transactional, subscription, nextPhase);
+
                 return null;
             }
         });
@@ -290,13 +298,19 @@ public class AuditedEntitlementDao implements EntitlementDao {
                 }
 
                 eventsDaoFromSameTransaction.insertAuditFromTransaction(audits, context);
+
+                // Notify the Bus of the latest requested change, if needed
+                if (initialEvents.size() > 0) {
+                    notifyBusOfRequestedChange(eventsDaoFromSameTransaction, subscription, initialEvents.get(initialEvents.size() - 1));
+                }
+
                 return null;
             }
         });
     }
 
     @Override
-    public void recreateSubscription(final UUID subscriptionId, final List<EntitlementEvent> recreateEvents, final CallContext context) {
+    public void recreateSubscription(final SubscriptionData subscription, final List<EntitlementEvent> recreateEvents, final CallContext context) {
         eventsDao.inTransaction(new Transaction<Void, EntitlementEventSqlDao>() {
             @Override
             public Void inTransaction(final EntitlementEventSqlDao transactional,
@@ -313,16 +327,21 @@ public class AuditedEntitlementDao implements EntitlementDao {
                 }
 
                 transactional.insertAuditFromTransaction(audits, context);
+
+                // Notify the Bus of the latest requested change
+                notifyBusOfRequestedChange(transactional, subscription, recreateEvents.get(recreateEvents.size() - 1));
+
                 return null;
             }
         });
     }
 
     @Override
-    public void cancelSubscription(final UUID subscriptionId, final EntitlementEvent cancelEvent, final CallContext context, final int seqId) {
+    public void cancelSubscription(final SubscriptionData subscription, final EntitlementEvent cancelEvent, final CallContext context, final int seqId) {
         eventsDao.inTransaction(new Transaction<Void, EntitlementEventSqlDao>() {
             @Override
             public Void inTransaction(final EntitlementEventSqlDao transactional, final TransactionStatus status) throws Exception {
+                final UUID subscriptionId = subscription.getId();
                 cancelNextCancelEventFromTransaction(subscriptionId, transactional, context);
                 cancelNextChangeEventFromTransaction(subscriptionId, transactional, context);
                 cancelNextPhaseEventFromTransaction(subscriptionId, transactional, context);
@@ -336,16 +355,21 @@ public class AuditedEntitlementDao implements EntitlementDao {
                 recordFutureNotificationFromTransaction(transactional,
                                                         cancelEvent.getEffectiveDate(),
                                                         new EntitlementNotificationKey(cancelEvent.getId(), seqId));
+
+                // Notify the Bus of the requested change
+                notifyBusOfRequestedChange(transactional, subscription, cancelEvent);
+
                 return null;
             }
         });
     }
 
     @Override
-    public void uncancelSubscription(final UUID subscriptionId, final List<EntitlementEvent> uncancelEvents, final CallContext context) {
+    public void uncancelSubscription(final SubscriptionData subscription, final List<EntitlementEvent> uncancelEvents, final CallContext context) {
         eventsDao.inTransaction(new Transaction<Void, EntitlementEventSqlDao>() {
             @Override
             public Void inTransaction(final EntitlementEventSqlDao transactional, final TransactionStatus status) throws Exception {
+                final UUID subscriptionId = subscription.getId();
                 EntitlementEvent cancelledEvent = null;
                 final Date now = clock.getUTCNow().toDate();
                 final List<EntitlementEvent> events = transactional.getFutureActiveEventForSubscription(subscriptionId.toString(), now);
@@ -377,17 +401,22 @@ public class AuditedEntitlementDao implements EntitlementDao {
                     }
 
                     transactional.insertAuditFromTransaction(eventAudits, context);
+
+                    // Notify the Bus of the latest requested change
+                    notifyBusOfRequestedChange(transactional, subscription, uncancelEvents.get(uncancelEvents.size() - 1));
                 }
+
                 return null;
             }
         });
     }
 
     @Override
-    public void changePlan(final UUID subscriptionId, final List<EntitlementEvent> changeEvents, final CallContext context) {
+    public void changePlan(final SubscriptionData subscription, final List<EntitlementEvent> changeEvents, final CallContext context) {
         eventsDao.inTransaction(new Transaction<Void, EntitlementEventSqlDao>() {
             @Override
             public Void inTransaction(final EntitlementEventSqlDao transactional, final TransactionStatus status) throws Exception {
+                final UUID subscriptionId = subscription.getId();
                 cancelNextChangeEventFromTransaction(subscriptionId, transactional, context);
                 cancelNextPhaseEventFromTransaction(subscriptionId, transactional, context);
 
@@ -403,6 +432,11 @@ public class AuditedEntitlementDao implements EntitlementDao {
                 }
 
                 transactional.insertAuditFromTransaction(eventAudits, context);
+
+                // Notify the Bus of the latest requested change
+                final EntitlementEvent finalEvent = changeEvents.get(changeEvents.size() - 1);
+                notifyBusOfRequestedChange(transactional, subscription, finalEvent);
+
                 return null;
             }
         });
@@ -559,7 +593,6 @@ public class AuditedEntitlementDao implements EntitlementDao {
                     final SubscriptionBundleData bundleData = curBundle.getData();
 
                     for (final SubscriptionMigrationData curSubscription : curBundle.getSubscriptions()) {
-
                         final SubscriptionData subData = curSubscription.getData();
                         for (final EntitlementEvent curEvent : curSubscription.getInitialEvents()) {
                             transactional.insertEvent(curEvent, context);
@@ -573,7 +606,12 @@ public class AuditedEntitlementDao implements EntitlementDao {
                         transSubDao.insertSubscription(subData, context);
                         recordId = transSubDao.getRecordId(subData.getId().toString());
                         audits.add(new EntityAudit(TableName.SUBSCRIPTIONS, recordId, ChangeType.INSERT));
+
+                        // Notify the Bus of the latest requested change
+                        final EntitlementEvent finalEvent = curSubscription.getInitialEvents().get(curSubscription.getInitialEvents().size() - 1);
+                        notifyBusOfRequestedChange(transactional, subData, finalEvent);
                     }
+
                     transBundleDao.insertBundle(bundleData, context);
                     recordId = transBundleDao.getRecordId(bundleData.getId().toString());
                     audits.add(new EntityAudit(TableName.BUNDLES, recordId, ChangeType.INSERT));
@@ -608,6 +646,7 @@ public class AuditedEntitlementDao implements EntitlementDao {
                 }
 
                 try {
+                    // Note: we don't send a requested change event here, but a repair event
                     final RepairEntitlementEvent busEvent = new DefaultRepairEntitlementEvent(context.getUserToken(), accountId, bundleId, clock.getUTCNow());
                     eventBus.postFromTransaction(busEvent, transactional);
                 } catch (EventBusException e) {
@@ -641,4 +680,12 @@ public class AuditedEntitlementDao implements EntitlementDao {
             throw new RuntimeException(e);
         }
     }
+
+    private void notifyBusOfRequestedChange(final EntitlementEventSqlDao transactional, final SubscriptionData subscription, final EntitlementEvent nextEvent) {
+        try {
+            eventBus.postFromTransaction(new DefaultRequestedSubscriptionEvent(subscription, nextEvent), transactional);
+        } catch (EventBusException e) {
+            log.warn("Failed to post requested change event for subscription " + subscription.getId(), e);
+        }
+    }
 }
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EntitlementDao.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EntitlementDao.java
index 710391b..b85a47a 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EntitlementDao.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EntitlementDao.java
@@ -56,7 +56,7 @@ public interface EntitlementDao {
     public void updateChargedThroughDate(final SubscriptionData subscription, final CallContext context);
 
     // Event apis
-    public void createNextPhaseEvent(final UUID subscriptionId, final EntitlementEvent nextPhase, final CallContext context);
+    public void createNextPhaseEvent(final SubscriptionData subscription, final EntitlementEvent nextPhase, final CallContext context);
 
     public EntitlementEvent getEventById(final UUID eventId);
 
@@ -69,13 +69,13 @@ public interface EntitlementDao {
     // Subscription creation, cancellation, changePlan apis
     public void createSubscription(final SubscriptionData subscription, final List<EntitlementEvent> initialEvents, final CallContext context);
 
-    public void recreateSubscription(final UUID subscriptionId, final List<EntitlementEvent> recreateEvents, final CallContext context);
+    public void recreateSubscription(final SubscriptionData subscription, final List<EntitlementEvent> recreateEvents, final CallContext context);
 
-    public void cancelSubscription(final UUID subscriptionId, final EntitlementEvent cancelEvent, final CallContext context, final int cancelSeq);
+    public void cancelSubscription(final SubscriptionData subscription, final EntitlementEvent cancelEvent, final CallContext context, final int cancelSeq);
 
-    public void uncancelSubscription(final UUID subscriptionId, final List<EntitlementEvent> uncancelEvents, final CallContext context);
+    public void uncancelSubscription(final SubscriptionData subscription, final List<EntitlementEvent> uncancelEvents, final CallContext context);
 
-    public void changePlan(final UUID subscriptionId, final List<EntitlementEvent> changeEvents, final CallContext context);
+    public void changePlan(final SubscriptionData subscription, final List<EntitlementEvent> changeEvents, final CallContext context);
 
     public void migrate(final UUID accountId, final AccountMigrationData data, final CallContext context);
 
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/RepairEntitlementDao.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/RepairEntitlementDao.java
index 653e1ca..ed9cf13 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/RepairEntitlementDao.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/RepairEntitlementDao.java
@@ -92,14 +92,15 @@ public class RepairEntitlementDao implements EntitlementDao, RepairEntitlementLi
     }
 
     @Override
-    public void recreateSubscription(final UUID subscriptionId,
+    public void recreateSubscription(final SubscriptionData subscription,
                                      final List<EntitlementEvent> recreateEvents, final CallContext context) {
-        addEvents(subscriptionId, recreateEvents);
+        addEvents(subscription.getId(), recreateEvents);
     }
 
     @Override
-    public void cancelSubscription(final UUID subscriptionId,
+    public void cancelSubscription(final SubscriptionData subscription,
                                    final EntitlementEvent cancelEvent, final CallContext context, final int cancelSeq) {
+        final UUID subscriptionId = subscription.getId();
         final long activeVersion = cancelEvent.getActiveVersion();
         addEvents(subscriptionId, Collections.singletonList(cancelEvent));
         final SubscriptionRepairEvent target = getRepairSubscriptionEvents(subscriptionId);
@@ -114,9 +115,9 @@ public class RepairEntitlementDao implements EntitlementDao, RepairEntitlementLi
     }
 
     @Override
-    public void changePlan(final UUID subscriptionId,
+    public void changePlan(final SubscriptionData subscription,
                            final List<EntitlementEvent> changeEvents, final CallContext context) {
-        addEvents(subscriptionId, changeEvents);
+        addEvents(subscription.getId(), changeEvents);
     }
 
     @Override
@@ -142,7 +143,7 @@ public class RepairEntitlementDao implements EntitlementDao, RepairEntitlementLi
     }
 
     @Override
-    public void uncancelSubscription(final UUID subscriptionId,
+    public void uncancelSubscription(final SubscriptionData subscription,
                                      final List<EntitlementEvent> uncancelEvents, final CallContext context) {
         throw new EntitlementError(NOT_IMPLEMENTED);
     }
@@ -204,7 +205,7 @@ public class RepairEntitlementDao implements EntitlementDao, RepairEntitlementLi
     }
 
     @Override
-    public void createNextPhaseEvent(final UUID subscriptionId,
+    public void createNextPhaseEvent(final SubscriptionData subscription,
                                      final EntitlementEvent nextPhase, final CallContext context) {
         throw new EntitlementError(NOT_IMPLEMENTED);
     }
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/alignment/TestPlanAligner.java b/entitlement/src/test/java/com/ning/billing/entitlement/alignment/TestPlanAligner.java
index 400da99..6579baa 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/alignment/TestPlanAligner.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/alignment/TestPlanAligner.java
@@ -36,6 +36,7 @@ import com.ning.billing.catalog.api.PriceListSet;
 import com.ning.billing.catalog.io.VersionedCatalogLoader;
 import com.ning.billing.config.CatalogConfig;
 import com.ning.billing.entitlement.api.user.DefaultSubscriptionFactory;
+import com.ning.billing.entitlement.api.user.EffectiveSubscriptionEvent;
 import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
 import com.ning.billing.entitlement.api.user.SubscriptionData;
 import com.ning.billing.entitlement.api.user.SubscriptionTransitionData;
@@ -201,7 +202,7 @@ public class TestPlanAligner {
 
         subscriptionData.rebuildTransitions(ImmutableList.<EntitlementEvent>of(previousEvent, event), catalogService.getFullCatalog());
 
-        final List<SubscriptionTransitionData> newTransitions = subscriptionData.getAllTransitions();
+        final List<EffectiveSubscriptionEvent> newTransitions = subscriptionData.getAllTransitions();
         Assert.assertEquals(newTransitions.size(), 2);
         Assert.assertNull(newTransitions.get(0).getPreviousPhase());
         Assert.assertEquals(newTransitions.get(0).getNextPhase(), newTransitions.get(1).getPreviousPhase());
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/MockEntitlementDaoMemory.java b/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/MockEntitlementDaoMemory.java
index ec5c17e..06032f7 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/MockEntitlementDaoMemory.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/MockEntitlementDaoMemory.java
@@ -169,7 +169,7 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
     }
 
     @Override
-    public void recreateSubscription(final UUID subscriptionId,
+    public void recreateSubscription(final SubscriptionData subscription,
                                      final List<EntitlementEvent> recreateEvents, final CallContext context) {
 
         synchronized (events) {
@@ -233,9 +233,9 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
     }
 
     @Override
-    public void createNextPhaseEvent(final UUID subscriptionId, final EntitlementEvent nextPhase,
+    public void createNextPhaseEvent(final SubscriptionData subscription, final EntitlementEvent nextPhase,
                                      final CallContext context) {
-        cancelNextPhaseEvent(subscriptionId);
+        cancelNextPhaseEvent(subscription.getId());
         insertEvent(nextPhase);
     }
 
@@ -271,20 +271,20 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
     }
 
     @Override
-    public void cancelSubscription(final UUID subscriptionId, final EntitlementEvent cancelEvent,
+    public void cancelSubscription(final SubscriptionData subscription, final EntitlementEvent cancelEvent,
                                    final CallContext context, final int seqId) {
         synchronized (events) {
-            cancelNextPhaseEvent(subscriptionId);
+            cancelNextPhaseEvent(subscription.getId());
             insertEvent(cancelEvent);
         }
     }
 
     @Override
-    public void changePlan(final UUID subscriptionId, final List<EntitlementEvent> changeEvents,
+    public void changePlan(final SubscriptionData subscription, final List<EntitlementEvent> changeEvents,
                            final CallContext context) {
         synchronized (events) {
-            cancelNextChangeEvent(subscriptionId);
-            cancelNextPhaseEvent(subscriptionId);
+            cancelNextChangeEvent(subscription.getId());
+            cancelNextPhaseEvent(subscription.getId());
             events.addAll(changeEvents);
             for (final EntitlementEvent cur : changeEvents) {
                 recordFutureNotificationFromTransaction(null, cur.getEffectiveDate(), new EntitlementNotificationKey(cur.getId()));
@@ -348,7 +348,7 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
     }
 
     @Override
-    public void uncancelSubscription(final UUID subscriptionId, final List<EntitlementEvent> uncancelEvents,
+    public void uncancelSubscription(final SubscriptionData subscription, final List<EntitlementEvent> uncancelEvents,
                                      final CallContext context) {
 
         synchronized (events) {
@@ -356,7 +356,7 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
             final Iterator<EntitlementEvent> it = events.descendingIterator();
             while (it.hasNext()) {
                 final EntitlementEvent cur = it.next();
-                if (cur.getSubscriptionId() != subscriptionId) {
+                if (cur.getSubscriptionId() != subscription.getId()) {
                     continue;
                 }
                 if (cur.getType() == EventType.API_USER &&
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/MockEntitlementDaoSql.java b/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/MockEntitlementDaoSql.java
index 929f03d..6a93d3c 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/MockEntitlementDaoSql.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/MockEntitlementDaoSql.java
@@ -24,6 +24,7 @@ import org.skife.jdbi.v2.sqlobject.mixins.CloseMe;
 import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
 
 import com.google.inject.Inject;
+import com.ning.billing.catalog.api.CatalogService;
 import com.ning.billing.entitlement.engine.addon.AddonUtils;
 import com.ning.billing.util.bus.Bus;
 import com.ning.billing.util.clock.Clock;
@@ -35,8 +36,8 @@ public class MockEntitlementDaoSql extends AuditedEntitlementDao implements Mock
 
     @Inject
     public MockEntitlementDaoSql(final IDBI dbi, final Clock clock, final AddonUtils addonUtils, final NotificationQueueService notificationQueueService,
-                                 final Bus eventBus) {
-        super(dbi, clock, addonUtils, notificationQueueService, eventBus);
+                                 final Bus eventBus, final CatalogService catalogService) {
+        super(dbi, clock, addonUtils, notificationQueueService, eventBus, catalogService);
         this.resetDao = dbi.onDemand(ResetSqlDao.class);
     }
 
diff --git a/invoice/src/main/java/com/ning/billing/invoice/api/invoice/DefaultInvoicePaymentApi.java b/invoice/src/main/java/com/ning/billing/invoice/api/invoice/DefaultInvoicePaymentApi.java
index a73fab7..be8aa79 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/api/invoice/DefaultInvoicePaymentApi.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/api/invoice/DefaultInvoicePaymentApi.java
@@ -111,8 +111,8 @@ public class DefaultInvoicePaymentApi implements InvoicePaymentApi {
 
     @Override
     public InvoicePayment createRefund(UUID paymentAttemptId,
-            BigDecimal amount, boolean isInvoiceAdjusted, CallContext context)
+            BigDecimal amount, boolean isInvoiceAdjusted, UUID paymentCookieId, CallContext context)
             throws InvoiceApiException {
-        return dao.createRefund(paymentAttemptId, amount, isInvoiceAdjusted, context);
+        return dao.createRefund(paymentAttemptId, amount, isInvoiceAdjusted, paymentCookieId, context);
     }
 }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/dao/DefaultInvoiceDao.java b/invoice/src/main/java/com/ning/billing/invoice/dao/DefaultInvoiceDao.java
index c0539a9..b09e347 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/dao/DefaultInvoiceDao.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/DefaultInvoiceDao.java
@@ -298,12 +298,13 @@ public class DefaultInvoiceDao implements InvoiceDao {
 
     @Override
     public InvoicePayment createRefund(final UUID paymentAttemptId,
-            final BigDecimal amount, final boolean isInvoiceAdjusted, final CallContext context)
+            final BigDecimal amount, final boolean isInvoiceAdjusted, final UUID paymentCookieId,  final CallContext context)
             throws InvoiceApiException {
 
         return invoicePaymentSqlDao.inTransaction(new Transaction<InvoicePayment, InvoicePaymentSqlDao>() {
             @Override
             public InvoicePayment inTransaction(final InvoicePaymentSqlDao transactional, final TransactionStatus status) throws Exception {
+
                 final InvoicePayment payment = transactional.getByPaymentAttemptId(paymentAttemptId.toString());
                 if (payment == null) {
                     throw new InvoiceApiException(ErrorCode.INVOICE_PAYMENT_BY_ATTEMPT_NOT_FOUND, paymentAttemptId);
@@ -322,7 +323,7 @@ public class DefaultInvoiceDao implements InvoiceDao {
 
 
                 final InvoicePayment refund = new DefaultInvoicePayment(UUID.randomUUID(), InvoicePaymentType.REFUND, paymentAttemptId,
-                        payment.getInvoiceId(), context.getCreatedDate(), requestedPositiveAmount.negate(), payment.getCurrency(), payment.getId());
+                        payment.getInvoiceId(), context.getCreatedDate(), requestedPositiveAmount.negate(), payment.getCurrency(), paymentCookieId, payment.getId());
                 transactional.create(refund, context);
 
                 // Retrieve invoice after the Refund
@@ -382,7 +383,7 @@ public class DefaultInvoiceDao implements InvoiceDao {
                     throw new InvoiceApiException(ErrorCode.INVOICE_PAYMENT_NOT_FOUND, invoicePaymentId.toString());
                 } else {
                     final InvoicePayment chargeBack = new DefaultInvoicePayment(UUID.randomUUID(), InvoicePaymentType.CHARGED_BACK, null,
-                            payment.getInvoiceId(), context.getCreatedDate(), requestedChargedBackAmout.negate(), payment.getCurrency(), payment.getId());
+                            payment.getInvoiceId(), context.getCreatedDate(), requestedChargedBackAmout.negate(), payment.getCurrency(), null, payment.getId());
                     invoicePaymentSqlDao.create(chargeBack, context);
                     return chargeBack;
                 }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceDao.java b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceDao.java
index 209285b..443f07a 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceDao.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceDao.java
@@ -67,7 +67,7 @@ public interface InvoiceDao {
     InvoicePayment postChargeback(final UUID invoicePaymentId, final BigDecimal amount, final CallContext context) throws InvoiceApiException;
 
     InvoicePayment createRefund(UUID paymentAttemptId,
-            BigDecimal amount, boolean isInvoiceAdjusted, CallContext context) throws InvoiceApiException;
+            BigDecimal amount, boolean isInvoiceAdjusted, UUID paymentCookieId,  CallContext context) throws InvoiceApiException;
 
     BigDecimal getRemainingAmountPaid(final UUID invoicePaymentId);
 
diff --git a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoicePaymentSqlDao.java b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoicePaymentSqlDao.java
index ed5b2c3..6ca968d 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoicePaymentSqlDao.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoicePaymentSqlDao.java
@@ -111,10 +111,11 @@ public interface InvoicePaymentSqlDao extends EntitySqlDao<InvoicePayment>, Tran
             final BigDecimal amount = result.getBigDecimal("amount");
             final String currencyString = result.getString("currency");
             final Currency currency = (currencyString == null) ? null : Currency.valueOf(currencyString);
+            final UUID paymentCookieId = getUUID(result, "payment_cookie_id");
             final UUID linkedInvoicePaymentId = getUUID(result, "linked_invoice_payment_id");
 
             return new DefaultInvoicePayment(id, type, paymentAttemptId, invoiceId, paymentAttemptDate,
-                                             amount, currency, linkedInvoicePaymentId);
+                                             amount, currency, paymentCookieId, linkedInvoicePaymentId);
         }
     }
 
@@ -136,6 +137,7 @@ public interface InvoicePaymentSqlDao extends EntitySqlDao<InvoicePayment>, Tran
                         q.bind("amount", payment.getAmount());
                         final Currency currency = payment.getCurrency();
                         q.bind("currency", (currency == null) ? null : currency.toString());
+                        q.bind("paymentCookieId", uuidToString(payment.getPaymentCookieId()));
                         q.bind("linkedInvoicePaymentId", uuidToString(payment.getLinkedInvoicePaymentId()));
                     }
                 };
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoicePayment.java b/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoicePayment.java
index 08371ed..6145f07 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoicePayment.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoicePayment.java
@@ -33,16 +33,17 @@ public class DefaultInvoicePayment extends EntityBase implements InvoicePayment 
     private final DateTime paymentDate;
     private final BigDecimal amount;
     private final Currency currency;
-    private final UUID reversedInvoicePaymentId;
+    private final UUID paymentCookieId;
+    private final UUID linkedInvoicePaymentId;
 
     public DefaultInvoicePayment(final InvoicePaymentType type, final UUID paymentAttemptId, final UUID invoiceId, final DateTime paymentDate,
                                  final BigDecimal amount, final Currency currency) {
-        this(UUID.randomUUID(), type, paymentAttemptId, invoiceId, paymentDate, amount, currency, null);
+        this(UUID.randomUUID(), type, paymentAttemptId, invoiceId, paymentDate, amount, currency, null, null);
     }
 
     public DefaultInvoicePayment(final UUID id, final InvoicePaymentType type, final UUID paymentAttemptId, final UUID invoiceId, final DateTime paymentDate,
-                                 @Nullable final BigDecimal amount, @Nullable final Currency currency,
-                                 @Nullable final UUID reversedInvoicePaymentId) {
+                                 @Nullable final BigDecimal amount, @Nullable final Currency currency, final UUID paymentCookieId,
+                                 @Nullable final UUID linkedInvoicePaymentId) {
         super(id);
         this.type = type;
         this.paymentAttemptId = paymentAttemptId;
@@ -50,7 +51,8 @@ public class DefaultInvoicePayment extends EntityBase implements InvoicePayment 
         this.invoiceId = invoiceId;
         this.paymentDate = paymentDate;
         this.currency = currency;
-        this.reversedInvoicePaymentId = reversedInvoicePaymentId;
+        this.paymentCookieId = paymentCookieId;
+        this.linkedInvoicePaymentId = linkedInvoicePaymentId;
     }
 
     @Override
@@ -85,6 +87,12 @@ public class DefaultInvoicePayment extends EntityBase implements InvoicePayment 
 
     @Override
     public UUID getLinkedInvoicePaymentId() {
-        return reversedInvoicePaymentId;
+        return linkedInvoicePaymentId;
     }
+
+    @Override
+    public UUID getPaymentCookieId() {
+        return paymentCookieId;
+    }
+
 }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceFormatter.java b/invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceFormatter.java
index 55a2e8f..9499378 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceFormatter.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceFormatter.java
@@ -29,6 +29,7 @@
  * License for the specific language governing permissions and limitations
  * under the License.
  */
+
 package com.ning.billing.invoice.template.formatters;
 
 import java.math.BigDecimal;
@@ -41,6 +42,9 @@ import org.joda.time.DateTime;
 import org.joda.time.format.DateTimeFormat;
 import org.joda.time.format.DateTimeFormatter;
 
+import com.google.common.base.Objects;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.invoice.api.Invoice;
 import com.ning.billing.invoice.api.InvoiceItem;
@@ -48,6 +52,9 @@ import com.ning.billing.invoice.api.InvoicePayment;
 import com.ning.billing.invoice.api.formatters.InvoiceFormatter;
 import com.ning.billing.util.template.translation.TranslatorConfig;
 
+/**
+ * Format invoice fields. Note that the Mustache engine won't accept null values.
+ */
 public class DefaultInvoiceFormatter implements InvoiceFormatter {
     private final TranslatorConfig config;
     private final Invoice invoice;
@@ -63,7 +70,7 @@ public class DefaultInvoiceFormatter implements InvoiceFormatter {
 
     @Override
     public Integer getInvoiceNumber() {
-        return invoice.getInvoiceNumber();
+        return Objects.firstNonNull(invoice.getInvoiceNumber(), 0);
     }
 
     @Override
@@ -87,7 +94,7 @@ public class DefaultInvoiceFormatter implements InvoiceFormatter {
 
     @Override
     public <T extends InvoiceItem> List<InvoiceItem> getInvoiceItems(final Class<T> clazz) {
-        return invoice.getInvoiceItems(clazz);
+        return Objects.firstNonNull(invoice.getInvoiceItems(clazz), ImmutableList.<InvoiceItem>of());
     }
 
     @Override
@@ -107,7 +114,7 @@ public class DefaultInvoiceFormatter implements InvoiceFormatter {
 
     @Override
     public List<InvoicePayment> getPayments() {
-        return invoice.getPayments();
+        return Objects.firstNonNull(invoice.getPayments(), ImmutableList.<InvoicePayment>of());
     }
 
     @Override
@@ -122,18 +129,17 @@ public class DefaultInvoiceFormatter implements InvoiceFormatter {
 
     @Override
     public BigDecimal getChargedAmount() {
-        return invoice.getChargedAmount();
+        return Objects.firstNonNull(invoice.getChargedAmount(), BigDecimal.ZERO);
     }
 
     @Override
     public BigDecimal getCBAAmount() {
-        return invoice.getCBAAmount();
+        return Objects.firstNonNull(invoice.getCBAAmount(), BigDecimal.ZERO);
     }
 
-
     @Override
     public BigDecimal getBalance() {
-        return invoice.getBalance();
+        return Objects.firstNonNull(invoice.getBalance(), BigDecimal.ZERO);
     }
 
     @Override
@@ -168,12 +174,17 @@ public class DefaultInvoiceFormatter implements InvoiceFormatter {
 
     @Override
     public BigDecimal getPaidAmount() {
-        return invoice.getPaidAmount();
+        return Objects.firstNonNull(invoice.getPaidAmount(), BigDecimal.ZERO);
     }
 
     @Override
     public String getFormattedInvoiceDate() {
-        return invoice.getInvoiceDate().toString(dateFormatter);
+        final DateTime invoiceDate = invoice.getInvoiceDate();
+        if (invoiceDate == null) {
+            return "";
+        } else {
+            return Strings.nullToEmpty(invoiceDate.toString(dateFormatter));
+        }
     }
 
     @Override
@@ -201,16 +212,16 @@ public class DefaultInvoiceFormatter implements InvoiceFormatter {
 
     @Override
     public BigDecimal getTotalAdjAmount() {
-        return invoice.getTotalAdjAmount();
+        return Objects.firstNonNull(invoice.getTotalAdjAmount(), BigDecimal.ZERO);
     }
 
     @Override
     public BigDecimal getCreditAdjAmount() {
-        return invoice.getCreditAdjAmount();
+        return Objects.firstNonNull(invoice.getCreditAdjAmount(), BigDecimal.ZERO);
     }
 
     @Override
     public BigDecimal getRefundAdjAmount() {
-        return invoice.getRefundAdjAmount();
+        return Objects.firstNonNull(invoice.getRefundAdjAmount(), BigDecimal.ZERO);
     }
 }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceItemFormatter.java b/invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceItemFormatter.java
index 3f4ff92..86be83b 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceItemFormatter.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceItemFormatter.java
@@ -23,6 +23,8 @@ import java.util.UUID;
 import org.joda.time.DateTime;
 import org.joda.time.format.DateTimeFormatter;
 
+import com.google.common.base.Objects;
+import com.google.common.base.Strings;
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.invoice.api.InvoiceItem;
 import com.ning.billing.invoice.api.InvoiceItemType;
@@ -31,6 +33,9 @@ import com.ning.billing.util.template.translation.DefaultCatalogTranslator;
 import com.ning.billing.util.template.translation.Translator;
 import com.ning.billing.util.template.translation.TranslatorConfig;
 
+/**
+ * Format invoice item fields. Note that the Mustache engine won't accept null values.
+ */
 public class DefaultInvoiceItemFormatter implements InvoiceItemFormatter {
     private final Translator translator;
 
@@ -48,7 +53,7 @@ public class DefaultInvoiceItemFormatter implements InvoiceItemFormatter {
 
     @Override
     public BigDecimal getAmount() {
-        return item.getAmount();
+        return Objects.firstNonNull(item.getAmount(), BigDecimal.ZERO);
     }
 
     @Override
@@ -60,16 +65,10 @@ public class DefaultInvoiceItemFormatter implements InvoiceItemFormatter {
     public InvoiceItemType getInvoiceItemType() {
         return item.getInvoiceItemType();
     }
-/*
-    @Override
-    public InvoiceItem asReversingItem() {
-        return item.asReversingItem();
-    }
-    */
 
     @Override
     public String getDescription() {
-        return item.getDescription();
+        return Strings.nullToEmpty(item.getDescription());
     }
 
     @Override
@@ -84,12 +83,12 @@ public class DefaultInvoiceItemFormatter implements InvoiceItemFormatter {
 
     @Override
     public String getFormattedStartDate() {
-        return item.getStartDate().toString(dateFormatter);
+        return Strings.nullToEmpty(item.getStartDate().toString(dateFormatter));
     }
 
     @Override
     public String getFormattedEndDate() {
-        return item.getEndDate().toString(dateFormatter);
+        return Strings.nullToEmpty(item.getEndDate().toString(dateFormatter));
     }
 
     @Override
@@ -114,12 +113,12 @@ public class DefaultInvoiceItemFormatter implements InvoiceItemFormatter {
 
     @Override
     public String getPlanName() {
-        return translator.getTranslation(locale, item.getPlanName());
+        return Strings.nullToEmpty(translator.getTranslation(locale, item.getPlanName()));
     }
 
     @Override
     public String getPhaseName() {
-        return translator.getTranslation(locale, item.getPhaseName());
+        return Strings.nullToEmpty(translator.getTranslation(locale, item.getPhaseName()));
     }
 
     @Override
@@ -134,7 +133,7 @@ public class DefaultInvoiceItemFormatter implements InvoiceItemFormatter {
 
     @Override
     public BigDecimal getRate() {
-        return null;
+        return BigDecimal.ZERO;
     }
 
     @Override
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 f27bce7..04c5075 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
@@ -16,6 +16,7 @@
 
 package com.ning.billing.invoice.template;
 
+import javax.annotation.Nullable;
 import java.io.IOException;
 import java.util.HashMap;
 import java.util.Locale;
@@ -42,7 +43,12 @@ public class HtmlInvoiceGenerator {
         this.config = config;
     }
 
-    public String generateInvoice(final Account account, final Invoice invoice) throws IOException {
+    public String generateInvoice(final Account account, @Nullable final Invoice invoice) throws IOException {
+        // Don't do anything if the invoice is null
+        if (invoice == null) {
+            return null;
+        }
+
         final Map<String, Object> data = new HashMap<String, Object>();
         final DefaultInvoiceTranslator invoiceTranslator = new DefaultInvoiceTranslator(config);
         final Locale locale = new Locale(account.getLocale());
diff --git a/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoicePaymentSqlDao.sql.stg b/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoicePaymentSqlDao.sql.stg
index f201331..a028cf6 100644
--- a/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoicePaymentSqlDao.sql.stg
+++ b/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoicePaymentSqlDao.sql.stg
@@ -8,6 +8,7 @@ invoicePaymentFields(prefix) ::= <<
   <prefix>payment_attempt_date,
   <prefix>amount,
   <prefix>currency,
+  <prefix>payment_cookie_id,
   <prefix>linked_invoice_payment_id,
   <prefix>created_by,
   <prefix>created_date
@@ -16,13 +17,13 @@ invoicePaymentFields(prefix) ::= <<
 create() ::= <<
   INSERT INTO invoice_payments(<invoicePaymentFields()>)
   VALUES(:id, :type, :invoiceId, :paymentAttemptId, :paymentAttemptDate, :amount, :currency,
-         :linkedInvoicePaymentId, :userName, :createdDate);
+         :paymentCookieId, :linkedInvoicePaymentId, :userName, :createdDate);
 >>
 
 batchCreateFromTransaction() ::= <<
   INSERT INTO invoice_payments(<invoicePaymentFields()>)
   VALUES(:id, :type, :invoiceId, :paymentAttemptId, :paymentAttemptDate, :amount, :currency,
-        :linkedInvoicePaymentId, :userName, :createdDate);
+        :paymentCookieId, :linkedInvoicePaymentId, :userName, :createdDate);
 >>
 
 getByPaymentAttemptId() ::= <<
@@ -51,7 +52,7 @@ getPaymentsForInvoice() ::= <<
 notifyOfPaymentAttempt() ::= <<
   INSERT INTO invoice_payments(<invoicePaymentFields()>)
   VALUES(:id, :type, :invoiceId, :paymentAttemptId, :paymentAttemptDate, :amount, :currency,
-        :linkedInvoicePaymentId, :userName, :createdDate);
+        :paymentCookieId, :linkedInvoicePaymentId, :userName, :createdDate);
 >>
 
 getInvoicePayment() ::= <<
diff --git a/invoice/src/main/resources/com/ning/billing/invoice/ddl.sql b/invoice/src/main/resources/com/ning/billing/invoice/ddl.sql
index a408d3c..b6bd51f 100644
--- a/invoice/src/main/resources/com/ning/billing/invoice/ddl.sql
+++ b/invoice/src/main/resources/com/ning/billing/invoice/ddl.sql
@@ -52,6 +52,7 @@ CREATE TABLE invoice_payments (
     payment_attempt_date datetime NOT NULL,
     amount numeric(10,4) NOT NULL,
     currency char(3) NOT NULL,
+    payment_cookie_id char(36) DEFAULT NULL,    
     linked_invoice_payment_id char(36) DEFAULT NULL,
     created_by varchar(50) NOT NULL,
     created_date datetime NOT NULL,
diff --git a/invoice/src/test/java/com/ning/billing/invoice/api/MockInvoicePaymentApi.java b/invoice/src/test/java/com/ning/billing/invoice/api/MockInvoicePaymentApi.java
index af2c854..a049bcf 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/api/MockInvoicePaymentApi.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/api/MockInvoicePaymentApi.java
@@ -108,7 +108,7 @@ public class MockInvoicePaymentApi implements InvoicePaymentApi {
 
         if (existingPayment != null) {
             invoicePayments.add(new DefaultInvoicePayment(UUID.randomUUID(), InvoicePaymentType.CHARGED_BACK, null, null, DateTime.now(DateTimeZone.UTC), amount,
-                    Currency.USD, existingPayment.getId()));
+                    Currency.USD, null, existingPayment.getId()));
         }
 
         return existingPayment;
@@ -168,7 +168,7 @@ public class MockInvoicePaymentApi implements InvoicePaymentApi {
 
     @Override
     public InvoicePayment createRefund(UUID paymentAttemptId,
-            BigDecimal amount, boolean isInvoiceAdjusted, CallContext context)
+            BigDecimal amount, boolean isInvoiceAdjusted, UUID paymentCookieId, CallContext context)
             throws InvoiceApiException {
         // TODO Auto-generated method stub
         return null;
diff --git a/invoice/src/test/java/com/ning/billing/invoice/dao/MockInvoiceDao.java b/invoice/src/test/java/com/ning/billing/invoice/dao/MockInvoiceDao.java
index a2e8a34..da91371 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/dao/MockInvoiceDao.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/dao/MockInvoiceDao.java
@@ -260,7 +260,7 @@ public class MockInvoiceDao implements InvoiceDao {
 
     @Override
     public InvoicePayment createRefund(UUID paymentAttemptId,
-            BigDecimal amount, boolean isInvoiceAdjusted, CallContext context)
+            BigDecimal amount, boolean isInvoiceAdjusted, UUID paymentCookieId, CallContext context)
             throws InvoiceApiException {
         // TODO Auto-generated method stub
         return null;
diff --git a/invoice/src/test/java/com/ning/billing/invoice/dao/TestInvoiceDao.java b/invoice/src/test/java/com/ning/billing/invoice/dao/TestInvoiceDao.java
index 160038e..bf8c4a4 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/dao/TestInvoiceDao.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/dao/TestInvoiceDao.java
@@ -584,7 +584,7 @@ public class TestInvoiceDao extends InvoiceDaoTestBase {
         balance = invoiceDao.getAccountBalance(accountId);
         assertEquals(balance.compareTo(new BigDecimal("0.00")), 0);
 
-        invoiceDao.createRefund(paymentAttemptId, refund1, withAdjustment, context);
+        invoiceDao.createRefund(paymentAttemptId, refund1, withAdjustment, null, context);
         balance = invoiceDao.getAccountBalance(accountId);
         if (withAdjustment) {
             assertEquals(balance.compareTo(BigDecimal.ZERO), 0);
@@ -678,7 +678,7 @@ public class TestInvoiceDao extends InvoiceDaoTestBase {
         assertEquals(cba.compareTo(new BigDecimal("10.00")), 0);
 
         // PARTIAL REFUND on the payment
-        invoiceDao.createRefund(paymentAttemptId, refundAmount, withAdjustment, context);
+        invoiceDao.createRefund(paymentAttemptId, refundAmount, withAdjustment, null, context);
 
         balance = invoiceDao.getAccountBalance(accountId);
         assertEquals(balance.compareTo(expectedFinalBalance), 0);
@@ -745,7 +745,7 @@ public class TestInvoiceDao extends InvoiceDaoTestBase {
         assertEquals(cba.compareTo(new BigDecimal("10.00")), 0);
 
         // partial REFUND on the payment (along with CBA generated by the system)
-        final InvoicePayment refund = new DefaultInvoicePayment(UUID.randomUUID(), InvoicePaymentType.ATTEMPT, UUID.randomUUID(), invoice1.getId(), new DateTime(), rate2.negate(), Currency.USD, payment.getId());
+        final InvoicePayment refund = new DefaultInvoicePayment(UUID.randomUUID(), InvoicePaymentType.ATTEMPT, UUID.randomUUID(), invoice1.getId(), new DateTime(), rate2.negate(), Currency.USD, null,  payment.getId());
         invoicePaymentDao.create(refund, context);
         final CreditBalanceAdjInvoiceItem cbaItem2 = new CreditBalanceAdjInvoiceItem(invoice1.getId(), accountId, new DateTime(), rate2.negate(), Currency.USD);
         invoiceItemSqlDao.create(cbaItem2, context);
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 2847e0b..8c9ace4 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/TestHtmlInvoiceGenerator.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/TestHtmlInvoiceGenerator.java
@@ -22,7 +22,9 @@ import java.util.List;
 import java.util.Locale;
 
 import org.joda.time.DateTime;
+import org.mockito.Mockito;
 import org.skife.config.ConfigurationObjectFactory;
+import org.testng.Assert;
 import org.testng.annotations.BeforeSuite;
 import org.testng.annotations.Test;
 
@@ -39,12 +41,10 @@ import com.ning.billing.util.email.templates.MustacheTemplateEngine;
 import com.ning.billing.util.email.templates.TemplateEngine;
 import com.ning.billing.util.template.translation.TranslatorConfig;
 
-import static org.testng.Assert.assertNotNull;
-
 public class TestHtmlInvoiceGenerator {
     private HtmlInvoiceGenerator g;
 
-    @BeforeSuite(groups = {"fast"})
+    @BeforeSuite(groups = "fast")
     public void setup() {
         final TranslatorConfig config = new ConfigurationObjectFactory(System.getProperties()).build(TranslatorConfig.class);
         final TemplateEngine templateEngine = new MustacheTemplateEngine();
@@ -52,11 +52,23 @@ public class TestHtmlInvoiceGenerator {
         g = new HtmlInvoiceGenerator(factory, templateEngine, config);
     }
 
-    @Test(groups = {"fast"})
+    @Test(groups = "fast")
     public void testGenerateInvoice() throws Exception {
         final String output = g.generateInvoice(createAccount(), createInvoice());
-        assertNotNull(output);
-        System.out.print(output);
+        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);
+        Assert.assertNotNull(output);
+    }
+
+    @Test(groups = "fast")
+    public void testGenerateNullInvoice() throws Exception {
+        final String output = g.generateInvoice(createAccount(), null);
+        Assert.assertNull(output);
     }
 
     private Account createAccount() {
@@ -110,7 +122,6 @@ public class TestHtmlInvoiceGenerator {
         zombie.addResult("getPlanName", planName);
         zombie.addResult("getDescription", networkName);
 
-
         return item;
     }
 }
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/TestChargeBacks.java b/invoice/src/test/java/com/ning/billing/invoice/tests/TestChargeBacks.java
index a9b2627..114fa8d 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/TestChargeBacks.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/TestChargeBacks.java
@@ -259,7 +259,7 @@ public class TestChargeBacks  {
         zombie.addResult("getAmount", amount);
         zombie.addResult("getCurrency", CURRENCY);
         zombie.addResult("getLinkedInvoicePaymentId", BrainDeadProxyFactory.ZOMBIE_VOID);
-
+        zombie.addResult("getPaymentCookieId", BrainDeadProxyFactory.ZOMBIE_VOID);
         invoicePaymentApi.notifyOfPaymentAttempt(payment, context);
 
         return payment;
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
new file mode 100644
index 0000000..8c4b812
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/RefundJson.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.jaxrs.json;
+
+import java.math.BigDecimal;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.ning.billing.payment.api.Refund;
+
+
+public class RefundJson {
+
+    private final String paymentId;
+    private final BigDecimal refundAmount;
+    private final Boolean isAdjusted;
+
+    public RefundJson(Refund input) {
+        this(input.getPaymentId().toString(), input.getRefundAmount(), input.isAdjusted());
+    }
+
+    @JsonCreator
+    public RefundJson(@JsonProperty("paymentId") String paymentId,
+            @JsonProperty("refundAmount") BigDecimal refundAmount,
+            @JsonProperty("isAdjusted") final Boolean isAdjusted) {
+        super();
+        this.paymentId = paymentId;
+        this.refundAmount = refundAmount;
+        this.isAdjusted = isAdjusted;
+    }
+
+    public RefundJson() {
+        this(null, null, null);
+    }
+
+    public String getPaymentId() {
+        return paymentId;
+    }
+
+    public BigDecimal getRefundAmount() {
+        return refundAmount;
+    }
+
+    public boolean isAdjusted() {
+        return isAdjusted;
+    }
+}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/AccountResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/AccountResource.java
index d96edd4..0dec7e0 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/AccountResource.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/AccountResource.java
@@ -68,6 +68,7 @@ import com.ning.billing.jaxrs.json.CustomFieldJson;
 import com.ning.billing.jaxrs.json.InvoiceEmailJson;
 import com.ning.billing.jaxrs.json.PaymentJsonSimple;
 import com.ning.billing.jaxrs.json.PaymentMethodJson;
+import com.ning.billing.jaxrs.json.RefundJson;
 import com.ning.billing.jaxrs.util.Context;
 import com.ning.billing.jaxrs.util.JaxrsUriBuilder;
 import com.ning.billing.jaxrs.util.TagHelper;
@@ -75,6 +76,7 @@ import com.ning.billing.payment.api.Payment;
 import com.ning.billing.payment.api.PaymentApi;
 import com.ning.billing.payment.api.PaymentApiException;
 import com.ning.billing.payment.api.PaymentMethod;
+import com.ning.billing.payment.api.Refund;
 import com.ning.billing.util.api.CustomFieldUserApi;
 import com.ning.billing.util.api.TagUserApi;
 import com.ning.billing.util.dao.ObjectType;
@@ -126,7 +128,11 @@ public class AccountResource extends JaxRsResourceBase {
             final AccountJson json = new AccountJson(account);
             return Response.status(Status.OK).entity(json).build();
         } catch (AccountApiException e) {
-            return Response.status(Status.NO_CONTENT).build();
+            if (e.getCode() == ErrorCode.ACCOUNT_DOES_NOT_EXIST_FOR_ID.getCode()) {
+                return Response.status(Status.NO_CONTENT).build();
+            } else {
+                return Response.status(Status.BAD_REQUEST).build();
+            }
         }
 
     }
@@ -148,7 +154,11 @@ public class AccountResource extends JaxRsResourceBase {
             });
             return Response.status(Status.OK).entity(result).build();
         } catch (AccountApiException e) {
-            return Response.status(Status.NO_CONTENT).build();
+            if (e.getCode() == ErrorCode.ACCOUNT_DOES_NOT_EXIST_FOR_ID.getCode()) {
+                return Response.status(Status.NO_CONTENT).build();
+            } else {
+                return Response.status(Status.BAD_REQUEST).build();
+            }
         }
     }
 
@@ -166,7 +176,11 @@ public class AccountResource extends JaxRsResourceBase {
             final AccountJson json = new AccountJson(account);
             return Response.status(Status.OK).entity(json).build();
         } catch (AccountApiException e) {
-            return Response.status(Status.NO_CONTENT).build();
+            if (e.getCode() == ErrorCode.ACCOUNT_DOES_NOT_EXIST_FOR_KEY.getCode()) {
+                return Response.status(Status.NO_CONTENT).build();
+            } else {
+                return Response.status(Status.BAD_REQUEST).build();
+            }
         }
     }
 
@@ -253,7 +267,11 @@ public class AccountResource extends JaxRsResourceBase {
             final AccountTimelineJson json = new AccountTimelineJson(account, invoices, payments, bundlesTimeline);
             return Response.status(Status.OK).entity(json).build();
         } catch (AccountApiException e) {
-            return Response.status(Status.NO_CONTENT).build();
+            if (e.getCode() == ErrorCode.ACCOUNT_DOES_NOT_EXIST_FOR_ID.getCode()) {
+                return Response.status(Status.NO_CONTENT).build();
+            } else {
+                return Response.status(Status.BAD_REQUEST).build();
+            }
         } catch (PaymentApiException e) {
             log.error(e.getMessage());
             return Response.status(Status.INTERNAL_SERVER_ERROR).build();
@@ -277,7 +295,11 @@ public class AccountResource extends JaxRsResourceBase {
 
             return Response.status(Status.OK).entity(invoiceEmailJson).build();
         } catch (AccountApiException e) {
-            return Response.status(Status.NOT_FOUND).build();
+            if (e.getCode() == ErrorCode.ACCOUNT_DOES_NOT_EXIST_FOR_ID.getCode()) {
+                return Response.status(Status.NO_CONTENT).build();
+            } else {
+                return Response.status(Status.BAD_REQUEST).build();
+            }
         }
     }
 
@@ -300,7 +322,11 @@ public class AccountResource extends JaxRsResourceBase {
 
             return Response.status(Status.OK).build();
         } catch (AccountApiException e) {
-            return Response.status(Status.NOT_FOUND).build();
+            if (e.getCode() == ErrorCode.ACCOUNT_DOES_NOT_EXIST_FOR_ID.getCode()) {
+                return Response.status(Status.NO_CONTENT).build();
+            } else {
+                return Response.status(Status.BAD_REQUEST).build();
+            }
         }
     }
 
@@ -322,7 +348,7 @@ public class AccountResource extends JaxRsResourceBase {
             }
             return Response.status(Status.OK).entity(result).build();
         } catch (PaymentApiException e) {
-            return Response.status(Status.NOT_FOUND).build();
+            return Response.status(Status.BAD_REQUEST).build();
         }
     }
 
@@ -343,9 +369,11 @@ public class AccountResource extends JaxRsResourceBase {
             final UUID paymentMethodId = paymentApi.addPaymentMethod(data.getPluginName(), account, isDefault, data.getPluginDetail(), context.createContext(createdBy, reason, comment));
             return uriBuilder.buildResponse(PaymentMethodResource.class, "getPaymentMethod", paymentMethodId, uriInfo.getBaseUri().toString());
         } catch (AccountApiException e) {
-            final String error = String.format("Failed to create account %s", json);
-            log.info(error, e);
-            return Response.status(Status.BAD_REQUEST).entity(error).build();
+            if (e.getCode() == ErrorCode.ACCOUNT_DOES_NOT_EXIST_FOR_ID.getCode()) {
+                return Response.status(Status.NO_CONTENT).build();
+            } else {
+                return Response.status(Status.BAD_REQUEST).build();
+            }
         } catch (PaymentApiException e) {
             final String error = String.format("Failed to create payment Method  %s", json);
             log.info(error, e);
@@ -377,7 +405,11 @@ public class AccountResource extends JaxRsResourceBase {
         } catch (PaymentApiException e) {
             return Response.status(Status.NOT_FOUND).build();
         } catch (AccountApiException e) {
-            return Response.status(Status.NOT_FOUND).build();
+            if (e.getCode() == ErrorCode.ACCOUNT_DOES_NOT_EXIST_FOR_ID.getCode()) {
+                return Response.status(Status.NO_CONTENT).build();
+            } else {
+                return Response.status(Status.BAD_REQUEST).build();
+            }
         }
     }
 
@@ -395,7 +427,11 @@ public class AccountResource extends JaxRsResourceBase {
             paymentApi.setDefaultPaymentMethod(account, UUID.fromString(paymentMethodId), context.createContext(createdBy, reason, comment));
             return Response.status(Status.OK).build();
         } catch (AccountApiException e) {
-            return Response.status(Status.BAD_REQUEST).build();
+            if (e.getCode() == ErrorCode.ACCOUNT_DOES_NOT_EXIST_FOR_ID.getCode()) {
+                return Response.status(Status.NO_CONTENT).build();
+            } else {
+                return Response.status(Status.BAD_REQUEST).build();
+            }
         } catch (PaymentApiException e) {
             return Response.status(Status.NOT_FOUND).build();
         } catch (IllegalArgumentException e) {
@@ -403,6 +439,38 @@ public class AccountResource extends JaxRsResourceBase {
         }
     }
 
+
+    /*
+     * ************************** REFUNDS ********************************
+     */
+    @GET
+    @Path("/{accountId:" + UUID_PATTERN + "}/" + REFUNDS)
+    @Produces(APPLICATION_JSON)
+    public Response getRefunds(@PathParam("accountId") final String accountId) {
+
+        try {
+            final Account account = accountApi.getAccountById(UUID.fromString(accountId));
+            List<Refund> refunds =  paymentApi.getAccountRefunds(account);
+            List<RefundJson> result = new ArrayList<RefundJson>(Collections2.transform(refunds, new Function<Refund, RefundJson>() {
+                @Override
+                public RefundJson apply(Refund input) {
+                    return new RefundJson(input);
+                }
+            }));
+            return Response.status(Status.OK).entity(result).build();
+        } catch (AccountApiException e) {
+            if (e.getCode() == ErrorCode.ACCOUNT_DOES_NOT_EXIST_FOR_ID.getCode()) {
+                return Response.status(Status.NO_CONTENT).build();
+            } else {
+                return Response.status(Status.BAD_REQUEST).build();
+            }
+        } catch (PaymentApiException e) {
+            return Response.status(Status.BAD_REQUEST).build();
+        }
+    }
+
+
+
     /*
      * *************************      CUSTOM FIELDS     *****************************
      */
@@ -518,7 +586,11 @@ public class AccountResource extends JaxRsResourceBase {
         } catch (RuntimeException e) {
             return Response.status(Response.Status.BAD_REQUEST).entity(e.getMessage()).build();
         } catch (AccountApiException e) {
-            return Response.status(Response.Status.BAD_REQUEST).entity(e.getMessage()).build();
+            if (e.getCode() == ErrorCode.ACCOUNT_DOES_NOT_EXIST_FOR_ID.getCode()) {
+                return Response.status(Status.NO_CONTENT).build();
+            } else {
+                return Response.status(Response.Status.BAD_REQUEST).entity(e.getMessage()).build();
+            }
         }
     }
 
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/ChargebackResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/ChargebackResource.java
index 2ce2493..c08c01a 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/ChargebackResource.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/ChargebackResource.java
@@ -159,6 +159,7 @@ public class ChargebackResource implements JaxrsResource {
                 return Response.status(Response.Status.NO_CONTENT).entity(error).build();
             }
 
+            // STEPH that does not seem to work, we need to find the correct attempt
             final UUID paymentAttemptId = attempts.iterator().next().getId();
             final InvoicePayment invoicePayment = invoicePaymentApi.getInvoicePayment(paymentAttemptId);
             if (invoicePayment == null) {
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/JaxrsResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/JaxrsResource.java
index 16c6bc5..984f633 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/JaxrsResource.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/JaxrsResource.java
@@ -1,4 +1,4 @@
-/* 
+/*
  * Copyright 2010-2011 Ning, Inc.
  *
  * Ning licenses this file to you under the Apache License, version 2.0
@@ -79,6 +79,9 @@ public interface JaxrsResource {
     public static final String PAYMENTS = "payments";
     public static final String PAYMENTS_PATH = PREFIX + "/" + PAYMENTS;
 
+    public static final String REFUNDS = "refunds";
+    public static final String REFUNDS_PATH = PREFIX + "/" + "refunds";
+
     public static final String PAYMENT_METHODS = "paymentMethods";
     public static final String PAYMENT_METHODS_PATH = PREFIX + "/" + PAYMENT_METHODS;
     public static final String PAYMENT_METHODS_DEFAULT_PATH_POSTFIX = "setDefault";
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/JaxRsResourceBase.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/JaxRsResourceBase.java
index bdff8e8..8dc98a9 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/JaxRsResourceBase.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/JaxRsResourceBase.java
@@ -42,10 +42,11 @@ import com.ning.billing.util.tag.Tag;
 import com.ning.billing.util.tag.TagDefinition;
 
 public abstract class JaxRsResourceBase implements JaxrsResource {
-    private final JaxrsUriBuilder uriBuilder;
-    private final TagUserApi tagUserApi;
-    private final TagHelper tagHelper;
-    private final CustomFieldUserApi customFieldUserApi;
+
+    protected final JaxrsUriBuilder uriBuilder;
+    protected final TagUserApi tagUserApi;
+    protected final TagHelper tagHelper;
+    protected final CustomFieldUserApi customFieldUserApi;
 
     protected abstract ObjectType getObjectType();
 
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/PaymentResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/PaymentResource.java
index 5f53ab3..264c620 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/PaymentResource.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/PaymentResource.java
@@ -26,14 +26,30 @@ import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
 import javax.ws.rs.QueryParam;
 import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+
+import java.util.ArrayList;
 import java.util.List;
 import java.util.UUID;
 
+import com.google.common.base.Function;
+import com.google.common.collect.Collections2;
 import com.google.inject.Inject;
+import com.ning.billing.ErrorCode;
+import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.AccountApiException;
+import com.ning.billing.account.api.AccountData;
+import com.ning.billing.account.api.AccountUserApi;
+import com.ning.billing.jaxrs.json.AccountJson;
 import com.ning.billing.jaxrs.json.CustomFieldJson;
+import com.ning.billing.jaxrs.json.RefundJson;
 import com.ning.billing.jaxrs.util.Context;
 import com.ning.billing.jaxrs.util.JaxrsUriBuilder;
 import com.ning.billing.jaxrs.util.TagHelper;
+import com.ning.billing.payment.api.Payment;
+import com.ning.billing.payment.api.PaymentApi;
+import com.ning.billing.payment.api.PaymentApiException;
+import com.ning.billing.payment.api.Refund;
 import com.ning.billing.util.api.CustomFieldUserApi;
 import com.ning.billing.util.api.TagUserApi;
 import com.ning.billing.util.dao.ObjectType;
@@ -48,13 +64,70 @@ public class PaymentResource extends JaxRsResourceBase {
     private static final String TAG_URI = JaxrsResource.TAGS + "/{" + ID_PARAM_NAME + ":" + UUID_PATTERN + "}";
 
     private final Context context;
+    private final PaymentApi paymentApi;
+    private final AccountUserApi accountApi;
 
     @Inject
-    public PaymentResource(final JaxrsUriBuilder uriBuilder, final TagUserApi tagUserApi,
-                           final TagHelper tagHelper, final CustomFieldUserApi customFieldUserApi,
-                           final Context context) {
+    public PaymentResource(final JaxrsUriBuilder uriBuilder,
+            final AccountUserApi accountApi,
+            final PaymentApi paymentApi,
+            final TagUserApi tagUserApi,
+            final TagHelper tagHelper,
+            final CustomFieldUserApi customFieldUserApi,
+            final Context context) {
         super(uriBuilder, tagUserApi, tagHelper, customFieldUserApi);
         this.context = context;
+        this.paymentApi = paymentApi;
+        this.accountApi = accountApi;
+    }
+
+
+    @GET
+    @Path("/{paymentId:" + UUID_PATTERN + "}/" + REFUNDS)
+    @Produces(APPLICATION_JSON)
+    public Response getRefunds(@PathParam("paymentId") final String paymentId) {
+
+        try {
+            List<Refund> refunds =  paymentApi.getPaymentRefunds(UUID.fromString(paymentId));
+            List<RefundJson> result = new ArrayList<RefundJson>(Collections2.transform(refunds, new Function<Refund, RefundJson>() {
+                @Override
+                public RefundJson apply(Refund input) {
+                    return new RefundJson(input);
+                }
+            }));
+            return Response.status(Status.OK).entity(result).build();
+        } catch (PaymentApiException e) {
+            return Response.status(Status.BAD_REQUEST).build();
+        }
+    }
+
+    @POST
+    @Path("/{paymentId:" + UUID_PATTERN + "}/" + REFUNDS)
+    @Consumes(APPLICATION_JSON)
+    @Produces(APPLICATION_JSON)
+    public Response createRefund(final RefundJson json,
+            @PathParam("paymentId") final String paymentId,
+            @HeaderParam(HDR_CREATED_BY) final String createdBy,
+            @HeaderParam(HDR_REASON) final String reason,
+            @HeaderParam(HDR_COMMENT) final String comment) {
+
+        try {
+            final UUID paymentUuid = UUID.fromString(paymentId);
+            final Payment payment = paymentApi.getPayment(paymentUuid);
+
+            final Account account = accountApi.getAccountById(payment.getAccountId());
+
+            Refund result = paymentApi.createRefund(account, paymentUuid, json.getRefundAmount(), json.isAdjusted(), context.createContext(createdBy, reason, comment));
+            return uriBuilder.buildResponse(RefundResource.class, "getRefund", result.getId());
+        } catch (AccountApiException e) {
+            if (e.getCode() == ErrorCode.ACCOUNT_DOES_NOT_EXIST_FOR_ID.getCode()) {
+                return Response.status(Status.NO_CONTENT).build();
+            } else {
+                return Response.status(Status.BAD_REQUEST).build();
+            }
+        } catch (PaymentApiException e) {
+            return Response.status(Status.BAD_REQUEST).build();
+        }
     }
 
     @GET
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/RefundResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/RefundResource.java
index 05acf58..63e72c0 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/RefundResource.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/RefundResource.java
@@ -16,6 +16,68 @@
 
 package com.ning.billing.jaxrs.resources;
 
-public class RefundResource {
+import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
 
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.inject.Inject;
+import com.ning.billing.jaxrs.json.RefundJson;
+import com.ning.billing.jaxrs.util.Context;
+import com.ning.billing.jaxrs.util.JaxrsUriBuilder;
+import com.ning.billing.jaxrs.util.TagHelper;
+import com.ning.billing.payment.api.PaymentApi;
+import com.ning.billing.payment.api.PaymentApiException;
+import com.ning.billing.payment.api.Refund;
+import com.ning.billing.util.api.CustomFieldUserApi;
+import com.ning.billing.util.api.TagUserApi;
+import com.ning.billing.util.dao.ObjectType;
+
+
+@Path(JaxrsResource.REFUNDS_PATH)
+public class RefundResource extends JaxRsResourceBase {
+
+    private static final Logger log = LoggerFactory.getLogger(RefundResource.class);
+
+    private final PaymentApi paymentApi;
+
+    @Inject
+    public RefundResource(final JaxrsUriBuilder uriBuilder,
+            final PaymentApi paymentApi,
+            final TagUserApi tagUserApi,
+            final TagHelper tagHelper,
+            final CustomFieldUserApi customFieldUserApi,
+            final Context context) {
+        super(uriBuilder, tagUserApi, tagHelper, customFieldUserApi);
+        this.paymentApi = paymentApi;
+    }
+
+    @GET
+    @Path("/{refundId:" + UUID_PATTERN + "}")
+    @Produces(APPLICATION_JSON)
+    public Response getRefund(@PathParam("refundId") final String refundId) {
+        try {
+            Refund refund = paymentApi.getRefund(UUID.fromString(refundId));
+            return Response.status(Status.OK).entity(new RefundJson(refund)).build();
+        } catch (PaymentApiException e) {
+            // STEPH NO_CONTENT if oes not exist
+            return Response.status(Status.BAD_REQUEST).build();
+        }
+    }
+
+    @Override
+    protected ObjectType getObjectType() {
+        return ObjectType.REFUND;
+    }
 }
diff --git a/junction/src/main/java/com/ning/billing/junction/plumbing/api/BlockingSubscription.java b/junction/src/main/java/com/ning/billing/junction/plumbing/api/BlockingSubscription.java
index aa03560..fb41a88 100644
--- a/junction/src/main/java/com/ning/billing/junction/plumbing/api/BlockingSubscription.java
+++ b/junction/src/main/java/com/ning/billing/junction/plumbing/api/BlockingSubscription.java
@@ -128,6 +128,11 @@ public class BlockingSubscription implements Subscription {
         return subscription.getBillingTransitions();
     }
 
+    @Override
+    public List<EffectiveSubscriptionEvent> getAllTransitions() {
+        return subscription.getAllTransitions();
+    }
+
     public BlockingState getBlockingState() {
         if (blockingState == null) {
             blockingState = blockingApi.getBlockingStateFor(this);
@@ -138,6 +143,4 @@ public class BlockingSubscription implements Subscription {
     public Subscription getDelegateSubscription() {
         return subscription;
     }
-
-
 }
diff --git a/junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestBillingApi.java b/junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestBillingApi.java
index ee83777..2a31a6e 100644
--- a/junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestBillingApi.java
+++ b/junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestBillingApi.java
@@ -16,7 +16,6 @@
 
 package com.ning.billing.junction.plumbing.billing;
 
-
 import java.math.BigDecimal;
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -67,6 +66,8 @@ import com.ning.billing.junction.api.DefaultBlockingState;
 import com.ning.billing.lifecycle.KillbillService.ServiceException;
 import com.ning.billing.mock.BrainDeadProxyFactory;
 import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
+import com.ning.billing.mock.MockEffectiveSubscriptionEvent;
+import com.ning.billing.mock.MockSubscription;
 import com.ning.billing.util.api.TagUserApi;
 import com.ning.billing.util.callcontext.CallContextFactory;
 import com.ning.billing.util.callcontext.DefaultCallContextFactory;
@@ -141,7 +142,6 @@ public class TestBillingApi {
 
     };
 
-
     private Clock clock;
     private Subscription subscription;
     private DateTime subscriptionStartDate;
@@ -163,39 +163,11 @@ public class TestBillingApi {
         //new SubscriptionBundleData( eventId,"TestKey", subId,  clock.getUTCNow().minusDays(4), null);
         bundles.add(bundle);
 
-
         effectiveSubscriptionTransitions = new LinkedList<EffectiveSubscriptionEvent>();
         final List<Subscription> subscriptions = new LinkedList<Subscription>();
 
         subscriptionStartDate = clock.getUTCNow().minusDays(3);
-        subscription = new MockSubscription() {
-            @Override
-            public List<EffectiveSubscriptionEvent> getBillingTransitions() {
-                return effectiveSubscriptionTransitions;
-            }
-
-            @Override
-            public Plan getCurrentPlan() {
-                return subscriptionPlan;
-            }
-
-            @Override
-            public UUID getId() {
-                return subId;
-            }
-
-            @Override
-            public UUID getBundleId() {
-                return bunId;
-            }
-
-            @Override
-            public DateTime getStartDate() {
-                return subscriptionStartDate;
-            }
-
-
-        };
+        subscription = new MockSubscription(subId, bunId, subscriptionPlan, subscriptionStartDate, effectiveSubscriptionTransitions);
 
         subscriptions.add(subscription);
 
@@ -308,7 +280,6 @@ public class TestBillingApi {
                 nextPriceList.getName(), 1L, null,
                 SubscriptionTransitionType.CREATE, 0, null);
 
-
         effectiveSubscriptionTransitions.add(t);
 
         final AccountUserApi accountApi = BrainDeadProxyFactory.createBrainDeadProxyFor(AccountUserApi.class);
@@ -376,7 +347,6 @@ public class TestBillingApi {
         final PlanPhase nextPhase = nextPlan.getAllPhases()[1];
         final PriceList nextPriceList = catalogService.getFullCatalog().findPriceList(PriceListSet.DEFAULT_PRICELIST_NAME, now);
 
-
         final EffectiveSubscriptionEvent t = new MockEffectiveSubscriptionEvent(
                 eventId, subId, bunId, then, now, null, null, null, null, SubscriptionState.ACTIVE,
                 nextPlan.getName(), nextPhase.getName(),
@@ -457,7 +427,6 @@ public class TestBillingApi {
                 nextPriceList.getName(), 1L, null,
                 SubscriptionTransitionType.CREATE, 0, null);
 
-
         effectiveSubscriptionTransitions.add(t);
 
         final AccountUserApi accountApi = BrainDeadProxyFactory.createBrainDeadProxyFor(AccountUserApi.class);
@@ -486,7 +455,6 @@ public class TestBillingApi {
         assertEquals(events.size(), 0);
     }
 
-
     @Test(enabled = true, groups = "fast")
     public void testBillingEventsAutoInvoicingOffBundle() throws CatalogApiException {
         final DateTime now = clock.getUTCNow();
@@ -501,7 +469,6 @@ public class TestBillingApi {
                 nextPriceList.getName(), 1L, null,
                 SubscriptionTransitionType.CREATE, 0, null);
 
-
         effectiveSubscriptionTransitions.add(t);
 
         final AccountUserApi accountApi = BrainDeadProxyFactory.createBrainDeadProxyFor(AccountUserApi.class);
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 ad55ff9..a43b1b5 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
@@ -78,13 +78,35 @@ public class DefaultPaymentApi implements PaymentApi {
     }
 
 
+
+    @Override
+    public Refund getRefund(UUID refundId) throws PaymentApiException {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
     @Override
-    public Refund createRefund(final Account account, final UUID paymentId, final CallContext context)
+    public Refund createRefund(Account account, UUID paymentId,
+            BigDecimal refundAmount, boolean isAdjusted, CallContext context)
             throws PaymentApiException {
-        return refundProcessor.createRefund(account, paymentId, context);
+        return refundProcessor.createRefund(account, paymentId, refundAmount, isAdjusted, context);
+
     }
 
     @Override
+    public List<Refund> getAccountRefunds(Account account)
+            throws PaymentApiException {
+        return refundProcessor.getAccountRefunds(account);
+    }
+
+    @Override
+    public List<Refund> getPaymentRefunds(UUID paymentId)
+            throws PaymentApiException {
+        return refundProcessor.getPaymentRefunds(paymentId);
+    }
+
+
+    @Override
     public Set<String> getAvailablePlugins() {
         return methodProcessor.getAvailablePlugins();
     }
@@ -118,6 +140,7 @@ public class DefaultPaymentApi implements PaymentApi {
         return methodProcessor.getPaymentMethods(account, withPluginDetail);
     }
 
+    @Override
     public PaymentMethod getPaymentMethodById(final UUID paymentMethodId)
             throws PaymentApiException {
         return methodProcessor.getPaymentMethodById(paymentMethodId);
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
new file mode 100644
index 0000000..729e701
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/api/DefaultRefund.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package com.ning.billing.payment.api;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.util.entity.EntityBase;
+
+public class DefaultRefund extends EntityBase implements Refund {
+
+    private final UUID paymentId;
+    private final BigDecimal amount;
+    private final Currency currency;
+    private final boolean isAdjusted;
+
+    public DefaultRefund(final UUID id, final UUID paymentId, final BigDecimal amount,
+            final Currency currency, final boolean isAdjusted) {
+        super(id);
+        this.paymentId = paymentId;
+        this.amount = amount;
+        this.currency = currency;
+        this.isAdjusted = isAdjusted;
+    }
+
+    @Override
+    public UUID getPaymentId() {
+        return paymentId;
+    }
+
+    @Override
+    public BigDecimal getRefundAmount() {
+        return amount;
+    }
+
+    @Override
+    public Currency getCurrency() {
+        return currency;
+    }
+
+    @Override
+    public boolean isAdjusted() {
+        return isAdjusted;
+    }
+}
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 4f21a9b..54fbeb9 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
@@ -1,4 +1,4 @@
-/* 
+/*
  * Copyright 2010-2011 Ning, Inc.
  *
  * Ning licenses this file to you under the Apache License, version 2.0
@@ -16,52 +16,217 @@
 package com.ning.billing.payment.core;
 
 import javax.inject.Inject;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
 import java.util.UUID;
 import java.util.concurrent.ExecutorService;
 
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Collections2;
 import com.google.inject.name.Named;
+import com.ning.billing.ErrorCode;
 import com.ning.billing.account.api.Account;
 import com.ning.billing.account.api.AccountUserApi;
+import com.ning.billing.invoice.api.InvoiceApiException;
+import com.ning.billing.invoice.api.InvoicePaymentApi;
+import com.ning.billing.payment.api.DefaultRefund;
 import com.ning.billing.payment.api.PaymentApiException;
+import com.ning.billing.payment.api.PaymentStatus;
 import com.ning.billing.payment.api.Refund;
+import com.ning.billing.payment.core.ProcessorBase.WithAccountLock;
+import com.ning.billing.payment.core.ProcessorBase.WithAccountLockCallback;
+import com.ning.billing.payment.dao.PaymentAttemptModelDao;
 import com.ning.billing.payment.dao.PaymentDao;
+import com.ning.billing.payment.dao.RefundModelDao;
+import com.ning.billing.payment.dao.RefundModelDao.RefundStatus;
+import com.ning.billing.payment.plugin.api.PaymentPluginApi;
+import com.ning.billing.payment.plugin.api.PaymentPluginApiException;
 import com.ning.billing.payment.provider.PaymentProviderPluginRegistry;
 import com.ning.billing.util.bus.Bus;
 import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.callcontext.CallContextFactory;
+import com.ning.billing.util.callcontext.CallOrigin;
+import com.ning.billing.util.callcontext.UserType;
 import com.ning.billing.util.globallocker.GlobalLocker;
 
 import static com.ning.billing.payment.glue.PaymentModule.PLUGIN_EXECUTOR_NAMED;
 
 public class RefundProcessor extends ProcessorBase {
 
+    private final static Logger log = LoggerFactory.getLogger(RefundProcessor.class);
+
+    private final InvoicePaymentApi invoicePaymentApi;
+    private final CallContextFactory factory;
+
     @Inject
     public RefundProcessor(final PaymentProviderPluginRegistry pluginRegistry,
-                           final AccountUserApi accountUserApi,
-                           final Bus eventBus,
-                           final PaymentDao paymentDao,
-                           final GlobalLocker locker,
-                           @Named(PLUGIN_EXECUTOR_NAMED) final ExecutorService executor) {
+            final AccountUserApi accountUserApi,
+            final InvoicePaymentApi invoicePaymentApi,
+            final Bus eventBus,
+            final CallContextFactory factory,
+            final PaymentDao paymentDao,
+            final GlobalLocker locker,
+            @Named(PLUGIN_EXECUTOR_NAMED) final ExecutorService executor) {
         super(pluginRegistry, accountUserApi, eventBus, paymentDao, locker, executor);
+        this.invoicePaymentApi = invoicePaymentApi;
+        this.factory = factory;
     }
 
-    public Refund createRefund(final Account account, final UUID paymentId, final CallContext context)
-            throws PaymentApiException {
-        /*
+
+    public Refund createRefund(final Account account, final UUID paymentId, final BigDecimal refundAmount, final boolean isAdjusted, final CallContext context)
+    throws PaymentApiException {
+
+        return new WithAccountLock<Refund>().processAccountWithLock(locker, account.getExternalKey(), new WithAccountLockCallback<Refund>() {
+
+            @Override
+            public Refund doOperation() throws PaymentApiException {
+                try {
+
+
+                    final PaymentAttemptModelDao successfulAttempt = getPaymentAttempt(paymentId);
+                    if (successfulAttempt == null) {
+                        throw new PaymentApiException(ErrorCode.PAYMENT_NO_SUCH_SUCCESS_PAYMENT, paymentId);
+                    }
+                    if (successfulAttempt.getRequestedAmount().compareTo(refundAmount) < 0) {
+                        throw new PaymentApiException(ErrorCode.PAYMENT_REFUND_AMOUNT_TOO_LARGE);
+                    }
+
+                    // Look for that refund entry and count any 'similar' refund left in CREATED state (same amount, same paymentId)
+                    int foundPluginCompletedRefunds = 0;
+                    RefundModelDao refundInfo = null;
+                    List<RefundModelDao> existingRefunds = paymentDao.getRefundsForPayment(paymentId);
+                    for (RefundModelDao cur : existingRefunds) {
+                        if (cur.getAmount().compareTo(refundAmount) == 0) {
+                            if (cur.getRefundStatus() == RefundStatus.CREATED) {
+                                if (refundInfo == null) {
+                                    refundInfo = cur;
+                                }
+                            } else {
+                                foundPluginCompletedRefunds++;
+                            }
+                        }
+                    }
+                    if (refundInfo == null) {
+                        refundInfo = new RefundModelDao(account.getId(), paymentId, refundAmount, account.getCurrency(), isAdjusted);
+                        paymentDao.insertRefund(refundInfo, context);
+                    }
+
+                    final PaymentPluginApi plugin = getPaymentProviderPlugin(account);
+                    int nbExistingRefunds = plugin.getNbRefundForPaymentAmount(account, paymentId, refundAmount);
+                    if (nbExistingRefunds > foundPluginCompletedRefunds) {
+                        log.info("Found existing plugin refund for paymentId {}, skip plugin", paymentId);
+                    } else {
+                        // If there is no such exitng refund we create it
+                        plugin.processRefund(account, paymentId, refundAmount);
+                    }
+                    paymentDao.updateRefundStatus(refundInfo.getId(), RefundStatus.PLUGIN_COMPLETED, context);
+
+                    invoicePaymentApi.createRefund(successfulAttempt.getId(), refundAmount, isAdjusted, refundInfo.getId(), context);
+
+                    paymentDao.updateRefundStatus(refundInfo.getId(), RefundStatus.COMPLETED, context);
+
+                    return new DefaultRefund(refundInfo.getId(), paymentId, refundInfo.getAmount(), account.getCurrency(), isAdjusted);
+                } catch (PaymentPluginApiException e) {
+                    throw new PaymentApiException(ErrorCode.PAYMENT_CREATE_REFUND, account.getId(), e.getMessage());
+                } catch (InvoiceApiException e) {
+                    throw new PaymentApiException(e);
+                }
+            }
+        });
+    }
+
+
+    public Refund getRefund(final UUID refundId)
+    throws PaymentApiException {
+        RefundModelDao result = paymentDao.getRefund(refundId);
+        if (result == null) {
+            throw new PaymentApiException(ErrorCode.PAYMENT_NO_SUCH_REFUND, refundId);
+        }
+        List<RefundModelDao> filteredInput = filterUncompletedPluginRefund(Collections.singletonList(result));
+        if (filteredInput.size() == 0) {
+            throw new PaymentApiException(ErrorCode.PAYMENT_NO_SUCH_REFUND, refundId);
+        }
+        if (completePluginCompletedRefund(filteredInput)) {
+            result = paymentDao.getRefund(refundId);
+        }
+        return new DefaultRefund(result.getId(), result.getPaymentId(), result.getAmount(), result.getCurrency(), result.isAdjsuted());
+    }
+
+
+    public List<Refund> getAccountRefunds(final Account account)
+    throws PaymentApiException {
+        List<RefundModelDao> result = paymentDao.getRefundsForAccount(account.getId());
+        if (completePluginCompletedRefund(result)) {
+            result = paymentDao.getRefundsForAccount(account.getId());
+        }
+        List<RefundModelDao> filteredInput = filterUncompletedPluginRefund(result);
+        return toRefunds(filteredInput);
+    }
+
+    public List<Refund> getPaymentRefunds(final UUID paymentId)
+    throws PaymentApiException {
+        List<RefundModelDao> result = paymentDao.getRefundsForPayment(paymentId);
+        if (completePluginCompletedRefund(result)) {
+            result = paymentDao.getRefundsForPayment(paymentId);
+        }
+        List<RefundModelDao> filteredInput = filterUncompletedPluginRefund(result);
+        return toRefunds(filteredInput);
+    }
+
+    public List<Refund> toRefunds(final List<RefundModelDao> in) {
+        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());
+            }
+        }));
+    }
+
+    private List<RefundModelDao> filterUncompletedPluginRefund(final List<RefundModelDao> input) {
+        return new ArrayList<RefundModelDao>(Collections2.filter(input, new Predicate<RefundModelDao>() {
+            @Override
+            public boolean apply(RefundModelDao in) {
+                return in.getRefundStatus() != RefundStatus.CREATED;
+            }
+        }));
+    }
+
+    private boolean completePluginCompletedRefund(final List<RefundModelDao> refunds) throws PaymentApiException {
+
+        boolean fixedTheWorld = false;
         try {
-            
-        final PaymentProviderPlugin plugin = getPaymentProviderPlugin(account);
-        List<PaymentInfoPlugin> result = plugin.processRefund(account);
-        List<PaymentInfoEvent> info =  new LinkedList<PaymentInfoEvent>();
-        int i = 0;
-        for (PaymentInfoPlugin cur : result) {
-            // STEPH
-            //info.add(new DefaultPaymentInfoEvent(cur, account.getId(), invoiceIds.get(i)));
+            final CallContext context = factory.createCallContext("RefundProcessor", CallOrigin.INTERNAL, UserType.SYSTEM);
+            for (RefundModelDao cur : refunds) {
+                if (cur.getRefundStatus() == RefundStatus.PLUGIN_COMPLETED) {
+                    final PaymentAttemptModelDao successfulAttempt = getPaymentAttempt(cur.getPaymentId());
+                    if (successfulAttempt != null) {
+                        invoicePaymentApi.createRefund(successfulAttempt.getId(), cur.getAmount(), cur.isAdjsuted(), cur.getId(), context);
+                        paymentDao.updateRefundStatus(cur.getId(), RefundStatus.COMPLETED, context);
+                        fixedTheWorld = true;
+                    }
+                }
+            }
+        } catch (InvoiceApiException e) {
+            throw new PaymentApiException(e);
         }
-        return info;
-        } catch (PaymentPluginApiException e) {
-            throw new PaymentApiException(ErrorCode.PAYMENT_CREATE_REFUND, account.getId(), e.getMessage());
+        return fixedTheWorld;
+    }
+
+    private PaymentAttemptModelDao getPaymentAttempt(final UUID paymentId) {
+        List<PaymentAttemptModelDao> attempts = paymentDao.getAttemptsForPayment(paymentId);
+        for (PaymentAttemptModelDao cur : attempts) {
+            if (cur.getPaymentStatus() == PaymentStatus.SUCCESS) {
+                return cur;
+            }
         }
-        */
         return null;
     }
+
 }
diff --git a/payment/src/main/java/com/ning/billing/payment/dao/AuditedPaymentDao.java b/payment/src/main/java/com/ning/billing/payment/dao/AuditedPaymentDao.java
index 5dad606..3c39f60 100644
--- a/payment/src/main/java/com/ning/billing/payment/dao/AuditedPaymentDao.java
+++ b/payment/src/main/java/com/ning/billing/payment/dao/AuditedPaymentDao.java
@@ -1,4 +1,4 @@
-/* 
+/*
  * Copyright 2010-2011 Ning, Inc.
  *
  * Ning licenses this file to you under the Apache License, version 2.0
@@ -25,6 +25,7 @@ import org.skife.jdbi.v2.TransactionStatus;
 
 import com.google.inject.Inject;
 import com.ning.billing.payment.api.PaymentStatus;
+import com.ning.billing.payment.dao.RefundModelDao.RefundStatus;
 import com.ning.billing.payment.retry.PluginFailureRetryService.PluginFailureRetryServiceScheduler;
 import com.ning.billing.util.ChangeType;
 import com.ning.billing.util.callcontext.CallContext;
@@ -34,9 +35,11 @@ import com.ning.billing.util.dao.TableName;
 
 public class AuditedPaymentDao implements PaymentDao {
 
+
     private final PaymentSqlDao paymentSqlDao;
     private final PaymentAttemptSqlDao paymentAttemptSqlDao;
     private final PaymentMethodSqlDao paymentMethodSqlDao;
+    private final RefundSqlDao refundSqlDao;
     //private final TimedoutPaymentRetryServiceScheduler timedoutSchduler;
 
     @Inject
@@ -44,6 +47,7 @@ public class AuditedPaymentDao implements PaymentDao {
         this.paymentSqlDao = dbi.onDemand(PaymentSqlDao.class);
         this.paymentAttemptSqlDao = dbi.onDemand(PaymentAttemptSqlDao.class);
         this.paymentMethodSqlDao = dbi.onDemand(PaymentMethodSqlDao.class);
+        this.refundSqlDao = dbi.onDemand(RefundSqlDao.class);
         // this.timedoutSchduler = timedoutSchduler;
     }
 
@@ -92,10 +96,10 @@ public class AuditedPaymentDao implements PaymentDao {
         }).size();
     }
 
-    
+
     private void scheduleTimeoutRetryFromTransaction(final UUID paymentId, final PaymentAttemptSqlDao transactional, final boolean scheduleTimeoutRetry) {
 
-        if (scheduleTimeoutRetry) { 
+        if (scheduleTimeoutRetry) {
             int retryAttempt = getNbTimedoutAttemptsFromTransaction(paymentId, transactional) + 1;
             timedoutSchduler.scheduleRetryFromTransaction(paymentId, retryAttempt, transactional);
         }
@@ -225,6 +229,74 @@ public class AuditedPaymentDao implements PaymentDao {
     }
 
     @Override
+    public RefundModelDao insertRefund(final RefundModelDao refundInfo, final CallContext context) {
+        return refundSqlDao.inTransaction(new Transaction<RefundModelDao, RefundSqlDao>() {
+
+            @Override
+            public RefundModelDao inTransaction(RefundSqlDao transactional,
+                    TransactionStatus status) throws Exception {
+                transactional.insertRefund(refundInfo, context);
+                return refundInfo;
+            }
+        });
+    }
+
+
+    @Override
+    public void updateRefundStatus(final UUID refundId,
+            final RefundStatus status, final CallContext context) {
+        refundSqlDao.inTransaction(new Transaction<Void, RefundSqlDao>() {
+
+            @Override
+            public Void inTransaction(RefundSqlDao transactional,
+                    TransactionStatus status) throws Exception {
+                transactional.updateStatus(refundId.toString(), status.toString());
+                return null;
+            }
+        });
+    }
+
+    @Override
+    public RefundModelDao getRefund(final UUID refundId) {
+        return refundSqlDao.inTransaction(new Transaction<RefundModelDao, RefundSqlDao>() {
+
+            @Override
+            public RefundModelDao inTransaction(RefundSqlDao transactional,
+                    TransactionStatus status) throws Exception {
+                return transactional.getById(refundId.toString());
+            }
+        });
+    }
+
+
+    @Override
+    public List<RefundModelDao> getRefundsForPayment(final UUID paymentId) {
+        return refundSqlDao.inTransaction(new Transaction<List<RefundModelDao>, RefundSqlDao>() {
+
+            @Override
+            public List<RefundModelDao> inTransaction(RefundSqlDao transactional,
+                    TransactionStatus status) throws Exception {
+                return transactional.getRefundsForPayment(paymentId.toString());
+            }
+        });
+    }
+
+
+    @Override
+    public List<RefundModelDao> getRefundsForAccount(final UUID accountId) {
+        return refundSqlDao.inTransaction(new Transaction<List<RefundModelDao>, RefundSqlDao>() {
+
+            @Override
+            public List<RefundModelDao> inTransaction(RefundSqlDao transactional,
+                    TransactionStatus status) throws Exception {
+                return transactional.getRefundsForAccount(accountId.toString());
+            }
+        });
+    }
+
+
+
+    @Override
     public PaymentMethodModelDao getPaymentMethod(final UUID paymentMethodId) {
         return paymentMethodSqlDao.inTransaction(new Transaction<PaymentMethodModelDao, PaymentMethodSqlDao>() {
             @Override
diff --git a/payment/src/main/java/com/ning/billing/payment/dao/PaymentDao.java b/payment/src/main/java/com/ning/billing/payment/dao/PaymentDao.java
index 6580217..65254fb 100644
--- a/payment/src/main/java/com/ning/billing/payment/dao/PaymentDao.java
+++ b/payment/src/main/java/com/ning/billing/payment/dao/PaymentDao.java
@@ -1,4 +1,4 @@
-/* 
+/*
  * Copyright 2010-2011 Ning, Inc.
  *
  * Ning licenses this file to you under the Apache License, version 2.0
@@ -18,8 +18,14 @@ package com.ning.billing.payment.dao;
 import java.util.List;
 import java.util.UUID;
 
+import org.skife.jdbi.v2.sqlobject.Bind;
+import org.skife.jdbi.v2.sqlobject.SqlQuery;
+
 import com.ning.billing.payment.api.PaymentStatus;
+import com.ning.billing.payment.dao.RefundModelDao.RefundStatus;
+import com.ning.billing.payment.dao.RefundSqlDao.RefundModelDaoBinder;
 import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.callcontext.CallContextBinder;
 
 public interface PaymentDao {
 
@@ -43,6 +49,16 @@ public interface PaymentDao {
 
     public List<PaymentAttemptModelDao> getAttemptsForPayment(final UUID paymentId);
 
+    public RefundModelDao insertRefund(RefundModelDao refundInfo, final CallContext context);
+
+    public void updateRefundStatus(UUID refundId, RefundStatus status, final CallContext context);
+
+    public RefundModelDao getRefund(UUID refundId);
+
+    public List<RefundModelDao> getRefundsForPayment(final UUID paymentId);
+
+    public List<RefundModelDao> getRefundsForAccount(final UUID accountId);
+
     public PaymentMethodModelDao insertPaymentMethod(final PaymentMethodModelDao paymentMethod, final CallContext context);
 
     public PaymentMethodModelDao getPaymentMethod(final UUID paymentMethodId);
diff --git a/payment/src/main/java/com/ning/billing/payment/dao/PaymentModelDao.java b/payment/src/main/java/com/ning/billing/payment/dao/PaymentModelDao.java
index 6ee7ea1..ab1fecc 100644
--- a/payment/src/main/java/com/ning/billing/payment/dao/PaymentModelDao.java
+++ b/payment/src/main/java/com/ning/billing/payment/dao/PaymentModelDao.java
@@ -1,4 +1,4 @@
-/* 
+/*
  * Copyright 2010-2011 Ning, Inc.
  *
  * Ning licenses this file to you under the Apache License, version 2.0
diff --git a/payment/src/main/java/com/ning/billing/payment/dao/PaymentSqlDao.java b/payment/src/main/java/com/ning/billing/payment/dao/PaymentSqlDao.java
index 4173ac2..f637976 100644
--- a/payment/src/main/java/com/ning/billing/payment/dao/PaymentSqlDao.java
+++ b/payment/src/main/java/com/ning/billing/payment/dao/PaymentSqlDao.java
@@ -1,4 +1,4 @@
-/* 
+/*
  * Copyright 2010-2011 Ning, Inc.
  *
  * Ning licenses this file to you under the Apache License, version 2.0
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
new file mode 100644
index 0000000..286fe51
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/dao/RefundModelDao.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package com.ning.billing.payment.dao;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.util.entity.EntityBase;
+
+
+public class RefundModelDao extends EntityBase {
+
+    private final UUID accountId;
+    private final UUID paymentId;
+    private final BigDecimal amount;
+    private final Currency currency;
+    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 id, final UUID accountId, final UUID paymentId,
+            final BigDecimal amount, final Currency currency, final boolean isAdjusted, final RefundStatus refundStatus) {
+        super(id);
+        this.accountId = accountId;
+        this.paymentId = paymentId;
+        this.amount = amount;
+        this.currency = currency;
+        this.refundStatus = refundStatus;
+        this.isAdjusted = isAdjusted;
+    }
+
+    public UUID getAccountId() {
+        return accountId;
+    }
+
+    public UUID getPaymentId() {
+        return paymentId;
+    }
+
+    public BigDecimal getAmount() {
+        return amount;
+    }
+
+    public Currency getCurrency() {
+        return currency;
+    }
+
+    public RefundStatus getRefundStatus() {
+        return refundStatus;
+    }
+
+    public boolean isAdjsuted() {
+        return isAdjusted;
+    }
+
+    public enum RefundStatus {
+        CREATED,
+        PLUGIN_COMPLETED,
+        COMPLETED,
+    }
+}
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
new file mode 100644
index 0000000..c29c6ed
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/dao/RefundSqlDao.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package com.ning.billing.payment.dao;
+
+import java.math.BigDecimal;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.List;
+import java.util.UUID;
+
+import org.skife.jdbi.v2.SQLStatement;
+import org.skife.jdbi.v2.StatementContext;
+import org.skife.jdbi.v2.sqlobject.Bind;
+import org.skife.jdbi.v2.sqlobject.Binder;
+import org.skife.jdbi.v2.sqlobject.SqlQuery;
+import org.skife.jdbi.v2.sqlobject.SqlUpdate;
+import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
+import org.skife.jdbi.v2.sqlobject.mixins.CloseMe;
+import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
+import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
+import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
+import org.skife.jdbi.v2.tweak.ResultSetMapper;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.payment.dao.RefundModelDao.RefundStatus;
+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 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);
+
+    @SqlUpdate
+    void updateStatus(@Bind("id") final String refundId, @Bind("refund_status") final String status);
+
+    @SqlQuery
+    RefundModelDao getRefund(@Bind("id") final String refundId);
+
+    @SqlQuery
+    List<RefundModelDao> getRefundsForPayment(@Bind("paymentId") final String paymentId);
+
+    @SqlQuery
+    List<RefundModelDao> getRefundsForAccount(@Bind("accountId") final String accountId);
+
+
+    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) {
+            stmt.bind("id", refund.getId().toString());
+            stmt.bind("accountId", refund.getAccountId().toString());
+            stmt.bind("paymentId", refund.getPaymentId().toString());
+            stmt.bind("amount", refund.getAmount());
+            stmt.bind("currency", refund.getCurrency().toString());
+            stmt.bind("is_adjusted", refund.isAdjsuted());
+            stmt.bind("refund_status", refund.getRefundStatus().toString());
+        }
+    }
+
+    public static class RefundModelDaoMapper extends MapperBase implements ResultSetMapper<RefundModelDao> {
+
+        @Override
+        public RefundModelDao map(final int index, final ResultSet rs, final StatementContext ctx)
+                throws SQLException {
+            final UUID id = getUUID(rs, "id");
+            final UUID accountId = getUUID(rs, "account_id");
+            final UUID paymentId = getUUID(rs, "payment_id");
+            final BigDecimal amount = rs.getBigDecimal("amount");
+            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);
+        }
+    }
+}
diff --git a/payment/src/main/java/com/ning/billing/payment/provider/DefaultNoOpPaymentProviderPlugin.java b/payment/src/main/java/com/ning/billing/payment/provider/DefaultNoOpPaymentProviderPlugin.java
index 5591937..cf23c59 100644
--- a/payment/src/main/java/com/ning/billing/payment/provider/DefaultNoOpPaymentProviderPlugin.java
+++ b/payment/src/main/java/com/ning/billing/payment/provider/DefaultNoOpPaymentProviderPlugin.java
@@ -192,8 +192,15 @@ public class DefaultNoOpPaymentProviderPlugin implements NoOpPaymentPluginApi {
     }
 
     @Override
-    public List<PaymentInfoPlugin> processRefund(final Account account)
-            throws PaymentPluginApiException {
-        return null;
+    public void processRefund(Account account, UUID paymentId,
+            BigDecimal refundAmout) throws PaymentPluginApiException {
+    }
+
+    @Override
+    public int getNbRefundForPaymentAmount(Account account, UUID paymentId,
+            BigDecimal refundAmount) throws PaymentPluginApiException {
+        // TODO Auto-generated method stub
+        return 0;
     }
+
 }
diff --git a/payment/src/main/resources/com/ning/billing/payment/ddl.sql b/payment/src/main/resources/com/ning/billing/payment/ddl.sql
index b8fa010..69db6a2 100644
--- a/payment/src/main/resources/com/ning/billing/payment/ddl.sql
+++ b/payment/src/main/resources/com/ning/billing/payment/ddl.sql
@@ -1,4 +1,24 @@
 
+DROP TABLE IF EXISTS refunds; 
+CREATE TABLE refunds (
+    record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
+    id char(36) NOT NULL,
+    account_id char(36) COLLATE utf8_bin NOT NULL,
+    payment_id char(36) COLLATE utf8_bin NOT NULL,    
+    amount decimal(8,2),
+    currency char(3),   
+    is_adjusted tinyint(1),
+    refund_status varchar(50), 
+    created_by varchar(50) NOT NULL,
+    created_date datetime NOT NULL,
+    updated_by varchar(50) NOT NULL,
+    updated_date datetime NOT NULL,
+    PRIMARY KEY (record_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
+CREATE UNIQUE INDEX refunds_id ON refunds(id);
+CREATE INDEX refunds_pay ON refunds(payment_id);
+CREATE INDEX refunds_accnt ON refunds(account_id);
+
 DROP TABLE IF EXISTS payments; 
 CREATE TABLE payments (
     record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
diff --git a/payment/src/test/java/com/ning/billing/payment/dao/MockPaymentDao.java b/payment/src/test/java/com/ning/billing/payment/dao/MockPaymentDao.java
index fb6a281..ce1a17c 100644
--- a/payment/src/test/java/com/ning/billing/payment/dao/MockPaymentDao.java
+++ b/payment/src/test/java/com/ning/billing/payment/dao/MockPaymentDao.java
@@ -1,4 +1,4 @@
-/* 
+/*
  * Copyright 2010-2011 Ning, Inc.
  *
  * Ning licenses this file to you under the Apache License, version 2.0
@@ -24,6 +24,7 @@ import java.util.Map;
 import java.util.UUID;
 
 import com.ning.billing.payment.api.PaymentStatus;
+import com.ning.billing.payment.dao.RefundModelDao.RefundStatus;
 import com.ning.billing.util.callcontext.CallContext;
 
 public class MockPaymentDao implements PaymentDao {
@@ -167,4 +168,36 @@ public class MockPaymentDao implements PaymentDao {
             }
         }
     }
+
+    @Override
+    public RefundModelDao insertRefund(RefundModelDao refundInfo,
+            CallContext context) {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public void updateRefundStatus(UUID refundId, RefundStatus status,
+            CallContext context) {
+        // TODO Auto-generated method stub
+
+    }
+
+    @Override
+    public RefundModelDao getRefund(UUID refundId) {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public List<RefundModelDao> getRefundsForPayment(UUID paymentId) {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public List<RefundModelDao> getRefundsForAccount(UUID accountId) {
+        // TODO Auto-generated method stub
+        return null;
+    }
 }
diff --git a/util/src/main/java/com/ning/billing/util/bus/PersistentBus.java b/util/src/main/java/com/ning/billing/util/bus/PersistentBus.java
index b315e79..c5e5375 100644
--- a/util/src/main/java/com/ning/billing/util/bus/PersistentBus.java
+++ b/util/src/main/java/com/ning/billing/util/bus/PersistentBus.java
@@ -171,7 +171,7 @@ public class PersistentBus extends PersistentQueueBase implements Bus {
             final BusEventEntry entry = new BusEventEntry(hostname, event.getClass().getName(), json);
             transactional.insertBusEvent(entry);
         } catch (Exception e) {
-            log.error("Failed to post BusEvent " + event.toString(), e);
+            log.error("Failed to post BusEvent " + event, e);
         }
     }
 }
diff --git a/util/src/test/java/com/ning/billing/mock/api/MockEntitlementUserApi.java b/util/src/test/java/com/ning/billing/mock/api/MockEntitlementUserApi.java
index 60da96b..0fdeeb1 100644
--- a/util/src/test/java/com/ning/billing/mock/api/MockEntitlementUserApi.java
+++ b/util/src/test/java/com/ning/billing/mock/api/MockEntitlementUserApi.java
@@ -36,6 +36,7 @@ import com.ning.billing.util.callcontext.CallContext;
 public class MockEntitlementUserApi implements EntitlementUserApi {
     private final Map<UUID, String> subscriptionBundles = new HashMap<UUID, String>();
     private final Map<UUID, UUID> accountForBundle = new HashMap<UUID, UUID>();
+    private final Map<UUID, Subscription> subscriptionsById = new HashMap<UUID, Subscription>();
 
     public synchronized void addBundle(final UUID bundleUUID, final String externalKey, final UUID accountId) {
         subscriptionBundles.put(bundleUUID, externalKey);
diff --git a/util/src/test/java/com/ning/billing/mock/MockPriceList.java b/util/src/test/java/com/ning/billing/mock/MockPriceList.java
new file mode 100644
index 0000000..8903a3c
--- /dev/null
+++ b/util/src/test/java/com/ning/billing/mock/MockPriceList.java
@@ -0,0 +1,59 @@
+/*
+ * 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.mock;
+
+import java.util.UUID;
+
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.catalog.api.PriceList;
+import com.ning.billing.catalog.api.Product;
+
+public class MockPriceList implements PriceList {
+    private final String name;
+    private final Boolean isRetired;
+    private final Plan plan;
+
+    public MockPriceList() {
+        this(false, UUID.randomUUID().toString(), new MockPlan());
+    }
+
+    public MockPriceList(final Boolean retired, final String name, final Plan plan) {
+        isRetired = retired;
+        this.name = name;
+        this.plan = plan;
+    }
+
+    @Override
+    public boolean isRetired() {
+        return isRetired;
+    }
+
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public Plan findPlan(final Product product, final BillingPeriod period) {
+        return plan;
+    }
+
+    public Plan getPlan() {
+        return plan;
+    }
+}
diff --git a/util/src/test/java/com/ning/billing/mock/MockProduct.java b/util/src/test/java/com/ning/billing/mock/MockProduct.java
new file mode 100644
index 0000000..ea341b0
--- /dev/null
+++ b/util/src/test/java/com/ning/billing/mock/MockProduct.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.mock;
+
+import com.ning.billing.catalog.api.Product;
+import com.ning.billing.catalog.api.ProductCategory;
+
+public class MockProduct implements Product {
+    private final String name;
+    private final ProductCategory category;
+    private final String catalogName;
+
+    public MockProduct() {
+        name = "TestProduct";
+        category = ProductCategory.BASE;
+        catalogName = "Vehicules";
+    }
+
+    public MockProduct(final String name, final ProductCategory category, final String catalogName) {
+        this.name = name;
+        this.category = category;
+        this.catalogName = catalogName;
+    }
+
+    @Override
+    public String getCatalogName() {
+        return catalogName;
+    }
+
+    @Override
+    public ProductCategory getCategory() {
+        return category;
+    }
+
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public boolean isRetired() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Product[] getAvailable() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Product[] getIncluded() {
+        throw new UnsupportedOperationException();
+    }
+
+    public static MockProduct createBicycle() {
+        return new MockProduct("Bicycle", ProductCategory.BASE, "Vehcles");
+    }
+
+    public static MockProduct createPickup() {
+        return new MockProduct("Pickup", ProductCategory.BASE, "Vehcles");
+    }
+
+    public static MockProduct createSportsCar() {
+        return new MockProduct("SportsCar", ProductCategory.BASE, "Vehcles");
+    }
+
+    public static MockProduct createJet() {
+        return new MockProduct("Jet", ProductCategory.BASE, "Vehcles");
+    }
+
+    public static MockProduct createHorn() {
+        return new MockProduct("Horn", ProductCategory.ADD_ON, "Vehcles");
+    }
+
+    public static MockProduct createSpotlight() {
+        return new MockProduct("spotlight", ProductCategory.ADD_ON, "Vehcles");
+    }
+
+    public static MockProduct createRedPaintJob() {
+        return new MockProduct("RedPaintJob", ProductCategory.ADD_ON, "Vehcles");
+    }
+
+    public static Product[] createAll() {
+        return new MockProduct[]{
+                createBicycle(),
+                createPickup(),
+                createSportsCar(),
+                createJet(),
+                createHorn(),
+                createRedPaintJob()
+        };
+    }
+}
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 2bb6a37..c21cf14 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
@@ -95,7 +95,7 @@ public class TestAuditedTagDao {
         final String definitionName = UUID.randomUUID().toString().substring(0, 5);
         final String description = UUID.randomUUID().toString().substring(0, 5);
         final UUID objectId = UUID.randomUUID();
-        final ObjectType objectType = ObjectType.RECURRING_INVOICE_ITEM;
+        final ObjectType objectType = ObjectType.INVOICE_ITEM;
 
         // Verify the initial state
         Assert.assertEquals(eventsListener.getEvents().size(), 0);