killbill-memoizeit
Changes
beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithInvoicePlugin.java 19(+19 -0)
invoice/src/main/java/org/killbill/billing/invoice/notification/DefaultNextBillingDateNotifier.java 13(+7 -6)
invoice/src/main/java/org/killbill/billing/invoice/notification/DefaultNextBillingDatePoster.java 9(+5 -4)
invoice/src/main/java/org/killbill/billing/invoice/notification/NextBillingDateNotificationKey.java 68(+63 -5)
invoice/src/main/java/org/killbill/billing/invoice/provider/DefaultNoOpInvoiceProviderPlugin.java 27(+23 -4)
invoice/src/test/java/org/killbill/billing/invoice/notification/TestNextBillingDateNotificationKey.java 8(+4 -4)
invoice/src/test/java/org/killbill/billing/invoice/notification/TestNextBillingDateNotifier.java 6(+3 -3)
pom.xml 2(+1 -1)
Details
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithInvoicePlugin.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithInvoicePlugin.java
index c8780b4..17a7e68 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithInvoicePlugin.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithInvoicePlugin.java
@@ -45,8 +45,12 @@ import org.killbill.billing.invoice.model.ExternalChargeInvoiceItem;
import org.killbill.billing.invoice.model.ItemAdjInvoiceItem;
import org.killbill.billing.invoice.model.TaxInvoiceItem;
import org.killbill.billing.invoice.notification.DefaultNextBillingDateNotifier;
+import org.killbill.billing.invoice.plugin.api.InvoiceContext;
import org.killbill.billing.invoice.plugin.api.InvoicePluginApi;
import org.killbill.billing.invoice.plugin.api.InvoicePluginApiRetryException;
+import org.killbill.billing.invoice.plugin.api.OnFailureInvoiceResult;
+import org.killbill.billing.invoice.plugin.api.OnSuccessInvoiceResult;
+import org.killbill.billing.invoice.plugin.api.PriorInvoiceResult;
import org.killbill.billing.osgi.api.OSGIServiceDescriptor;
import org.killbill.billing.osgi.api.OSGIServiceRegistration;
import org.killbill.billing.payment.api.PluginProperty;
@@ -402,6 +406,11 @@ public class TestWithInvoicePlugin extends TestIntegrationBase {
InvoiceItem additionalInvoiceItem;
@Override
+ public PriorInvoiceResult priorCall(final InvoiceContext invoiceContext, final Iterable<PluginProperty> iterable) {
+ return null;
+ }
+
+ @Override
public List<InvoiceItem> getAdditionalInvoiceItems(final Invoice invoice, final boolean isDryRun, final Iterable<PluginProperty> pluginProperties, final CallContext callContext) {
if (shouldThrowException) {
throw new InvoicePluginApiRetryException();
@@ -412,6 +421,16 @@ public class TestWithInvoicePlugin extends TestIntegrationBase {
}
}
+ @Override
+ public OnSuccessInvoiceResult onSuccessCall(final InvoiceContext invoiceContext, final Iterable<PluginProperty> iterable) {
+ return null;
+ }
+
+ @Override
+ public OnFailureInvoiceResult onFailureCall(final InvoiceContext invoiceContext, final Iterable<PluginProperty> iterable) {
+ return null;
+ }
+
private InvoiceItem createTaxInvoiceItem(final Invoice invoice) {
return new TaxInvoiceItem(invoice.getId(), invoice.getAccountId(), null, "Tax Item", clock.getUTCNow().toLocalDate(), BigDecimal.ONE, invoice.getCurrency());
}
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithTaxItems.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithTaxItems.java
index c02877a..d0cfcee 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithTaxItems.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithTaxItems.java
@@ -40,7 +40,11 @@ import org.killbill.billing.invoice.api.InvoiceItem;
import org.killbill.billing.invoice.api.InvoiceItemType;
import org.killbill.billing.invoice.model.ExternalChargeInvoiceItem;
import org.killbill.billing.invoice.model.TaxInvoiceItem;
+import org.killbill.billing.invoice.plugin.api.InvoiceContext;
import org.killbill.billing.invoice.plugin.api.InvoicePluginApi;
+import org.killbill.billing.invoice.plugin.api.OnFailureInvoiceResult;
+import org.killbill.billing.invoice.plugin.api.OnSuccessInvoiceResult;
+import org.killbill.billing.invoice.plugin.api.PriorInvoiceResult;
import org.killbill.billing.osgi.api.OSGIServiceDescriptor;
import org.killbill.billing.osgi.api.OSGIServiceRegistration;
import org.killbill.billing.payment.api.PluginProperty;
@@ -293,15 +297,30 @@ public class TestWithTaxItems extends TestIntegrationBase {
}
@Override
+ public PriorInvoiceResult priorCall(final InvoiceContext invoiceContext, final Iterable<PluginProperty> iterable) {
+ return null;
+ }
+
+ @Override
public List<InvoiceItem> getAdditionalInvoiceItems(final Invoice invoice, final boolean isDryRun, final Iterable<PluginProperty> pluginProperties, final CallContext callContext) {
final List<InvoiceItem> result = new ArrayList<InvoiceItem>();
- for (TaxInvoiceItem item : taxItems) {
+ for (final TaxInvoiceItem item : taxItems) {
result.add(new TaxInvoiceItem(item.getId(), invoice.getId(), invoice.getAccountId(), item.getBundleId(), "Tax Item", item.getStartDate(), item.getAmount(), invoice.getCurrency()));
}
taxItems.clear();
return result;
}
+ @Override
+ public OnSuccessInvoiceResult onSuccessCall(final InvoiceContext invoiceContext, final Iterable<PluginProperty> iterable) {
+ return null;
+ }
+
+ @Override
+ public OnFailureInvoiceResult onFailureCall(final InvoiceContext invoiceContext, final Iterable<PluginProperty> iterable) {
+ return null;
+ }
+
public void reset() {
taxItems.clear();
}
@@ -309,6 +328,5 @@ public class TestWithTaxItems extends TestIntegrationBase {
public void addTaxItem(final TaxInvoiceItem item) {
taxItems.add(item);
}
-
}
}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/api/DefaultInvoiceContext.java b/invoice/src/main/java/org/killbill/billing/invoice/api/DefaultInvoiceContext.java
new file mode 100644
index 0000000..8f7f37b
--- /dev/null
+++ b/invoice/src/main/java/org/killbill/billing/invoice/api/DefaultInvoiceContext.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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.invoice.api;
+
+import java.util.List;
+
+import org.joda.time.LocalDate;
+import org.killbill.billing.callcontext.DefaultCallContext;
+import org.killbill.billing.invoice.plugin.api.InvoiceContext;
+import org.killbill.billing.util.callcontext.CallContext;
+
+public class DefaultInvoiceContext extends DefaultCallContext implements InvoiceContext {
+
+ private final LocalDate targetDate;
+ private final Invoice invoice;
+ private final List<Invoice> existingInvoices;
+ private final boolean isDryRun;
+ private final boolean isRescheduled;
+
+ public DefaultInvoiceContext(final LocalDate targetDate,
+ final Invoice invoice,
+ final List<Invoice> existingInvoices,
+ final boolean isDryRun,
+ final boolean isRescheduled,
+ final CallContext context) {
+ super(context.getAccountId(),
+ context.getTenantId(),
+ context.getUserName(),
+ context.getCallOrigin(),
+ context.getUserType(),
+ context.getReasonCode(),
+ context.getComments(),
+ context.getUserToken(),
+ context.getCreatedDate(),
+ context.getUpdatedDate());
+ this.targetDate = targetDate;
+ this.invoice = invoice;
+ this.existingInvoices = existingInvoices;
+ this.isDryRun = isDryRun;
+ this.isRescheduled = isRescheduled;
+ }
+
+ @Override
+ public LocalDate getTargetDate() {
+ return targetDate;
+ }
+
+ @Override
+ public Invoice getInvoice() {
+ return invoice;
+ }
+
+ @Override
+ public List<Invoice> getExistingInvoices() {
+ return existingInvoices;
+ }
+
+ @Override
+ public boolean isDryRun() {
+ return isDryRun;
+ }
+
+ @Override
+ public boolean isRescheduled() {
+ return isRescheduled;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("DefaultInvoiceContext{");
+ sb.append("targetDate=").append(targetDate);
+ sb.append(", invoice=").append(invoice);
+ sb.append(", isDryRun=").append(isDryRun);
+ sb.append(", isRescheduled=").append(isRescheduled);
+ sb.append(", accountId=").append(accountId);
+ sb.append(", tenantId=").append(tenantId);
+ sb.append(", userToken=").append(userToken);
+ sb.append(", userName='").append(userName).append('\'');
+ sb.append(", callOrigin=").append(callOrigin);
+ sb.append(", userType=").append(userType);
+ sb.append(", reasonCode='").append(reasonCode).append('\'');
+ sb.append(", comments='").append(comments).append('\'');
+ sb.append('}');
+ return sb.toString();
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ if (!super.equals(o)) {
+ return false;
+ }
+
+ final DefaultInvoiceContext that = (DefaultInvoiceContext) o;
+
+ if (isDryRun != that.isDryRun) {
+ return false;
+ }
+ if (isRescheduled != that.isRescheduled) {
+ return false;
+ }
+ if (targetDate != null ? targetDate.compareTo(that.targetDate) != 0 : that.targetDate != null) {
+ return false;
+ }
+ if (invoice != null ? !invoice.equals(that.invoice) : that.invoice != null) {
+ return false;
+ }
+ return existingInvoices != null ? existingInvoices.equals(that.existingInvoices) : that.existingInvoices == null;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = super.hashCode();
+ result = 31 * result + (targetDate != null ? targetDate.hashCode() : 0);
+ result = 31 * result + (invoice != null ? invoice.hashCode() : 0);
+ result = 31 * result + (existingInvoices != null ? existingInvoices.hashCode() : 0);
+ result = 31 * result + (isDryRun ? 1 : 0);
+ result = 31 * result + (isRescheduled ? 1 : 0);
+ return result;
+ }
+}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/api/InvoiceApiHelper.java b/invoice/src/main/java/org/killbill/billing/invoice/api/InvoiceApiHelper.java
index c4e791f..0f6c011 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/api/InvoiceApiHelper.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/api/InvoiceApiHelper.java
@@ -28,14 +28,17 @@ import java.util.UUID;
import javax.annotation.Nullable;
import javax.inject.Inject;
+import org.joda.time.DateTime;
import org.joda.time.LocalDate;
import org.killbill.billing.ErrorCode;
import org.killbill.billing.callcontext.InternalCallContext;
+import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.catalog.api.Currency;
import org.killbill.billing.invoice.InvoicePluginDispatcher;
import org.killbill.billing.invoice.dao.InvoiceDao;
import org.killbill.billing.invoice.dao.InvoiceItemModelDao;
import org.killbill.billing.invoice.dao.InvoiceModelDao;
+import org.killbill.billing.invoice.model.DefaultInvoice;
import org.killbill.billing.invoice.model.InvoiceItemFactory;
import org.killbill.billing.invoice.model.ItemAdjInvoiceItem;
import org.killbill.billing.util.UUIDs;
@@ -76,11 +79,24 @@ public class InvoiceApiHelper {
}
public List<InvoiceItem> dispatchToInvoicePluginsAndInsertItems(final UUID accountId, final boolean isDryRun, final WithAccountLock withAccountLock, final CallContext context) throws InvoiceApiException {
+ // Invoked by User API call
+ final LocalDate targetDate = null;
+ final List<Invoice> existingInvoices = null;
+ final boolean isRescheduled = false;
+
+ final InternalTenantContext internalTenantContext = internalCallContextFactory.createInternalTenantContext(accountId, context);
+ final DateTime rescheduleDate = invoicePluginDispatcher.priorCall(targetDate, existingInvoices, isDryRun, isRescheduled, context, internalTenantContext);
+ if (rescheduleDate != null) {
+ throw new InvoiceApiException(ErrorCode.INVOICE_PLUGIN_API_ABORTED, "delayed scheduling is unsupported for API calls");
+ }
+
+ boolean success = false;
GlobalLock lock = null;
+ Iterable<Invoice> invoicesForPlugins = null;
try {
lock = locker.lockWithNumberOfTries(LockerType.ACCNT_INV_PAY.toString(), accountId.toString(), invoiceConfig.getMaxGlobalLockRetries());
- final Iterable<Invoice> invoicesForPlugins = withAccountLock.prepareInvoices();
+ invoicesForPlugins = withAccountLock.prepareInvoices();
final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(accountId, context);
final List<InvoiceModelDao> invoiceModelDaos = new LinkedList<InvoiceModelDao>();
@@ -100,6 +116,8 @@ public class InvoiceApiHelper {
}
final List<InvoiceItemModelDao> createdInvoiceItems = dao.createInvoices(invoiceModelDaos, internalCallContext);
+ success = true;
+
return fromInvoiceItemModelDao(createdInvoiceItems);
} catch (final LockFailedException e) {
log.warn("Failed to process invoice items for accountId='{}'", accountId.toString(), e);
@@ -108,6 +126,15 @@ public class InvoiceApiHelper {
if (lock != null) {
lock.release();
}
+
+ if (success) {
+ for (final Invoice invoiceForPlugin : invoicesForPlugins) {
+ final DefaultInvoice refreshedInvoice = new DefaultInvoice(dao.getById(invoiceForPlugin.getId(), internalTenantContext));
+ invoicePluginDispatcher.onSuccessCall(targetDate, refreshedInvoice, existingInvoices, isDryRun, isRescheduled, context, internalTenantContext);
+ }
+ } else {
+ invoicePluginDispatcher.onFailureCall(targetDate, null, existingInvoices, isDryRun, isRescheduled, context, internalTenantContext);
+ }
}
}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/api/user/DefaultInvoiceUserApi.java b/invoice/src/main/java/org/killbill/billing/invoice/api/user/DefaultInvoiceUserApi.java
index 29784f4..29668fb 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/api/user/DefaultInvoiceUserApi.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/api/user/DefaultInvoiceUserApi.java
@@ -230,7 +230,7 @@ public class DefaultInvoiceUserApi implements InvoiceUserApi {
final CallContext context) throws InvoiceApiException {
final InternalCallContext internalContext = internalCallContextFactory.createInternalCallContext(accountId, context);
- final Invoice result = dispatcher.processAccount(true, accountId, targetDate, dryRunArguments, internalContext);
+ final Invoice result = dispatcher.processAccount(true, accountId, targetDate, dryRunArguments, false, internalContext);
if (result == null) {
throw new InvoiceApiException(ErrorCode.INVOICE_NOTHING_TO_DO, accountId, targetDate != null ? targetDate : "null");
} else {
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/dao/DefaultInvoiceDao.java b/invoice/src/main/java/org/killbill/billing/invoice/dao/DefaultInvoiceDao.java
index c51221b..1283805 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/dao/DefaultInvoiceDao.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/dao/DefaultInvoiceDao.java
@@ -1000,13 +1000,14 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
});
}
- private void notifyOfFutureBillingEvents(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory, final UUID accountId,
- final FutureAccountNotifications callbackDateTimePerSubscriptions, final InternalCallContext internalCallContext) {
-
+ private void notifyOfFutureBillingEvents(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory,
+ final UUID accountId,
+ final FutureAccountNotifications callbackDateTimePerSubscriptions,
+ final InternalCallContext internalCallContext) {
for (final LocalDate notificationDate : callbackDateTimePerSubscriptions.getNotificationsForTrigger().keySet()) {
final DateTime notificationDateTime = internalCallContext.toUTCDateTime(notificationDate);
final Set<UUID> subscriptionIds = callbackDateTimePerSubscriptions.getNotificationsForTrigger().get(notificationDate);
- nextBillingDatePoster.insertNextBillingNotificationFromTransaction(entitySqlDaoWrapperFactory, accountId, subscriptionIds, notificationDateTime, internalCallContext);
+ nextBillingDatePoster.insertNextBillingNotificationFromTransaction(entitySqlDaoWrapperFactory, accountId, subscriptionIds, notificationDateTime, callbackDateTimePerSubscriptions.isRescheduled(), internalCallContext);
}
final long dryRunNotificationTime = invoiceConfig.getDryRunNotificationSchedule(internalCallContext).getMillis();
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java b/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java
index fa42a24..71accaa 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java
@@ -116,6 +116,7 @@ import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Ordering;
@@ -214,27 +215,21 @@ public class InvoiceDispatcher {
} catch (final CatalogApiException e) {
log.warn("Failed to retrieve BillingEvents for accountId='{}'", accountId, e);
}
-
-
-
-
}
-
-
public void processSubscriptionForInvoiceGeneration(final EffectiveSubscriptionInternalEvent transition,
final InternalCallContext context) throws InvoiceApiException {
final UUID subscriptionId = transition.getSubscriptionId();
final LocalDate targetDate = context.toLocalDate(transition.getEffectiveTransitionTime());
- processSubscriptionForInvoiceGeneration(subscriptionId, targetDate, context);
+ processSubscriptionForInvoiceGeneration(subscriptionId, targetDate, false, context);
}
- public void processSubscriptionForInvoiceGeneration(final UUID subscriptionId, final LocalDate targetDate, final InternalCallContext context) throws InvoiceApiException {
- processSubscriptionInternal(subscriptionId, targetDate, false, context);
+ public void processSubscriptionForInvoiceGeneration(final UUID subscriptionId, final LocalDate targetDate, final boolean isRescheduled, final InternalCallContext context) throws InvoiceApiException {
+ processSubscriptionInternal(subscriptionId, targetDate, false, isRescheduled, context);
}
public void processSubscriptionForInvoiceNotification(final UUID subscriptionId, final LocalDate targetDate, final InternalCallContext context) throws InvoiceApiException {
- final Invoice dryRunInvoice = processSubscriptionInternal(subscriptionId, targetDate, true, context);
+ final Invoice dryRunInvoice = processSubscriptionInternal(subscriptionId, targetDate, true, false, context);
if (dryRunInvoice != null && dryRunInvoice.getBalance().compareTo(BigDecimal.ZERO) > 0) {
final InvoiceNotificationInternalEvent event = new DefaultInvoiceNotificationInternalEvent(dryRunInvoice.getAccountId(), dryRunInvoice.getBalance(), dryRunInvoice.getCurrency(),
context.toUTCDateTime(targetDate), context.getAccountRecordId(), context.getTenantRecordId(), context.getUserToken());
@@ -246,7 +241,7 @@ public class InvoiceDispatcher {
}
}
- private Invoice processSubscriptionInternal(final UUID subscriptionId, final LocalDate targetDate, final boolean dryRunForNotification, final InternalCallContext context) throws InvoiceApiException {
+ private Invoice processSubscriptionInternal(final UUID subscriptionId, final LocalDate targetDate, final boolean dryRunForNotification, final boolean isRescheduled, final InternalCallContext context) throws InvoiceApiException {
try {
if (subscriptionId == null) {
log.warn("Failed handling SubscriptionBase change.", new InvoiceApiException(ErrorCode.INVOICE_INVALID_TRANSITION));
@@ -255,7 +250,7 @@ public class InvoiceDispatcher {
final UUID accountId = subscriptionApi.getAccountIdFromSubscriptionId(subscriptionId, context);
final DryRunArguments dryRunArguments = dryRunForNotification ? TARGET_DATE_DRY_RUN_ARGUMENTS : null;
- return processAccountFromNotificationOrBusEvent(accountId, targetDate, dryRunArguments, context);
+ return processAccountFromNotificationOrBusEvent(accountId, targetDate, dryRunArguments, isRescheduled, context);
} catch (final SubscriptionBaseApiException e) {
log.warn("Failed handling SubscriptionBase change.",
new InvoiceApiException(ErrorCode.INVOICE_NO_ACCOUNT_ID_FOR_SUBSCRIPTION_ID, subscriptionId.toString()));
@@ -266,6 +261,7 @@ public class InvoiceDispatcher {
public Invoice processAccountFromNotificationOrBusEvent(final UUID accountId,
@Nullable final LocalDate targetDate,
@Nullable final DryRunArguments dryRunArguments,
+ final boolean isRescheduled,
final InternalCallContext context) throws InvoiceApiException {
if (!invoiceConfig.isInvoicingSystemEnabled(context)) {
log.warn("Invoicing system is off, parking accountId='{}'", accountId);
@@ -273,13 +269,14 @@ public class InvoiceDispatcher {
return null;
}
- return processAccount(false, accountId, targetDate, dryRunArguments, context);
+ return processAccount(false, accountId, targetDate, dryRunArguments, isRescheduled, context);
}
public Invoice processAccount(final boolean isApiCall,
final UUID accountId,
@Nullable final LocalDate targetDate,
@Nullable final DryRunArguments dryRunArguments,
+ final boolean isRescheduled,
final InternalCallContext context) throws InvoiceApiException {
boolean parkedAccount = false;
try {
@@ -296,7 +293,7 @@ public class InvoiceDispatcher {
try {
lock = locker.lockWithNumberOfTries(LockerType.ACCNT_INV_PAY.toString(), accountId.toString(), invoiceConfig.getMaxGlobalLockRetries());
- return processAccountWithLock(parkedAccount, accountId, targetDate, dryRunArguments, context);
+ return processAccountWithLock(parkedAccount, accountId, targetDate, dryRunArguments, isRescheduled, context);
} catch (final LockFailedException e) {
log.warn("Failed to process invoice for accountId='{}', targetDate='{}'", accountId.toString(), targetDate, e);
} finally {
@@ -311,6 +308,7 @@ public class InvoiceDispatcher {
final UUID accountId,
@Nullable final LocalDate inputTargetDateMaybeNull,
@Nullable final DryRunArguments dryRunArguments,
+ final boolean isRescheduled,
final InternalCallContext context) throws InvoiceApiException {
final boolean isDryRun = dryRunArguments != null;
final boolean upcomingInvoiceDryRun = isDryRun && DryRunType.UPCOMING_INVOICE.equals(dryRunArguments.getDryRunType());
@@ -340,9 +338,9 @@ public class InvoiceDispatcher {
return new DefaultInvoice(input);
}
}));
- Invoice invoice;
+ final Invoice invoice;
if (!isDryRun) {
- invoice = processAccountWithLockAndInputTargetDate(accountId, inputTargetDate, billingEvents, existingInvoices, false, context);
+ invoice = processAccountWithLockAndInputTargetDate(accountId, inputTargetDate, billingEvents, existingInvoices, false, isRescheduled, context);
if (parkedAccount) {
try {
log.info("Illegal invoicing state fixed for accountId='{}', unparking account", accountId);
@@ -392,6 +390,10 @@ public class InvoiceDispatcher {
log.warn("Failed to retrieve BillingEvents for accountId='{}', dryRunArguments='{}'", accountId, dryRunArguments, e);
return null;
} catch (final InvoiceApiException e) {
+ if (e.getCode() == ErrorCode.INVOICE_PLUGIN_API_ABORTED.getCode()) {
+ return null;
+ }
+
if (e.getCode() == ErrorCode.UNEXPECTED_ERROR.getCode() && !isDryRun) {
log.warn("Illegal invoicing state detected for accountId='{}', dryRunArguments='{}', parking account", accountId, dryRunArguments, e);
parkAccount(accountId, context);
@@ -428,7 +430,7 @@ public class InvoiceDispatcher {
private Invoice processDryRun_UPCOMING_INVOICE_Invoice(final UUID accountId, final List<LocalDate> allCandidateTargetDates, final BillingEventSet billingEvents, final List<Invoice> existingInvoices, final InternalCallContext context) throws InvoiceApiException {
for (final LocalDate curTargetDate : allCandidateTargetDates) {
- final Invoice invoice = processAccountWithLockAndInputTargetDate(accountId, curTargetDate, billingEvents, existingInvoices, true, context);
+ final Invoice invoice = processAccountWithLockAndInputTargetDate(accountId, curTargetDate, billingEvents, existingInvoices, true, false, context);
if (invoice != null) {
return invoice;
}
@@ -459,14 +461,14 @@ public class InvoiceDispatcher {
// Generate a dryRun invoice for such date if required in such a way that dryRun invoice on our targetDate only contains items that we expect to see
final Invoice additionalInvoice = prevLocalDate != null ?
- processAccountWithLockAndInputTargetDate(accountId, prevLocalDate, billingEvents, existingInvoices, true, context) :
+ processAccountWithLockAndInputTargetDate(accountId, prevLocalDate, billingEvents, existingInvoices, true, false, context) :
null;
final List<Invoice> augmentedExistingInvoices = additionalInvoice != null ?
new ImmutableList.Builder().addAll(existingInvoices).add(additionalInvoice).build() :
existingInvoices;
- final Invoice targetInvoice = processAccountWithLockAndInputTargetDate(accountId, targetDate, billingEvents, augmentedExistingInvoices, true, context);
+ final Invoice targetInvoice = processAccountWithLockAndInputTargetDate(accountId, targetDate, billingEvents, augmentedExistingInvoices, true, false, context);
// If our targetDate -- user specified -- did not align with any boundary, we return previous 'additionalInvoice' invoice
return targetInvoice != null ? targetInvoice : additionalInvoice;
}
@@ -508,12 +510,27 @@ public class InvoiceDispatcher {
final BillingEventSet billingEvents,
final List<Invoice> existingInvoices,
final boolean isDryRun,
+ final boolean isRescheduled,
final InternalCallContext internalCallContext) throws InvoiceApiException {
+ final CallContext callContext = buildCallContext(internalCallContext);
+
final ImmutableAccountData account;
try {
account = accountApi.getImmutableAccountDataById(accountId, internalCallContext);
} catch (final AccountApiException e) {
log.error("Unable to generate invoice for accountId='{}', a future notification has NOT been recorded", accountId, e);
+ invoicePluginDispatcher.onFailureCall(targetDate, null, existingInvoices, isDryRun, isRescheduled, callContext, internalCallContext);
+ return null;
+ }
+
+ final DateTime rescheduleDate = invoicePluginDispatcher.priorCall(targetDate, existingInvoices, isDryRun, isRescheduled, callContext, internalCallContext);
+ if (rescheduleDate != null) {
+ if (isDryRun) {
+ log.warn("Ignoring rescheduleDate='{}', delayed scheduling is unsupported in dry-run", rescheduleDate);
+ } else {
+ final FutureAccountNotifications futureAccountNotifications = createNextFutureNotificationDate(rescheduleDate, billingEvents, internalCallContext);
+ commitInvoiceAndSetFutureNotifications(account, null, futureAccountNotifications, internalCallContext);
+ }
return null;
}
@@ -525,6 +542,8 @@ public class InvoiceDispatcher {
// If invoice comes back null, there is nothing new to generate, we can bail early
if (invoice == null) {
+ invoicePluginDispatcher.onSuccessCall(targetDate, null, existingInvoices, isDryRun, isRescheduled, callContext, internalCallContext);
+
if (isDryRun) {
log.info("Generated null dryRun invoice for accountId='{}', targetDate='{}'", accountId, targetDate);
} else {
@@ -551,7 +570,6 @@ public class InvoiceDispatcher {
//
// Ask external invoice plugins if additional items (tax, etc) shall be added to the invoice
//
- final CallContext callContext = buildCallContext(internalCallContext);
final List<InvoiceItem> additionalInvoiceItemsFromPlugins = invoicePluginDispatcher.getAdditionalInvoiceItems(tmpInvoiceForInvoicePlugins, isDryRun, callContext, internalCallContext);
if (additionalInvoiceItemsFromPlugins.isEmpty()) {
// PERF: avoid re-computing the CBA if no change was made
@@ -559,7 +577,6 @@ public class InvoiceDispatcher {
invoice.addInvoiceItem(cbaItemPreInvoicePlugins);
}
} else {
-
// Add or update items from generated invoice
for (final InvoiceItem cur : additionalInvoiceItemsFromPlugins) {
final InvoiceItem exitingItem = Iterables.tryFind(tmpInvoiceForInvoicePlugins.getInvoiceItems(), new Predicate<InvoiceItem>() {
@@ -605,7 +622,6 @@ public class InvoiceDispatcher {
}
if (!isDryRun) {
-
// Compute whether this is a new invoice object (or just some adjustments on an existing invoice), and extract invoiceIds for later use
final Set<UUID> uniqueInvoiceIds = getUniqueInvoiceIds(invoice);
final boolean isRealInvoiceWithItems = uniqueInvoiceIds.remove(invoice.getId());
@@ -634,6 +650,13 @@ public class InvoiceDispatcher {
if (!isDryRun && !success) {
commitInvoiceAndSetFutureNotifications(account, null, futureAccountNotifications, internalCallContext);
}
+
+ if (success) {
+ final DefaultInvoice refreshedInvoice = new DefaultInvoice(invoiceDao.getById(invoice.getId(), internalCallContext));
+ invoicePluginDispatcher.onSuccessCall(targetDate, refreshedInvoice, existingInvoices, isDryRun, isRescheduled, callContext, internalCallContext);
+ } else {
+ invoicePluginDispatcher.onFailureCall(targetDate, invoice, existingInvoices, isDryRun, isRescheduled, callContext, internalCallContext);
+ }
}
return invoice;
@@ -657,17 +680,37 @@ public class InvoiceDispatcher {
return generator.generateInvoice(account, billingEvents, existingInvoices, targetInvoiceId, targetDate, account.getCurrency(), context);
}
+ private FutureAccountNotifications createNextFutureNotificationDate(final DateTime rescheduleDate, final BillingEventSet billingEvents, final InternalCallContext context) {
+ final FutureAccountNotificationsBuilder notificationsBuilder = new FutureAccountNotificationsBuilder();
+ notificationsBuilder.setRescheduled(true);
+
+ final Set<UUID> subscriptionIds = ImmutableSet.<UUID>copyOf(Iterables.<BillingEvent, UUID>transform(billingEvents,
+ new Function<BillingEvent, UUID>() {
+ @Override
+ public UUID apply(final BillingEvent billingEvent) {
+ return billingEvent.getSubscription().getId();
+ }
+ }));
+ populateNextFutureNotificationDate(rescheduleDate, subscriptionIds, notificationsBuilder, context);
+
+ // Even though a plugin forced us to reschedule the invoice generation, honor the dry run notifications settings
+ populateNextFutureDryRunNotificationDate(billingEvents, notificationsBuilder, context);
+ return notificationsBuilder.build();
+ }
- private FutureAccountNotifications createNextFutureNotificationDate(final InvoiceWithMetadata invoiceWithMetadata, final BillingEventSet billingEvents, final InternalCallContext context) {
+ private void populateNextFutureNotificationDate(final DateTime notificationDateTime, final Set<UUID> subscriptionIds, final FutureAccountNotificationsBuilder notificationsBuilder, final InternalCallContext context) {
+ final LocalDate notificationDate = context.toLocalDate(notificationDateTime);
+ notificationsBuilder.setNotificationListForTrigger(ImmutableMap.<LocalDate, Set<UUID>>of(notificationDate, subscriptionIds));
+ }
+ private FutureAccountNotifications createNextFutureNotificationDate(final InvoiceWithMetadata invoiceWithMetadata, final BillingEventSet billingEvents, final InternalCallContext context) {
final FutureAccountNotificationsBuilder notificationsBuilder = new FutureAccountNotificationsBuilder();
populateNextFutureNotificationDate(invoiceWithMetadata, notificationsBuilder);
populateNextFutureDryRunNotificationDate(billingEvents, notificationsBuilder, context);
return notificationsBuilder.build();
}
-
private void populateNextFutureNotificationDate(final InvoiceWithMetadata invoiceWithMetadata, final FutureAccountNotificationsBuilder notificationsBuilder) {
final Map<LocalDate, Set<UUID>> notificationListForTrigger = new HashMap<LocalDate, Set<UUID>>();
@@ -857,14 +900,16 @@ public class InvoiceDispatcher {
private final Map<LocalDate, Set<UUID>> notificationListForTrigger;
private final Map<LocalDate, Set<UUID>> notificationListForDryRun;
+ private final boolean isRescheduled;
public FutureAccountNotifications() {
- this(ImmutableMap.<LocalDate, Set<UUID>>of(), ImmutableMap.<LocalDate, Set<UUID>>of());
+ this(ImmutableMap.<LocalDate, Set<UUID>>of(), ImmutableMap.<LocalDate, Set<UUID>>of(), false);
}
- public FutureAccountNotifications(final Map<LocalDate, Set<UUID>> notificationListForTrigger, final Map<LocalDate, Set<UUID>> notificationListForDryRun) {
+ public FutureAccountNotifications(final Map<LocalDate, Set<UUID>> notificationListForTrigger, final Map<LocalDate, Set<UUID>> notificationListForDryRun, final boolean isRescheduled) {
this.notificationListForTrigger = notificationListForTrigger;
this.notificationListForDryRun = notificationListForDryRun;
+ this.isRescheduled = isRescheduled;
}
public Map<LocalDate, Set<UUID>> getNotificationsForTrigger() {
@@ -875,12 +920,15 @@ public class InvoiceDispatcher {
return notificationListForDryRun;
}
-
+ public boolean isRescheduled() {
+ return isRescheduled;
+ }
public static class FutureAccountNotificationsBuilder {
private Map<LocalDate, Set<UUID>> notificationListForTrigger;
private Map<LocalDate, Set<UUID>> notificationListForDryRun;
+ private boolean isRescheduled = false;
public FutureAccountNotificationsBuilder() {
}
@@ -893,6 +941,10 @@ public class InvoiceDispatcher {
this.notificationListForDryRun = notificationListForDryRun;
}
+ public void setRescheduled(final boolean rescheduled) {
+ isRescheduled = rescheduled;
+ }
+
public Map<LocalDate, Set<UUID>> getNotificationListForTrigger() {
return MoreObjects.firstNonNull(notificationListForTrigger, ImmutableMap.<LocalDate, Set<UUID>>of());
}
@@ -901,8 +953,12 @@ public class InvoiceDispatcher {
return MoreObjects.firstNonNull(notificationListForDryRun, ImmutableMap.<LocalDate, Set<UUID>>of());
}
+ public boolean isRescheduled() {
+ return isRescheduled;
+ }
+
public FutureAccountNotifications build() {
- return new FutureAccountNotifications(getNotificationListForTrigger(), getNotificationListForDryRun());
+ return new FutureAccountNotifications(getNotificationListForTrigger(), getNotificationListForDryRun(), isRescheduled());
}
}
}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/InvoiceListener.java b/invoice/src/main/java/org/killbill/billing/invoice/InvoiceListener.java
index eee8749..a57f621 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/InvoiceListener.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/InvoiceListener.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2017 Groupon, Inc
- * Copyright 2014-2017 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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
@@ -109,7 +109,7 @@ public class InvoiceListener extends RetryableService implements InvoiceListener
try {
final InternalCallContext context = internalCallContextFactory.createInternalCallContext(event.getSearchKey2(), event.getSearchKey1(), "SubscriptionBaseTransition", CallOrigin.INTERNAL, UserType.SYSTEM, event.getUserToken());
final UUID accountId = accountApi.getByRecordId(event.getSearchKey1(), context);
- dispatcher.processAccountFromNotificationOrBusEvent(accountId, null, null, context);
+ dispatcher.processAccountFromNotificationOrBusEvent(accountId, null, null, false, context);
} catch (final InvoiceApiException e) {
log.warn("Unable to process event {}", event, e);
} catch (final AccountApiException e) {
@@ -206,12 +206,10 @@ public class InvoiceListener extends RetryableService implements InvoiceListener
retryableSubscriber.handleEvent(event);
}
-
-
- public void handleNextBillingDateEvent(final UUID subscriptionId, final DateTime eventDateTime, final UUID userToken, final Long accountRecordId, final Long tenantRecordId) {
+ public void handleNextBillingDateEvent(final UUID subscriptionId, final DateTime eventDateTime, final boolean isRescheduled, final UUID userToken, final Long accountRecordId, final Long tenantRecordId) {
final InternalCallContext context = internalCallContextFactory.createInternalCallContext(tenantRecordId, accountRecordId, "Next Billing Date", CallOrigin.INTERNAL, UserType.SYSTEM, userToken);
try {
- dispatcher.processSubscriptionForInvoiceGeneration(subscriptionId, context.toLocalDate(eventDateTime), context);
+ dispatcher.processSubscriptionForInvoiceGeneration(subscriptionId, context.toLocalDate(eventDateTime), isRescheduled, context);
} catch (final InvoiceApiException e) {
log.warn("Unable to process subscriptionId='{}', eventDateTime='{}'", subscriptionId, eventDateTime, e);
}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/InvoicePluginDispatcher.java b/invoice/src/main/java/org/killbill/billing/invoice/InvoicePluginDispatcher.java
index 45b12f8..d1abded 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/InvoicePluginDispatcher.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/InvoicePluginDispatcher.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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
@@ -19,20 +19,27 @@ package org.killbill.billing.invoice;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import javax.inject.Inject;
+import org.joda.time.DateTime;
+import org.joda.time.LocalDate;
import org.killbill.billing.ErrorCode;
import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.invoice.api.DefaultInvoiceContext;
import org.killbill.billing.invoice.api.Invoice;
import org.killbill.billing.invoice.api.InvoiceApiException;
import org.killbill.billing.invoice.api.InvoiceItem;
import org.killbill.billing.invoice.api.InvoiceItemType;
import org.killbill.billing.invoice.model.DefaultInvoice;
+import org.killbill.billing.invoice.plugin.api.InvoiceContext;
import org.killbill.billing.invoice.plugin.api.InvoicePluginApi;
+import org.killbill.billing.invoice.plugin.api.PriorInvoiceResult;
import org.killbill.billing.osgi.api.OSGIServiceRegistration;
import org.killbill.billing.payment.api.PluginProperty;
import org.killbill.billing.util.callcontext.CallContext;
@@ -48,14 +55,13 @@ public class InvoicePluginDispatcher {
private static final Logger log = LoggerFactory.getLogger(InvoicePluginDispatcher.class);
public static final Collection<InvoiceItemType> ALLOWED_INVOICE_ITEM_TYPES = ImmutableList.<InvoiceItemType>of(InvoiceItemType.EXTERNAL_CHARGE,
- InvoiceItemType.ITEM_ADJ,
- InvoiceItemType.CREDIT_ADJ,
- InvoiceItemType.TAX);
+ InvoiceItemType.ITEM_ADJ,
+ InvoiceItemType.CREDIT_ADJ,
+ InvoiceItemType.TAX);
private final OSGIServiceRegistration<InvoicePluginApi> pluginRegistry;
private final InvoiceConfig invoiceConfig;
-
@Inject
public InvoicePluginDispatcher(final OSGIServiceRegistration<InvoicePluginApi> pluginRegistry,
final InvoiceConfig invoiceConfig) {
@@ -63,15 +69,102 @@ public class InvoicePluginDispatcher {
this.invoiceConfig = invoiceConfig;
}
+ public DateTime priorCall(final LocalDate targetDate, final List<Invoice> existingInvoices, final boolean isDryRun, final boolean isRescheduled, final CallContext callContext, final InternalTenantContext internalTenantContext) throws InvoiceApiException {
+ log.debug("Invoking invoice plugins priorCall: targetDate='{}', isDryRun='{}', isRescheduled='{}'", targetDate, isDryRun, isRescheduled);
+ final Map<String, InvoicePluginApi> invoicePlugins = getInvoicePlugins(internalTenantContext);
+ if (invoicePlugins.isEmpty()) {
+ return null;
+ }
+
+ DateTime earliestRescheduleDate = null;
+ final InvoiceContext invoiceContext = new DefaultInvoiceContext(targetDate, null, existingInvoices, isDryRun, isRescheduled, callContext);
+ for (final String invoicePluginName : invoicePlugins.keySet()) {
+ final PriorInvoiceResult priorInvoiceResult = invoicePlugins.get(invoicePluginName).priorCall(invoiceContext, ImmutableList.<PluginProperty>of());
+ log.debug("Invoice plugin {} returned priorInvoiceResult='{}'", invoicePluginName, priorInvoiceResult);
+ if (priorInvoiceResult == null) {
+ // Naughty plugin...
+ continue;
+ }
+
+ if (priorInvoiceResult.getRescheduleDate() != null &&
+ (earliestRescheduleDate == null || earliestRescheduleDate.compareTo(priorInvoiceResult.getRescheduleDate()) > 0)) {
+ earliestRescheduleDate = priorInvoiceResult.getRescheduleDate();
+ log.info("Invoice plugin {} rescheduled invoice generation to {} for targetDate {}", invoicePluginName, earliestRescheduleDate, targetDate);
+ }
+
+ if (priorInvoiceResult.isAborted()) {
+ log.info("Invoice plugin {} aborted invoice generation for targetDate {}", invoicePluginName, targetDate);
+ throw new InvoiceApiException(ErrorCode.INVOICE_PLUGIN_API_ABORTED, invoicePluginName);
+ }
+ }
+
+ return earliestRescheduleDate;
+ }
+
+ public void onSuccessCall(final LocalDate targetDate,
+ final DefaultInvoice invoice,
+ final List<Invoice> existingInvoices,
+ final boolean isDryRun,
+ final boolean isRescheduled,
+ final CallContext callContext,
+ final InternalTenantContext internalTenantContext) {
+ log.debug("Invoking invoice plugins onSuccessCall: targetDate='{}', isDryRun='{}', isRescheduled='{}', invoice='{}'", targetDate, isDryRun, isRescheduled, invoice);
+ onCompletionCall(true, targetDate, invoice, existingInvoices, isDryRun, isRescheduled, callContext, internalTenantContext);
+ }
+
+ public void onFailureCall(final LocalDate targetDate,
+ final DefaultInvoice invoice,
+ final List<Invoice> existingInvoices,
+ final boolean isDryRun,
+ final boolean isRescheduled,
+ final CallContext callContext,
+ final InternalTenantContext internalTenantContext) {
+ log.debug("Invoking invoice plugins onFailureCall: targetDate='{}', isDryRun='{}', isRescheduled='{}', invoice='{}'", targetDate, isDryRun, isRescheduled, invoice);
+ onCompletionCall(false, targetDate, invoice, existingInvoices, isDryRun, isRescheduled, callContext, internalTenantContext);
+ }
+
+ private void onCompletionCall(final boolean isSuccess,
+ final LocalDate targetDate,
+ final DefaultInvoice originalInvoice,
+ final List<Invoice> existingInvoices,
+ final boolean isDryRun,
+ final boolean isRescheduled,
+ final CallContext callContext,
+ final InternalTenantContext internalTenantContext) {
+ final Collection<InvoicePluginApi> invoicePlugins = getInvoicePlugins(internalTenantContext).values();
+ if (invoicePlugins.isEmpty()) {
+ return;
+ }
+
+ // We clone the original invoice so plugins don't remove/add items
+ final Invoice clonedInvoice = (Invoice) originalInvoice.clone();
+ final InvoiceContext invoiceContext = new DefaultInvoiceContext(targetDate, clonedInvoice, existingInvoices, isDryRun, isRescheduled, callContext);
+
+ for (final InvoicePluginApi invoicePlugin : invoicePlugins) {
+ if (isSuccess) {
+ invoicePlugin.onSuccessCall(invoiceContext, ImmutableList.<PluginProperty>of());
+ } else {
+ invoicePlugin.onFailureCall(invoiceContext, ImmutableList.<PluginProperty>of());
+ }
+ }
+ }
+
//
// If we have multiple plugins there is a question of plugin ordering and also a 'product' questions to decide whether
// subsequent plugins should have access to items added by previous plugins
//
public List<InvoiceItem> getAdditionalInvoiceItems(final Invoice originalInvoice, final boolean isDryRun, final CallContext callContext, final InternalTenantContext tenantContext) throws InvoiceApiException {
+ log.debug("Invoking invoice plugins getAdditionalInvoiceItems: isDryRun='{}', originalInvoice='{}'", isDryRun, originalInvoice);
+
+ final List<InvoiceItem> additionalInvoiceItems = new LinkedList<InvoiceItem>();
+
+ final Collection<InvoicePluginApi> invoicePlugins = getInvoicePlugins(tenantContext).values();
+ if (invoicePlugins.isEmpty()) {
+ return additionalInvoiceItems;
+ }
+
// We clone the original invoice so plugins don't remove/add items
final Invoice clonedInvoice = (Invoice) ((DefaultInvoice) originalInvoice).clone();
- final List<InvoiceItem> additionalInvoiceItems = new LinkedList<InvoiceItem>();
- final List<InvoicePluginApi> invoicePlugins = getInvoicePlugins(tenantContext);
for (final InvoicePluginApi invoicePlugin : invoicePlugins) {
final List<InvoiceItem> items = invoicePlugin.getAdditionalInvoiceItems(clonedInvoice, isDryRun, ImmutableList.<PluginProperty>of(), callContext);
if (items != null) {
@@ -91,15 +184,13 @@ public class InvoicePluginDispatcher {
}
}
- private List<InvoicePluginApi> getInvoicePlugins(final InternalTenantContext tenantContext) {
-
-
+ private Map<String, InvoicePluginApi> getInvoicePlugins(final InternalTenantContext tenantContext) {
final Collection<String> resultingPluginList = getResultingPluginNameList(tenantContext);
- final List<InvoicePluginApi> invoicePlugins = new ArrayList<InvoicePluginApi>();
+ final Map<String, InvoicePluginApi> invoicePlugins = new HashMap<String, InvoicePluginApi>();
for (final String name : resultingPluginList) {
final InvoicePluginApi serviceForName = pluginRegistry.getServiceForName(name);
- invoicePlugins.add(serviceForName);
+ invoicePlugins.put(name, serviceForName);
}
return invoicePlugins;
}
@@ -112,7 +203,7 @@ public class InvoicePluginDispatcher {
if (configuredPlugins == null || configuredPlugins.isEmpty()) {
return registeredPlugins;
} else {
- final List<String> result = new ArrayList<String>(configuredPlugins.size());
+ final List<String> result = new ArrayList<String>(configuredPlugins.size());
for (final String name : configuredPlugins) {
if (pluginRegistry.getServiceForName(name) != null) {
result.add(name);
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/InvoiceTagHandler.java b/invoice/src/main/java/org/killbill/billing/invoice/InvoiceTagHandler.java
index afb7750..98eb563 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/InvoiceTagHandler.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/InvoiceTagHandler.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2017 Groupon, Inc
- * Copyright 2014-2017 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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
@@ -107,7 +107,7 @@ public class InvoiceTagHandler extends RetryableService implements KillbillServi
private void processUnpaid_AUTO_INVOICING_OFF_invoices(final UUID accountId, final InternalCallContext context) {
try {
- dispatcher.processAccountFromNotificationOrBusEvent(accountId, null, null, context);
+ dispatcher.processAccountFromNotificationOrBusEvent(accountId, null, null, false, context);
} catch (final InvoiceApiException e) {
log.warn("Failed to process tag removal AUTO_INVOICING_OFF for accountId='{}'", accountId, e);
}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/notification/DefaultNextBillingDateNotifier.java b/invoice/src/main/java/org/killbill/billing/invoice/notification/DefaultNextBillingDateNotifier.java
index 47dc673..98ee28a 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/notification/DefaultNextBillingDateNotifier.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/notification/DefaultNextBillingDateNotifier.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2017 Groupon, Inc
- * Copyright 2014-2017 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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
@@ -94,9 +94,10 @@ public class DefaultNextBillingDateNotifier extends RetryableService implements
key.isDryRunForInvoiceNotification()) {
processEventForInvoiceNotification(firstSubscriptionId, targetDate, userToken, accountRecordId, tenantRecordId);
} else {
- processEventForInvoiceGeneration(firstSubscriptionId, targetDate, userToken, accountRecordId, tenantRecordId);
+ final boolean isRescheduled = key.isRescheduled() == Boolean.TRUE; // Handle null value (old versions < 0.19.7)
+ processEventForInvoiceGeneration(firstSubscriptionId, targetDate, isRescheduled, userToken, accountRecordId, tenantRecordId);
}
- } catch (SubscriptionBaseApiException e) {
+ } catch (final SubscriptionBaseApiException e) {
log.warn("Error retrieving subscriptionId='{}'", firstSubscriptionId, e);
}
}
@@ -127,8 +128,8 @@ public class DefaultNextBillingDateNotifier extends RetryableService implements
super.stop();
}
- private void processEventForInvoiceGeneration(final UUID subscriptionId, final DateTime eventDateTime, final UUID userToken, final Long accountRecordId, final Long tenantRecordId) {
- listener.handleNextBillingDateEvent(subscriptionId, eventDateTime, userToken, accountRecordId, tenantRecordId);
+ private void processEventForInvoiceGeneration(final UUID subscriptionId, final DateTime eventDateTime, final boolean isRescheduled, final UUID userToken, final Long accountRecordId, final Long tenantRecordId) {
+ listener.handleNextBillingDateEvent(subscriptionId, eventDateTime, isRescheduled, userToken, accountRecordId, tenantRecordId);
}
private void processEventForInvoiceNotification(final UUID subscriptionId, final DateTime eventDateTime, final UUID userToken, final Long accountRecordId, final Long tenantRecordId) {
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/notification/DefaultNextBillingDatePoster.java b/invoice/src/main/java/org/killbill/billing/invoice/notification/DefaultNextBillingDatePoster.java
index 6cfcb83..92b816a 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/notification/DefaultNextBillingDatePoster.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/notification/DefaultNextBillingDatePoster.java
@@ -35,7 +35,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Joiner;
-import com.google.common.collect.ImmutableList;
import com.google.inject.Inject;
public class DefaultNextBillingDatePoster implements NextBillingDatePoster {
@@ -56,8 +55,9 @@ public class DefaultNextBillingDatePoster implements NextBillingDatePoster {
final UUID accountId,
final Iterable<UUID> subscriptionIds,
final DateTime futureNotificationTime,
+ final boolean isRescheduled,
final InternalCallContext internalCallContext) {
- insertNextBillingFromTransactionInternal(entitySqlDaoWrapperFactory, subscriptionIds, Boolean.FALSE, futureNotificationTime, futureNotificationTime, internalCallContext);
+ insertNextBillingFromTransactionInternal(entitySqlDaoWrapperFactory, subscriptionIds, Boolean.FALSE, isRescheduled, futureNotificationTime, futureNotificationTime, internalCallContext);
}
@Override
@@ -67,12 +67,13 @@ public class DefaultNextBillingDatePoster implements NextBillingDatePoster {
final DateTime futureNotificationTime,
final DateTime targetDate,
final InternalCallContext internalCallContext) {
- insertNextBillingFromTransactionInternal(entitySqlDaoWrapperFactory, subscriptionIds, Boolean.TRUE, futureNotificationTime, targetDate, internalCallContext);
+ insertNextBillingFromTransactionInternal(entitySqlDaoWrapperFactory, subscriptionIds, Boolean.TRUE, null, futureNotificationTime, targetDate, internalCallContext);
}
private void insertNextBillingFromTransactionInternal(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory,
final Iterable<UUID> subscriptionIds,
final Boolean isDryRunForInvoiceNotification,
+ final Boolean isRescheduled,
final DateTime futureNotificationTime,
final DateTime targetDate,
final InternalCallContext internalCallContext) {
@@ -111,7 +112,7 @@ public class DefaultNextBillingDatePoster implements NextBillingDatePoster {
if (existingNotificationForEffectiveDate == null) {
log.info("Queuing next billing date notification at {} for subscriptionId {}", futureNotificationTime.toString(), JOINER.join(subscriptionIds));
- final NextBillingDateNotificationKey newNotificationEvent = new NextBillingDateNotificationKey(null, subscriptionIds, targetDate, isDryRunForInvoiceNotification);
+ final NextBillingDateNotificationKey newNotificationEvent = new NextBillingDateNotificationKey(null, subscriptionIds, targetDate, isDryRunForInvoiceNotification, isRescheduled);
nextBillingQueue.recordFutureNotificationFromTransaction(entitySqlDaoWrapperFactory.getHandle().getConnection(), futureNotificationTime,
newNotificationEvent, internalCallContext.getUserToken(),
internalCallContext.getAccountRecordId(), internalCallContext.getTenantRecordId());
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/notification/NextBillingDateNotificationKey.java b/invoice/src/main/java/org/killbill/billing/invoice/notification/NextBillingDateNotificationKey.java
index 1b64c60..4f23d1d 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/notification/NextBillingDateNotificationKey.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/notification/NextBillingDateNotificationKey.java
@@ -1,7 +1,9 @@
/*
* Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 The Billing Project, LLC
*
- * Ning licenses this file to you under the Apache License, version 2.0
+ * 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:
*
@@ -29,27 +31,31 @@ import com.google.common.collect.Iterables;
public class NextBillingDateNotificationKey extends DefaultUUIDNotificationKey {
- private Boolean isDryRunForInvoiceNotification;
- private DateTime targetDate;
+ private final Boolean isDryRunForInvoiceNotification;
+ private final Boolean isRescheduled;
+ private final DateTime targetDate;
private final Iterable<UUID> uuidKeys;
@JsonCreator
public NextBillingDateNotificationKey(@Deprecated @JsonProperty("uuidKey") final UUID uuidKey,
@JsonProperty("uuidKeys") final Iterable<UUID> uuidKeys,
@JsonProperty("targetDate") final DateTime targetDate,
- @JsonProperty("isDryRunForInvoiceNotification") final Boolean isDryRunForInvoiceNotification) {
+ @JsonProperty("isDryRunForInvoiceNotification") final Boolean isDryRunForInvoiceNotification,
+ @JsonProperty("isRescheduled") final Boolean isRescheduled) {
super(uuidKey);
this.uuidKeys = uuidKeys;
this.targetDate = targetDate;
this.isDryRunForInvoiceNotification = isDryRunForInvoiceNotification;
+ this.isRescheduled = isRescheduled;
}
- public NextBillingDateNotificationKey(NextBillingDateNotificationKey existing,
+ public NextBillingDateNotificationKey(final NextBillingDateNotificationKey existing,
final Iterable<UUID> newUUIDKeys) {
super(null);
this.uuidKeys = ImmutableSet.copyOf(Iterables.concat(existing.getUuidKeys(), newUUIDKeys));
this.targetDate = existing.getTargetDate();
this.isDryRunForInvoiceNotification = existing.isDryRunForInvoiceNotification();
+ this.isRescheduled = existing.isRescheduled();
}
@JsonProperty("isDryRunForInvoiceNotification")
@@ -57,6 +63,11 @@ public class NextBillingDateNotificationKey extends DefaultUUIDNotificationKey {
return isDryRunForInvoiceNotification;
}
+ @JsonProperty("isRescheduled")
+ public Boolean isRescheduled() {
+ return isRescheduled;
+ }
+
public DateTime getTargetDate() {
return targetDate;
}
@@ -69,4 +80,51 @@ public class NextBillingDateNotificationKey extends DefaultUUIDNotificationKey {
return uuidKeys;
}
}
+
+ @Override
+ public String toString() {
+ final StringBuffer sb = new StringBuffer("NextBillingDateNotificationKey{");
+ sb.append("isDryRunForInvoiceNotification=").append(isDryRunForInvoiceNotification);
+ sb.append(", isRescheduled=").append(isRescheduled);
+ sb.append(", targetDate=").append(targetDate);
+ sb.append(", uuidKeys=").append(uuidKeys);
+ sb.append('}');
+ return sb.toString();
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ if (!super.equals(o)) {
+ return false;
+ }
+
+ final NextBillingDateNotificationKey that = (NextBillingDateNotificationKey) o;
+
+ if (isDryRunForInvoiceNotification != null ? !isDryRunForInvoiceNotification.equals(that.isDryRunForInvoiceNotification) : that.isDryRunForInvoiceNotification != null) {
+ return false;
+ }
+ if (isRescheduled != null ? !isRescheduled.equals(that.isRescheduled) : that.isRescheduled != null) {
+ return false;
+ }
+ if (targetDate != null ? targetDate.compareTo(that.targetDate) != 0 : that.targetDate != null) {
+ return false;
+ }
+ return uuidKeys != null ? uuidKeys.equals(that.uuidKeys) : that.uuidKeys == null;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = super.hashCode();
+ result = 31 * result + (isDryRunForInvoiceNotification != null ? isDryRunForInvoiceNotification.hashCode() : 0);
+ result = 31 * result + (isRescheduled != null ? isRescheduled.hashCode() : 0);
+ result = 31 * result + (targetDate != null ? targetDate.hashCode() : 0);
+ result = 31 * result + (uuidKeys != null ? uuidKeys.hashCode() : 0);
+ return result;
+ }
}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/notification/NextBillingDatePoster.java b/invoice/src/main/java/org/killbill/billing/invoice/notification/NextBillingDatePoster.java
index d2dce63..f4b4a03 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/notification/NextBillingDatePoster.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/notification/NextBillingDatePoster.java
@@ -27,7 +27,7 @@ import org.killbill.billing.util.entity.dao.EntitySqlDaoWrapperFactory;
public interface NextBillingDatePoster {
void insertNextBillingNotificationFromTransaction(EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory, UUID accountId,
- Iterable<UUID> subscriptionId, DateTime futureNotificationTime, InternalCallContext internalCallContext);
+ Iterable<UUID> subscriptionId, DateTime futureNotificationTime, final boolean isRescheduled, InternalCallContext internalCallContext);
void insertNextBillingDryRunNotificationFromTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory, final UUID accountId,
final Iterable<UUID> subscriptionId, final DateTime futureNotificationTime, final DateTime targetDate, final InternalCallContext internalCallContext);
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/provider/DefaultNoOpInvoiceProviderPlugin.java b/invoice/src/main/java/org/killbill/billing/invoice/provider/DefaultNoOpInvoiceProviderPlugin.java
index 503d5e9..30e4371 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/provider/DefaultNoOpInvoiceProviderPlugin.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/provider/DefaultNoOpInvoiceProviderPlugin.java
@@ -1,7 +1,8 @@
/*
- * Copyright 2014 Groupon, Inc
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 The Billing Project, LLC
*
- * Groupon licenses this file to you under the Apache License, version 2.0
+ * 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:
*
@@ -20,10 +21,13 @@ import java.util.List;
import org.killbill.billing.invoice.api.Invoice;
import org.killbill.billing.invoice.api.InvoiceItem;
+import org.killbill.billing.invoice.plugin.api.InvoiceContext;
import org.killbill.billing.invoice.plugin.api.NoOpInvoicePluginApi;
+import org.killbill.billing.invoice.plugin.api.OnFailureInvoiceResult;
+import org.killbill.billing.invoice.plugin.api.OnSuccessInvoiceResult;
+import org.killbill.billing.invoice.plugin.api.PriorInvoiceResult;
import org.killbill.billing.payment.api.PluginProperty;
import org.killbill.billing.util.callcontext.CallContext;
-import org.killbill.clock.Clock;
import com.google.common.collect.ImmutableList;
import com.google.inject.Inject;
@@ -35,7 +39,22 @@ public class DefaultNoOpInvoiceProviderPlugin implements NoOpInvoicePluginApi {
}
@Override
- public List<InvoiceItem> getAdditionalInvoiceItems(final Invoice invoice, final boolean isDryRun, final Iterable<PluginProperty> properties, CallContext context) {
+ public PriorInvoiceResult priorCall(final InvoiceContext invoiceContext, final Iterable<PluginProperty> properties) {
+ return null;
+ }
+
+ @Override
+ public List<InvoiceItem> getAdditionalInvoiceItems(final Invoice invoice, final boolean isDryRun, final Iterable<PluginProperty> properties, final CallContext context) {
return ImmutableList.<InvoiceItem>of();
}
+
+ @Override
+ public OnSuccessInvoiceResult onSuccessCall(final InvoiceContext invoiceContext, final Iterable<PluginProperty> properties) {
+ return null;
+ }
+
+ @Override
+ public OnFailureInvoiceResult onFailureCall(final InvoiceContext invoiceContext, final Iterable<PluginProperty> properties) {
+ return null;
+ }
}
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/notification/TestNextBillingDateNotificationKey.java b/invoice/src/test/java/org/killbill/billing/invoice/notification/TestNextBillingDateNotificationKey.java
index b837bdf..0e6e941 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/notification/TestNextBillingDateNotificationKey.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/notification/TestNextBillingDateNotificationKey.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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
@@ -38,7 +38,7 @@ public class TestNextBillingDateNotificationKey {
final DateTime targetDate = new DateTime();
final Boolean isDryRunForInvoiceNotification = Boolean.FALSE;
- final NextBillingDateNotificationKey key = new NextBillingDateNotificationKey(uuidKey, null, targetDate, isDryRunForInvoiceNotification);
+ final NextBillingDateNotificationKey key = new NextBillingDateNotificationKey(uuidKey, null, targetDate, isDryRunForInvoiceNotification, false);
final String json = mapper.writeValueAsString(key);
final NextBillingDateNotificationKey result = mapper.readValue(json, NextBillingDateNotificationKey.class);
@@ -56,7 +56,7 @@ public class TestNextBillingDateNotificationKey {
final DateTime targetDate = new DateTime();
final Boolean isDryRunForInvoiceNotification = Boolean.FALSE;
- final NextBillingDateNotificationKey key = new NextBillingDateNotificationKey(null, ImmutableList.of(uuidKey1, uuidKey2), targetDate, isDryRunForInvoiceNotification);
+ final NextBillingDateNotificationKey key = new NextBillingDateNotificationKey(null, ImmutableList.of(uuidKey1, uuidKey2), targetDate, isDryRunForInvoiceNotification, false);
final String json = mapper.writeValueAsString(key);
final NextBillingDateNotificationKey result = mapper.readValue(json, NextBillingDateNotificationKey.class);
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/notification/TestNextBillingDateNotifier.java b/invoice/src/test/java/org/killbill/billing/invoice/notification/TestNextBillingDateNotifier.java
index b620fab..fe11d11 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/notification/TestNextBillingDateNotifier.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/notification/TestNextBillingDateNotifier.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2017 Groupon, Inc
- * Copyright 2014-2017 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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
@@ -49,7 +49,7 @@ public class TestNextBillingDateNotifier extends InvoiceTestSuiteWithEmbeddedDB
final NotificationQueue nextBillingQueue = notificationQueueService.getNotificationQueue(DefaultInvoiceService.INVOICE_SERVICE_NAME, DefaultNextBillingDateNotifier.NEXT_BILLING_DATE_NOTIFIER_QUEUE);
- nextBillingQueue.recordFutureNotification(now, new NextBillingDateNotificationKey(null, ImmutableList.<UUID>of(subscriptionId), now, Boolean.FALSE), internalCallContext.getUserToken(), accountRecordId, internalCallContext.getTenantRecordId());
+ nextBillingQueue.recordFutureNotification(now, new NextBillingDateNotificationKey(null, ImmutableList.<UUID>of(subscriptionId), now, Boolean.FALSE, Boolean.FALSE), internalCallContext.getUserToken(), accountRecordId, internalCallContext.getTenantRecordId());
// Move time in the future after the notification effectiveDate
clock.setDeltaFromReality(3000);
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceDispatcher.java b/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceDispatcher.java
index ea9001c..c4db83f 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceDispatcher.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceDispatcher.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2016 Groupon, Inc
- * Copyright 2014-2016 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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
@@ -104,21 +104,21 @@ public class TestInvoiceDispatcher extends InvoiceTestSuiteWithEmbeddedDB {
internalCallContextFactory, invoicePluginDispatcher, locker, busService.getBus(),
notificationQueueService, invoiceConfig, clock, parkedAccountsManager);
- Invoice invoice = dispatcher.processAccountFromNotificationOrBusEvent(accountId, target, new DryRunFutureDateArguments(), context);
+ Invoice invoice = dispatcher.processAccountFromNotificationOrBusEvent(accountId, target, new DryRunFutureDateArguments(), false, context);
Assert.assertNotNull(invoice);
List<InvoiceModelDao> invoices = invoiceDao.getInvoicesByAccount(false, context);
Assert.assertEquals(invoices.size(), 0);
// Try it again to double check
- invoice = dispatcher.processAccountFromNotificationOrBusEvent(accountId, target, new DryRunFutureDateArguments(), context);
+ invoice = dispatcher.processAccountFromNotificationOrBusEvent(accountId, target, new DryRunFutureDateArguments(), false, context);
Assert.assertNotNull(invoice);
invoices = invoiceDao.getInvoicesByAccount(false, context);
Assert.assertEquals(invoices.size(), 0);
// This time no dry run
- invoice = dispatcher.processAccountFromNotificationOrBusEvent(accountId, target, null, context);
+ invoice = dispatcher.processAccountFromNotificationOrBusEvent(accountId, target, null, false, context);
Assert.assertNotNull(invoice);
invoices = invoiceDao.getInvoicesByAccount(false, context);
@@ -194,7 +194,7 @@ public class TestInvoiceDispatcher extends InvoiceTestSuiteWithEmbeddedDB {
invoiceDao.createInvoices(ImmutableList.<InvoiceModelDao>of(invoiceModelDao), context);
try {
- dispatcher.processAccountFromNotificationOrBusEvent(accountId, target, new DryRunFutureDateArguments(), context);
+ dispatcher.processAccountFromNotificationOrBusEvent(accountId, target, new DryRunFutureDateArguments(), false, context);
Assert.fail();
} catch (final InvoiceApiException e) {
Assert.assertEquals(e.getCode(), ErrorCode.UNEXPECTED_ERROR.getCode());
@@ -205,7 +205,7 @@ public class TestInvoiceDispatcher extends InvoiceTestSuiteWithEmbeddedDB {
Assert.assertTrue(tagUserApi.getTagsForAccount(accountId, true, callContext).isEmpty());
try {
- dispatcher.processAccountFromNotificationOrBusEvent(accountId, target, null, context);
+ dispatcher.processAccountFromNotificationOrBusEvent(accountId, target, null, false, context);
Assert.fail();
} catch (final InvoiceApiException e) {
Assert.assertEquals(e.getCode(), ErrorCode.UNEXPECTED_ERROR.getCode());
@@ -218,12 +218,12 @@ public class TestInvoiceDispatcher extends InvoiceTestSuiteWithEmbeddedDB {
Assert.assertEquals(tags.get(0).getTagDefinitionId(), SystemTags.PARK_TAG_DEFINITION_ID);
// isApiCall=false
- final Invoice nullInvoice1 = dispatcher.processAccountFromNotificationOrBusEvent(accountId, target, null, context);
+ final Invoice nullInvoice1 = dispatcher.processAccountFromNotificationOrBusEvent(accountId, target, null, false, context);
Assert.assertNull(nullInvoice1);
// No dry-run and isApiCall=true
try {
- dispatcher.processAccount(true, accountId, target, null, context);
+ dispatcher.processAccount(true, accountId, target, null, false, context);
Assert.fail();
} catch (final InvoiceApiException e) {
Assert.assertEquals(e.getCode(), ErrorCode.UNEXPECTED_ERROR.getCode());
@@ -244,18 +244,18 @@ public class TestInvoiceDispatcher extends InvoiceTestSuiteWithEmbeddedDB {
});
// Dry-run and isApiCall=false: still parked
- final Invoice nullInvoice2 = dispatcher.processAccountFromNotificationOrBusEvent(accountId, target, new DryRunFutureDateArguments(), context);
+ final Invoice nullInvoice2 = dispatcher.processAccountFromNotificationOrBusEvent(accountId, target, new DryRunFutureDateArguments(), false, context);
Assert.assertNull(nullInvoice2);
// Dry-run and isApiCall=true: call goes through
- final Invoice invoice1 = dispatcher.processAccount(true, accountId, target, new DryRunFutureDateArguments(), context);
+ final Invoice invoice1 = dispatcher.processAccount(true, accountId, target, new DryRunFutureDateArguments(), false, context);
Assert.assertNotNull(invoice1);
Assert.assertEquals(invoiceDao.getInvoicesByAccount(false, context).size(), 0);
// Dry-run: still parked
Assert.assertEquals(tagUserApi.getTagsForAccount(accountId, false, callContext).size(), 1);
// No dry-run and isApiCall=true: call goes through
- final Invoice invoice2 = dispatcher.processAccount(true, accountId, target, null, context);
+ final Invoice invoice2 = dispatcher.processAccount(true, accountId, target, null, false, context);
Assert.assertNotNull(invoice2);
Assert.assertEquals(invoiceDao.getInvoicesByAccount(false, context).size(), 1);
// No dry-run: now unparked
@@ -293,7 +293,7 @@ public class TestInvoiceDispatcher extends InvoiceTestSuiteWithEmbeddedDB {
final InvoiceDispatcher dispatcher = new InvoiceDispatcher(generator, accountApi, billingApi, subscriptionApi, invoiceDao,
internalCallContextFactory, invoicePluginDispatcher, locker, busService.getBus(),
notificationQueueService, invoiceConfig, clock, parkedAccountsManager);
- final Invoice invoice = dispatcher.processAccountFromNotificationOrBusEvent(account.getId(), new LocalDate("2012-07-30"), null, context);
+ final Invoice invoice = dispatcher.processAccountFromNotificationOrBusEvent(account.getId(), new LocalDate("2012-07-30"), null, false, context);
Assert.assertNotNull(invoice);
final List<InvoiceItem> invoiceItems = invoice.getInvoiceItems();
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceHelper.java b/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceHelper.java
index f5e9bd1..30d07e7 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceHelper.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceHelper.java
@@ -238,7 +238,7 @@ public class TestInvoiceHelper {
invoiceDao, internalCallContextFactory, invoicePluginDispatcher, locker, busService.getBus(),
notificationQueueService, invoiceConfig, clock, parkedAccountsManager);
- return dispatcher.processAccountFromNotificationOrBusEvent(accountId, targetDate, dryRunArguments, internalCallContext);
+ return dispatcher.processAccountFromNotificationOrBusEvent(accountId, targetDate, dryRunArguments, false, internalCallContext);
}
public SubscriptionBase createSubscription() throws SubscriptionBaseApiException {
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceNotificationQListener.java b/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceNotificationQListener.java
index ef72b84..3d8a454 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceNotificationQListener.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceNotificationQListener.java
@@ -1,7 +1,9 @@
/*
* Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 The Billing Project, LLC
*
- * Ning licenses this file to you under the Apache License, version 2.0
+ * 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:
*
@@ -21,17 +23,16 @@ import java.util.UUID;
import javax.inject.Inject;
import org.joda.time.DateTime;
-
import org.killbill.billing.account.api.AccountInternalApi;
import org.killbill.billing.invoice.api.InvoiceInternalApi;
-import org.killbill.clock.Clock;
import org.killbill.billing.util.callcontext.InternalCallContextFactory;
+import org.killbill.clock.Clock;
import org.killbill.notificationq.api.NotificationQueueService;
public class TestInvoiceNotificationQListener extends InvoiceListener {
- int eventCount = 0;
- UUID latestSubscriptionId = null;
+ private int eventCount = 0;
+ private UUID latestSubscriptionId = null;
@Inject
public TestInvoiceNotificationQListener(final AccountInternalApi accountApi,
@@ -44,7 +45,7 @@ public class TestInvoiceNotificationQListener extends InvoiceListener {
}
@Override
- public void handleNextBillingDateEvent(final UUID subscriptionId, final DateTime eventDateTime, final UUID userToken, final Long accountRecordId, final Long tenantRecordId) {
+ public void handleNextBillingDateEvent(final UUID subscriptionId, final DateTime eventDateTime, final boolean isRescheduled, final UUID userToken, final Long accountRecordId, final Long tenantRecordId) {
eventCount++;
latestSubscriptionId = subscriptionId;
}
@@ -56,5 +57,4 @@ public class TestInvoiceNotificationQListener extends InvoiceListener {
public UUID getLatestSubscriptionId() {
return latestSubscriptionId;
}
-
}
pom.xml 2(+1 -1)
diff --git a/pom.xml b/pom.xml
index 6f1bea2..3521af4 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.141.46</version>
+ <version>0.141.47</version>
</parent>
<artifactId>killbill</artifactId>
<version>0.19.7-SNAPSHOT</version>