killbill-aplcache
Changes
beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithEntilementPlugin.java 15(+8 -7)
entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlementApi.java 89(+81 -8)
entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlementContext.java 28(+8 -20)
entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlementSpecifier.java 45(+45 -0)
entitlement/src/main/java/org/killbill/billing/entitlement/api/svcs/DefaultEntitlementApiBase.java 2(+0 -2)
entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultEntitlementApi.java 101(+101 -0)
pom.xml 2(+1 -1)
subscription/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseApiService.java 4(+4 -0)
subscription/src/main/java/org/killbill/billing/subscription/api/svcs/DefaultSubscriptionInternalApi.java 55(+55 -0)
subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBaseApiService.java 50(+50 -0)
subscription/src/main/java/org/killbill/billing/subscription/api/user/SubscriptionSpecifier.java 106(+106 -0)
subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java 29(+29 -0)
Details
diff --git a/api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseInternalApi.java b/api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseInternalApi.java
index b42b21f..5679190 100644
--- a/api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseInternalApi.java
+++ b/api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseInternalApi.java
@@ -28,6 +28,7 @@ import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
import org.killbill.billing.entitlement.api.EntitlementAOStatusDryRun;
+import org.killbill.billing.entitlement.api.EntitlementSpecifier;
import org.killbill.billing.events.EffectiveSubscriptionInternalEvent;
import org.killbill.billing.invoice.api.DryRunArguments;
import org.killbill.billing.subscription.api.user.SubscriptionBaseApiException;
@@ -40,6 +41,9 @@ public interface SubscriptionBaseInternalApi {
public SubscriptionBase createSubscription(UUID bundleId, PlanPhaseSpecifier spec, List<PlanPhasePriceOverride> overrides, DateTime requestedDateWithMs,
InternalCallContext context) throws SubscriptionBaseApiException;
+ public SubscriptionBase createBaseSubscriptionWithAddOns(UUID bundleId, Iterable<EntitlementSpecifier> entitlements, DateTime requestedDateWithMs,
+ InternalCallContext context) throws SubscriptionBaseApiException;
+
public SubscriptionBaseBundle createBundleForAccount(UUID accountId, String bundleName, InternalCallContext context)
throws SubscriptionBaseApiException;
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestSubscription.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestSubscription.java
index 0d21887..c1cfa81 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestSubscription.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestSubscription.java
@@ -23,25 +23,30 @@ import java.util.List;
import org.joda.time.DateTimeZone;
import org.joda.time.LocalDate;
-import org.killbill.billing.entitlement.api.SubscriptionEventType;
-import org.killbill.billing.invoice.api.DryRunType;
-import org.testng.annotations.Test;
-
import org.killbill.billing.account.api.Account;
import org.killbill.billing.api.TestApiListener.NextEvent;
import org.killbill.billing.beatrix.util.InvoiceChecker.ExpectedInvoiceItemCheck;
import org.killbill.billing.catalog.api.BillingActionPolicy;
import org.killbill.billing.catalog.api.BillingPeriod;
+import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
import org.killbill.billing.catalog.api.PriceListSet;
import org.killbill.billing.catalog.api.ProductCategory;
import org.killbill.billing.entitlement.api.DefaultEntitlement;
+import org.killbill.billing.entitlement.api.DefaultEntitlementSpecifier;
+import org.killbill.billing.entitlement.api.Entitlement;
+import org.killbill.billing.entitlement.api.EntitlementSpecifier;
+import org.killbill.billing.entitlement.api.SubscriptionEventType;
+import org.killbill.billing.invoice.api.DryRunType;
import org.killbill.billing.invoice.api.Invoice;
import org.killbill.billing.invoice.api.InvoiceItemType;
+import org.killbill.billing.payment.api.PluginProperty;
+import org.testng.annotations.Test;
import com.google.common.collect.ImmutableList;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
public class TestSubscription extends TestIntegrationBase {
@@ -175,4 +180,61 @@ public class TestSubscription extends TestIntegrationBase {
checkNoMoreInvoiceToGenerate(account);
}
+
+ @Test(groups = "slow")
+ public void testCreateSubscriptionWithAddOns() throws Exception {
+ final LocalDate initialDate = new LocalDate(2015, 10, 1);
+ clock.setDay(initialDate);
+
+ final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(1));
+
+ final PlanPhaseSpecifier baseSpec = new PlanPhaseSpecifier("Shotgun", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
+ final PlanPhaseSpecifier addOnSpec1 = new PlanPhaseSpecifier("Telescopic-Scope", ProductCategory.ADD_ON, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
+ final PlanPhaseSpecifier addOnSpec2 = new PlanPhaseSpecifier("Laser-Scope", ProductCategory.ADD_ON, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
+
+ final String externalKey = "baseExternalKey";
+ EntitlementSpecifier baseEntitlementSpecifier = new DefaultEntitlementSpecifier(baseSpec, null);
+ EntitlementSpecifier addOnEntitlementSpecifier1 = new DefaultEntitlementSpecifier(addOnSpec1, null);
+ EntitlementSpecifier addOnEntitlementSpecifier2 = new DefaultEntitlementSpecifier(addOnSpec2, null);
+
+ final List<EntitlementSpecifier> specifierList = new ArrayList<EntitlementSpecifier>();
+ specifierList.add(baseEntitlementSpecifier);
+ specifierList.add(addOnEntitlementSpecifier1);
+ specifierList.add(addOnEntitlementSpecifier2);
+
+ busHandler.pushExpectedEvents(NextEvent.CREATE, NextEvent.CREATE, NextEvent.CREATE, NextEvent.INVOICE, NextEvent.PAYMENT);
+ final Entitlement entitlement = entitlementApi.createBaseEntitlementWithAddOns(account.getId(), externalKey, specifierList, initialDate, ImmutableList.<PluginProperty>of(), callContext);
+ assertListenerStatus();
+ checkNoMoreInvoiceToGenerate(account);
+
+ assertNotNull(entitlement);
+
+ final List<Entitlement> allEntitlementsForBundle = entitlementApi.getAllEntitlementsForBundle(entitlement.getBundleId(), callContext);
+ assertTrue(allEntitlementsForBundle.size() == 3);
+
+ final Entitlement baseEntitlement = allEntitlementsForBundle.get(0);
+ final Entitlement addOnEntitlement1 = allEntitlementsForBundle.get(1);
+ final Entitlement addOnEntitlement2 = allEntitlementsForBundle.get(2);
+
+ assertEquals(baseEntitlement.getLastActiveProduct().getName(), "Shotgun");
+ assertEquals(baseEntitlement.getLastActiveProductCategory(), ProductCategory.BASE);
+
+ assertEquals(addOnEntitlement1.getLastActiveProduct().getName(), "Telescopic-Scope");
+ assertEquals(addOnEntitlement1.getLastActiveProductCategory(), ProductCategory.ADD_ON);
+
+ assertEquals(addOnEntitlement2.getLastActiveProduct().getName(), "Laser-Scope");
+ assertEquals(addOnEntitlement2.getLastActiveProductCategory(), ProductCategory.ADD_ON);
+
+ final List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), callContext);
+ assertTrue(invoices.size() == 1); // ONLY ONE INVOICE
+ assertTrue(invoices.get(0).getInvoiceItems().size() == 3);
+
+ final ImmutableList<ExpectedInvoiceItemCheck> toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
+ new ExpectedInvoiceItemCheck(initialDate, new LocalDate(2015, 10, 31), InvoiceItemType.RECURRING, new BigDecimal("387.05")), // amount=387.05, rate=399.95 -> Telescopic-Scope
+ new ExpectedInvoiceItemCheck(initialDate, new LocalDate(2015, 10, 31), InvoiceItemType.RECURRING, new BigDecimal("967.69")), // amount=967.69, rate=999.95 -> Laser-Scope
+ new ExpectedInvoiceItemCheck(initialDate, null, InvoiceItemType.FIXED, new BigDecimal("0"))); // Shotgun
+
+ invoiceChecker.checkInvoice(invoices.get(0).getId(), callContext, toBeChecked);
+
+ }
}
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithEntilementPlugin.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithEntilementPlugin.java
index 1b7ccbc..2003ef2 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithEntilementPlugin.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithEntilementPlugin.java
@@ -34,6 +34,8 @@ import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
import org.killbill.billing.catalog.api.ProductCategory;
import org.killbill.billing.entitlement.api.DefaultEntitlement;
+import org.killbill.billing.entitlement.api.DefaultEntitlementSpecifier;
+import org.killbill.billing.entitlement.api.EntitlementSpecifier;
import org.killbill.billing.entitlement.plugin.api.EntitlementContext;
import org.killbill.billing.entitlement.plugin.api.EntitlementPluginApi;
import org.killbill.billing.entitlement.plugin.api.EntitlementPluginApiException;
@@ -138,6 +140,10 @@ public class TestWithEntilementPlugin extends TestIntegrationBase {
@Override
public PriorEntitlementResult priorCall(final EntitlementContext entitlementContext, final Iterable<PluginProperty> properties) throws EntitlementPluginApiException {
if (planPhasePriceOverride != null) {
+ final EntitlementSpecifier entitlementSpecifier = new DefaultEntitlementSpecifier(null, planPhasePriceOverride);
+ final List<EntitlementSpecifier> entitlementSpecifiers = new ArrayList<EntitlementSpecifier>();
+ entitlementSpecifiers.add(entitlementSpecifier);
+
return new PriorEntitlementResult() {
@Override
public boolean isAborted() {
@@ -145,18 +151,13 @@ public class TestWithEntilementPlugin extends TestIntegrationBase {
}
@Override
- public PlanPhaseSpecifier getAdjustedPlanPhaseSpecifier() {
- return null;
- }
-
- @Override
public LocalDate getAdjustedEffectiveDate() {
return null;
}
@Override
- public List<PlanPhasePriceOverride> getAdjustedPlanPhasePriceOverride() {
- return planPhasePriceOverride;
+ public List<EntitlementSpecifier> getAdjustedEntitlementSpecifiers() {
+ return entitlementSpecifiers;
}
@Override
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlement.java b/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlement.java
index 59d7909..9ff10b3 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlement.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlement.java
@@ -21,7 +21,6 @@ import java.util.Collection;
import java.util.List;
import java.util.UUID;
-import org.apache.shiro.SecurityUtils;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.LocalDate;
@@ -372,7 +371,6 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
getAccountId(),
null,
getBundleId(),
- null,
getExternalKey(),
null,
localCancelDate,
@@ -439,7 +437,6 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
getAccountId(),
null,
getBundleId(),
- null,
getExternalKey(),
null,
null,
@@ -488,7 +485,6 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
getAccountId(),
null,
getBundleId(),
- null,
getExternalKey(),
null,
localDate,
@@ -537,7 +533,6 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
getAccountId(),
null,
getBundleId(),
- null,
getExternalKey(),
null,
localDate,
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlementApi.java b/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlementApi.java
index 8d5f729..e8983c2 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlementApi.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlementApi.java
@@ -16,6 +16,7 @@
package org.killbill.billing.entitlement.api;
+import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
@@ -31,6 +32,7 @@ import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.catalog.api.BillingActionPolicy;
import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
+import org.killbill.billing.catalog.api.ProductCategory;
import org.killbill.billing.entitlement.AccountEventsStreams;
import org.killbill.billing.entitlement.EntitlementService;
import org.killbill.billing.entitlement.EventsStream;
@@ -117,13 +119,15 @@ public class DefaultEntitlementApi extends DefaultEntitlementApiBase implements
@Override
public Entitlement createBaseEntitlement(final UUID accountId, final PlanPhaseSpecifier planPhaseSpecifier, final String externalKey, final List<PlanPhasePriceOverride> overrides, final LocalDate effectiveDate, final Iterable<PluginProperty> properties, final CallContext callContext) throws EntitlementApiException {
+ EntitlementSpecifier entitlementSpecifier = new DefaultEntitlementSpecifier(planPhaseSpecifier, overrides);
+ final List<EntitlementSpecifier> entitlementSpecifierList = new ArrayList<EntitlementSpecifier>();
+ entitlementSpecifierList.add(entitlementSpecifier);
final EntitlementContext pluginContext = new DefaultEntitlementContext(OperationType.CREATE_SUBSCRIPTION,
accountId,
null,
null,
- planPhaseSpecifier,
externalKey,
- overrides,
+ entitlementSpecifierList,
effectiveDate,
properties,
callContext);
@@ -142,7 +146,8 @@ public class DefaultEntitlementApi extends DefaultEntitlementApiBase implements
final DateTime referenceTime = clock.getUTCNow();
final DateTime requestedDate = dateHelper.fromLocalDateAndReferenceTime(updatedPluginContext.getEffectiveDate(), referenceTime, contextWithValidAccountRecordId);
- final SubscriptionBase subscription = subscriptionBaseInternalApi.createSubscription(bundle.getId(), updatedPluginContext.getPlanPhaseSpecifier(), updatedPluginContext.getPlanPhasePriceOverride(), requestedDate, contextWithValidAccountRecordId);
+ final EntitlementSpecifier specifier = getFirstEntitlementSpecifier(updatedPluginContext.getEntitlementSpecifiers());
+ final SubscriptionBase subscription = subscriptionBaseInternalApi.createSubscription(bundle.getId(), specifier.getPlanPhaseSpecifier(), specifier.getOverrides(), requestedDate, contextWithValidAccountRecordId);
return new DefaultEntitlement(subscription.getId(), eventsStreamBuilder, entitlementApi, pluginExecution,
blockingStateDao, subscriptionBaseInternalApi, checker, notificationQueueService,
@@ -155,16 +160,84 @@ public class DefaultEntitlementApi extends DefaultEntitlementApiBase implements
return pluginExecution.executeWithPlugin(createBaseEntitlementWithPlugin, pluginContext);
}
+ private EntitlementSpecifier getFirstEntitlementSpecifier(final List<EntitlementSpecifier> entitlementSpecifiers) throws SubscriptionBaseApiException {
+ if ((entitlementSpecifiers == null) || entitlementSpecifiers.isEmpty()) {
+ throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_INVALID_ENTITLEMENT_SPECIFIER);
+ }
+ return entitlementSpecifiers.get(0);
+ }
+
+ @Override
+ public Entitlement createBaseEntitlementWithAddOns(final UUID accountId, final String externalKey, final Iterable<EntitlementSpecifier> entitlementSpecifiers,
+ final LocalDate effectiveDate, final Iterable<PluginProperty> properties, final CallContext callContext)
+ throws EntitlementApiException {
+
+ final EntitlementSpecifier baseSpecifier = Iterables.tryFind(entitlementSpecifiers, new Predicate<EntitlementSpecifier>() {
+ @Override
+ public boolean apply(final EntitlementSpecifier specifier) {
+ return specifier.getPlanPhaseSpecifier() != null && ProductCategory.BASE.equals(specifier.getPlanPhaseSpecifier().getProductCategory());
+ }
+ }).orNull();
+
+ if (baseSpecifier == null) {
+ throw new EntitlementApiException(new IllegalArgumentException(), ErrorCode.SUB_CREATE_NO_BP.getCode(), "Missing Base Subscription.");
+ }
+
+ final List<EntitlementSpecifier> entitlementSpecifierList = new ArrayList<EntitlementSpecifier>();
+ Iterables.addAll(entitlementSpecifierList, entitlementSpecifiers);
+
+ final EntitlementContext pluginContext = new DefaultEntitlementContext(OperationType.CREATE_SUBSCRIPTIONS_WITH_AO,
+ accountId,
+ null,
+ null,
+ externalKey,
+ entitlementSpecifierList,
+ effectiveDate,
+ properties,
+ callContext);
+
+ final WithEntitlementPlugin<Entitlement> createBaseEntitlementWithAddOn = new WithEntitlementPlugin<Entitlement>() {
+ @Override
+ public Entitlement doCall(final EntitlementApi entitlementApi, final EntitlementContext updatedPluginContext) throws EntitlementApiException {
+ final InternalCallContext contextWithValidAccountRecordId = internalCallContextFactory.createInternalCallContext(accountId, callContext);
+
+ try {
+ if (entitlementUtils.getFirstActiveSubscriptionIdForKeyOrNull(externalKey, contextWithValidAccountRecordId) != null) {
+ throw new EntitlementApiException(new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_ACTIVE_BUNDLE_KEY_EXISTS, externalKey));
+ }
+
+ final SubscriptionBaseBundle bundle = subscriptionBaseInternalApi.createBundleForAccount(accountId, externalKey, contextWithValidAccountRecordId);
+
+ final DateTime referenceTime = clock.getUTCNow();
+ final DateTime requestedDate = dateHelper.fromLocalDateAndReferenceTime(updatedPluginContext.getEffectiveDate(), referenceTime, contextWithValidAccountRecordId);
+ final SubscriptionBase subscription = subscriptionBaseInternalApi.createBaseSubscriptionWithAddOns(bundle.getId(), entitlementSpecifiers, requestedDate, contextWithValidAccountRecordId);
+
+ return new DefaultEntitlement(subscription.getId(), eventsStreamBuilder, entitlementApi, pluginExecution,
+ blockingStateDao, subscriptionBaseInternalApi, checker, notificationQueueService,
+ entitlementUtils, dateHelper, clock, securityApi, internalCallContextFactory, callContext);
+
+
+ } catch (SubscriptionBaseApiException e) {
+ throw new EntitlementApiException(e);
+ }
+
+ }
+ };
+ return pluginExecution.executeWithPlugin(createBaseEntitlementWithAddOn, pluginContext);
+ }
+
@Override
public Entitlement addEntitlement(final UUID bundleId, final PlanPhaseSpecifier planPhaseSpecifier, final List<PlanPhasePriceOverride> overrides, final LocalDate effectiveDate, final Iterable<PluginProperty> properties, final CallContext callContext) throws EntitlementApiException {
+ EntitlementSpecifier entitlementSpecifier = new DefaultEntitlementSpecifier(planPhaseSpecifier, overrides);
+ final List<EntitlementSpecifier> entitlementSpecifierList = new ArrayList<EntitlementSpecifier>();
+ entitlementSpecifierList.add(entitlementSpecifier);
final EntitlementContext pluginContext = new DefaultEntitlementContext(OperationType.CREATE_SUBSCRIPTION,
null,
null,
bundleId,
- planPhaseSpecifier,
null,
- overrides,
+ entitlementSpecifierList,
effectiveDate,
properties,
callContext);
@@ -188,7 +261,8 @@ public class DefaultEntitlementApi extends DefaultEntitlementApiBase implements
try {
final InternalCallContext context = internalCallContextFactory.createInternalCallContext(callContext);
- final SubscriptionBase subscription = subscriptionBaseInternalApi.createSubscription(bundleId, updatedPluginContext.getPlanPhaseSpecifier(), updatedPluginContext.getPlanPhasePriceOverride(), requestedDate, context);
+ final EntitlementSpecifier specifier = getFirstEntitlementSpecifier(updatedPluginContext.getEntitlementSpecifiers());
+ final SubscriptionBase subscription = subscriptionBaseInternalApi.createSubscription(bundleId, specifier.getPlanPhaseSpecifier(), specifier.getOverrides(), requestedDate, context);
return new DefaultEntitlement(subscription.getId(), eventsStreamBuilder, entitlementApi, pluginExecution,
blockingStateDao, subscriptionBaseInternalApi, checker, notificationQueueService,
@@ -313,9 +387,8 @@ public class DefaultEntitlementApi extends DefaultEntitlementApiBase implements
sourceAccountId,
destAccountId,
null,
- null,
externalKey,
- null,
+ new ArrayList<EntitlementSpecifier>(),
effectiveDate,
properties,
context);
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlementContext.java b/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlementContext.java
index 47ada7b..79da30c 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlementContext.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlementContext.java
@@ -24,8 +24,6 @@ import javax.annotation.Nullable;
import org.joda.time.DateTime;
import org.joda.time.LocalDate;
-import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
-import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
import org.killbill.billing.entitlement.plugin.api.EntitlementContext;
import org.killbill.billing.entitlement.plugin.api.OperationType;
import org.killbill.billing.entitlement.plugin.api.PriorEntitlementResult;
@@ -41,9 +39,8 @@ public class DefaultEntitlementContext implements EntitlementContext {
private final UUID accountId;
private final UUID destinationAccountId;
private final UUID bundleId;
- private final PlanPhaseSpecifier spec;
private final String externalKey;
- private final List<PlanPhasePriceOverride> planPhasePriceOverrides;
+ private final List<EntitlementSpecifier> entitlementSpecifiers;
private final LocalDate effectiveDate;
private final Iterable<PluginProperty> pluginProperties;
private final UUID userToken;
@@ -63,9 +60,8 @@ public class DefaultEntitlementContext implements EntitlementContext {
prev.getAccountId(),
prev.getDestinationAccountId(),
prev.getBundleId(),
- pluginResult != null && pluginResult.getAdjustedPlanPhaseSpecifier() != null ? pluginResult.getAdjustedPlanPhaseSpecifier() : prev.getPlanPhaseSpecifier(),
prev.getExternalKey(),
- pluginResult != null && pluginResult.getAdjustedPlanPhasePriceOverride() != null ? pluginResult.getAdjustedPlanPhasePriceOverride() : prev.getPlanPhasePriceOverride(),
+ pluginResult != null && pluginResult.getAdjustedEntitlementSpecifiers() != null ? pluginResult.getAdjustedEntitlementSpecifiers() : prev.getEntitlementSpecifiers(),
pluginResult != null && pluginResult.getAdjustedEffectiveDate() != null ? pluginResult.getAdjustedEffectiveDate() : prev.getEffectiveDate(),
pluginResult != null && pluginResult.getAdjustedPluginProperties() != null ? pluginResult.getAdjustedPluginProperties() : prev.getPluginProperties(),
prev);
@@ -75,13 +71,12 @@ public class DefaultEntitlementContext implements EntitlementContext {
final UUID accountId,
final UUID destinationAccountId,
final UUID bundleId,
- final PlanPhaseSpecifier spec,
final String externalKey,
- final List<PlanPhasePriceOverride> planPhasePriceOverrides,
+ final List<EntitlementSpecifier> entitlementSpecifiers,
final LocalDate effectiveDate,
final Iterable<PluginProperty> pluginProperties,
final CallContext callContext) {
- this(operationType, accountId, destinationAccountId, bundleId, spec, externalKey, planPhasePriceOverrides, effectiveDate, pluginProperties,
+ this(operationType, accountId, destinationAccountId, bundleId, externalKey, entitlementSpecifiers, effectiveDate, pluginProperties,
callContext.getUserToken(), callContext.getUserName(), callContext.getCallOrigin(), callContext.getUserType(), callContext.getReasonCode(),
callContext.getComments(), callContext.getCreatedDate(), callContext.getUpdatedDate(), callContext.getTenantId());
}
@@ -91,9 +86,8 @@ public class DefaultEntitlementContext implements EntitlementContext {
final UUID accountId,
final UUID destinationAccountId,
final UUID bundleId,
- final PlanPhaseSpecifier spec,
final String externalKey,
- final List<PlanPhasePriceOverride> planPhasePriceOverrides,
+ final List<EntitlementSpecifier> entitlementSpecifiers,
final LocalDate effectiveDate,
final Iterable<PluginProperty> pluginProperties,
final UUID userToken,
@@ -109,9 +103,8 @@ public class DefaultEntitlementContext implements EntitlementContext {
this.accountId = accountId;
this.destinationAccountId = destinationAccountId;
this.bundleId = bundleId;
- this.spec = spec;
this.externalKey = externalKey;
- this.planPhasePriceOverrides = planPhasePriceOverrides;
+ this.entitlementSpecifiers = entitlementSpecifiers;
this.effectiveDate = effectiveDate;
this.pluginProperties = pluginProperties;
this.userToken = userToken;
@@ -146,18 +139,13 @@ public class DefaultEntitlementContext implements EntitlementContext {
}
@Override
- public PlanPhaseSpecifier getPlanPhaseSpecifier() {
- return spec;
- }
-
- @Override
public String getExternalKey() {
return externalKey;
}
@Override
- public List<PlanPhasePriceOverride> getPlanPhasePriceOverride() {
- return planPhasePriceOverrides;
+ public List<EntitlementSpecifier> getEntitlementSpecifiers() {
+ return entitlementSpecifiers;
}
@Override
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlementSpecifier.java b/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlementSpecifier.java
new file mode 100644
index 0000000..dbf7876
--- /dev/null
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlementSpecifier.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 The Billing Project, LLC
+ *
+ * The Billing Project 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 org.killbill.billing.entitlement.api;
+
+import java.util.List;
+
+import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
+import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
+
+public class DefaultEntitlementSpecifier implements EntitlementSpecifier {
+
+ private final PlanPhaseSpecifier planPhaseSpecifier;
+ private final List<PlanPhasePriceOverride> overrides;
+
+ public DefaultEntitlementSpecifier(final PlanPhaseSpecifier planPhaseSpecifier, final List<PlanPhasePriceOverride> overrides) {
+ this.planPhaseSpecifier = planPhaseSpecifier;
+ this.overrides = overrides;
+ }
+
+ @Override
+ public PlanPhaseSpecifier getPlanPhaseSpecifier() {
+ return planPhaseSpecifier;
+ }
+
+ @Override
+ public List<PlanPhasePriceOverride> getOverrides() {
+ return overrides;
+ }
+
+}
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/api/svcs/DefaultEntitlementApiBase.java b/entitlement/src/main/java/org/killbill/billing/entitlement/api/svcs/DefaultEntitlementApiBase.java
index e092b73..9651c95 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/api/svcs/DefaultEntitlementApiBase.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/api/svcs/DefaultEntitlementApiBase.java
@@ -157,7 +157,6 @@ public class DefaultEntitlementApiBase {
bundleId,
null,
null,
- null,
localEffectiveDate,
properties,
internalCallContextFactory.createCallContext(internalCallContext));
@@ -216,7 +215,6 @@ public class DefaultEntitlementApiBase {
bundleId,
null,
null,
- null,
localEffectiveDate,
properties,
internalCallContextFactory.createCallContext(internalCallContext));
diff --git a/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultEntitlementApi.java b/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultEntitlementApi.java
index 8d87964..9a397ae 100644
--- a/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultEntitlementApi.java
+++ b/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultEntitlementApi.java
@@ -16,6 +16,7 @@
package org.killbill.billing.entitlement.api;
+import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
@@ -41,8 +42,10 @@ import org.testng.annotations.Test;
import com.google.common.collect.ImmutableList;
import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertNull;
import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
public class TestDefaultEntitlementApi extends EntitlementTestSuiteWithEmbeddedDB {
@@ -558,4 +561,102 @@ public class TestDefaultEntitlementApi extends EntitlementTestSuiteWithEmbeddedD
}
+ @Test(groups = "slow")
+ public void testCreateBaseEntitlementWithAddOns() throws AccountApiException, EntitlementApiException, SubscriptionBaseApiException {
+ final LocalDate initialDate = new LocalDate(2013, 8, 7);
+ clock.setDay(initialDate);
+
+ final Account account = accountApi.createAccount(getAccountData(7), callContext);
+
+ final PlanPhaseSpecifier baseSpec = new PlanPhaseSpecifier("Pistol", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
+ final PlanPhaseSpecifier addOnSpec = new PlanPhaseSpecifier("Cleaning", ProductCategory.ADD_ON, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
+
+ final String externalKey = "baseExternalKey";
+ EntitlementSpecifier baseEntitlementSpecifier = new DefaultEntitlementSpecifier(baseSpec, null);
+ EntitlementSpecifier addOnEntitlementSpecifier = new DefaultEntitlementSpecifier(addOnSpec, null);
+
+ final List<EntitlementSpecifier> specifierList = new ArrayList<EntitlementSpecifier>();
+ specifierList.add(baseEntitlementSpecifier);
+ specifierList.add(addOnEntitlementSpecifier);
+
+ testListener.pushExpectedEvents(NextEvent.CREATE, NextEvent.CREATE);
+ final Entitlement entitlement = entitlementApi.createBaseEntitlementWithAddOns(account.getId(), externalKey, specifierList, initialDate, ImmutableList.<PluginProperty>of(), callContext);
+ assertListenerStatus();
+
+ assertNotNull(entitlement);
+
+ final List<Entitlement> allEntitlementsForBundle = entitlementApi.getAllEntitlementsForBundle(entitlement.getBundleId(), callContext);
+ assertTrue(allEntitlementsForBundle.size() == 2);
+
+ final Entitlement baseEntitlement = allEntitlementsForBundle.get(0);
+ final Entitlement addOnEntitlement = allEntitlementsForBundle.get(1);
+
+ assertEquals(baseEntitlement.getLastActiveProduct().getName(), "Pistol");
+ assertEquals(baseEntitlement.getLastActiveProductCategory(), ProductCategory.BASE);
+
+ assertEquals(addOnEntitlement.getLastActiveProduct().getName(), "Cleaning");
+ assertEquals(addOnEntitlement.getLastActiveProductCategory(), ProductCategory.ADD_ON);
+
+ }
+
+ @Test(groups = "slow")
+ public void testCreateBaseEntitlementWithInvalidAddOn() throws AccountApiException, EntitlementApiException {
+ final LocalDate initialDate = new LocalDate(2013, 8, 7);
+ clock.setDay(initialDate);
+
+ final Account account = accountApi.createAccount(getAccountData(7), callContext);
+
+ final PlanPhaseSpecifier baseSpec = new PlanPhaseSpecifier("Pistol", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
+ final PlanPhaseSpecifier addOnSpec = new PlanPhaseSpecifier("Invalid", ProductCategory.ADD_ON, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
+
+ final String externalKey = "baseExternalKey";
+ EntitlementSpecifier baseEntitlementSpecifier = new DefaultEntitlementSpecifier(baseSpec, null);
+ EntitlementSpecifier addOnEntitlementSpecifier = new DefaultEntitlementSpecifier(addOnSpec, null);
+
+ final List<EntitlementSpecifier> specifierList = new ArrayList<EntitlementSpecifier>();
+ specifierList.add(baseEntitlementSpecifier);
+ specifierList.add(addOnEntitlementSpecifier);
+
+ try {
+ entitlementApi.createBaseEntitlementWithAddOns(account.getId(), externalKey, specifierList, initialDate, ImmutableList.<PluginProperty>of(), callContext);
+ fail();
+ } catch (EntitlementApiException e) {
+ assertEquals(e.getMessage(), "Could not find any product named 'Invalid'");
+ }
+
+ final List<Entitlement> allEntitlementsForAccount = entitlementApi.getAllEntitlementsForAccountId(account.getId(), callContext);
+ assertTrue(allEntitlementsForAccount.size() == 0);
+
+ }
+
+ @Test(groups = "slow")
+ public void testCreateBaseEntitlementWithoutBaseEntitlement() throws AccountApiException, EntitlementApiException {
+ final LocalDate initialDate = new LocalDate(2013, 8, 7);
+ clock.setDay(initialDate);
+
+ final Account account = accountApi.createAccount(getAccountData(7), callContext);
+
+ final PlanPhaseSpecifier baseSpec = new PlanPhaseSpecifier("Cleaning", ProductCategory.ADD_ON, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
+ final PlanPhaseSpecifier addOnSpec = new PlanPhaseSpecifier("Bullets", ProductCategory.ADD_ON, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
+
+ final String externalKey = "addOnExternalKey";
+ EntitlementSpecifier addOnEntitlementSpecifier1 = new DefaultEntitlementSpecifier(baseSpec, null);
+ EntitlementSpecifier addOnEntitlementSpecifier2 = new DefaultEntitlementSpecifier(addOnSpec, null);
+
+ final List<EntitlementSpecifier> specifierList = new ArrayList<EntitlementSpecifier>();
+ specifierList.add(addOnEntitlementSpecifier1);
+ specifierList.add(addOnEntitlementSpecifier2);
+
+ try {
+ entitlementApi.createBaseEntitlementWithAddOns(account.getId(), externalKey, specifierList, initialDate, ImmutableList.<PluginProperty>of(), callContext);
+ fail();
+ } catch (EntitlementApiException e) {
+ assertEquals(e.getMessage(), "Missing Base Subscription.");
+ }
+
+ final List<Entitlement> allEntitlementsForAccount = entitlementApi.getAllEntitlementsForAccountId(account.getId(), callContext);
+ assertTrue(allEntitlementsForAccount.size() == 0);
+
+ }
+
}
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxRsResourceBase.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxRsResourceBase.java
index 28cd1aa..6292294 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxRsResourceBase.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxRsResourceBase.java
@@ -470,4 +470,9 @@ public abstract class JaxRsResourceBase implements JaxrsResource {
Preconditions.checkArgument(expression, errorMessage);
}
}
+
+ protected void verifyNumberOfElements(int actual, int expected, String errorMessage) {
+ Preconditions.checkArgument(actual == expected, errorMessage);
+ }
+
}
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/SubscriptionResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/SubscriptionResource.java
index 3609a65..b8ea3d4 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/SubscriptionResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/SubscriptionResource.java
@@ -19,6 +19,7 @@
package org.killbill.billing.jaxrs.resources;
import java.math.BigDecimal;
+import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeoutException;
@@ -54,6 +55,7 @@ import org.killbill.billing.entitlement.api.Entitlement;
import org.killbill.billing.entitlement.api.Entitlement.EntitlementActionPolicy;
import org.killbill.billing.entitlement.api.EntitlementApi;
import org.killbill.billing.entitlement.api.EntitlementApiException;
+import org.killbill.billing.entitlement.api.EntitlementSpecifier;
import org.killbill.billing.entitlement.api.Subscription;
import org.killbill.billing.entitlement.api.SubscriptionApi;
import org.killbill.billing.entitlement.api.SubscriptionApiException;
@@ -88,7 +90,9 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Preconditions;
+import com.google.common.base.Predicate;
import com.google.common.base.Strings;
+import com.google.common.collect.Iterables;
import com.google.inject.Inject;
import com.wordnik.swagger.annotations.Api;
import com.wordnik.swagger.annotations.ApiOperation;
@@ -222,7 +226,117 @@ public class SubscriptionResource extends JaxRsResourceBase {
return callCompletionCreation.withSynchronization(callback, timeoutSec, callCompletion, callContext);
}
+ @TimedResource
+ @POST
+ @Path("/createEntitlementWithAddOns")
+ @Consumes(APPLICATION_JSON)
+ @Produces(APPLICATION_JSON)
+ @ApiOperation(value = "Create an entitlement with addOn products")
+ @ApiResponses(value = {@ApiResponse(code = 400, message = "Invalid entitlement supplied")})
+ public Response createEntitlementWithAddOns(final List<SubscriptionJson> entitlements,
+ @QueryParam(QUERY_REQUESTED_DT) final String requestedDate,
+ @QueryParam(QUERY_CALL_COMPLETION) @DefaultValue("false") final Boolean callCompletion,
+ @QueryParam(QUERY_CALL_TIMEOUT) @DefaultValue("3") final long timeoutSec,
+ @QueryParam(QUERY_PLUGIN_PROPERTY) final List<String> pluginPropertiesString,
+ @HeaderParam(HDR_CREATED_BY) final String createdBy,
+ @HeaderParam(HDR_REASON) final String reason,
+ @HeaderParam(HDR_COMMENT) final String comment,
+ @javax.ws.rs.core.Context final HttpServletRequest request,
+ @javax.ws.rs.core.Context final UriInfo uriInfo) throws EntitlementApiException, AccountApiException, SubscriptionApiException {
+
+ Preconditions.checkArgument(Iterables.size(entitlements) > 0, "Subscription list mustn't be null or empty.");
+
+ for (SubscriptionJson entitlement : entitlements) {
+ verifyNonNullOrEmpty(entitlement, "SubscriptionJson body should be specified for each element");
+ verifyNonNullOrEmpty(entitlement.getProductName(), "SubscriptionJson productName needs to be set for each element",
+ entitlement.getProductCategory(), "SubscriptionJson productCategory needs to be set for each element",
+ entitlement.getBillingPeriod(), "SubscriptionJson billingPeriod needs to be set for each element",
+ entitlement.getPriceList(), "SubscriptionJson priceList needs to be set for each element");
+ }
+
+ final int baseSubscriptionsSize = Iterables.size(Iterables.filter(entitlements, new Predicate<SubscriptionJson>() {
+ @Override
+ public boolean apply(final SubscriptionJson subscription) {
+ return subscription.getProductCategory().equals(ProductCategory.BASE.toString());
+ }
+ }));
+ verifyNumberOfElements(baseSubscriptionsSize, 1, "Only one BASE product is allowed.");
+
+ final int addOnSubscriptionsSize = Iterables.size(Iterables.filter(entitlements, new Predicate<SubscriptionJson>() {
+ @Override
+ public boolean apply(final SubscriptionJson subscription) {
+ return subscription.getProductCategory().equals(ProductCategory.ADD_ON.toString());
+ }
+ }));
+ verifyNumberOfElements(addOnSubscriptionsSize, entitlements.size() - 1, "It should be " + (entitlements.size() - 1) + " ADD_ON products.");
+
+ final Iterable<PluginProperty> pluginProperties = extractPluginProperties(pluginPropertiesString);
+ final SubscriptionJson baseEntitlement = Iterables.tryFind(entitlements, new Predicate<SubscriptionJson>() {
+ @Override
+ public boolean apply(final SubscriptionJson subscription) {
+ return ProductCategory.BASE.toString().equalsIgnoreCase(subscription.getProductCategory());
+ }
+ }).orNull();
+
+ verifyNonNull(baseEntitlement.getAccountId(), "SubscriptionJson accountId needs to be set for BASE product.");
+
+ final CallContext callContext = context.createContext(createdBy, reason, comment, request);
+
+ final EntitlementCallCompletionCallback<Entitlement> callback = new EntitlementCallCompletionCallback<Entitlement>() {
+
+ @Override
+ public Entitlement doOperation(final CallContext ctx) throws InterruptedException, TimeoutException, EntitlementApiException, SubscriptionApiException, AccountApiException {
+
+ List<EntitlementSpecifier> entitlementSpecifierList = new ArrayList<EntitlementSpecifier>();
+ final Account account = getAccountFromSubscriptionJson(baseEntitlement, callContext);
+
+ for (final SubscriptionJson entitlement : entitlements) {
+
+ final PlanPhaseSpecifier planPhaseSpecifier = new PlanPhaseSpecifier(entitlement.getProductName(),
+ ProductCategory.valueOf(entitlement.getProductCategory()),
+ BillingPeriod.valueOf(entitlement.getBillingPeriod()), entitlement.getPriceList(), null);
+
+ final PlanSpecifier planSpec = new PlanSpecifier(entitlement.getProductName(),
+ ProductCategory.valueOf(entitlement.getProductCategory()),
+ BillingPeriod.valueOf(entitlement.getBillingPeriod()), entitlement.getPriceList());
+ final List<PlanPhasePriceOverride> overrides = PhasePriceOverrideJson.toPlanPhasePriceOverrides(entitlement.getPriceOverrides(), planSpec, account.getCurrency());
+ EntitlementSpecifier specifier = new EntitlementSpecifier() {
+
+ @Override
+ public PlanPhaseSpecifier getPlanPhaseSpecifier() {
+ return planPhaseSpecifier;
+ }
+
+ @Override
+ public List<PlanPhasePriceOverride> getOverrides() {
+ return overrides;
+ }
+ };
+
+ entitlementSpecifierList.add(specifier);
+ }
+
+ final LocalDate inputLocalDate = toLocalDate(account, requestedDate, callContext);
+ return entitlementApi.createBaseEntitlementWithAddOns(account.getId(), baseEntitlement.getExternalKey(), entitlementSpecifierList,
+ inputLocalDate, pluginProperties, callContext);
+ }
+
+ @Override
+ public boolean isImmOperation() {
+ return true;
+ }
+
+ @Override
+ public Response doResponseOk(final Entitlement entitlement) {
+ return uriBuilder.buildResponse(uriInfo, BundleResource.class, "getBundle", entitlement.getBundleId());
+ }
+
+ };
+
+ final EntitlementCallCompletion<Entitlement> callCompletionCreation = new EntitlementCallCompletion<Entitlement>();
+ return callCompletionCreation.withSynchronization(callback, timeoutSec, callCompletion, callContext);
+ }
@TimedResource
@PUT
pom.xml 2(+1 -1)
diff --git a/pom.xml b/pom.xml
index e986e47..786a1da 100644
--- a/pom.xml
+++ b/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>killbill-oss-parent</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.63-SNAPSHOT</version>
+ <version>0.65-SNAPSHOT</version>
</parent>
<artifactId>killbill</artifactId>
<version>0.15.10-SNAPSHOT</version>
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestEntitlement.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestEntitlement.java
index bb7c40d..2f5ca6d 100644
--- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestEntitlement.java
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestEntitlement.java
@@ -228,114 +228,49 @@ public class TestEntitlement extends TestJaxrsBase {
assertEquals(invoices.get(0).getAmount().compareTo(BigDecimal.TEN), 0);
}
- @Test(groups = "slow", description = "Create base and addOn subscription with bundle external key")
- public void testBaseAndAddOnEntitlementsCreation() throws Exception {
- final DateTime initialDate = new DateTime(2015, 11, 1, 0, 3, 42, 0);
- clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis());
-
- final Account accountJson = createAccountWithDefaultPaymentMethod();
-
- final BillingPeriod term = BillingPeriod.MONTHLY;
- final String externalKey = "bundleKey";
-
- final Subscription baseEntitlementJson = createEntitlement(accountJson.getAccountId(), externalKey, "Shotgun",
- ProductCategory.BASE, term, true);
-
- final Subscription addOnEntitlementJson = createEntitlement(accountJson.getAccountId(), externalKey, "Telescopic-Scope",
- ProductCategory.ADD_ON, term, true);
-
- // Retrieves with GET
- Bundle objFromJson = killBillClient.getBundle(externalKey);
- final List<Subscription> subscriptions = objFromJson.getSubscriptions();
-
- assertEquals(subscriptions.size(), 2);
- assertTrue(baseEntitlementJson.equals(subscriptions.get(0)));
- assertTrue(addOnEntitlementJson.equals(subscriptions.get(1)));
-
- }
-
- @Test(groups = "slow", description = "Create base and addOn subscription with bundle id")
- public void testBaseAndAddOnEntitlementsCreationWithBundleId() throws Exception {
- final DateTime initialDate = new DateTime(2015, 11, 1, 0, 3, 42, 0);
- clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis());
-
- final Account accountJson = createAccountWithDefaultPaymentMethod();
-
- final BillingPeriod term = BillingPeriod.MONTHLY;
- final String externalKey = "bundleKey";
-
- final Subscription baseEntitlementJson = createEntitlement(accountJson.getAccountId(), externalKey, "Shotgun",
- ProductCategory.BASE, term, true);
-
- final Subscription input = new Subscription();
- input.setAccountId(accountJson.getAccountId());
- input.setBundleId(baseEntitlementJson.getBundleId());
- input.setExternalKey("ignoreExternalkey");
- input.setProductName("Telescopic-Scope");
- input.setProductCategory(ProductCategory.ADD_ON);
- input.setBillingPeriod(term);
- input.setPriceList(PriceListSet.DEFAULT_PRICELIST_NAME);
-
- final Subscription addOnEntitlementJson = killBillClient.createSubscription(input, DEFAULT_WAIT_COMPLETION_TIMEOUT_SEC, createdBy, reason, comment);
-
- // Retrieves with GET
- Bundle bundleJson = killBillClient.getBundle(externalKey);
- final List<Subscription> subscriptions = bundleJson.getSubscriptions();
-
- assertEquals(subscriptions.size(), 2);
- assertTrue(baseEntitlementJson.equals(subscriptions.get(0)));
- assertTrue(addOnEntitlementJson.equals(subscriptions.get(1)));
-
- }
-
- @Test(groups = "slow", description = "Try to create an ADD_ON subscription for an invalid bundle external key",
- expectedExceptions = KillBillClientException.class, expectedExceptionsMessageRegExp = "Could not find a bundle matching key invalidKey")
- public void testAddOnEntitlementInvalidKey() throws Exception {
- final DateTime initialDate = new DateTime(2015, 11, 1, 0, 3, 42, 0);
- clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis());
-
- final Account accountJson = createAccountWithDefaultPaymentMethod();
- createEntitlement(accountJson.getAccountId(), "invalidKey", "Telescopic-Scope",
- ProductCategory.ADD_ON, BillingPeriod.MONTHLY, true);
- }
-
- @Test(groups = "slow", description = "Try to create an ADD_ON subscription for an invalid bundle external key",
- expectedExceptions = KillBillClientException.class, expectedExceptionsMessageRegExp = "Object id=.* type=BUNDLE doesn't exist!")
- public void testAddOnEntitlementInvalidBundleId() throws Exception {
- final DateTime initialDate = new DateTime(2015, 11, 1, 0, 3, 42, 0);
+ @Test(groups = "slow", description = "Create a base entitlement and also addOns entitlements under the same bundle")
+ public void testEntitlementWithAddOns() throws Exception {
+ final DateTime initialDate = new DateTime(2012, 4, 25, 0, 3, 42, 0);
clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis());
final Account accountJson = createAccountWithDefaultPaymentMethod();
- final Subscription input = new Subscription();
- input.setAccountId(accountJson.getAccountId());
- input.setBundleId(UUID.randomUUID()); // <--- invalid bundleId
- input.setProductName("Telescopic-Scope");
- input.setProductCategory(ProductCategory.ADD_ON);
- input.setBillingPeriod(BillingPeriod.MONTHLY);
- input.setPriceList(PriceListSet.DEFAULT_PRICELIST_NAME);
-
- killBillClient.createSubscription(input, DEFAULT_WAIT_COMPLETION_TIMEOUT_SEC, createdBy, reason, comment);
- }
- @Test(groups = "slow", description = "Try to create an ADD_ON subscription without bundle info",
- expectedExceptions = KillBillClientException.class, expectedExceptionsMessageRegExp = "SubscriptionJson bundleId or externalKey should be specified for ADD_ON")
- public void testAddOnEntitlementNoBundle() throws Exception {
- final DateTime initialDate = new DateTime(2015, 11, 1, 0, 3, 42, 0);
- clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis());
+ final Subscription base = new Subscription();
+ base.setAccountId(accountJson.getAccountId());
+ base.setExternalKey("base");
+ base.setProductName("Shotgun");
+ base.setProductCategory(ProductCategory.BASE);
+ base.setBillingPeriod(BillingPeriod.MONTHLY);
+ base.setPriceList(PriceListSet.DEFAULT_PRICELIST_NAME);
+
+ final Subscription addOn1 = new Subscription();
+ addOn1.setAccountId(accountJson.getAccountId());
+ addOn1.setExternalKey("");
+ addOn1.setProductName("Telescopic-Scope");
+ addOn1.setProductCategory(ProductCategory.ADD_ON);
+ addOn1.setBillingPeriod(BillingPeriod.MONTHLY);
+ addOn1.setPriceList(PriceListSet.DEFAULT_PRICELIST_NAME);
+
+ final Subscription addOn2 = new Subscription();
+ addOn2.setAccountId(accountJson.getAccountId());
+ addOn2.setExternalKey("");
+ addOn2.setProductName("Laser-Scope");
+ addOn2.setProductCategory(ProductCategory.ADD_ON);
+ addOn2.setBillingPeriod(BillingPeriod.MONTHLY);
+ addOn2.setPriceList(PriceListSet.DEFAULT_PRICELIST_NAME);
+
+ final List<Subscription> subscriptions = new ArrayList<Subscription>();
+ subscriptions.add(base);
+ subscriptions.add(addOn1);
+ subscriptions.add(addOn2);
+ final Bundle bundle = killBillClient.createSubscriptionWithAddOns(subscriptions, initialDate, 10, "createdBy", "", "");
+ assertNotNull(bundle);
+ assertEquals(bundle.getExternalKey(), "base");
+ assertEquals(bundle.getSubscriptions().size(), 3);
- final Account accountJson = createAccountWithDefaultPaymentMethod();
-
- final Subscription input = new Subscription();
- input.setAccountId(accountJson.getAccountId());
- input.setExternalKey(null);
- input.setBundleId(null);
- input.setProductName("Telescopic-Scope");
- input.setProductCategory(ProductCategory.ADD_ON);
- input.setBillingPeriod(BillingPeriod.MONTHLY);
- input.setPriceList(PriceListSet.DEFAULT_PRICELIST_NAME);
-
- killBillClient.createSubscription(input, DEFAULT_WAIT_COMPLETION_TIMEOUT_SEC, createdBy, reason, comment);
+ final List<Invoice> invoices = killBillClient.getInvoicesForAccount(accountJson.getAccountId(), true, false, AuditLevel.FULL);
+ assertEquals(invoices.size(), 1);
}
}
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseApiService.java b/subscription/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseApiService.java
index a96cac4..26c6a3d 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseApiService.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseApiService.java
@@ -35,6 +35,7 @@ import org.killbill.billing.catalog.api.Product;
import org.killbill.billing.subscription.api.user.DefaultSubscriptionBase;
import org.killbill.billing.subscription.api.user.SubscriptionBaseApiException;
import org.killbill.billing.subscription.api.user.SubscriptionBuilder;
+import org.killbill.billing.subscription.api.user.SubscriptionSpecifier;
import org.killbill.billing.subscription.events.SubscriptionBaseEvent;
import org.killbill.billing.util.callcontext.CallContext;
import org.killbill.billing.util.callcontext.TenantContext;
@@ -46,6 +47,9 @@ public interface SubscriptionBaseApiService {
CallContext context)
throws SubscriptionBaseApiException;
+ public DefaultSubscriptionBase createPlans(Iterable<SubscriptionSpecifier> subscriptions, CallContext context)
+ throws SubscriptionBaseApiException;
+
@Deprecated
public boolean recreatePlan(DefaultSubscriptionBase subscription, PlanPhaseSpecifier spec, List<PlanPhasePriceOverride> overrides, DateTime requestedDateWithMs, CallContext context)
throws SubscriptionBaseApiException;
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/svcs/DefaultSubscriptionInternalApi.java b/subscription/src/main/java/org/killbill/billing/subscription/api/svcs/DefaultSubscriptionInternalApi.java
index 33a57d3..5155064 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/api/svcs/DefaultSubscriptionInternalApi.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/api/svcs/DefaultSubscriptionInternalApi.java
@@ -46,6 +46,7 @@ import org.killbill.billing.catalog.api.ProductCategory;
import org.killbill.billing.entitlement.api.Entitlement.EntitlementState;
import org.killbill.billing.entitlement.api.EntitlementAOStatusDryRun;
import org.killbill.billing.entitlement.api.EntitlementAOStatusDryRun.DryRunChangeReason;
+import org.killbill.billing.entitlement.api.EntitlementSpecifier;
import org.killbill.billing.events.EffectiveSubscriptionInternalEvent;
import org.killbill.billing.invoice.api.DryRunArguments;
import org.killbill.billing.subscription.api.SubscriptionApiBase;
@@ -62,6 +63,7 @@ import org.killbill.billing.subscription.api.user.SubscriptionBaseBundle;
import org.killbill.billing.subscription.api.user.SubscriptionBaseTransition;
import org.killbill.billing.subscription.api.user.SubscriptionBaseTransitionData;
import org.killbill.billing.subscription.api.user.SubscriptionBuilder;
+import org.killbill.billing.subscription.api.user.SubscriptionSpecifier;
import org.killbill.billing.subscription.engine.addon.AddonUtils;
import org.killbill.billing.subscription.engine.core.DefaultSubscriptionBaseService;
import org.killbill.billing.subscription.engine.dao.SubscriptionDao;
@@ -162,6 +164,59 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
}
@Override
+ public SubscriptionBase createBaseSubscriptionWithAddOns(final UUID bundleId, final Iterable<EntitlementSpecifier> entitlements, final DateTime requestedDateWithMs, final InternalCallContext context) throws SubscriptionBaseApiException {
+
+ final DateTime now = clock.getUTCNow();
+ final DateTime requestedDate = (requestedDateWithMs != null) ? DefaultClock.truncateMs(requestedDateWithMs) : now;
+
+ try {
+ final List<SubscriptionSpecifier> subscriptions = new ArrayList<SubscriptionSpecifier>();
+ final Catalog catalog = catalogService.getFullCatalog(context);
+ final CallContext callContext = internalCallContextFactory.createCallContext(context);
+
+ for (EntitlementSpecifier entitlement : entitlements) {
+
+ final PlanPhaseSpecifier spec = entitlement.getPlanPhaseSpecifier();
+ final String realPriceList = (spec.getPriceListName() == null) ? PriceListSet.DEFAULT_PRICELIST_NAME : spec.getPriceListName();
+
+ final PlanPhasePriceOverridesWithCallContext overridesWithContext = new DefaultPlanPhasePriceOverridesWithCallContext(entitlement.getOverrides(), callContext);
+
+ final Plan plan = catalog.createOrFindPlan(spec.getProductName(), spec.getBillingPeriod(), realPriceList, overridesWithContext, requestedDate);
+ final PlanPhase phase = plan.getAllPhases()[0];
+ if (phase == null) {
+ throw new SubscriptionBaseError(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 SubscriptionBaseBundle bundle = dao.getSubscriptionBundleFromId(bundleId, context);
+ if (bundle == null) {
+ throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_NO_BUNDLE, bundleId);
+ }
+
+ SubscriptionSpecifier subscription = new SubscriptionSpecifier();
+ subscription.setRealPriceList(realPriceList);
+ subscription.setRequestedDate(requestedDate);
+ subscription.setEffectiveDate(requestedDate);
+ subscription.setProcessedDate(now);
+ subscription.setPlan(plan);
+ subscription.setInitialPhase(spec.getPhaseType());
+ subscription.setBuilder(new SubscriptionBuilder()
+ .setId(UUIDs.randomUUID())
+ .setBundleId(bundleId)
+ .setCategory(plan.getProduct().getCategory())
+ .setBundleStartDate(requestedDate)
+ .setAlignStartDate(requestedDate));
+
+ subscriptions.add(subscription);
+ }
+
+ return apiService.createPlans(subscriptions, callContext);
+ } catch (final CatalogApiException e) {
+ throw new SubscriptionBaseApiException(e);
+ }
+ }
+
+ @Override
public SubscriptionBaseBundle createBundleForAccount(final UUID accountId, final String bundleKey, final InternalCallContext context) throws SubscriptionBaseApiException {
final List<SubscriptionBaseBundle> existingBundles = dao.getSubscriptionBundlesForKey(bundleKey, context);
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBaseApiService.java b/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBaseApiService.java
index 2e829f3..6e4b949 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBaseApiService.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBaseApiService.java
@@ -19,8 +19,10 @@
package org.killbill.billing.subscription.api.user;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
+import java.util.Map;
import java.util.UUID;
import org.joda.time.DateTime;
@@ -70,7 +72,9 @@ import org.killbill.billing.util.callcontext.TenantContext;
import org.killbill.clock.Clock;
import org.killbill.clock.DefaultClock;
+import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
import com.google.inject.Inject;
public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiService {
@@ -104,6 +108,52 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
return subscription;
}
+ @Override
+ public DefaultSubscriptionBase createPlans(final Iterable<SubscriptionSpecifier> subscriptions, final CallContext context) throws SubscriptionBaseApiException {
+
+ Map<UUID, List<SubscriptionBaseEvent>> eventsMap = new HashMap<UUID, List<SubscriptionBaseEvent>>();
+ List<DefaultSubscriptionBase> subscriptionBaseList = new ArrayList<DefaultSubscriptionBase>();
+ for (SubscriptionSpecifier subscription : subscriptions) {
+
+ try {
+ final DefaultSubscriptionBase subscriptionBase = new DefaultSubscriptionBase(subscription.getBuilder(), this, clock);
+ final InternalCallContext internalCallContext = createCallContextFromBundleId(subscriptionBase.getBundleId(), context);
+ final List<SubscriptionBaseEvent> events = getEventsOnCreation(subscriptionBase.getBundleId(), subscriptionBase.getId(), subscriptionBase.getAlignStartDate(),
+ subscriptionBase.getBundleStartDate(), subscriptionBase.getActiveVersion(), subscription.getPlan(),
+ subscription.getInitialPhase(), subscription.getRealPriceList(), subscription.getRequestedDate(),
+ subscription.getEffectiveDate(), subscription.getProcessedDate(), false, internalCallContext);
+
+ eventsMap.put(subscriptionBase.getId(), events);
+ subscriptionBaseList.add(subscriptionBase);
+
+ } catch (final CatalogApiException e) {
+ throw new SubscriptionBaseApiException(e);
+ }
+ }
+
+ final InternalCallContext internalCallContext = createCallContextFromBundleId(subscriptionBaseList.get(0).getBundleId(), context);
+ dao.createSubscriptionWithAddOns(subscriptionBaseList, eventsMap, internalCallContext);
+
+ final DefaultSubscriptionBase baseSubscription = findBaseSubscription(subscriptionBaseList);
+ try {
+ baseSubscription.rebuildTransitions(dao.getEventsForSubscription(baseSubscription.getId(), internalCallContext),
+ catalogService.getFullCatalog(internalCallContext));
+ } catch (CatalogApiException e) {
+ throw new SubscriptionBaseApiException(e);
+ }
+
+ return baseSubscription;
+ }
+
+ private DefaultSubscriptionBase findBaseSubscription(final List<DefaultSubscriptionBase> subscriptionBaseList) {
+ return Iterables.tryFind(subscriptionBaseList, new Predicate<DefaultSubscriptionBase>() {
+ @Override
+ public boolean apply(final DefaultSubscriptionBase subscription) {
+ return ProductCategory.BASE.equals(subscription.getCategory());
+ }
+ }).orNull();
+ }
+
@Deprecated
@Override
public boolean recreatePlan(final DefaultSubscriptionBase subscription, final PlanPhaseSpecifier spec, final List<PlanPhasePriceOverride> overrides, final DateTime requestedDateWithMs, final CallContext context)
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/user/SubscriptionSpecifier.java b/subscription/src/main/java/org/killbill/billing/subscription/api/user/SubscriptionSpecifier.java
new file mode 100644
index 0000000..154ee62
--- /dev/null
+++ b/subscription/src/main/java/org/killbill/billing/subscription/api/user/SubscriptionSpecifier.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 The Billing Project, LLC
+ *
+ * The Billing Project 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 org.killbill.billing.subscription.api.user;
+
+import org.joda.time.DateTime;
+import org.killbill.billing.catalog.api.PhaseType;
+import org.killbill.billing.catalog.api.Plan;
+
+public class SubscriptionSpecifier {
+
+ private SubscriptionBuilder builder;
+ private Plan plan;
+ private PhaseType initialPhase;
+ private String realPriceList;
+ private DateTime requestedDate;
+ private DateTime effectiveDate;
+ private DateTime processedDate;
+
+ public SubscriptionSpecifier() {
+ }
+
+ public SubscriptionSpecifier(final SubscriptionBuilder builder, final Plan plan,
+ final PhaseType initialPhase, final String realPriceList,
+ final DateTime requestedDate, final DateTime effectiveDate,
+ final DateTime processedDate) {
+ this.builder = builder;
+ this.plan = plan;
+ this.initialPhase = initialPhase;
+ this.realPriceList = realPriceList;
+ this.requestedDate = requestedDate;
+ this.effectiveDate = effectiveDate;
+ this.processedDate = processedDate;
+ }
+
+ public SubscriptionBuilder getBuilder() {
+ return builder;
+ }
+
+ public void setBuilder(final SubscriptionBuilder builder) {
+ this.builder = builder;
+ }
+
+ public Plan getPlan() {
+ return plan;
+ }
+
+ public void setPlan(final Plan plan) {
+ this.plan = plan;
+ }
+
+ public PhaseType getInitialPhase() {
+ return initialPhase;
+ }
+
+ public void setInitialPhase(final PhaseType initialPhase) {
+ this.initialPhase = initialPhase;
+ }
+
+ public String getRealPriceList() {
+ return realPriceList;
+ }
+
+ public void setRealPriceList(final String realPriceList) {
+ this.realPriceList = realPriceList;
+ }
+
+ public DateTime getRequestedDate() {
+ return requestedDate;
+ }
+
+ public void setRequestedDate(final DateTime requestedDate) {
+ this.requestedDate = requestedDate;
+ }
+
+ public DateTime getEffectiveDate() {
+ return effectiveDate;
+ }
+
+ public void setEffectiveDate(final DateTime effectiveDate) {
+ this.effectiveDate = effectiveDate;
+ }
+
+ public DateTime getProcessedDate() {
+ return processedDate;
+ }
+
+ public void setProcessedDate(final DateTime processedDate) {
+ this.processedDate = processedDate;
+ }
+
+}
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java
index 63d5d56..4a72049 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java
@@ -510,6 +510,35 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
}
@Override
+ public void createSubscriptionWithAddOns(final List<DefaultSubscriptionBase> subscriptions, final Map<UUID, List<SubscriptionBaseEvent>> initialEventsMap, final InternalCallContext context) {
+ transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<Void>() {
+ @Override
+ public Void inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
+ final SubscriptionSqlDao transactional = entitySqlDaoWrapperFactory.become(SubscriptionSqlDao.class);
+ final SubscriptionEventSqlDao eventsDaoFromSameTransaction = entitySqlDaoWrapperFactory.become(SubscriptionEventSqlDao.class);
+
+ for (DefaultSubscriptionBase subscription : subscriptions) {
+ transactional.create(new SubscriptionModelDao(subscription), context);
+
+ final List<SubscriptionBaseEvent> initialEvents = initialEventsMap.get(subscription.getId());
+ for (final SubscriptionBaseEvent cur : initialEvents) {
+ eventsDaoFromSameTransaction.create(new SubscriptionEventModelDao(cur), context);
+
+ final boolean isBusEvent = cur.getEffectiveDate().compareTo(clock.getUTCNow()) <= 0 && (cur.getType() == EventType.API_USER);
+ recordBusOrFutureNotificationFromTransaction(subscription, cur, entitySqlDaoWrapperFactory, isBusEvent, 0, context);
+
+ }
+ // Notify the Bus of the latest requested change, if needed
+ if (initialEvents.size() > 0) {
+ notifyBusOfRequestedChange(entitySqlDaoWrapperFactory, subscription, initialEvents.get(initialEvents.size() - 1), SubscriptionBaseTransitionType.CREATE, context);
+ }
+ }
+ return null;
+ }
+ });
+ }
+
+ @Override
public void recreateSubscription(final DefaultSubscriptionBase subscription, final List<SubscriptionBaseEvent> recreateEvents, final InternalCallContext context) {
transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<Void>() {
@Override
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionDao.java b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionDao.java
index 13cfa2b..893c5d4 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionDao.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionDao.java
@@ -82,6 +82,8 @@ public interface SubscriptionDao extends EntityDao<SubscriptionBundleModelDao, S
// SubscriptionBase creation, cancellation, changePlanWithRequestedDate apis
public void createSubscription(DefaultSubscriptionBase subscription, List<SubscriptionBaseEvent> initialEvents, InternalCallContext context);
+ public void createSubscriptionWithAddOns(List<DefaultSubscriptionBase> subscriptions, Map<UUID, List<SubscriptionBaseEvent>> initialEventsMap, InternalCallContext context);
+
public void recreateSubscription(DefaultSubscriptionBase subscription, List<SubscriptionBaseEvent> recreateEvents, InternalCallContext context);
public void cancelSubscription(DefaultSubscriptionBase subscription, SubscriptionBaseEvent cancelEvent, InternalCallContext context, int cancelSeq);
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/engine/dao/MockSubscriptionDaoMemory.java b/subscription/src/test/java/org/killbill/billing/subscription/engine/dao/MockSubscriptionDaoMemory.java
index 1d95133..6aaed33 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/engine/dao/MockSubscriptionDaoMemory.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/engine/dao/MockSubscriptionDaoMemory.java
@@ -213,6 +213,24 @@ public class MockSubscriptionDaoMemory extends MockEntityDaoBase<SubscriptionBun
}
@Override
+ public void createSubscriptionWithAddOns(final List<DefaultSubscriptionBase> subscriptions,
+ final Map<UUID, List<SubscriptionBaseEvent>> initialEventsMap,
+ final InternalCallContext context) {
+ synchronized (events) {
+ for (DefaultSubscriptionBase subscription : subscriptions) {
+ final List<SubscriptionBaseEvent> initialEvents = initialEventsMap.get(subscription.getId());
+ events.addAll(initialEvents);
+ for (final SubscriptionBaseEvent cur : initialEvents) {
+ recordFutureNotificationFromTransaction(null, cur.getEffectiveDate(), new SubscriptionNotificationKey(cur.getId()), context);
+ }
+ final SubscriptionBase updatedSubscription = buildSubscription(subscription, context);
+ this.subscriptions.add(updatedSubscription);
+ mockNonEntityDao.addTenantRecordIdMapping(updatedSubscription.getId(), context);
+ }
+ }
+ }
+
+ @Override
public void recreateSubscription(final DefaultSubscriptionBase subscription, final List<SubscriptionBaseEvent> recreateEvents, final InternalCallContext context) {
synchronized (events) {
events.addAll(recreateEvents);