killbill-uncached

New entitlement api (ongoing work)

7/22/2013 10:03:42 PM

Changes

entitlement/src/main/java/com/ning/billing/entitlement/api/BlockingAccount.java 171(+0 -171)

entitlement/src/main/java/com/ning/billing/entitlement/api/BlockingAccountUserApi.java 106(+0 -106)

entitlement/src/main/java/com/ning/billing/entitlement/api/BlockingSubscription.java 234(+0 -234)

entitlement/src/main/java/com/ning/billing/entitlement/api/BlockingSubscriptionBundle.java 122(+0 -122)

entitlement/src/main/java/com/ning/billing/entitlement/api/BlockingSubscriptionUserApi.java 159(+0 -159)

Details

diff --git a/account/killbill-account.iml b/account/killbill-account.iml
index ae75673..529e756 100644
--- a/account/killbill-account.iml
+++ b/account/killbill-account.iml
@@ -11,6 +11,7 @@
     </content>
     <orderEntry type="inheritedJdk" />
     <orderEntry type="sourceFolder" forTests="false" />
+    <orderEntry type="library" name="Maven: com.ning.billing:killbill-api:0.3.3-SNAPSHOT" level="project" />
     <orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-annotations:2.1.0" level="project" />
     <orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-databind:2.1.0" level="project" />
     <orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-core:2.1.0" level="project" />
diff --git a/api/killbill-internal-api.iml b/api/killbill-internal-api.iml
index 5cba95a..1d87293 100644
--- a/api/killbill-internal-api.iml
+++ b/api/killbill-internal-api.iml
@@ -9,6 +9,7 @@
     </content>
     <orderEntry type="inheritedJdk" />
     <orderEntry type="sourceFolder" forTests="false" />
+    <orderEntry type="library" name="Maven: com.ning.billing:killbill-api:0.3.3-SNAPSHOT" level="project" />
     <orderEntry type="library" scope="PROVIDED" name="Maven: com.google.code.findbugs:jsr305:1.3.9" level="project" />
     <orderEntry type="library" name="Maven: com.ning.billing:killbill-api:0.3.3-SNAPSHOT" level="project" />
     <orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-annotations:2.1.0" level="project" />
diff --git a/api/src/main/java/com/ning/billing/glue/EntitlementModule.java b/api/src/main/java/com/ning/billing/glue/EntitlementModule.java
index 29ac72f..5e686af 100644
--- a/api/src/main/java/com/ning/billing/glue/EntitlementModule.java
+++ b/api/src/main/java/com/ning/billing/glue/EntitlementModule.java
@@ -17,11 +17,6 @@
 package com.ning.billing.glue;
 
 public interface EntitlementModule {
-    public void installAccountUserApi();
-
-    public void installSubscriptionUserApi();
-
-
     public void installBlockingStateDao();
 
     public void installBlockingApi();
diff --git a/beatrix/killbill-beatrix.iml b/beatrix/killbill-beatrix.iml
index c296529..053b292 100644
--- a/beatrix/killbill-beatrix.iml
+++ b/beatrix/killbill-beatrix.iml
@@ -12,6 +12,7 @@
     </content>
     <orderEntry type="inheritedJdk" />
     <orderEntry type="sourceFolder" forTests="false" />
+    <orderEntry type="library" name="Maven: com.ning.billing:killbill-api:0.3.3-SNAPSHOT" level="project" />
     <orderEntry type="library" scope="PROVIDED" name="Maven: com.google.guava:guava:14.0.1" level="project" />
     <orderEntry type="library" scope="PROVIDED" name="Maven: com.google.inject:guice:3.0" level="project" />
     <orderEntry type="library" scope="PROVIDED" name="Maven: javax.inject:javax.inject:1" level="project" />
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegrationBase.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegrationBase.java
index 2a516f6..66b5fbd 100644
--- a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegrationBase.java
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegrationBase.java
@@ -59,7 +59,6 @@ import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.catalog.api.PlanPhaseSpecifier;
 import com.ning.billing.catalog.api.PriceListSet;
 import com.ning.billing.catalog.api.ProductCategory;
-import com.ning.billing.entitlement.api.BlockingSubscription;
 import com.ning.billing.invoice.api.Invoice;
 import com.ning.billing.invoice.api.InvoiceApiException;
 import com.ning.billing.invoice.api.InvoicePayment;
@@ -320,7 +319,8 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB implemen
     }
 
     protected SubscriptionData subscriptionDataFromSubscription(final Subscription sub) {
-        return (SubscriptionData) ((BlockingSubscription) sub).getDelegateSubscription();
+        // STEPH_ENT
+        return (SubscriptionData) sub;
     }
 
     protected Account createAccountWithOsgiPaymentMethod(final AccountData accountData) throws Exception {
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/util/SubscriptionChecker.java b/beatrix/src/test/java/com/ning/billing/beatrix/util/SubscriptionChecker.java
index 7db4b52..959d58e 100644
--- a/beatrix/src/test/java/com/ning/billing/beatrix/util/SubscriptionChecker.java
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/util/SubscriptionChecker.java
@@ -16,10 +16,8 @@
 
 package com.ning.billing.beatrix.util;
 
-import com.ning.billing.entitlement.api.BlockingSubscription;
 import com.ning.billing.subscription.api.user.Subscription;
 import com.ning.billing.subscription.api.user.SubscriptionBundle;
-import com.ning.billing.subscription.api.user.SubscriptionData;
 import com.ning.billing.subscription.api.user.SubscriptionTransition;
 import com.ning.billing.subscription.api.user.SubscriptionTransitionData;
 import com.ning.billing.subscription.api.user.SubscriptionUserApi;
@@ -75,7 +73,8 @@ public class SubscriptionChecker {
     }
 
     private List<SubscriptionTransition> getSubscriptionEvents(final Subscription subscription) {
-        return ((SubscriptionData) ((BlockingSubscription) subscription).getDelegateSubscription()).getAllTransitions();
+        // STEPH_ENT
+        return subscription.getAllTransitions();
     }
 
 
diff --git a/catalog/killbill-catalog.iml b/catalog/killbill-catalog.iml
index 791ac91..2b23402 100644
--- a/catalog/killbill-catalog.iml
+++ b/catalog/killbill-catalog.iml
@@ -12,6 +12,7 @@
     </content>
     <orderEntry type="inheritedJdk" />
     <orderEntry type="sourceFolder" forTests="false" />
+    <orderEntry type="library" name="Maven: com.ning.billing:killbill-api:0.3.3-SNAPSHOT" level="project" />
     <orderEntry type="library" scope="PROVIDED" name="Maven: com.google.code.findbugs:jsr305:1.3.9" level="project" />
     <orderEntry type="library" scope="PROVIDED" name="Maven: com.google.guava:guava:14.0.1" level="project" />
     <orderEntry type="library" scope="PROVIDED" name="Maven: com.google.inject:guice:3.0" level="project" />
diff --git a/entitlement/killbill-entitlement.iml b/entitlement/killbill-entitlement.iml
index 6a94abc..6c55f74 100644
--- a/entitlement/killbill-entitlement.iml
+++ b/entitlement/killbill-entitlement.iml
@@ -12,6 +12,7 @@
     </content>
     <orderEntry type="inheritedJdk" />
     <orderEntry type="sourceFolder" forTests="false" />
+    <orderEntry type="library" name="Maven: com.ning.billing:killbill-api:0.3.3-SNAPSHOT" level="project" />
     <orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-annotations:2.1.0" level="project" />
     <orderEntry type="library" scope="PROVIDED" name="Maven: com.google.code.findbugs:jsr305:1.3.9" level="project" />
     <orderEntry type="library" scope="PROVIDED" name="Maven: com.google.guava:guava:14.0.1" level="project" />
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultEntitlement.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultEntitlement.java
index aa2a545..c02dd14 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultEntitlement.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultEntitlement.java
@@ -24,6 +24,7 @@ import com.ning.billing.account.api.AccountApiException;
 import com.ning.billing.catalog.api.ActionPolicy;
 import com.ning.billing.catalog.api.BillingPeriod;
 import com.ning.billing.clock.Clock;
+import com.ning.billing.entitlement.block.BlockingChecker;
 import com.ning.billing.subscription.api.user.Subscription;
 import com.ning.billing.subscription.api.user.SubscriptionUserApiException;
 import com.ning.billing.util.callcontext.CallContext;
@@ -38,16 +39,17 @@ public class DefaultEntitlement implements Entitlement {
     private final Subscription subscription;
     private final InternalCallContextFactory internalCallContextFactory;
     private final Clock clock;
+    private final BlockingChecker checker;
 
-    public DefaultEntitlement(final AccountInternalApi accountApi, final Subscription subscription, final InternalCallContextFactory internalCallContextFactory, final Clock clock) {
+    public DefaultEntitlement(final AccountInternalApi accountApi, final Subscription subscription, final InternalCallContextFactory internalCallContextFactory, final Clock clock, final BlockingChecker checker) {
         this.accountApi = accountApi;
         this.subscription = subscription;
         this.internalCallContextFactory = internalCallContextFactory;
         this.clock = clock;
+        this.checker = checker;
     }
 
 
-
     @Override
     public boolean cancelEntitlementWithDate(final LocalDate localDate, final CallContext callContext) throws EntitlementApiException {
 
@@ -81,8 +83,18 @@ public class DefaultEntitlement implements Entitlement {
     }
 
     @Override
-    public boolean changePlan(final String s, final BillingPeriod billingPeriod, final String s2, final LocalDate localDate, final CallContext callContext) throws EntitlementApiException {
-        return false;
+    public boolean changePlan(final String productName, final BillingPeriod billingPeriod, final String priceList, final LocalDate localDate, final CallContext callContext) throws EntitlementApiException {
+
+        final InternalCallContext context = internalCallContextFactory.createInternalCallContext(callContext);
+        final DateTime requestedDate = fromLocalDateAndReferenceTime(localDate, subscription.getStartDate(), clock, context);
+        try {
+            checker.checkBlockedChange(subscription, context);
+            return subscription.changePlan(productName, billingPeriod, priceList, requestedDate, callContext);
+        } catch (BlockingApiException e) {
+            throw new EntitlementApiException(e, e.getCode(), e.getMessage());
+        } catch (SubscriptionUserApiException e) {
+            throw new EntitlementApiException(e);
+        }
     }
 
     @Override
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultEntitlementApi.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultEntitlementApi.java
index e05385b..e3ccde1 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultEntitlementApi.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultEntitlementApi.java
@@ -16,27 +16,144 @@
 
 package com.ning.billing.entitlement.api;
 
+import java.util.LinkedList;
+import java.util.List;
+import java.util.UUID;
+
+import javax.annotation.Nullable;
+import javax.inject.Inject;
+
+import com.ning.billing.ErrorCode;
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.clock.Clock;
+import com.ning.billing.entitlement.block.BlockingChecker;
 import com.ning.billing.entitlement.dao.BlockingStateDao;
+import com.ning.billing.subscription.api.user.Subscription;
+import com.ning.billing.subscription.api.user.SubscriptionBundle;
+import com.ning.billing.subscription.api.user.SubscriptionState;
+import com.ning.billing.subscription.api.user.SubscriptionUserApiException;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.callcontext.InternalCallContext;
 import com.ning.billing.util.callcontext.InternalCallContextFactory;
+import com.ning.billing.util.callcontext.InternalTenantContext;
 import com.ning.billing.util.callcontext.TenantContext;
+import com.ning.billing.util.svcapi.account.AccountInternalApi;
+import com.ning.billing.util.svcapi.subscription.SubscriptionInternalApi;
 
-import javax.inject.Inject;
-import java.util.List;
-import java.util.UUID;
+import com.google.common.base.Function;
+import com.google.common.collect.Collections2;
+import com.google.common.collect.ImmutableList;
 
 public class DefaultEntitlementApi implements EntitlementApi {
 
     private final BlockingStateDao dao;
+    private final SubscriptionInternalApi subscriptionInternalApi;
+    private final AccountInternalApi accountApi;
+    private final Clock clock;
     private final InternalCallContextFactory internalCallContextFactory;
+    private final BlockingChecker checker;
 
     @Inject
-    public DefaultEntitlementApi(final BlockingStateDao dao, final InternalCallContextFactory internalCallContextFactory) {
+    public DefaultEntitlementApi(final BlockingStateDao dao, final InternalCallContextFactory internalCallContextFactory, final SubscriptionInternalApi subscriptionInternalApi, final AccountInternalApi accountApi, final Clock clock, final BlockingChecker checker) {
         this.dao = dao;
         this.internalCallContextFactory = internalCallContextFactory;
+        this.subscriptionInternalApi = subscriptionInternalApi;
+        this.accountApi = accountApi;
+        this.clock = clock;
+        this.checker = checker;
+    }
+
+
+    @Override
+    public Entitlement createBaseEntitlement(final UUID accountId, final PlanPhaseSpecifier planPhaseSpecifier, final String externalKey, final CallContext callContext) throws EntitlementApiException {
+        final InternalCallContext context = internalCallContextFactory.createInternalCallContext(callContext);
+        try {
+            final SubscriptionBundle bundle = subscriptionInternalApi.createBundleForAccount(accountId, externalKey, context);
+            final Subscription subscription = subscriptionInternalApi.createSubscription(bundle.getId(), planPhaseSpecifier, clock.getUTCNow(), context);
+            return new DefaultEntitlement(accountApi, subscription, internalCallContextFactory, clock, checker);
+        } catch (SubscriptionUserApiException e) {
+            throw new EntitlementApiException(e);
+        }
+    }
+
+    @Override
+    public Entitlement addEntitlement(final UUID baseSubscriptionId, final PlanPhaseSpecifier planPhaseSpecifier, final CallContext callContext) throws EntitlementApiException {
+        final InternalCallContext context = internalCallContextFactory.createInternalCallContext(callContext);
+        try {
+            final Subscription baseSubscription = subscriptionInternalApi.getSubscriptionFromId(baseSubscriptionId, context);
+            if (baseSubscription.getCategory() != ProductCategory.BASE ||
+                baseSubscription.getState() != SubscriptionState.ACTIVE) {
+                throw new EntitlementApiException(new SubscriptionUserApiException(ErrorCode.SUB_GET_NO_SUCH_BASE_SUBSCRIPTION, baseSubscription.getBundleId()));
+            }
+            final Subscription subscription = subscriptionInternalApi.createSubscription(baseSubscription.getBundleId(), planPhaseSpecifier, clock.getUTCNow(), context);
+            return new DefaultEntitlement(accountApi, subscription, internalCallContextFactory, clock, checker);
+        } catch (SubscriptionUserApiException e) {
+            throw new EntitlementApiException(e);
+        }
+    }
+
+    @Override
+    public Entitlement getEntitlementFromId(final UUID uuid, final TenantContext tenantContext) throws EntitlementApiException {
+        final InternalTenantContext context = internalCallContextFactory.createInternalTenantContext(tenantContext);
+        try {
+            final Subscription subscription = subscriptionInternalApi.getSubscriptionFromId(uuid, context);
+            return new DefaultEntitlement(accountApi, subscription, internalCallContextFactory, clock, checker);
+        } catch (SubscriptionUserApiException e) {
+            throw new EntitlementApiException(e);
+        }
+    }
+
+    @Override
+    public List<Entitlement> getAllEntitlementFromBaseId(final UUID baseSubscriptionId, final TenantContext tenantContext) throws EntitlementApiException {
+        final InternalTenantContext context = internalCallContextFactory.createInternalTenantContext(tenantContext);
+        try {
+            final Subscription baseSubscription = subscriptionInternalApi.getSubscriptionFromId(baseSubscriptionId, context);
+            return getAllEntitlementFromBundleId(baseSubscription.getBundleId(), context);
+        } catch (SubscriptionUserApiException e) {
+            throw new EntitlementApiException(e);
+        }
+    }
+
+    @Override
+    public List<Entitlement> getAllEntitlementForAccountIdAndExternalKey(final UUID accountId, final String externalKey, final TenantContext tenantContext) throws EntitlementApiException {
+        final InternalTenantContext context = internalCallContextFactory.createInternalTenantContext(tenantContext);
+
+        try {
+            final SubscriptionBundle bundle = subscriptionInternalApi.getBundleForAccountAndKey(accountId, externalKey, context);
+            return getAllEntitlementFromBundleId(bundle.getId(), context);
+        } catch (SubscriptionUserApiException e) {
+            throw new EntitlementApiException(e);
+        }
+    }
+
+
+    private List<Entitlement> getAllEntitlementFromBundleId(final UUID bundleId, final InternalTenantContext context) throws EntitlementApiException {
+        final List<Subscription> subscriptions = subscriptionInternalApi.getSubscriptionsForBundle(bundleId, context);
+        return ImmutableList.<Entitlement>copyOf(Collections2.transform(subscriptions, new Function<Subscription, Entitlement>() {
+            @Nullable
+            @Override
+            public Entitlement apply(@Nullable final Subscription input) {
+                return new DefaultEntitlement(accountApi, input, internalCallContextFactory, clock, checker);
+            }
+        }));
     }
 
 
     @Override
+    public List<Entitlement> getAllEntitlementFromAccountId(final UUID accountId, final TenantContext tenantContext) throws EntitlementApiException {
+
+        final List<Entitlement> result = new LinkedList<Entitlement>();
+        final InternalTenantContext context = internalCallContextFactory.createInternalTenantContext(tenantContext);
+        final List<SubscriptionBundle> bundles = subscriptionInternalApi.getBundlesForAccount(accountId, context);
+        for (final SubscriptionBundle bundle : bundles) {
+            final List<Entitlement> entitlements = getAllEntitlementFromBundleId(bundle.getId(), context);
+            result.addAll(entitlements);
+        }
+        return result;
+    }
+
+    @Override
     public List<BlockingState> getBlockingHistory(final UUID overdueableId, final TenantContext context) {
         return dao.getBlockingHistoryFor(overdueableId, internalCallContextFactory.createInternalTenantContext(context));
     }
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/glue/DefaultEntitlementModule.java b/entitlement/src/main/java/com/ning/billing/entitlement/glue/DefaultEntitlementModule.java
index 991c545..6de4ac7 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/glue/DefaultEntitlementModule.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/glue/DefaultEntitlementModule.java
@@ -16,21 +16,19 @@
 
 package com.ning.billing.entitlement.glue;
 
-import com.google.inject.AbstractModule;
-import com.ning.billing.account.api.AccountUserApi;
-import com.ning.billing.entitlement.api.BlockingAccountUserApi;
-import com.ning.billing.entitlement.api.BlockingSubscriptionUserApi;
+import org.skife.config.ConfigSource;
+
 import com.ning.billing.entitlement.api.DefaultEntitlementApi;
+import com.ning.billing.entitlement.api.EntitlementApi;
 import com.ning.billing.entitlement.api.svcs.DefaultInternalBlockingApi;
 import com.ning.billing.entitlement.block.BlockingChecker;
 import com.ning.billing.entitlement.block.DefaultBlockingChecker;
 import com.ning.billing.entitlement.dao.BlockingStateDao;
 import com.ning.billing.entitlement.dao.DefaultBlockingStateDao;
 import com.ning.billing.glue.EntitlementModule;
-import com.ning.billing.entitlement.api.EntitlementApi;
-import com.ning.billing.subscription.api.user.SubscriptionUserApi;
 import com.ning.billing.util.svcapi.junction.BlockingInternalApi;
-import org.skife.config.ConfigSource;
+
+import com.google.inject.AbstractModule;
 
 public class DefaultEntitlementModule extends AbstractModule implements EntitlementModule {
 
@@ -40,8 +38,6 @@ public class DefaultEntitlementModule extends AbstractModule implements Entitlem
 
     @Override
     protected void configure() {
-        installAccountUserApi();
-        installSubscriptionUserApi();
         installBlockingStateDao();
         installBlockingApi();
         installEntitlementApi();
@@ -58,16 +54,6 @@ public class DefaultEntitlementModule extends AbstractModule implements Entitlem
         bind(BlockingInternalApi.class).to(DefaultInternalBlockingApi.class).asEagerSingleton();
     }
 
-    @Override
-    public void installAccountUserApi() {
-        bind(AccountUserApi.class).to(BlockingAccountUserApi.class).asEagerSingleton();
-    }
-
-
-    @Override
-    public void installSubscriptionUserApi() {
-        bind(SubscriptionUserApi.class).to(BlockingSubscriptionUserApi.class).asEagerSingleton();
-    }
 
     @Override
     public void installEntitlementApi() {
diff --git a/invoice/killbill-invoice.iml b/invoice/killbill-invoice.iml
index 01944d4..64752f9 100644
--- a/invoice/killbill-invoice.iml
+++ b/invoice/killbill-invoice.iml
@@ -12,6 +12,7 @@
     </content>
     <orderEntry type="inheritedJdk" />
     <orderEntry type="sourceFolder" forTests="false" />
+    <orderEntry type="library" name="Maven: com.ning.billing:killbill-api:0.3.3-SNAPSHOT" level="project" />
     <orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-annotations:2.1.0" level="project" />
     <orderEntry type="library" scope="PROVIDED" name="Maven: com.google.code.findbugs:jsr305:1.3.9" level="project" />
     <orderEntry type="library" scope="PROVIDED" name="Maven: com.google.guava:guava:14.0.1" level="project" />
diff --git a/invoice/src/main/java/com/ning/billing/invoice/InvoiceDispatcher.java b/invoice/src/main/java/com/ning/billing/invoice/InvoiceDispatcher.java
index ccb4c4e..2d85364 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/InvoiceDispatcher.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/InvoiceDispatcher.java
@@ -29,9 +29,7 @@ import java.util.UUID;
 import javax.annotation.Nullable;
 
 import org.joda.time.DateTime;
-import org.joda.time.DateTimeZone;
 import org.joda.time.LocalDate;
-import org.joda.time.LocalTime;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -73,6 +71,7 @@ import com.ning.billing.util.svcapi.account.AccountInternalApi;
 import com.ning.billing.util.svcapi.subscription.SubscriptionInternalApi;
 import com.ning.billing.util.svcapi.junction.BillingEventSet;
 import com.ning.billing.util.svcapi.junction.BillingInternalApi;
+import com.ning.billing.util.timezone.DateAndTimeZoneContext;
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Function;
@@ -328,40 +327,4 @@ public class InvoiceDispatcher {
         }
     }
 
-    final static class DateAndTimeZoneContext {
-
-        private final LocalTime referenceTime;
-        private final DateTimeZone accountTimeZone;
-        private final Clock clock;
-
-        public DateAndTimeZoneContext(final DateTime effectiveDateTime, final DateTimeZone accountTimeZone, final Clock clock) {
-            this.clock = clock;
-            this.referenceTime = effectiveDateTime != null ? effectiveDateTime.toLocalTime() : null;
-            this.accountTimeZone = accountTimeZone;
-        }
-
-        public LocalDate computeTargetDate(final DateTime targetDateTime) {
-            return new LocalDate(targetDateTime, accountTimeZone);
-        }
-
-        public DateTime computeUTCDateTimeFromLocalDate(final LocalDate invoiceItemEndDate) {
-            //
-            // Since we create the targetDate for next invoice using the date from the notificationQ, we need to make sure
-            // that this datetime once transformed into a LocalDate points to the correct day.
-            //
-            // e.g If accountTimeZone is -8 and we want to invoice on the 16, with a toDateTimeAtCurrentTime = 00:00:23,
-            // we will generate a datetime that is 16T08:00:23 => LocalDate in that timeZone stays on the 16.
-            //
-            //
-            // We use clock.getUTCNow() to get the offset with account timezone but that may not be correct
-            // when we transition from standard time and daylight saving time. We could end up with a result
-            // that is slightly in advance and therefore results in a null invoice.
-            // We will fix that by re-inserting ourselves in the notificationQ if we detect that there is no invoice
-            // and yet the subscription is recurring and not cancelled.
-            //
-            final int utcOffest = accountTimeZone.getOffset(clock.getUTCNow());
-            final int localToUTCOffest = -1 * utcOffest;
-            return invoiceItemEndDate.toDateTime(referenceTime, DateTimeZone.UTC).plusMillis(localToUTCOffest);
-        }
-    }
 }
diff --git a/invoice/src/test/java/com/ning/billing/invoice/TestInvoiceDispatcher.java b/invoice/src/test/java/com/ning/billing/invoice/TestInvoiceDispatcher.java
index 467cf0a..d2dbca6 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/TestInvoiceDispatcher.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/TestInvoiceDispatcher.java
@@ -41,7 +41,7 @@ import com.ning.billing.catalog.api.Plan;
 import com.ning.billing.catalog.api.PlanPhase;
 import com.ning.billing.subscription.api.SubscriptionTransitionType;
 import com.ning.billing.subscription.api.user.Subscription;
-import com.ning.billing.invoice.InvoiceDispatcher.DateAndTimeZoneContext;
+import com.ning.billing.util.timezone.DateAndTimeZoneContext;
 import com.ning.billing.invoice.api.Invoice;
 import com.ning.billing.invoice.api.InvoiceApiException;
 import com.ning.billing.invoice.api.InvoiceItem;
@@ -196,7 +196,7 @@ public class TestInvoiceDispatcher extends InvoiceTestSuiteWithEmbeddedDB {
 
         ((ClockMock) clock).setTime(new DateTime(2012, 10, 26, 1, 12, 23, DateTimeZone.UTC));
 
-        final InvoiceDispatcher.DateAndTimeZoneContext dateAndTimeZoneContext = new DateAndTimeZoneContext(clock.getUTCNow(), DateTimeZone.forID("Pacific/Pitcairn"), clock);
+        final DateAndTimeZoneContext dateAndTimeZoneContext = new DateAndTimeZoneContext(clock.getUTCNow(), DateTimeZone.forID("Pacific/Pitcairn"), clock);
 
         final InvoiceItemModelDao item = new InvoiceItemModelDao(UUID.randomUUID(), clock.getUTCNow(), InvoiceItemType.RECURRING, UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID(),
                                                                  "planName", "phaseName", startDate, endDate, new BigDecimal("23.9"), new BigDecimal("23.9"), Currency.EUR, null);
diff --git a/jaxrs/killbill-jaxrs.iml b/jaxrs/killbill-jaxrs.iml
index 3891ad4..3642870 100644
--- a/jaxrs/killbill-jaxrs.iml
+++ b/jaxrs/killbill-jaxrs.iml
@@ -11,6 +11,7 @@
     </content>
     <orderEntry type="inheritedJdk" />
     <orderEntry type="sourceFolder" forTests="false" />
+    <orderEntry type="library" name="Maven: com.ning.billing:killbill-api:0.3.3-SNAPSHOT" level="project" />
     <orderEntry type="library" scope="PROVIDED" name="Maven: com.google.code.findbugs:jsr305:1.3.9" level="project" />
     <orderEntry type="library" scope="PROVIDED" name="Maven: com.google.guava:guava:14.0.1" level="project" />
     <orderEntry type="library" scope="PROVIDED" name="Maven: com.google.inject:guice:3.0" level="project" />
diff --git a/junction/killbill-junction.iml b/junction/killbill-junction.iml
index 53d4625..9272fcc 100644
--- a/junction/killbill-junction.iml
+++ b/junction/killbill-junction.iml
@@ -12,6 +12,7 @@
     </content>
     <orderEntry type="inheritedJdk" />
     <orderEntry type="sourceFolder" forTests="false" />
+    <orderEntry type="library" name="Maven: com.ning.billing:killbill-api:0.3.3-SNAPSHOT" level="project" />
     <orderEntry type="library" scope="PROVIDED" name="Maven: com.google.code.findbugs:jsr305:1.3.9" level="project" />
     <orderEntry type="library" scope="PROVIDED" name="Maven: com.google.guava:guava:14.0.1" level="project" />
     <orderEntry type="library" scope="PROVIDED" name="Maven: com.google.inject:guice:3.0" level="project" />
diff --git a/osgi/killbill-osgi.iml b/osgi/killbill-osgi.iml
index e0c8736..9a77311 100644
--- a/osgi/killbill-osgi.iml
+++ b/osgi/killbill-osgi.iml
@@ -11,6 +11,7 @@
     </content>
     <orderEntry type="inheritedJdk" />
     <orderEntry type="sourceFolder" forTests="false" />
+    <orderEntry type="library" name="Maven: com.ning.billing:killbill-api:0.3.3-SNAPSHOT" level="project" />
     <orderEntry type="library" scope="PROVIDED" name="Maven: com.google.guava:guava:14.0.1" level="project" />
     <orderEntry type="library" scope="PROVIDED" name="Maven: com.google.inject:guice:3.0" level="project" />
     <orderEntry type="library" scope="PROVIDED" name="Maven: javax.inject:javax.inject:1" level="project" />
diff --git a/osgi-bundles/bundles/jruby/killbill-osgi-bundles-jruby.iml b/osgi-bundles/bundles/jruby/killbill-osgi-bundles-jruby.iml
index 82f2b79..da99a24 100644
--- a/osgi-bundles/bundles/jruby/killbill-osgi-bundles-jruby.iml
+++ b/osgi-bundles/bundles/jruby/killbill-osgi-bundles-jruby.iml
@@ -9,6 +9,7 @@
     </content>
     <orderEntry type="inheritedJdk" />
     <orderEntry type="sourceFolder" forTests="false" />
+    <orderEntry type="library" name="Maven: com.ning.billing:killbill-api:0.3.3-SNAPSHOT" level="project" />
     <orderEntry type="library" name="Maven: com.google.guava:guava:14.0.1" level="project" />
     <orderEntry type="library" name="Maven: com.ning.billing:killbill-api:0.3.3-SNAPSHOT" level="project" />
     <orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-annotations:2.1.0" level="project" />
diff --git a/osgi-bundles/defaultbundles/killbill-osgi-bundles-defaultbundles.iml b/osgi-bundles/defaultbundles/killbill-osgi-bundles-defaultbundles.iml
index e2399bf..cfc4020 100644
--- a/osgi-bundles/defaultbundles/killbill-osgi-bundles-defaultbundles.iml
+++ b/osgi-bundles/defaultbundles/killbill-osgi-bundles-defaultbundles.iml
@@ -8,6 +8,7 @@
     </content>
     <orderEntry type="inheritedJdk" />
     <orderEntry type="sourceFolder" forTests="false" />
+    <orderEntry type="library" name="Maven: com.ning.billing:killbill-api:0.3.3-SNAPSHOT" level="project" />
     <orderEntry type="library" name="Maven: com.ning.billing:killbill-osgi-bundles-analytics:0.3.3" level="project" />
     <orderEntry type="module" module-name="killbill-osgi-bundles-jruby" />
     <orderEntry type="library" scope="PROVIDED" name="Maven: com.google.guava:guava:14.0.1" level="project" />
diff --git a/osgi-bundles/libs/killbill/killbill-osgi-bundles-lib-killbill.iml b/osgi-bundles/libs/killbill/killbill-osgi-bundles-lib-killbill.iml
index 66797cd..363bf73 100644
--- a/osgi-bundles/libs/killbill/killbill-osgi-bundles-lib-killbill.iml
+++ b/osgi-bundles/libs/killbill/killbill-osgi-bundles-lib-killbill.iml
@@ -12,6 +12,7 @@
     </content>
     <orderEntry type="inheritedJdk" />
     <orderEntry type="sourceFolder" forTests="false" />
+    <orderEntry type="library" scope="PROVIDED" name="Maven: com.ning.billing:killbill-api:0.3.3-SNAPSHOT" level="project" />
     <orderEntry type="library" name="Maven: com.google.code.findbugs:jsr305:1.3.9" level="project" />
     <orderEntry type="library" scope="PROVIDED" name="Maven: com.ning.billing:killbill-api:0.3.3-SNAPSHOT" level="project" />
     <orderEntry type="library" scope="PROVIDED" name="Maven: com.fasterxml.jackson.core:jackson-annotations:2.1.0" level="project" />
diff --git a/osgi-bundles/tests/beatrix/killbill-osgi-bundles-test-beatrix.iml b/osgi-bundles/tests/beatrix/killbill-osgi-bundles-test-beatrix.iml
index 2efd2ba..a2aa6ce 100644
--- a/osgi-bundles/tests/beatrix/killbill-osgi-bundles-test-beatrix.iml
+++ b/osgi-bundles/tests/beatrix/killbill-osgi-bundles-test-beatrix.iml
@@ -13,6 +13,7 @@
     <orderEntry type="inheritedJdk" />
     <orderEntry type="sourceFolder" forTests="false" />
     <orderEntry type="library" name="Maven: com.ning.billing:killbill-api:0.3.3-SNAPSHOT" level="project" />
+    <orderEntry type="library" name="Maven: com.ning.billing:killbill-api:0.3.3-SNAPSHOT" level="project" />
     <orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-annotations:2.1.0" level="project" />
     <orderEntry type="library" name="Maven: joda-time:joda-time:2.0" level="project" />
     <orderEntry type="module" module-name="killbill-osgi-bundles-lib-killbill" />
diff --git a/osgi-bundles/tests/payment/killbill-osgi-bundles-test-payment.iml b/osgi-bundles/tests/payment/killbill-osgi-bundles-test-payment.iml
index ed68635..a965893 100644
--- a/osgi-bundles/tests/payment/killbill-osgi-bundles-test-payment.iml
+++ b/osgi-bundles/tests/payment/killbill-osgi-bundles-test-payment.iml
@@ -12,6 +12,7 @@
     <orderEntry type="inheritedJdk" />
     <orderEntry type="sourceFolder" forTests="false" />
     <orderEntry type="library" name="Maven: com.ning.billing:killbill-api:0.3.3-SNAPSHOT" level="project" />
+    <orderEntry type="library" name="Maven: com.ning.billing:killbill-api:0.3.3-SNAPSHOT" level="project" />
     <orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-annotations:2.1.0" level="project" />
     <orderEntry type="library" name="Maven: joda-time:joda-time:2.0" level="project" />
     <orderEntry type="module" module-name="killbill-osgi-bundles-lib-killbill" />
diff --git a/overdue/killbill-overdue.iml b/overdue/killbill-overdue.iml
index a05d8c5..cfb0e8a 100644
--- a/overdue/killbill-overdue.iml
+++ b/overdue/killbill-overdue.iml
@@ -11,6 +11,7 @@
     </content>
     <orderEntry type="inheritedJdk" />
     <orderEntry type="sourceFolder" forTests="false" />
+    <orderEntry type="library" name="Maven: com.ning.billing:killbill-api:0.3.3-SNAPSHOT" level="project" />
     <orderEntry type="library" scope="PROVIDED" name="Maven: com.google.code.findbugs:jsr305:1.3.9" level="project" />
     <orderEntry type="library" scope="PROVIDED" name="Maven: com.google.guava:guava:14.0.1" level="project" />
     <orderEntry type="library" scope="PROVIDED" name="Maven: com.google.inject:guice:3.0" level="project" />
diff --git a/payment/killbill-payment.iml b/payment/killbill-payment.iml
index d0831dc..a07337d 100644
--- a/payment/killbill-payment.iml
+++ b/payment/killbill-payment.iml
@@ -12,6 +12,7 @@
     </content>
     <orderEntry type="inheritedJdk" />
     <orderEntry type="sourceFolder" forTests="false" />
+    <orderEntry type="library" name="Maven: com.ning.billing:killbill-api:0.3.3-SNAPSHOT" level="project" />
     <orderEntry type="library" scope="PROVIDED" name="Maven: com.google.code.findbugs:jsr305:1.3.9" level="project" />
     <orderEntry type="library" scope="PROVIDED" name="Maven: com.google.guava:guava:14.0.1" level="project" />
     <orderEntry type="library" scope="PROVIDED" name="Maven: com.google.inject:guice:3.0" level="project" />
diff --git a/server/killbill-server.iml b/server/killbill-server.iml
index 81dfb17..be0f59e 100644
--- a/server/killbill-server.iml
+++ b/server/killbill-server.iml
@@ -12,6 +12,7 @@
     </content>
     <orderEntry type="inheritedJdk" />
     <orderEntry type="sourceFolder" forTests="false" />
+    <orderEntry type="library" name="Maven: com.ning.billing:killbill-api:0.3.3-SNAPSHOT" level="project" />
     <orderEntry type="library" scope="RUNTIME" name="Maven: ch.qos.logback:logback-classic:1.0.1" level="project" />
     <orderEntry type="library" scope="RUNTIME" name="Maven: ch.qos.logback:logback-core:1.0.1" level="project" />
     <orderEntry type="library" name="Maven: org.slf4j:slf4j-api:1.7.5" level="project" />
diff --git a/subscription/killbill-subscription.iml b/subscription/killbill-subscription.iml
index 1dee000..b78690f 100644
--- a/subscription/killbill-subscription.iml
+++ b/subscription/killbill-subscription.iml
@@ -12,6 +12,7 @@
     </content>
     <orderEntry type="inheritedJdk" />
     <orderEntry type="sourceFolder" forTests="false" />
+    <orderEntry type="library" name="Maven: com.ning.billing:killbill-api:0.3.3-SNAPSHOT" level="project" />
     <orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-annotations:2.1.0" level="project" />
     <orderEntry type="library" scope="PROVIDED" name="Maven: com.google.code.findbugs:jsr305:1.3.9" level="project" />
     <orderEntry type="library" scope="PROVIDED" name="Maven: com.google.guava:guava:14.0.1" level="project" />
diff --git a/subscription/src/main/java/com/ning/billing/subscription/api/svcs/DefaultSubscriptionInternalApi.java b/subscription/src/main/java/com/ning/billing/subscription/api/svcs/DefaultSubscriptionInternalApi.java
index 0d3a128..5729a38 100644
--- a/subscription/src/main/java/com/ning/billing/subscription/api/svcs/DefaultSubscriptionInternalApi.java
+++ b/subscription/src/main/java/com/ning/billing/subscription/api/svcs/DefaultSubscriptionInternalApi.java
@@ -16,6 +16,7 @@
 
 package com.ning.billing.subscription.api.svcs;
 
+import java.util.LinkedList;
 import java.util.List;
 import java.util.UUID;
 
@@ -26,19 +27,34 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import com.ning.billing.ErrorCode;
+import com.ning.billing.catalog.api.Catalog;
+import com.ning.billing.catalog.api.CatalogApiException;
 import com.ning.billing.catalog.api.CatalogService;
+import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.catalog.api.PlanPhase;
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.catalog.api.PriceListSet;
+import com.ning.billing.catalog.api.ProductCategory;
 import com.ning.billing.clock.Clock;
+import com.ning.billing.clock.DefaultClock;
 import com.ning.billing.subscription.api.SubscriptionApiBase;
 import com.ning.billing.subscription.api.user.DefaultEffectiveSubscriptionEvent;
 import com.ning.billing.subscription.api.user.DefaultSubscriptionApiService;
-import com.ning.billing.subscription.api.user.SubscriptionBuilder;
-import com.ning.billing.subscription.api.user.SubscriptionData;
-import com.ning.billing.subscription.api.user.SubscriptionTransitionData;
-import com.ning.billing.subscription.engine.dao.SubscriptionDao;
+import com.ning.billing.subscription.api.user.DefaultSubscriptionStatusDryRun;
 import com.ning.billing.subscription.api.user.Subscription;
+import com.ning.billing.subscription.api.user.SubscriptionBuilder;
 import com.ning.billing.subscription.api.user.SubscriptionBundle;
+import com.ning.billing.subscription.api.user.SubscriptionBundleData;
+import com.ning.billing.subscription.api.user.SubscriptionData;
+import com.ning.billing.subscription.api.user.SubscriptionState;
+import com.ning.billing.subscription.api.user.SubscriptionStatusDryRun;
+import com.ning.billing.subscription.api.user.SubscriptionStatusDryRun.DryRunChangeReason;
 import com.ning.billing.subscription.api.user.SubscriptionTransition;
+import com.ning.billing.subscription.api.user.SubscriptionTransitionData;
 import com.ning.billing.subscription.api.user.SubscriptionUserApiException;
+import com.ning.billing.subscription.engine.addon.AddonUtils;
+import com.ning.billing.subscription.engine.dao.SubscriptionDao;
+import com.ning.billing.subscription.exceptions.SubscriptionError;
 import com.ning.billing.util.callcontext.InternalCallContext;
 import com.ning.billing.util.callcontext.InternalTenantContext;
 import com.ning.billing.util.events.EffectiveSubscriptionInternalEvent;
@@ -53,13 +69,106 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
 
     private final Logger log = LoggerFactory.getLogger(DefaultSubscriptionInternalApi.class);
 
+    private final AddonUtils addonUtils;
 
     @Inject
     public DefaultSubscriptionInternalApi(final SubscriptionDao dao,
                                           final DefaultSubscriptionApiService apiService,
                                           final Clock clock,
-                                          final CatalogService catalogService) {
+                                          final CatalogService catalogService,
+                                          final AddonUtils addonUtils) {
         super(dao, apiService, clock, catalogService);
+        this.addonUtils = addonUtils;
+    }
+
+    @Override
+    public Subscription createSubscription(final UUID bundleId, final PlanPhaseSpecifier spec, final DateTime requestedDateWithMs, final InternalCallContext context) throws SubscriptionUserApiException {
+        try {
+            final String realPriceList = (spec.getPriceListName() == null) ? PriceListSet.DEFAULT_PRICELIST_NAME : spec.getPriceListName();
+            final DateTime now = clock.getUTCNow();
+            final DateTime requestedDate = (requestedDateWithMs != null) ? DefaultClock.truncateMs(requestedDateWithMs) : now;
+            if (requestedDate.isAfter(now)) {
+                throw new SubscriptionUserApiException(ErrorCode.SUB_INVALID_REQUESTED_DATE, now.toString(), requestedDate.toString());
+            }
+            final DateTime effectiveDate = requestedDate;
+
+            final Catalog catalog = catalogService.getFullCatalog();
+            final Plan plan = catalog.findPlan(spec.getProductName(), spec.getBillingPeriod(), realPriceList, requestedDate);
+
+            final PlanPhase phase = plan.getAllPhases()[0];
+            if (phase == null) {
+                throw new SubscriptionError(String.format("No initial PlanPhase for Product %s, term %s and set %s does not exist in the catalog",
+                                                          spec.getProductName(), spec.getBillingPeriod().toString(), realPriceList));
+            }
+
+            final SubscriptionBundle bundle = dao.getSubscriptionBundleFromId(bundleId, context);
+            if (bundle == null) {
+                throw new SubscriptionUserApiException(ErrorCode.SUB_CREATE_NO_BUNDLE, bundleId);
+            }
+
+            DateTime bundleStartDate = null;
+            final SubscriptionData baseSubscription = (SubscriptionData) dao.getBaseSubscription(bundleId, context);
+            switch (plan.getProduct().getCategory()) {
+                case BASE:
+                    if (baseSubscription != null) {
+                        if (baseSubscription.getState() == SubscriptionState.ACTIVE) {
+                            throw new SubscriptionUserApiException(ErrorCode.SUB_CREATE_BP_EXISTS, bundleId);
+                        } else {
+                            // If we do create on an existing CANCELLED BP, this is equivalent to call recreate on that Subscription.
+                            final Subscription recreatedSubscriptionForApiUse = createSubscriptionForApiUse(baseSubscription);
+                            recreatedSubscriptionForApiUse.recreate(spec, requestedDate, context.toCallContext());
+                            return recreatedSubscriptionForApiUse;
+                        }
+                    }
+                    bundleStartDate = requestedDate;
+                    break;
+                case ADD_ON:
+                    if (baseSubscription == null) {
+                        throw new SubscriptionUserApiException(ErrorCode.SUB_CREATE_NO_BP, bundleId);
+                    }
+                    if (effectiveDate.isBefore(baseSubscription.getStartDate())) {
+                        throw new SubscriptionUserApiException(ErrorCode.SUB_INVALID_REQUESTED_DATE, effectiveDate.toString(), baseSubscription.getStartDate().toString());
+                    }
+                    addonUtils.checkAddonCreationRights(baseSubscription, plan);
+                    bundleStartDate = baseSubscription.getStartDate();
+                    break;
+                case STANDALONE:
+                    if (baseSubscription != null) {
+                        throw new SubscriptionUserApiException(ErrorCode.SUB_CREATE_BP_EXISTS, bundleId);
+                    }
+                    // Not really but we don't care, there is no alignment for STANDALONE subscriptions
+                    bundleStartDate = requestedDate;
+                    break;
+                default:
+                    throw new SubscriptionError(String.format("Can't create subscription of type %s",
+                                                              plan.getProduct().getCategory().toString()));
+            }
+
+            return apiService.createPlan(new SubscriptionBuilder()
+                                                 .setId(UUID.randomUUID())
+                                                 .setBundleId(bundleId)
+                                                 .setCategory(plan.getProduct().getCategory())
+                                                 .setBundleStartDate(bundleStartDate)
+                                                 .setAlignStartDate(effectiveDate),
+                                         plan, spec.getPhaseType(), realPriceList, requestedDate, effectiveDate, now, context.toCallContext());
+        } catch (CatalogApiException e) {
+            throw new SubscriptionUserApiException(e);
+        }
+    }
+
+    @Override
+    public SubscriptionBundle createBundleForAccount(final UUID accountId, final String bundleName, final InternalCallContext context) throws SubscriptionUserApiException {
+        final SubscriptionBundleData bundle = new SubscriptionBundleData(bundleName, accountId, clock.getUTCNow());
+        return dao.createSubscriptionBundle(bundle, context);
+    }
+
+    @Override
+    public SubscriptionBundle getBundleForAccountAndKey(final UUID accountId, final String bundleKey, final InternalTenantContext context) throws SubscriptionUserApiException {
+        final SubscriptionBundle result = dao.getSubscriptionBundleFromAccountAndKey(accountId, bundleKey, context);
+        if (result == null) {
+            throw new SubscriptionUserApiException(ErrorCode.SUB_GET_INVALID_BUNDLE_KEY, bundleKey);
+        }
+        return result;
     }
 
     @Override
@@ -132,6 +241,65 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
         return convertEffectiveSubscriptionInternalEventFromSubscriptionTransitions(subscription, context, transitions);
     }
 
+    @Override
+    public DateTime getNextBillingDate(final UUID accountId, final InternalTenantContext context) {
+        final List<SubscriptionBundle> bundles = getBundlesForAccount(accountId, context);
+        DateTime result = null;
+        for (final SubscriptionBundle bundle : bundles) {
+            final List<Subscription> subscriptions = getSubscriptionsForBundle(bundle.getId(), context);
+            for (final Subscription subscription : subscriptions) {
+                final DateTime chargedThruDate = subscription.getChargedThroughDate();
+                if (result == null ||
+                    (chargedThruDate != null && chargedThruDate.isBefore(result))) {
+                    result = subscription.getChargedThroughDate();
+                }
+            }
+        }
+        return result;
+    }
+
+    @Override
+    public List<SubscriptionStatusDryRun> getDryRunChangePlanStatus(final UUID subscriptionId, @Nullable final String baseProductName, final DateTime requestedDate, final InternalTenantContext context) throws SubscriptionUserApiException {
+        final Subscription subscription = dao.getSubscriptionFromId(subscriptionId, context);
+        if (subscription == null) {
+            throw new SubscriptionUserApiException(ErrorCode.SUB_INVALID_SUBSCRIPTION_ID, subscriptionId);
+        }
+        if (subscription.getCategory() != ProductCategory.BASE) {
+            throw new SubscriptionUserApiException(ErrorCode.SUB_CHANGE_DRY_RUN_NOT_BP);
+        }
+
+        final List<SubscriptionStatusDryRun> result = new LinkedList<SubscriptionStatusDryRun>();
+
+        final List<Subscription> bundleSubscriptions = dao.getSubscriptions(subscription.getBundleId(), context);
+        for (final Subscription cur : bundleSubscriptions) {
+            if (cur.getId().equals(subscriptionId)) {
+                continue;
+            }
+
+            // If ADDON is cancelled, skip
+            if (cur.getState() == SubscriptionState.CANCELLED) {
+                continue;
+            }
+
+            final DryRunChangeReason reason;
+            // If baseProductName is null, it's a cancellation dry-run. In this case, return all addons, so they are cancelled
+            if (baseProductName != null && addonUtils.isAddonIncludedFromProdName(baseProductName, requestedDate, cur.getCurrentPlan())) {
+                reason = DryRunChangeReason.AO_INCLUDED_IN_NEW_PLAN;
+            } else if (baseProductName != null && addonUtils.isAddonAvailableFromProdName(baseProductName, requestedDate, cur.getCurrentPlan())) {
+                reason = DryRunChangeReason.AO_AVAILABLE_IN_NEW_PLAN;
+            } else {
+                reason = DryRunChangeReason.AO_NOT_AVAILABLE_IN_NEW_PLAN;
+            }
+            final SubscriptionStatusDryRun status = new DefaultSubscriptionStatusDryRun(cur.getId(),
+                                                                                        cur.getCurrentPlan().getProduct().getName(),
+                                                                                        cur.getCurrentPhase().getPhaseType(),
+                                                                                        cur.getCurrentPlan().getBillingPeriod(),
+                                                                                        cur.getCurrentPriceList().getName(), reason);
+            result.add(status);
+        }
+        return result;
+    }
+
     private List<EffectiveSubscriptionInternalEvent> convertEffectiveSubscriptionInternalEventFromSubscriptionTransitions(final Subscription subscription,
                                                                                                                           final InternalTenantContext context, final List<SubscriptionTransition> transitions) {
         return ImmutableList.<EffectiveSubscriptionInternalEvent>copyOf(Collections2.transform(transitions, new Function<SubscriptionTransition, EffectiveSubscriptionInternalEvent>() {
diff --git a/tenant/killbill-tenant.iml b/tenant/killbill-tenant.iml
index 53cd6d8..3ae480f 100644
--- a/tenant/killbill-tenant.iml
+++ b/tenant/killbill-tenant.iml
@@ -11,6 +11,7 @@
     </content>
     <orderEntry type="inheritedJdk" />
     <orderEntry type="sourceFolder" forTests="false" />
+    <orderEntry type="library" name="Maven: com.ning.billing:killbill-api:0.3.3-SNAPSHOT" level="project" />
     <orderEntry type="library" scope="PROVIDED" name="Maven: com.google.code.findbugs:jsr305:1.3.9" level="project" />
     <orderEntry type="library" scope="PROVIDED" name="Maven: com.google.guava:guava:14.0.1" level="project" />
     <orderEntry type="library" scope="PROVIDED" name="Maven: com.google.inject:guice:3.0" level="project" />
diff --git a/usage/killbill-usage.iml b/usage/killbill-usage.iml
index 3d33b47..22324bc 100644
--- a/usage/killbill-usage.iml
+++ b/usage/killbill-usage.iml
@@ -11,6 +11,7 @@
     </content>
     <orderEntry type="inheritedJdk" />
     <orderEntry type="sourceFolder" forTests="false" />
+    <orderEntry type="library" name="Maven: com.ning.billing:killbill-api:0.3.3-SNAPSHOT" level="project" />
     <orderEntry type="library" scope="PROVIDED" name="Maven: com.google.guava:guava:14.0.1" level="project" />
     <orderEntry type="library" scope="PROVIDED" name="Maven: com.google.inject:guice:3.0" level="project" />
     <orderEntry type="library" scope="PROVIDED" name="Maven: javax.inject:javax.inject:1" level="project" />
diff --git a/util/killbill-util.iml b/util/killbill-util.iml
index bd5ff00..5706f01 100644
--- a/util/killbill-util.iml
+++ b/util/killbill-util.iml
@@ -12,6 +12,7 @@
     </content>
     <orderEntry type="inheritedJdk" />
     <orderEntry type="sourceFolder" forTests="false" />
+    <orderEntry type="library" name="Maven: com.ning.billing:killbill-api:0.3.3-SNAPSHOT" level="project" />
     <orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-annotations:2.1.0" level="project" />
     <orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-databind:2.1.0" level="project" />
     <orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-core:2.1.0" level="project" />
diff --git a/util/src/main/java/com/ning/billing/util/svcapi/subscription/SubscriptionInternalApi.java b/util/src/main/java/com/ning/billing/util/svcapi/subscription/SubscriptionInternalApi.java
index 5a51de9..ef4d4c1 100644
--- a/util/src/main/java/com/ning/billing/util/svcapi/subscription/SubscriptionInternalApi.java
+++ b/util/src/main/java/com/ning/billing/util/svcapi/subscription/SubscriptionInternalApi.java
@@ -19,18 +19,34 @@ package com.ning.billing.util.svcapi.subscription;
 import java.util.List;
 import java.util.UUID;
 
+import javax.annotation.Nullable;
+
 import org.joda.time.DateTime;
 
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
 import com.ning.billing.subscription.api.user.Subscription;
 import com.ning.billing.subscription.api.user.SubscriptionBundle;
+import com.ning.billing.subscription.api.user.SubscriptionStatusDryRun;
 import com.ning.billing.subscription.api.user.SubscriptionUserApiException;
+import com.ning.billing.util.callcontext.CallContext;
 import com.ning.billing.util.callcontext.InternalCallContext;
 import com.ning.billing.util.callcontext.InternalTenantContext;
+import com.ning.billing.util.callcontext.TenantContext;
 import com.ning.billing.util.events.EffectiveSubscriptionInternalEvent;
 
 
 public interface SubscriptionInternalApi {
 
+    public Subscription createSubscription(final UUID bundleId, final PlanPhaseSpecifier spec, final DateTime requestedDateWithMs,
+                                           final InternalCallContext context) throws SubscriptionUserApiException;
+
+
+    public SubscriptionBundle createBundleForAccount(final UUID accountId, final String bundleName, final InternalCallContext context)
+            throws SubscriptionUserApiException;
+
+    public SubscriptionBundle getBundleForAccountAndKey(final UUID accountId, final String bundleKey, final InternalTenantContext context)
+            throws SubscriptionUserApiException;
+
     public List<SubscriptionBundle> getBundlesForAccount(final UUID accountId, final InternalTenantContext context);
 
     public List<Subscription> getSubscriptionsForBundle(final UUID bundleId, final InternalTenantContext context);
@@ -48,4 +64,9 @@ public interface SubscriptionInternalApi {
     public List<EffectiveSubscriptionInternalEvent> getAllTransitions(final Subscription subscription, final InternalTenantContext context);
 
     public List<EffectiveSubscriptionInternalEvent> getBillingTransitions(final Subscription subscription, final InternalTenantContext context);
+
+    public DateTime getNextBillingDate(final UUID accountId, final InternalTenantContext context);
+
+    public List<SubscriptionStatusDryRun> getDryRunChangePlanStatus(final UUID subscriptionId, @Nullable final String baseProductName,
+                                                                    final DateTime requestedDate, final InternalTenantContext context) throws SubscriptionUserApiException;
 }
diff --git a/util/src/main/java/com/ning/billing/util/timezone/DateAndTimeZoneContext.java b/util/src/main/java/com/ning/billing/util/timezone/DateAndTimeZoneContext.java
new file mode 100644
index 0000000..1aba246
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/timezone/DateAndTimeZoneContext.java
@@ -0,0 +1,50 @@
+package com.ning.billing.util.timezone;
+
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.joda.time.LocalDate;
+import org.joda.time.LocalTime;
+
+import com.ning.billing.clock.Clock;
+
+/**
+ * Used by entitlement and invoice to calculate:
+ * - a LocalDate from DateTime and the timeZone set on the account
+ * - A DateTime from a LocalDate and the referenceTime attached to the account.
+ */
+public final class DateAndTimeZoneContext {
+
+    private final LocalTime referenceTime;
+    private final DateTimeZone accountTimeZone;
+    private final Clock clock;
+
+    public DateAndTimeZoneContext(final DateTime effectiveDateTime, final DateTimeZone accountTimeZone, final Clock clock) {
+        this.clock = clock;
+        this.referenceTime = effectiveDateTime != null ? effectiveDateTime.toLocalTime() : null;
+        this.accountTimeZone = accountTimeZone;
+    }
+
+    public LocalDate computeTargetDate(final DateTime targetDateTime) {
+        return new LocalDate(targetDateTime, accountTimeZone);
+    }
+
+    public DateTime computeUTCDateTimeFromLocalDate(final LocalDate invoiceItemEndDate) {
+        //
+        // Since we create the targetDate for next invoice using the date from the notificationQ, we need to make sure
+        // that this datetime once transformed into a LocalDate points to the correct day.
+        //
+        // e.g If accountTimeZone is -8 and we want to invoice on the 16, with a toDateTimeAtCurrentTime = 00:00:23,
+        // we will generate a datetime that is 16T08:00:23 => LocalDate in that timeZone stays on the 16.
+        //
+        //
+        // We use clock.getUTCNow() to get the offset with account timezone but that may not be correct
+        // when we transition from standard time and daylight saving time. We could end up with a result
+        // that is slightly in advance and therefore results in a null invoice.
+        // We will fix that by re-inserting ourselves in the notificationQ if we detect that there is no invoice
+        // and yet the subscription is recurring and not cancelled.
+        //
+        final int utcOffest = accountTimeZone.getOffset(clock.getUTCNow());
+        final int localToUTCOffest = -1 * utcOffest;
+        return invoiceItemEndDate.toDateTime(referenceTime, DateTimeZone.UTC).plusMillis(localToUTCOffest);
+    }
+}
diff --git a/util/src/test/java/com/ning/billing/mock/glue/MockEntitlementModule.java b/util/src/test/java/com/ning/billing/mock/glue/MockEntitlementModule.java
index 111a0ea..fdd1c1e 100644
--- a/util/src/test/java/com/ning/billing/mock/glue/MockEntitlementModule.java
+++ b/util/src/test/java/com/ning/billing/mock/glue/MockEntitlementModule.java
@@ -26,31 +26,17 @@ import org.mockito.Mockito;
 
 public class MockEntitlementModule extends AbstractModule implements EntitlementModule {
 
-    private final AccountUserApi userApi = Mockito.mock(AccountUserApi.class);
-    private final SubscriptionUserApi entUserApi = Mockito.mock(SubscriptionUserApi.class);
     private final BlockingInternalApi blockingApi = Mockito.mock(BlockingInternalApi.class);
     private final EntitlementApi entitlementApi = Mockito.mock(EntitlementApi.class);
 
     @Override
     protected void configure() {
-        installAccountUserApi();
-        installSubscriptionUserApi();
         installBlockingStateDao();
         installBlockingApi();
         installEntitlementApi();
     }
 
     @Override
-    public void installAccountUserApi() {
-        bind(AccountUserApi.class).toInstance(userApi);
-    }
-
-    @Override
-    public void installSubscriptionUserApi() {
-        bind(SubscriptionUserApi.class).toInstance(entUserApi);
-    }
-
-    @Override
     public void installBlockingStateDao() {
     }