killbill-memoizeit
Changes
.travis.yml 10(+10 -0)
beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestBillingAlignment.java 6(+4 -2)
beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueIntegration.java 432(+208 -224)
beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueWithOverdueEnforcementOffTag.java 8(+5 -3)
beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueWithSubscriptionCancellation.java 13(+7 -6)
beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestCatalogRetireElements.java 22(+11 -11)
beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationInvoice.java 16(+8 -8)
beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationInvoiceWithRepairLogic.java 18(+9 -9)
beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithAutoInvoiceOffTag.java 8(+5 -3)
beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithAutoPayOff.java 14(+8 -6)
beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithDifferentBillingPeriods.java 30(+16 -14)
beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestInvoiceNotifications.java 8(+4 -4)
beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithEntilementPlugin.java 11(+4 -7)
beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithPriceOverride.java 24(+8 -16)
beatrix/src/test/java/org/killbill/billing/beatrix/integration/usage/TestConsumableInArrear.java 17(+9 -8)
entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlementApi.java 39(+18 -21)
entitlement/src/main/java/org/killbill/billing/entitlement/api/EntitlementPluginExecution.java 47(+42 -5)
entitlement/src/main/java/org/killbill/billing/entitlement/api/svcs/DefaultEntitlementApiBase.java 6(+4 -2)
entitlement/src/main/java/org/killbill/billing/entitlement/api/svcs/DefaultEntitlementInternalApi.java 200(+199 -1)
entitlement/src/main/java/org/killbill/billing/entitlement/block/DefaultBlockingChecker.java 35(+19 -16)
entitlement/src/main/java/org/killbill/billing/entitlement/block/StatelessBlockingChecker.java 41(+41 -0)
entitlement/src/main/java/org/killbill/billing/entitlement/dao/DefaultBlockingStateDao.java 241(+195 -46)
entitlement/src/main/java/org/killbill/billing/entitlement/dao/OptimizedProxyBlockingStateDao.java 12(+8 -4)
entitlement/src/main/java/org/killbill/billing/entitlement/dao/ProxyBlockingStateDao.java 24(+15 -9)
entitlement/src/main/java/org/killbill/billing/entitlement/DefaultEntitlementService.java 39(+36 -3)
entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/EntitlementUtils.java 154(+37 -117)
entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/EventsStreamBuilder.java 11(+8 -3)
entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultEntitlement.java 63(+58 -5)
entitlement/src/test/java/org/killbill/billing/entitlement/block/TestBlockingChecker.java 25(+14 -11)
entitlement/src/test/java/org/killbill/billing/entitlement/dao/MockBlockingStateDao.java 24(+14 -10)
entitlement/src/test/java/org/killbill/billing/entitlement/dao/TestDefaultBlockingStateDao.java 20(+12 -8)
entitlement/src/test/java/org/killbill/billing/entitlement/dao/TestProxyBlockingStateDao.java 107(+107 -0)
invoice/src/main/java/org/killbill/billing/invoice/api/DefaultInvoicePaymentErrorEvent.java 58(+58 -0)
invoice/src/main/java/org/killbill/billing/invoice/api/DefaultInvoicePaymentInfoEvent.java 58(+58 -0)
invoice/src/main/java/org/killbill/billing/invoice/api/DefaultInvoicePaymentInternalEvent.java 200(+200 -0)
invoice/src/main/java/org/killbill/billing/invoice/template/formatters/DefaultInvoiceItemFormatter.java 2(+1 -1)
junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBlockingCalculator.java 44(+23 -21)
overdue/src/main/java/org/killbill/billing/overdue/applicator/OverdueStateApplicator.java 32(+15 -17)
overdue/src/main/java/org/killbill/billing/overdue/notification/OverdueAsyncBusNotifier.java 17(+7 -10)
overdue/src/test/java/org/killbill/billing/overdue/glue/ApplicatorMockJunctionModule.java 76(+0 -76)
payment/src/main/java/org/killbill/billing/payment/invoice/InvoicePaymentControlPluginApi.java 52(+30 -22)
payment/src/main/java/org/killbill/billing/payment/retry/PaymentRetryNotificationKey.java 13(+12 -1)
payment/src/test/java/org/killbill/billing/payment/glue/MockEntitlementModuleForPayment.java 50(+50 -0)
pom.xml 2(+1 -1)
subscription/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseApiService.java 15(+11 -4)
subscription/src/main/java/org/killbill/billing/subscription/api/svcs/DefaultSubscriptionInternalApi.java 44(+42 -2)
subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBaseApiService.java 190(+131 -59)
subscription/src/main/java/org/killbill/billing/subscription/engine/core/DefaultSubscriptionBaseService.java 49(+26 -23)
subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java 84(+47 -37)
subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionDao.java 6(+3 -3)
Details
.travis.yml 10(+10 -0)
diff --git a/.travis.yml b/.travis.yml
index dfb5cdc..e8847be 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -32,6 +32,16 @@ matrix:
jdk: oraclejdk7
- env: PHASE="-Pmysql,jdk18"
jdk: oraclejdk8
+ - env: PHASE="-Ppostgresql"
+ jdk: openjdk7
+ - env: PHASE="-Ppostgresql"
+ jdk: oraclejdk7
+ - env: PHASE="-Ppostgresql,jdk17"
+ jdk: openjdk7
+ - env: PHASE="-Ppostgresql,jdk17"
+ jdk: oraclejdk7
+ - env: PHASE="-Ppostgresql,jdk18"
+ jdk: oraclejdk8
- env: PHASE="-Ptravis"
jdk: openjdk7
- env: PHASE="-Ptravis"
diff --git a/api/src/main/java/org/killbill/billing/entitlement/EntitlementInternalApi.java b/api/src/main/java/org/killbill/billing/entitlement/EntitlementInternalApi.java
index cf812c5..f2eef61 100644
--- a/api/src/main/java/org/killbill/billing/entitlement/EntitlementInternalApi.java
+++ b/api/src/main/java/org/killbill/billing/entitlement/EntitlementInternalApi.java
@@ -1,7 +1,9 @@
/*
* Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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,6 +23,7 @@ import java.util.UUID;
import org.joda.time.LocalDate;
import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.catalog.api.BillingActionPolicy;
import org.killbill.billing.entitlement.api.Entitlement;
import org.killbill.billing.entitlement.api.EntitlementApiException;
import org.killbill.billing.payment.api.PluginProperty;
@@ -34,4 +37,6 @@ public interface EntitlementInternalApi {
void pause(UUID bundleId, LocalDate effectiveDate, Iterable<PluginProperty> properties, InternalCallContext context) throws EntitlementApiException;
void resume(UUID bundleId, LocalDate localEffectiveDate, Iterable<PluginProperty> properties, InternalCallContext context) throws EntitlementApiException;
+
+ void cancel(Iterable<Entitlement> entitlements, LocalDate effectiveDate, BillingActionPolicy billingPolicy, Iterable<PluginProperty> properties, InternalCallContext context) throws EntitlementApiException;
}
diff --git a/api/src/main/java/org/killbill/billing/events/BusInternalEvent.java b/api/src/main/java/org/killbill/billing/events/BusInternalEvent.java
index 7802660..374b969 100644
--- a/api/src/main/java/org/killbill/billing/events/BusInternalEvent.java
+++ b/api/src/main/java/org/killbill/billing/events/BusInternalEvent.java
@@ -1,7 +1,9 @@
/*
* Copyright 2010-2011 Ning, Inc.
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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:
*
@@ -16,11 +18,12 @@
package org.killbill.billing.events;
-
import org.killbill.bus.api.BusEvent;
public interface BusInternalEvent extends BusEvent {
+ public BusInternalEventType getBusEventType();
+
public enum BusInternalEventType {
ACCOUNT_CHANGE,
ACCOUNT_CREATE,
@@ -38,6 +41,8 @@ public interface BusInternalEvent extends BusEvent {
INVOICE_CREATION,
INVOICE_NOTIFICATION,
INVOICE_EMPTY,
+ INVOICE_PAYMENT_ERROR,
+ INVOICE_PAYMENT_INFO,
OVERDUE_CHANGE,
PAYMENT_ERROR,
PAYMENT_PLUGIN_ERROR,
@@ -50,7 +55,4 @@ public interface BusInternalEvent extends BusEvent {
TENANT_CONFIG_CHANGE,
TENANT_CONFIG_DELETION;
}
-
- public BusInternalEventType getBusEventType();
-
}
diff --git a/api/src/main/java/org/killbill/billing/events/InvoicePaymentErrorInternalEvent.java b/api/src/main/java/org/killbill/billing/events/InvoicePaymentErrorInternalEvent.java
new file mode 100644
index 0000000..39681d1
--- /dev/null
+++ b/api/src/main/java/org/killbill/billing/events/InvoicePaymentErrorInternalEvent.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2016 Groupon, Inc
+ * Copyright 2016 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.events;
+
+public interface InvoicePaymentErrorInternalEvent extends InvoicePaymentInternalEvent {
+}
diff --git a/api/src/main/java/org/killbill/billing/events/InvoicePaymentInfoInternalEvent.java b/api/src/main/java/org/killbill/billing/events/InvoicePaymentInfoInternalEvent.java
new file mode 100644
index 0000000..9c55a0f
--- /dev/null
+++ b/api/src/main/java/org/killbill/billing/events/InvoicePaymentInfoInternalEvent.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2016 Groupon, Inc
+ * Copyright 2016 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.events;
+
+public interface InvoicePaymentInfoInternalEvent extends InvoicePaymentInternalEvent {
+}
diff --git a/api/src/main/java/org/killbill/billing/events/InvoicePaymentInternalEvent.java b/api/src/main/java/org/killbill/billing/events/InvoicePaymentInternalEvent.java
new file mode 100644
index 0000000..1ab6692
--- /dev/null
+++ b/api/src/main/java/org/killbill/billing/events/InvoicePaymentInternalEvent.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2016 Groupon, Inc
+ * Copyright 2016 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.events;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.killbill.billing.catalog.api.Currency;
+import org.killbill.billing.invoice.api.InvoicePaymentType;
+
+public interface InvoicePaymentInternalEvent extends InvoiceInternalEvent {
+
+ public UUID getPaymentId();
+
+ public InvoicePaymentType getType();
+
+ public UUID getInvoiceId();
+
+ public DateTime getPaymentDate();
+
+ public BigDecimal getAmount();
+
+ public Currency getCurrency();
+
+ public UUID getLinkedInvoicePaymentId();
+
+ public String getPaymentCookieId();
+
+ public Currency getProcessedCurrency();
+}
diff --git a/api/src/main/java/org/killbill/billing/junction/DefaultBlockingState.java b/api/src/main/java/org/killbill/billing/junction/DefaultBlockingState.java
index dd450d4..b548665 100644
--- a/api/src/main/java/org/killbill/billing/junction/DefaultBlockingState.java
+++ b/api/src/main/java/org/killbill/billing/junction/DefaultBlockingState.java
@@ -151,7 +151,7 @@ public class DefaultBlockingState extends EntityBase implements BlockingState {
if (comparison == 0) {
// Keep a stable ordering for ties
final int comparison2 = createdDate.compareTo(arg0.getCreatedDate());
- if (comparison2 == 0 && getClass() != arg0.getClass()) {
+ if (comparison2 == 0 && arg0 instanceof DefaultBlockingState) {
final DefaultBlockingState other = (DefaultBlockingState) arg0;
// New element is last
if (totalOrdering == null) {
diff --git a/api/src/main/java/org/killbill/billing/overdue/config/api/BillingState.java b/api/src/main/java/org/killbill/billing/overdue/config/api/BillingState.java
index 2f781c5..dfdc986 100644
--- a/api/src/main/java/org/killbill/billing/overdue/config/api/BillingState.java
+++ b/api/src/main/java/org/killbill/billing/overdue/config/api/BillingState.java
@@ -1,7 +1,9 @@
/*
* Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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:
*
@@ -17,6 +19,7 @@
package org.killbill.billing.overdue.config.api;
import java.math.BigDecimal;
+import java.util.Arrays;
import java.util.UUID;
import org.joda.time.DateTimeZone;
@@ -85,4 +88,19 @@ public class BillingState {
public DateTimeZone getAccountTimeZone() {
return accountTimeZone;
}
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("BillingState{");
+ sb.append("objectId=").append(objectId);
+ sb.append(", numberOfUnpaidInvoices=").append(numberOfUnpaidInvoices);
+ sb.append(", balanceOfUnpaidInvoices=").append(balanceOfUnpaidInvoices);
+ sb.append(", dateOfEarliestUnpaidInvoice=").append(dateOfEarliestUnpaidInvoice);
+ sb.append(", accountTimeZone=").append(accountTimeZone);
+ sb.append(", idOfEarliestUnpaidInvoice=").append(idOfEarliestUnpaidInvoice);
+ sb.append(", responseForLastFailedPayment=").append(responseForLastFailedPayment);
+ sb.append(", tags=").append(Arrays.toString(tags));
+ sb.append('}');
+ return sb.toString();
+ }
}
diff --git a/api/src/main/java/org/killbill/billing/overdue/OverdueInternalApi.java b/api/src/main/java/org/killbill/billing/overdue/OverdueInternalApi.java
index 0b376b8..77ad539 100644
--- a/api/src/main/java/org/killbill/billing/overdue/OverdueInternalApi.java
+++ b/api/src/main/java/org/killbill/billing/overdue/OverdueInternalApi.java
@@ -1,7 +1,9 @@
/*
* Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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:
*
@@ -16,8 +18,10 @@
package org.killbill.billing.overdue;
+import java.util.UUID;
+
import org.killbill.billing.account.api.ImmutableAccountData;
-import org.killbill.billing.overdue.api.OverdueApiException;
+import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.overdue.api.OverdueState;
import org.killbill.billing.overdue.config.api.BillingState;
import org.killbill.billing.overdue.config.api.OverdueException;
@@ -26,12 +30,13 @@ import org.killbill.billing.util.callcontext.TenantContext;
public interface OverdueInternalApi {
- public OverdueState refreshOverdueStateFor(ImmutableAccountData overdueable, CallContext context) throws OverdueException, OverdueApiException;
+ public void scheduleOverdueRefresh(UUID accountId, InternalCallContext context);
+
+ public void scheduleOverdueClear(UUID accountId, InternalCallContext context);
public void setOverrideBillingStateForAccount(ImmutableAccountData overdueable, BillingState state, CallContext context) throws OverdueException;
public OverdueState getOverdueStateFor(ImmutableAccountData overdueable, TenantContext context) throws OverdueException;
public BillingState getBillingStateFor(ImmutableAccountData overdueable, TenantContext context) throws OverdueException;
-
}
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 5679190..df97745 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
@@ -1,7 +1,9 @@
/*
* Copyright 2010-2011 Ning, Inc.
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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:
*
@@ -25,6 +27,8 @@ import javax.annotation.Nullable;
import org.joda.time.DateTime;
import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.catalog.api.BillingActionPolicy;
+import org.killbill.billing.catalog.api.BillingPeriod;
import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
import org.killbill.billing.entitlement.api.EntitlementAOStatusDryRun;
@@ -44,6 +48,8 @@ public interface SubscriptionBaseInternalApi {
public SubscriptionBase createBaseSubscriptionWithAddOns(UUID bundleId, Iterable<EntitlementSpecifier> entitlements, DateTime requestedDateWithMs,
InternalCallContext context) throws SubscriptionBaseApiException;
+ public void cancelBaseSubscriptions(Iterable<SubscriptionBase> subscriptions, BillingActionPolicy policy, InternalCallContext context) throws SubscriptionBaseApiException;
+
public SubscriptionBaseBundle createBundleForAccount(UUID accountId, String bundleName, InternalCallContext context)
throws SubscriptionBaseApiException;
@@ -79,6 +85,9 @@ public interface SubscriptionBaseInternalApi {
public List<EffectiveSubscriptionInternalEvent> getBillingTransitions(SubscriptionBase subscription, InternalTenantContext context);
+ public DateTime getDryRunChangePlanEffectiveDate(SubscriptionBase subscription, String productName, BillingPeriod term,
+ String priceList, DateTime requestedDate, BillingActionPolicy policy, InternalTenantContext context) throws SubscriptionBaseApiException;
+
public List<EntitlementAOStatusDryRun> getDryRunChangePlanStatus(UUID subscriptionId, @Nullable String baseProductName,
DateTime requestedDate, InternalTenantContext context) throws SubscriptionBaseApiException;
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestBillingAlignment.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestBillingAlignment.java
index 4301dfd..086fab5 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestBillingAlignment.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestBillingAlignment.java
@@ -1,7 +1,9 @@
/*
* Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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:
*
@@ -59,7 +61,7 @@ public class TestBillingAlignment extends TestIntegrationBase {
invoiceChecker.checkInvoice(account.getId(), 1, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 4, 1), null, InvoiceItemType.FIXED, new BigDecimal("0")));
// GET OUT TRIAL
- addDaysAndCheckForCompletion(33, NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT);
+ addDaysAndCheckForCompletion(33, NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
//
// Change plan to annual that has been configured to have a 'SubscriptionBase' billing alignment
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueIntegration.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueIntegration.java
index 0cbd559..a974451 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueIntegration.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueIntegration.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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
@@ -115,7 +115,7 @@ public class TestOverdueIntegration extends TestOverdueBase {
return configXml;
}
- @Test(groups = "slow", description = "Test overdue stages and return to clear prior to CTD", enabled = false)
+ @Test(groups = "slow", description = "Test overdue stages and return to clear prior to CTD")
public void testOverdueStages1() throws Exception {
// 2012-05-01T00:03:42.000Z
clock.setTime(new DateTime(2012, 5, 1, 0, 3, 42, 0));
@@ -128,55 +128,55 @@ public class TestOverdueIntegration extends TestOverdueBase {
invoiceChecker.checkInvoice(account.getId(), 1, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), null, InvoiceItemType.FIXED, new BigDecimal("0")));
invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 5, 1), callContext);
- // 2012, 5, 31 => DAY 30 have to get out of trial {I0, P0}
- addDaysAndCheckForCompletion(30, NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR);
+ // 2012-05-31 => DAY 30 have to get out of trial {I0, P0}
+ addDaysAndCheckForCompletion(30, NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
invoiceChecker.checkInvoice(account.getId(), 2, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 31), new LocalDate(2012, 6, 30), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 6, 30), callContext);
- // 2012, 6, 8 => Retry P0
- addDaysAndCheckForCompletion(8, NextEvent.PAYMENT_ERROR);
+ // 2012-06-08 => Retry P0
+ addDaysAndCheckForCompletion(8, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
- // 2012, 6, 16 => Retry P0
- addDaysAndCheckForCompletion(8, NextEvent.PAYMENT_ERROR);
+ // 2012-06-16 => Retry P0
+ addDaysAndCheckForCompletion(8, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
- // 2012, 6, 24 => Retry P0
- addDaysAndCheckForCompletion(8, NextEvent.PAYMENT_ERROR);
+ // 2012-06-24 => Retry P0
+ addDaysAndCheckForCompletion(8, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
- // 2012, 6, 30 => P1
- addDaysAndCheckForCompletion(6, NextEvent.BLOCK, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR);
+ // 2012-06-30 => P1
+ addDaysAndCheckForCompletion(6, NextEvent.BLOCK, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
checkODState("OD1");
checkChangePlanWithOverdueState(baseEntitlement, true, true);
invoiceChecker.checkInvoice(account.getId(), 3, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 6, 30), new LocalDate(2012, 7, 31), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 7, 31), callContext);
- // 2012, 7, 2 => Retry P0
- addDaysAndCheckForCompletion(2, NextEvent.PAYMENT_ERROR);
+ // 2012-07-02 => Retry P0
+ addDaysAndCheckForCompletion(2, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
checkODState("OD1");
- // 2012, 7, 9 => Retry P1
- addDaysAndCheckForCompletion(7, NextEvent.PAYMENT_ERROR);
+ // 2012-07-08 => Retry P1
+ addDaysAndCheckForCompletion(6, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
checkODState("OD1");
- // 2012, 7, 10 => Retry P0
+ // 2012-07-10 => Retry P0
//
// This is the first stage that will block the billing (and entitlement).
//
- addDaysAndCheckForCompletion(1, NextEvent.BLOCK, NextEvent.TAG, NextEvent.PAYMENT_ERROR);
+ addDaysAndCheckForCompletion(2, NextEvent.BLOCK, NextEvent.TAG, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
checkODState("OD2");
- // 2012, 7, 17 => Retry P1
- addDaysAndCheckForCompletion(7, NextEvent.PAYMENT_ERROR);
+ // 2012-07-16 => Retry P1
+ addDaysAndCheckForCompletion(6, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
checkODState("OD2");
- // 2012, 7, 18 => Retry P0
- addDaysAndCheckForCompletion(1, NextEvent.PAYMENT_ERROR);
+ // 2012-07-18 => Retry P0
+ addDaysAndCheckForCompletion(2, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
checkODState("OD2");
- // 2012, 7, 20
+ // 2012-07-20
addDaysAndCheckForCompletion(2, NextEvent.BLOCK);
checkODState("OD3");
@@ -190,11 +190,11 @@ public class TestOverdueIntegration extends TestOverdueBase {
new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 20), new LocalDate(2012, 7, 20), InvoiceItemType.CBA_ADJ, new BigDecimal("80.63")));
// Add 11 days to generate next invoice. We verify that we indeed have a notification for nextBillingDate
- addDaysAndCheckForCompletion(11, NextEvent.INVOICE, NextEvent.PAYMENT);
+ addDaysAndCheckForCompletion(11, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
invoiceChecker.checkInvoice(account.getId(), 5, callContext,
new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 31), new LocalDate(2012, 8, 31), InvoiceItemType.RECURRING, new BigDecimal("249.95")),
- new ExpectedInvoiceItemCheck(new LocalDate(2012, 8, 2), new LocalDate(2012, 8, 2), InvoiceItemType.CBA_ADJ, new BigDecimal("-80.63")));
+ new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 31), new LocalDate(2012, 7, 31), InvoiceItemType.CBA_ADJ, new BigDecimal("-80.63")));
invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 8, 31), callContext);
@@ -202,7 +202,7 @@ public class TestOverdueIntegration extends TestOverdueBase {
assertEquals(invoiceUserApi.getAccountBalance(account.getId(), callContext).compareTo(BigDecimal.ZERO), 0);
}
- @Test(groups = "slow", description = "Test overdue stages and return to clear on CTD", enabled = false)
+ @Test(groups = "slow", description = "Test overdue stages and return to clear on CTD")
public void testOverdueStages2() throws Exception {
// 2012-05-01T00:03:42.000Z
clock.setTime(new DateTime(2012, 5, 1, 0, 3, 42, 0));
@@ -215,93 +215,88 @@ public class TestOverdueIntegration extends TestOverdueBase {
invoiceChecker.checkInvoice(account.getId(), 1, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), null, InvoiceItemType.FIXED, new BigDecimal("0")));
invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 5, 1), callContext);
- // 2012, 5, 31 => DAY 30 have to get out of trial {I0, P0}
- addDaysAndCheckForCompletion(30, NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR);
+ // 2012-05-31 => DAY 30 have to get out of trial {I0, P0}
+ addDaysAndCheckForCompletion(30, NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
invoiceChecker.checkInvoice(account.getId(), 2, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 31), new LocalDate(2012, 6, 30), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 6, 30), callContext);
- // 2012, 6, 8 => Retry P0
- addDaysAndCheckForCompletion(8, NextEvent.PAYMENT_ERROR);
+ // 2012-06-08 => Retry P0
+ addDaysAndCheckForCompletion(8, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
- // 2012, 6, 16 => Retry P0
- addDaysAndCheckForCompletion(8, NextEvent.PAYMENT_ERROR);
+ // 2012-06-16 => Retry P0
+ addDaysAndCheckForCompletion(8, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
- // 2012, 6, 24 => Retry P0
- addDaysAndCheckForCompletion(8, NextEvent.PAYMENT_ERROR);
+ // 2012-06-24 => Retry P0
+ addDaysAndCheckForCompletion(8, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
- // 2012, 6, 30 => P1
- addDaysAndCheckForCompletion(6, NextEvent.BLOCK, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR);
+ // 2012-06-30 => P1
+ addDaysAndCheckForCompletion(6, NextEvent.BLOCK, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
checkODState("OD1");
checkChangePlanWithOverdueState(baseEntitlement, true, true);
invoiceChecker.checkInvoice(account.getId(), 3, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 6, 30), new LocalDate(2012, 7, 31), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 7, 31), callContext);
- // 2012, 7, 2 => Retry P0
- addDaysAndCheckForCompletion(2, NextEvent.PAYMENT_ERROR);
+ // 2012-07-02 => Retry P0
+ addDaysAndCheckForCompletion(2, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
checkODState("OD1");
- // 2012, 7, 9 => Retry P1
- addDaysAndCheckForCompletion(7, NextEvent.PAYMENT_ERROR);
+ // 2012-07-08 => Retry P1
+ addDaysAndCheckForCompletion(6, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
checkODState("OD1");
- // 2012, 7, 10 => Retry P0
+ // 2012-07-10 => Retry P0
//
// This is the first stage that will block the billing (and entitlement).
//
- addDaysAndCheckForCompletion(1, NextEvent.BLOCK, NextEvent.TAG, NextEvent.PAYMENT_ERROR);
+ addDaysAndCheckForCompletion(2, NextEvent.BLOCK, NextEvent.TAG, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
checkODState("OD2");
- // 2012, 7, 17 => Retry P1
- addDaysAndCheckForCompletion(7, NextEvent.PAYMENT_ERROR);
+ // 2012-07-16 => Retry P1
+ addDaysAndCheckForCompletion(6, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
checkODState("OD2");
- // 2012, 7, 18 => Retry P0
- addDaysAndCheckForCompletion(1, NextEvent.PAYMENT_ERROR);
+ // 2012-07-18 => Retry P0
+ addDaysAndCheckForCompletion(2, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
checkODState("OD2");
- // 2012, 7, 20
+ // 2012-07-20
addDaysAndCheckForCompletion(2, NextEvent.BLOCK);
checkODState("OD3");
- // 2012, 7, 25 => Retry P0
- addDaysAndCheckForCompletion(5, NextEvent.PAYMENT_ERROR);
- // 2012, 7, 26 => Retry P0
- addDaysAndCheckForCompletion(1, NextEvent.PAYMENT_ERROR);
+ // 2012-07-24 => Retry P1
+ addDaysAndCheckForCompletion(4, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
+ // 2012-07-26 => Retry P0
+ addDaysAndCheckForCompletion(2, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
- // 2012, 7, 31 => No NEW INVOICE because OD2 -> still blocked
+ // 2012-07-31 => No NEW INVOICE because OD2 -> still blocked
addDaysAndCheckForCompletion(5);
invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 7, 31), callContext);
allowPaymentsAndResetOverdueToClearByPayingAllUnpaidInvoices(true);
invoiceChecker.checkInvoice(account.getId(), 3, callContext,
- // New invoice for the partial period since we unblocked on the 1st and so are missing the 31 july
new ExpectedInvoiceItemCheck(new LocalDate(2012, 6, 30), new LocalDate(2012, 7, 31), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
invoiceChecker.checkInvoice(account.getId(), 4, callContext,
- // New invoice for the partial period since we unblocked on the 1st and so are missing the 31 july
- new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 31), new LocalDate(2012, 8, 31), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
-
- invoiceChecker.checkInvoice(account.getId(), 5, callContext,
- new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 10), new LocalDate(2012, 7, 31), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-169.32")),
- new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 31), new LocalDate(2012, 7, 31), InvoiceItemType.CBA_ADJ, new BigDecimal("169.32")));
+ // New invoice for the partial period
+ new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 31), new LocalDate(2012, 8, 31), InvoiceItemType.RECURRING, new BigDecimal("249.95")),
+ new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 10), new LocalDate(2012, 7, 31), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-169.32")));
// Move one month ahead, and check if we get the next invoice
- addDaysAndCheckForCompletion(31, NextEvent.INVOICE, NextEvent.PAYMENT);
+ addDaysAndCheckForCompletion(31, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
- invoiceChecker.checkInvoice(account.getId(), 6, callContext,
- // New invoice for the partial period since we unblocked on the 1st and so are missing the 31 july
+ invoiceChecker.checkInvoice(account.getId(), 5, callContext,
new ExpectedInvoiceItemCheck(new LocalDate(2012, 8, 31), new LocalDate(2012, 9, 30), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
// Verify the account balance is now 0
assertEquals(invoiceUserApi.getAccountBalance(account.getId(), callContext).compareTo(BigDecimal.ZERO), 0);
}
- @Test(groups = "slow", description = "Test overdue stages and return to clear after CTD", enabled = false)
+ @Test(groups = "slow", description = "Test overdue stages and return to clear after CTD")
public void testOverdueStages3() throws Exception {
// 2012-05-01T00:03:42.000Z
clock.setTime(new DateTime(2012, 5, 1, 0, 3, 42, 0));
@@ -314,88 +309,84 @@ public class TestOverdueIntegration extends TestOverdueBase {
invoiceChecker.checkInvoice(account.getId(), 1, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), null, InvoiceItemType.FIXED, new BigDecimal("0")));
invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 5, 1), callContext);
- // 2012, 5, 31 => DAY 30 have to get out of trial {I0, P0}
- addDaysAndCheckForCompletion(30, NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR);
+ // 2012-05-31 => DAY 30 have to get out of trial {I0, P0}
+ addDaysAndCheckForCompletion(30, NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
invoiceChecker.checkInvoice(account.getId(), 2, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 31), new LocalDate(2012, 6, 30), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 6, 30), callContext);
- // 2012, 6, 8 => Retry P0
- addDaysAndCheckForCompletion(8, NextEvent.PAYMENT_ERROR);
+ // 2012-06-08 => Retry P0
+ addDaysAndCheckForCompletion(8, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
- // 2012, 6, 16 => Retry P0
- addDaysAndCheckForCompletion(8, NextEvent.PAYMENT_ERROR);
+ // 2012-06-16 => Retry P0
+ addDaysAndCheckForCompletion(8, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
- // 2012, 6, 24 => Retry P0
- addDaysAndCheckForCompletion(8, NextEvent.PAYMENT_ERROR);
+ // 2012-06-24 => Retry P0
+ addDaysAndCheckForCompletion(8, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
- // 2012, 6, 30
- addDaysAndCheckForCompletion(6, NextEvent.BLOCK, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR);
+ // 2012-06-30
+ addDaysAndCheckForCompletion(6, NextEvent.BLOCK, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
checkODState("OD1");
checkChangePlanWithOverdueState(baseEntitlement, true, true);
invoiceChecker.checkInvoice(account.getId(), 3, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 6, 30), new LocalDate(2012, 7, 31), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 7, 31), callContext);
- // 2012, 7, 2 => Retry P0
- addDaysAndCheckForCompletion(2, NextEvent.PAYMENT_ERROR);
+ // 2012-07-02 => Retry P0
+ addDaysAndCheckForCompletion(2, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
checkODState("OD1");
- // 2012, 7, 9 => Retry P1
- addDaysAndCheckForCompletion(7, NextEvent.PAYMENT_ERROR);
+ // 2012-07-08 => Retry P1
+ addDaysAndCheckForCompletion(6, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
checkODState("OD1");
- // 2012, 7, 10 => Retry P0
+ // 2012-07-10 => Retry P0
//
// This is the first stage that will block the billing (and entitlement).
//
- addDaysAndCheckForCompletion(1, NextEvent.BLOCK, NextEvent.TAG, NextEvent.PAYMENT_ERROR);
+ addDaysAndCheckForCompletion(2, NextEvent.BLOCK, NextEvent.TAG, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
checkODState("OD2");
- // 2012, 7, 17 => Retry P1
- addDaysAndCheckForCompletion(7, NextEvent.PAYMENT_ERROR);
+ // 2012-07-16 => Retry P1
+ addDaysAndCheckForCompletion(6, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
checkODState("OD2");
- // 2012, 7, 18 => Retry P0
- addDaysAndCheckForCompletion(1, NextEvent.PAYMENT_ERROR);
+ // 2012-07-18 => Retry P0
+ addDaysAndCheckForCompletion(2, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
checkODState("OD2");
- // 2012, 7, 20
+ // 2012-07-20
addDaysAndCheckForCompletion(2, NextEvent.BLOCK);
checkODState("OD3");
- // 2012, 7, 25 => Retry P0
- addDaysAndCheckForCompletion(5, NextEvent.PAYMENT_ERROR);
- // 2012, 7, 26 => Retry P0
- addDaysAndCheckForCompletion(1, NextEvent.PAYMENT_ERROR);
+ // 2012-07-24 => Retry P1
+ addDaysAndCheckForCompletion(4, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
+ // 2012-07-26 => Retry P0
+ addDaysAndCheckForCompletion(2, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
- // 2012, 7, 31 => No NEW INVOICE because OD2 -> still blocked
+ // 2012-07-31 => No NEW INVOICE because OD2 -> still blocked
addDaysAndCheckForCompletion(5);
invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 7, 31), callContext);
- // 2012, 8, 1 => Nothing should have happened
- addDaysAndCheckForCompletion(1);
+ // 2012-08-01 => Retry P1
+ addDaysAndCheckForCompletion(1, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
allowPaymentsAndResetOverdueToClearByPayingAllUnpaidInvoices(true);
invoiceChecker.checkInvoice(account.getId(), 3, callContext,
- // New invoice for the partial period since we unblocked on the 1st and so are missing the 31 july
new ExpectedInvoiceItemCheck(new LocalDate(2012, 6, 30), new LocalDate(2012, 7, 31), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
invoiceChecker.checkInvoice(account.getId(), 4, callContext,
- // New invoice for the partial period since we unblocked on the 1st and so are missing the 31 july
+ // New invoice for the partial period
new ExpectedInvoiceItemCheck(new LocalDate(2012, 8, 1), new LocalDate(2012, 8, 31), InvoiceItemType.RECURRING, new BigDecimal("241.89")),
- new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 10), new LocalDate(2012, 7, 31), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-169.32")),
- new ExpectedInvoiceItemCheck(new LocalDate(2012, 8, 1), new LocalDate(2012, 8, 1), InvoiceItemType.CBA_ADJ, new BigDecimal("-72.57")));
-
+ new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 10), new LocalDate(2012, 7, 31), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-169.32")));
// Move one month ahead, and check if we get the next invoice
- addDaysAndCheckForCompletion(30, NextEvent.INVOICE, NextEvent.PAYMENT);
+ addDaysAndCheckForCompletion(30, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
invoiceChecker.checkInvoice(account.getId(), 5, callContext,
- // New invoice for the partial period since we unblocked on the 1st and so are missing the 31 july
new ExpectedInvoiceItemCheck(new LocalDate(2012, 8, 31), new LocalDate(2012, 9, 30), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
// Verify the account balance is now 0
@@ -406,7 +397,7 @@ public class TestOverdueIntegration extends TestOverdueBase {
// This test is similar to the previous one except that instead of moving the clock to check we will get the next invoice
// at the end, we carry a change of plan.
//
- @Test(groups = "slow", description = "Test overdue stages and follow with an immediate change of plan", enabled = false)
+ @Test(groups = "slow", description = "Test overdue stages and follow with an immediate change of plan")
public void testOverdueStagesFollowedWithImmediateChange1() throws Exception {
// 2012-05-01T00:03:42.000Z
clock.setTime(new DateTime(2012, 5, 1, 0, 3, 42, 0));
@@ -418,52 +409,52 @@ public class TestOverdueIntegration extends TestOverdueBase {
invoiceChecker.checkInvoice(account.getId(), 1, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), null, InvoiceItemType.FIXED, new BigDecimal("0")));
invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 5, 1), callContext);
- // 2012, 5, 31 => DAY 30 have to get out of trial {I0, P0}
- addDaysAndCheckForCompletion(30, NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR);
+ // 2012-05-31 => DAY 30 have to get out of trial {I0, P0}
+ addDaysAndCheckForCompletion(30, NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
invoiceChecker.checkInvoice(account.getId(), 2, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 31), new LocalDate(2012, 6, 30), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 6, 30), callContext);
- // 2012, 6, 8 => Retry P0
- addDaysAndCheckForCompletion(8, NextEvent.PAYMENT_ERROR);
+ // 2012-06-08 => Retry P0
+ addDaysAndCheckForCompletion(8, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
- // 2012, 6, 16 => Retry P0
- addDaysAndCheckForCompletion(8, NextEvent.PAYMENT_ERROR);
+ // 2012-06-16 => Retry P0
+ addDaysAndCheckForCompletion(8, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
- // 2012, 6, 24 => Retry P0
- addDaysAndCheckForCompletion(8, NextEvent.PAYMENT_ERROR);
+ // 2012-06-24 => Retry P0
+ addDaysAndCheckForCompletion(8, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
- // 2012, 6, 30
- addDaysAndCheckForCompletion(6, NextEvent.BLOCK, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR);
+ // 2012-06-30
+ addDaysAndCheckForCompletion(6, NextEvent.BLOCK, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
checkODState("OD1");
checkChangePlanWithOverdueState(baseEntitlement, true, true);
invoiceChecker.checkInvoice(account.getId(), 3, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 6, 30), new LocalDate(2012, 7, 31), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 7, 31), callContext);
- // 2012, 7, 2 => Retry P0
- addDaysAndCheckForCompletion(2, NextEvent.PAYMENT_ERROR);
+ // 2012-07-02 => Retry P0
+ addDaysAndCheckForCompletion(2, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
checkODState("OD1");
- // 2012, 7, 9 => Retry P1
- addDaysAndCheckForCompletion(7, NextEvent.PAYMENT_ERROR);
+ // 2012-07-08 => Retry P1
+ addDaysAndCheckForCompletion(6, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
checkODState("OD1");
- // 2012, 7, 10 => Retry P0
- addDaysAndCheckForCompletion(1, NextEvent.BLOCK, NextEvent.TAG, NextEvent.PAYMENT_ERROR);
+ // 2012-07-10 => Retry P0
+ addDaysAndCheckForCompletion(2, NextEvent.BLOCK, NextEvent.TAG, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
checkODState("OD2");
- // 2012, 7, 17 => Retry P1
- addDaysAndCheckForCompletion(7, NextEvent.PAYMENT_ERROR);
+ // 2012-07-16 => Retry P1
+ addDaysAndCheckForCompletion(6, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
checkODState("OD2");
- // 2012, 7, 18 => Retry P0
- addDaysAndCheckForCompletion(1, NextEvent.PAYMENT_ERROR);
+ // 2012-07-18 => Retry P0
+ addDaysAndCheckForCompletion(2, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
checkODState("OD2");
- // 2012, 7, 20
+ // 2012-07-20
addDaysAndCheckForCompletion(2, NextEvent.BLOCK);
checkODState("OD3");
@@ -477,27 +468,24 @@ public class TestOverdueIntegration extends TestOverdueBase {
new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 20), new LocalDate(2012, 7, 20), InvoiceItemType.CBA_ADJ, new BigDecimal("80.63")));
// Do an upgrade now
- checkChangePlanWithOverdueState(baseEntitlement, false, false);
+ checkChangePlanWithOverdueState(baseEntitlement, false, true);
invoiceChecker.checkInvoice(account.getId(), 4, callContext,
new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 10), new LocalDate(2012, 7, 20), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-80.63")),
new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 20), new LocalDate(2012, 7, 20), InvoiceItemType.CBA_ADJ, new BigDecimal("80.63")));
-
-
-
invoiceChecker.checkInvoice(account.getId(), 5, callContext,
// Item for the upgraded recurring plan
- new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 20), new LocalDate(2012, 7, 31), InvoiceItemType.RECURRING, new BigDecimal("154.83")),
- new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 20), new LocalDate(2012, 7, 31), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-64.50")),
- new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 20), new LocalDate(2012, 7, 20), InvoiceItemType.CBA_ADJ, new BigDecimal("-90.33")));
+ new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 20), new LocalDate(2012, 7, 31), InvoiceItemType.RECURRING, new BigDecimal("212.89")),
+ new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 20), new LocalDate(2012, 7, 31), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-88.69")),
+ new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 20), new LocalDate(2012, 7, 20), InvoiceItemType.CBA_ADJ, new BigDecimal("-80.63")));
invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 7, 31), callContext);
- // Verify the account balance:
- assertEquals(invoiceUserApi.getAccountBalance(account.getId(), callContext).compareTo(new BigDecimal("-14.49")), 0);
+ // Verify the account balance is now 0
+ assertEquals(invoiceUserApi.getAccountBalance(account.getId(), callContext).compareTo(BigDecimal.ZERO), 0);
}
- @Test(groups = "slow", description = "Test overdue stages and follow with an immediate change of plan and use of credit", enabled = false)
+ @Test(groups = "slow", description = "Test overdue stages and follow with an immediate change of plan and use of credit")
public void testOverdueStagesFollowedWithImmediateChange2() throws Exception {
// 2012-05-01T00:03:42.000Z
clock.setTime(new DateTime(2012, 5, 1, 0, 3, 42, 0));
@@ -510,73 +498,69 @@ public class TestOverdueIntegration extends TestOverdueBase {
invoiceChecker.checkInvoice(account.getId(), 1, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), null, InvoiceItemType.FIXED, new BigDecimal("0")));
invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 5, 1), callContext);
- // 2012, 5, 31 => DAY 30 have to get out of trial {I0, P0}
- addDaysAndCheckForCompletion(30, NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR);
+ // 2012-05-31 => DAY 30 have to get out of trial {I0, P0}
+ addDaysAndCheckForCompletion(30, NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
invoiceChecker.checkInvoice(account.getId(), 2, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 31), new LocalDate(2013, 5, 31), InvoiceItemType.RECURRING, new BigDecimal("2399.95")));
invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2013, 5, 31), callContext);
- // 2012, 6, 8 => Retry P0
- addDaysAndCheckForCompletion(8, NextEvent.PAYMENT_ERROR);
+ // 2012-06-08 => Retry P0
+ addDaysAndCheckForCompletion(8, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
- // 2012, 6, 16 => Retry P0
- addDaysAndCheckForCompletion(8, NextEvent.PAYMENT_ERROR);
+ // 2012-06-16 => Retry P0
+ addDaysAndCheckForCompletion(8, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
- // 2012, 6, 24 => Retry P0
- addDaysAndCheckForCompletion(8, NextEvent.PAYMENT_ERROR);
+ // 2012-06-24 => Retry P0
+ addDaysAndCheckForCompletion(8, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
- // 2012, 7, 2 => Retry P0
- addDaysAndCheckForCompletion(8, NextEvent.BLOCK, NextEvent.PAYMENT_ERROR);
+ // 2012-06-30 => OD1
+ addDaysAndCheckForCompletion(6, NextEvent.BLOCK);
+ checkODState("OD1");
+
+ // 2012-07-02 => Retry P0
+ addDaysAndCheckForCompletion(2, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
checkODState("OD1");
- // 2012, 7, 10 => Retry P0
- addDaysAndCheckForCompletion(8, NextEvent.BLOCK, NextEvent.PAYMENT_ERROR, NextEvent.TAG);
+ // 2012-07-10 => Retry P0 & transition to OD2
+ addDaysAndCheckForCompletion(8, NextEvent.BLOCK, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR, NextEvent.TAG);
checkODState("OD2");
- // 2012, 7, 18 => Retry P0
- addDaysAndCheckForCompletion(8, NextEvent.PAYMENT_ERROR);
+ // 2012-07-18 => Retry P0
+ addDaysAndCheckForCompletion(8, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
checkODState("OD2");
- // 2012, 7, 20
+ // 2012-07-20 => OD3
addDaysAndCheckForCompletion(2, NextEvent.BLOCK);
checkODState("OD3");
allowPaymentsAndResetOverdueToClearByPayingAllUnpaidInvoices(false);
invoiceChecker.checkInvoice(account.getId(), 2, callContext,
- // New invoice for the part that was unblocked up to the BCD
new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 31), new LocalDate(2013, 5, 31), InvoiceItemType.RECURRING, new BigDecimal("2399.95")));
invoiceChecker.checkInvoice(account.getId(), 3, callContext,
- new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 10), new LocalDate(2012, 7, 20), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-85.4588")),
- new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 31), new LocalDate(2013, 5, 31), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-1998.9012")),
- new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 20), new LocalDate(2012, 7, 20), InvoiceItemType.CBA_ADJ, new BigDecimal("2084.36")));
+ new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 10), new LocalDate(2012, 7, 20), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-65.75")),
+ new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 31), new LocalDate(2013, 5, 31), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-1998.86")),
+ new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 20), new LocalDate(2012, 7, 20), InvoiceItemType.CBA_ADJ, new BigDecimal("2064.61")));
- // Move to 2012, 7, 31 and Make a change of plan
- addDaysAndCheckForCompletion(8, NextEvent.INVOICE, NextEvent.PAYMENT);
-
- invoiceChecker.checkInvoice(account.getId(), 4, callContext,
- // New invoice for the part that was unblocked up to the BCD
- new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 31), new LocalDate(2013, 7, 31), InvoiceItemType.RECURRING, new BigDecimal("2399.95")));
+ invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2013, 5, 31), callContext);
+ // Move to 2012-07-31 and make a change of plan
+ addDaysAndCheckForCompletion(11);
checkChangePlanWithOverdueState(baseEntitlement, false, false);
- invoiceChecker.checkRepairedInvoice(account.getId(), 4, callContext,
- new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 31), new LocalDate(2013, 7, 31), InvoiceItemType.RECURRING, new BigDecimal("2399.95")));
-
- invoiceChecker.checkInvoice(account.getId(), 5, callContext,
- // Item for the upgraded recurring plan
+ invoiceChecker.checkInvoice(account.getId(), 4, callContext,
new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 31), new LocalDate(2012, 8, 31), InvoiceItemType.RECURRING, new BigDecimal("599.95")),
- new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 31), new LocalDate(2013, 7, 31), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-2399.95")),
- new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 31), new LocalDate(2012, 7, 31), InvoiceItemType.CBA_ADJ, new BigDecimal("1800")));
+ new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 31), new LocalDate(2012, 7, 31), InvoiceItemType.CBA_ADJ, new BigDecimal("-599.95")));
+
invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 8, 31), callContext);
- assertEquals(invoiceUserApi.getAccountBalance(account.getId(), callContext).compareTo(new BigDecimal("-1800")), 0);
+ assertEquals(invoiceUserApi.getAccountBalance(account.getId(), callContext).compareTo(new BigDecimal("-1464.66")), 0);
}
- @Test(groups = "slow", description = "Test overdue stages with missing payment method", enabled = false)
+ @Test(groups = "slow", description = "Test overdue stages with missing payment method")
public void testOverdueStateIfNoPaymentMethod() throws Exception {
// This test is similar to the previous one - but there is no default payment method on the account, so there
// won't be any payment retry
@@ -594,8 +578,8 @@ public class TestOverdueIntegration extends TestOverdueBase {
invoiceChecker.checkInvoice(account.getId(), 1, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), null, InvoiceItemType.FIXED, new BigDecimal("0")));
invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 5, 1), callContext);
- // DAY 30 have to get out of trial before first payment. A payment error, one for each invoice, should be on the bus (because there is no payment method)
- addDaysAndCheckForCompletion(30, NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR);
+ // 2012-05-31 => DAY 30 have to get out of trial before first payment. A payment error, one for each invoice, should be on the bus (because there is no payment method)
+ addDaysAndCheckForCompletion(30, NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
invoiceChecker.checkInvoice(account.getId(), 2, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 31), new LocalDate(2012, 6, 30), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 6, 30), callContext);
@@ -603,15 +587,15 @@ public class TestOverdueIntegration extends TestOverdueBase {
// Should still be in clear state
checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
- // DAY 45 - 15 days after invoice
+ // 2012-06-15 => DAY 45 - 15 days after invoice
addDaysAndCheckForCompletion(15);
// Should still be in clear state
checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
- // DAY 65 - 35 days after invoice
+ // 2012-07-05 => DAY 65 - 35 days after invoice
// Single PAYMENT_ERROR here here triggered by the invoice
- addDaysAndCheckForCompletion(20, NextEvent.BLOCK, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR);
+ addDaysAndCheckForCompletion(20, NextEvent.BLOCK, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
invoiceChecker.checkInvoice(account.getId(), 3, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 6, 30), new LocalDate(2012, 7, 31), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 7, 31), callContext);
@@ -620,21 +604,21 @@ public class TestOverdueIntegration extends TestOverdueBase {
checkODState("OD1");
checkChangePlanWithOverdueState(baseEntitlement, true, true);
- // DAY 67 - 37 days after invoice
+ // 2012-07-07 => DAY 67 - 37 days after invoice
addDaysAndCheckForCompletion(2);
// Should still be in OD1
checkODState("OD1");
checkChangePlanWithOverdueState(baseEntitlement, true, true);
- // DAY 75 - 45 days after invoice
+ // 2012-07-15 => DAY 75 - 45 days after invoice
addDaysAndCheckForCompletion(8, NextEvent.BLOCK, NextEvent.TAG);
// Should now be in OD2
checkODState("OD2");
checkChangePlanWithOverdueState(baseEntitlement, true, true);
- // DAY 85 - 55 days after invoice
+ // 2012-07-25 => DAY 85 - 55 days after invoice
addDaysAndCheckForCompletion(10, NextEvent.BLOCK);
// Should now be in OD3
@@ -651,6 +635,7 @@ public class TestOverdueIntegration extends TestOverdueBase {
new ExpectedInvoiceItemCheck(new LocalDate(2012, 6, 30), new LocalDate(2012, 7, 31), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
invoiceChecker.checkInvoice(account.getId(), 4, callContext,
+ // Item for the blocked period
new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 15), new LocalDate(2012, 7, 25), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-80.63")),
new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 25), new LocalDate(2012, 7, 25), InvoiceItemType.CBA_ADJ, new BigDecimal("80.63")));
@@ -659,19 +644,21 @@ public class TestOverdueIntegration extends TestOverdueBase {
checkChangePlanWithOverdueState(baseEntitlement, false, false);
invoiceChecker.checkInvoice(account.getId(), 4, callContext,
+ // Item for the blocked period
new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 15), new LocalDate(2012, 7, 25), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-80.63")),
new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 25), new LocalDate(2012, 7, 25), InvoiceItemType.CBA_ADJ, new BigDecimal("80.63")));
invoiceChecker.checkInvoice(account.getId(), 5, callContext,
new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 25), new LocalDate(2012, 7, 31), InvoiceItemType.RECURRING, new BigDecimal("116.12")),
- new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 25), new LocalDate(2012, 7, 31), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-48.38")));
+ new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 25), new LocalDate(2012, 7, 31), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-48.38")),
+ new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 25), new LocalDate(2012, 7, 25), InvoiceItemType.CBA_ADJ, new BigDecimal("-67.74")));
invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 7, 31), callContext);
assertEquals(invoiceUserApi.getAccountBalance(account.getId(), callContext).compareTo(new BigDecimal("-12.89")), 0);
}
- @Test(groups = "slow", description = "Test overdue from non paid external charge", enabled = false)
+ @Test(groups = "slow", description = "Test overdue from non paid external charge")
public void testShouldBeInOverdueAfterExternalCharge() throws Exception {
// 2012-05-01T00:03:42.000Z
clock.setTime(new DateTime(2012, 5, 1, 0, 3, 42, 0));
@@ -683,7 +670,7 @@ public class TestOverdueIntegration extends TestOverdueBase {
invoiceChecker.checkInvoice(account.getId(), 1, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), null, InvoiceItemType.FIXED, new BigDecimal("0")));
invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 5, 1), callContext);
- // Create an external charge on a new invoice
+ // 2012-05-06 => Create an external charge on a new invoice
addDaysAndCheckForCompletion(5);
busHandler.pushExpectedEvents(NextEvent.INVOICE_ADJUSTMENT);
final InvoiceItem externalCharge = new ExternalChargeInvoiceItem(null, account.getId(), bundle.getId(), "For overdue", new LocalDate(2012, 5, 6), BigDecimal.TEN, Currency.USD);
@@ -691,8 +678,8 @@ public class TestOverdueIntegration extends TestOverdueBase {
assertListenerStatus();
invoiceChecker.checkInvoice(account.getId(), 2, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 6), null, InvoiceItemType.EXTERNAL_CHARGE, BigDecimal.TEN));
- // DAY 30 have to get out of trial before first payment
- addDaysAndCheckForCompletion(25, NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT);
+ // 2012-05-31 => DAY 30 have to get out of trial before first payment
+ addDaysAndCheckForCompletion(25, NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
invoiceChecker.checkInvoice(account.getId(), 3, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 31), new LocalDate(2012, 6, 30), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 6, 30), callContext);
@@ -701,22 +688,19 @@ public class TestOverdueIntegration extends TestOverdueBase {
// We refresh overdue just to be safe, see below
checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
- // Past 30 days since the external charge
+ // 2012-06-06 => Past 30 days since the external charge
addDaysAndCheckForCompletion(6, NextEvent.BLOCK);
- // Note! We need to explicitly refresh here because overdue won't get notified to refresh up until the next
- // payment (when the next invoice is generated)
- // TODO - we should fix this
// We should now be in OD1
checkODState("OD1");
// Pay the invoice
final Invoice externalChargeInvoice = invoiceUserApi.getUnpaidInvoicesByAccountId(account.getId(), clock.getUTCToday(), callContext).iterator().next();
- createExternalPaymentAndCheckForCompletion(account, externalChargeInvoice, NextEvent.PAYMENT, NextEvent.BLOCK);
+ createExternalPaymentAndCheckForCompletion(account, externalChargeInvoice, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT, NextEvent.BLOCK);
// We should be clear now
checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
}
- @Test(groups = "slow", description = "Test overdue after refund with no adjustment", enabled = false)
+ @Test(groups = "slow", description = "Test overdue after refund with no adjustment")
public void testShouldBeInOverdueAfterRefundWithoutAdjustment() throws Exception {
// 2012-05-01T00:03:42.000Z
clock.setTime(new DateTime(2012, 5, 1, 0, 3, 42, 0));
@@ -728,8 +712,8 @@ public class TestOverdueIntegration extends TestOverdueBase {
invoiceChecker.checkInvoice(account.getId(), 1, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), null, InvoiceItemType.FIXED, new BigDecimal("0")));
invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 5, 1), callContext);
- // DAY 30 have to get out of trial before first payment
- addDaysAndCheckForCompletion(30, NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT);
+ // 2012-05-31 => DAY 30 have to get out of trial before first payment
+ addDaysAndCheckForCompletion(30, NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
invoiceChecker.checkInvoice(account.getId(), 2, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 31), new LocalDate(2012, 6, 30), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 6, 30), callContext);
@@ -737,14 +721,14 @@ public class TestOverdueIntegration extends TestOverdueBase {
// Should still be in clear state
checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
- // DAY 45 - 15 days after invoice
+ // 2012-06-15 => DAY 45 - 15 days after invoice
addDaysAndCheckForCompletion(15);
// Should still be in clear state
checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
- // DAY 65 - 35 days after invoice
- addDaysAndCheckForCompletion(20, NextEvent.INVOICE, NextEvent.PAYMENT);
+ // 2012-07-05 => DAY 65 - 35 days after invoice
+ addDaysAndCheckForCompletion(20, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
invoiceChecker.checkInvoice(account.getId(), 3, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 6, 30), new LocalDate(2012, 7, 31), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 7, 31), callContext);
@@ -754,13 +738,13 @@ public class TestOverdueIntegration extends TestOverdueBase {
// Now, refund the second (first non-zero dollar) invoice
final Payment payment = paymentApi.getPayment(invoiceUserApi.getInvoicesByAccount(account.getId(), callContext).get(1).getPayments().get(0).getPaymentId(), false, PLUGIN_PROPERTIES, callContext);
- refundPaymentAndCheckForCompletion(account, payment, NextEvent.BLOCK, NextEvent.INVOICE_ADJUSTMENT);
+ refundPaymentAndCheckForCompletion(account, payment, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT, NextEvent.BLOCK);
// We should now be in OD1
checkODState("OD1");
checkChangePlanWithOverdueState(baseEntitlement, true, true);
}
- @Test(groups = "slow", description = "Test overdue after chargeback", enabled = false)
+ @Test(groups = "slow", description = "Test overdue after chargeback")
public void testShouldBeInOverdueAfterChargeback() throws Exception {
// 2012-05-01T00:03:42.000Z
clock.setTime(new DateTime(2012, 5, 1, 0, 3, 42, 0));
@@ -772,8 +756,8 @@ public class TestOverdueIntegration extends TestOverdueBase {
invoiceChecker.checkInvoice(account.getId(), 1, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), null, InvoiceItemType.FIXED, new BigDecimal("0")));
invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 5, 1), callContext);
- // DAY 30 have to get out of trial before first payment
- addDaysAndCheckForCompletion(30, NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT);
+ // 2012-05-31 => DAY 30 have to get out of trial before first payment
+ addDaysAndCheckForCompletion(30, NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
invoiceChecker.checkInvoice(account.getId(), 2, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 31), new LocalDate(2012, 6, 30), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 6, 30), callContext);
@@ -781,14 +765,14 @@ public class TestOverdueIntegration extends TestOverdueBase {
// Should still be in clear state
checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
- // DAY 45 - 15 days after invoice
+ // 2012-06-15 => DAY 45 - 15 days after invoice
addDaysAndCheckForCompletion(15);
// Should still be in clear state
checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
- // DAY 65 - 35 days after invoice
- addDaysAndCheckForCompletion(20, NextEvent.INVOICE, NextEvent.PAYMENT);
+ // 2012-07-05 => DAY 65 - 35 days after invoice
+ addDaysAndCheckForCompletion(20, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
invoiceChecker.checkInvoice(account.getId(), 3, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 6, 30), new LocalDate(2012, 7, 31), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 7, 31), callContext);
@@ -799,13 +783,13 @@ public class TestOverdueIntegration extends TestOverdueBase {
// Now, create a chargeback for the second (first non-zero dollar) invoice
final InvoicePayment invoicePayment = invoicePaymentApi.getInvoicePayments(invoiceUserApi.getInvoicesByAccount(account.getId(), callContext).get(1).getPayments().get(0).getPaymentId(), callContext).get(0);
final Payment payment = paymentApi.getPayment(invoicePayment.getPaymentId(), false, ImmutableList.<PluginProperty>of(), callContext);
- createChargeBackAndCheckForCompletion(account, payment, NextEvent.BLOCK, NextEvent.INVOICE_ADJUSTMENT);
+ createChargeBackAndCheckForCompletion(account, payment, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT, NextEvent.BLOCK);
// We should now be in OD1
checkODState("OD1");
checkChangePlanWithOverdueState(baseEntitlement, true, true);
}
- @Test(groups = "slow", description = "Test overdue clear after external payment", enabled = false)
+ @Test(groups = "slow", description = "Test overdue clear after external payment")
public void testOverdueStateShouldClearAfterExternalPayment() throws Exception {
// 2012-05-01T00:03:42.000Z
clock.setTime(new DateTime(2012, 5, 1, 0, 3, 42, 0));
@@ -818,8 +802,8 @@ public class TestOverdueIntegration extends TestOverdueBase {
invoiceChecker.checkInvoice(account.getId(), 1, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), null, InvoiceItemType.FIXED, new BigDecimal("0")));
invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 5, 1), callContext);
- // DAY 30 have to get out of trial before first payment
- addDaysAndCheckForCompletion(30, NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR);
+ // 2012-05-31 => DAY 30 have to get out of trial before first payment
+ addDaysAndCheckForCompletion(30, NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
invoiceChecker.checkInvoice(account.getId(), 2, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 31), new LocalDate(2012, 6, 30), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 6, 30), callContext);
@@ -827,14 +811,14 @@ public class TestOverdueIntegration extends TestOverdueBase {
// Should still be in clear state
checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
- // DAY 45 - 15 days after invoice
- addDaysAndCheckForCompletion(15, NextEvent.PAYMENT_ERROR);
+ // 2012-06-15 => DAY 45 - 15 days after invoice
+ addDaysAndCheckForCompletion(15, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
// Should still be in clear state
checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
- // DAY 65 - 35 days after invoice
- addDaysAndCheckForCompletion(20, NextEvent.BLOCK, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR, NextEvent.PAYMENT_ERROR);
+ // 2012-07-05 => DAY 65 - 35 days after invoice
+ addDaysAndCheckForCompletion(20, NextEvent.BLOCK, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
invoiceChecker.checkInvoice(account.getId(), 3, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 6, 30), new LocalDate(2012, 7, 31), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 7, 31), callContext);
@@ -846,13 +830,14 @@ public class TestOverdueIntegration extends TestOverdueBase {
// We have two unpaid non-zero dollar invoices at this point
// Pay the first one via an external payment - we should then be 5 days apart from the second invoice
// (which is the earliest unpaid one) and hence come back to a clear state (see configuration)
+ paymentPlugin.makeAllInvoicesFailWithError(false);
final Invoice firstNonZeroInvoice = invoiceUserApi.getUnpaidInvoicesByAccountId(account.getId(), clock.getUTCToday(), callContext).iterator().next();
- createExternalPaymentAndCheckForCompletion(account, firstNonZeroInvoice, NextEvent.PAYMENT, NextEvent.BLOCK);
+ createExternalPaymentAndCheckForCompletion(account, firstNonZeroInvoice, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT, NextEvent.BLOCK);
// We should be clear now
checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
}
- @Test(groups = "slow", description = "Test overdue clear after item adjustment", enabled = false)
+ @Test(groups = "slow", description = "Test overdue clear after item adjustment")
public void testOverdueStateShouldClearAfterCreditOrInvoiceItemAdjustment() throws Exception {
// 2012-05-01T00:03:42.000Z
clock.setTime(new DateTime(2012, 5, 1, 0, 3, 42, 0));
@@ -866,7 +851,7 @@ public class TestOverdueIntegration extends TestOverdueBase {
invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 5, 1), callContext);
// DAY 30 have to get out of trial before first payment
- addDaysAndCheckForCompletion(30, NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR);
+ addDaysAndCheckForCompletion(30, NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
invoiceChecker.checkInvoice(account.getId(), 2, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 31), new LocalDate(2012, 6, 30), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 6, 30), callContext);
@@ -875,13 +860,13 @@ public class TestOverdueIntegration extends TestOverdueBase {
checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
// DAY 45 - 15 days after invoice
- addDaysAndCheckForCompletion(15, NextEvent.PAYMENT_ERROR);
+ addDaysAndCheckForCompletion(15, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
// Should still be in clear state
checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
// DAY 65 - 35 days after invoice
- addDaysAndCheckForCompletion(20, NextEvent.BLOCK, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR, NextEvent.PAYMENT_ERROR);
+ addDaysAndCheckForCompletion(20, NextEvent.BLOCK, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
invoiceChecker.checkInvoice(account.getId(), 3, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 6, 30), new LocalDate(2012, 7, 31), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 7, 31), callContext);
@@ -898,9 +883,9 @@ public class TestOverdueIntegration extends TestOverdueBase {
// We should be clear now
checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
- invoiceChecker.checkRepairedInvoice(account.getId(), 2,
- callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 31), new LocalDate(2012, 6, 30), InvoiceItemType.RECURRING, new BigDecimal("249.95")),
- new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 31), new LocalDate(2012, 5, 31), InvoiceItemType.ITEM_ADJ, new BigDecimal("-249.95")));
+ invoiceChecker.checkInvoice(account.getId(), 2,
+ callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 31), new LocalDate(2012, 6, 30), InvoiceItemType.RECURRING, new BigDecimal("249.95")),
+ new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 31), new LocalDate(2012, 5, 31), InvoiceItemType.ITEM_ADJ, new BigDecimal("-249.95")));
invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 7, 31), callContext);
// DAY 70 - 10 days after second invoice
@@ -910,13 +895,13 @@ public class TestOverdueIntegration extends TestOverdueBase {
checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
// DAY 80 - 20 days after second invoice
- addDaysAndCheckForCompletion(10, NextEvent.PAYMENT_ERROR);
+ addDaysAndCheckForCompletion(10, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
// We should still be clear
checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
// DAY 95 - 35 days after second invoice
- addDaysAndCheckForCompletion(15, NextEvent.BLOCK, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR);
+ addDaysAndCheckForCompletion(15, NextEvent.BLOCK, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
// We should now be in OD1
checkODState("OD1");
@@ -943,8 +928,7 @@ public class TestOverdueIntegration extends TestOverdueBase {
// TODO add/remove tag to invoice
}
- private void allowPaymentsAndResetOverdueToClearByPayingAllUnpaidInvoices(boolean extraInvoice) {
-
+ private void allowPaymentsAndResetOverdueToClearByPayingAllUnpaidInvoices(final boolean extraPayment) {
// Reset plugin so payments should now succeed
paymentPlugin.makeAllInvoicesFailWithError(false);
@@ -952,7 +936,7 @@ public class TestOverdueIntegration extends TestOverdueBase {
// We now pay all unpaid invoices.
//
// Upon paying the last invoice, the overdue system will clear the state and notify invoice that it should re-generate a new invoice
- // for the part hat was unblocked, which explains why on the last payment we expect an additional invoice and payment.
+ // for the part that was unblocked, which explains why on the last payment we expect an additional invoice (and payment if needed).
//
final List<Invoice> sortedInvoices = getUnpaidInvoicesOrderFromRecent();
@@ -961,12 +945,12 @@ public class TestOverdueIntegration extends TestOverdueBase {
if (invoice.getBalance().compareTo(BigDecimal.ZERO) > 0) {
remainingUnpaidInvoices--;
if (remainingUnpaidInvoices > 0) {
- createPaymentAndCheckForCompletion(account, invoice, NextEvent.PAYMENT);
+ createPaymentAndCheckForCompletion(account, invoice, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
} else {
- if (extraInvoice) {
- createPaymentAndCheckForCompletion(account, invoice, NextEvent.BLOCK, NextEvent.TAG, NextEvent.PAYMENT, NextEvent.INVOICE, NextEvent.INVOICE, NextEvent.PAYMENT);
+ if (extraPayment) {
+ createPaymentAndCheckForCompletion(account, invoice, NextEvent.BLOCK, NextEvent.TAG, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
} else {
- createPaymentAndCheckForCompletion(account, invoice, NextEvent.BLOCK, NextEvent.TAG, NextEvent.INVOICE, NextEvent.PAYMENT);
+ createPaymentAndCheckForCompletion(account, invoice, NextEvent.BLOCK, NextEvent.TAG, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
}
}
}
@@ -996,11 +980,11 @@ public class TestOverdueIntegration extends TestOverdueBase {
String.format("Cause is %s, message is %s", e.getCause(), e.getMessage()));
}
} else {
- // Upgrade - we don't expect a payment here due to the scenario (the account will have some CBA)
+ // Upgrade - we don't expect a payment here due to the scenario (the account will have enough CBA)
if (expectedPayment) {
- changeEntitlementAndCheckForCompletion(entitlement, "Assault-Rifle", BillingPeriod.MONTHLY, null, NextEvent.CHANGE, NextEvent.INVOICE_ADJUSTMENT, NextEvent.INVOICE, NextEvent.PAYMENT);
+ changeEntitlementAndCheckForCompletion(entitlement, "Assault-Rifle", BillingPeriod.MONTHLY, null, NextEvent.CHANGE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
} else {
- changeEntitlementAndCheckForCompletion(entitlement, "Assault-Rifle", BillingPeriod.MONTHLY, null, NextEvent.CHANGE, NextEvent.INVOICE_ADJUSTMENT, NextEvent.INVOICE);
+ changeEntitlementAndCheckForCompletion(entitlement, "Assault-Rifle", BillingPeriod.MONTHLY, null, NextEvent.CHANGE, NextEvent.INVOICE);
}
}
}
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueWithOverdueEnforcementOffTag.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueWithOverdueEnforcementOffTag.java
index c3dc531..e110c57 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueWithOverdueEnforcementOffTag.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueWithOverdueEnforcementOffTag.java
@@ -1,7 +1,9 @@
/*
* Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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:
*
@@ -78,7 +80,7 @@ public class TestOverdueWithOverdueEnforcementOffTag extends TestOverdueBase {
invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 5, 1), callContext);
// DAY 30 have to get out of trial before first payment
- addDaysAndCheckForCompletion(30, NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR);
+ addDaysAndCheckForCompletion(30, NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
invoiceChecker.checkInvoice(account.getId(), 2, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 31), new LocalDate(2012, 6, 30), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 6, 30), callContext);
@@ -110,7 +112,7 @@ public class TestOverdueWithOverdueEnforcementOffTag extends TestOverdueBase {
invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 5, 1), callContext);
// DAY 30 have to get out of trial before first payment
- addDaysAndCheckForCompletion(30, NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR);
+ addDaysAndCheckForCompletion(30, NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
invoiceChecker.checkInvoice(account.getId(), 2, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 31), new LocalDate(2012, 6, 30), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 6, 30), callContext);
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueWithSubscriptionCancellation.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueWithSubscriptionCancellation.java
index 18095ec..026d500 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueWithSubscriptionCancellation.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueWithSubscriptionCancellation.java
@@ -1,7 +1,9 @@
/*
* Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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:
*
@@ -84,7 +86,7 @@ public class TestOverdueWithSubscriptionCancellation extends TestOverdueBase {
cancelEntitlementAndCheckForCompletion(addOn1, clock.getUTCNow(), NextEvent.BLOCK, NextEvent.CANCEL);
// DAY 30 have to get out of trial before first payment
- addDaysAndCheckForCompletion(29, NextEvent.PHASE, NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR);
+ addDaysAndCheckForCompletion(29, NextEvent.PHASE, NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 6, 30), callContext);
@@ -108,8 +110,7 @@ public class TestOverdueWithSubscriptionCancellation extends TestOverdueBase {
assertTrue(cancelledAddon1.getState() == EntitlementState.CANCELLED);
}
-
- @Test(groups = "slow", enabled = false, description = "https://github.com/killbill/killbill/issues/248")
+ @Test(groups = "slow")
public void testCheckSubscriptionCancellationWithMultipleBundles() throws Exception {
// 2012-05-01T00:03:53.000Z
clock.setTime(new DateTime(2012, 5, 1, 0, 3, 42, 0));
@@ -138,7 +139,7 @@ public class TestOverdueWithSubscriptionCancellation extends TestOverdueBase {
assertTrue(cancelledBaseSubscription2.getState() == EntitlementState.CANCELLED);
// DAY 30 have to get out of trial before first payment (2012-05-31)
- addDaysAndCheckForCompletion(29, NextEvent.PHASE, NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR);
+ addDaysAndCheckForCompletion(29, NextEvent.PHASE, NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
invoiceChecker.checkInvoice(account.getId(),
4,
callContext,
@@ -152,7 +153,7 @@ public class TestOverdueWithSubscriptionCancellation extends TestOverdueBase {
// DAY 36 (2012-06-06)-- RIGHT AFTER OD1 (two block events, for the cancellation and the OD1 state)
// One BLOCK event is for the overdue state transition
// The 2 other BLOCK are for the entitlement blocking states for both baseEntitlement and baseEntitlement3
- addDaysAndCheckForCompletion(6, NextEvent.BLOCK, NextEvent.BLOCK, NextEvent.BLOCK, NextEvent.CANCEL, NextEvent.CANCEL, NextEvent.INVOICE, NextEvent.INVOICE);
+ addDaysAndCheckForCompletion(6, NextEvent.BLOCK, NextEvent.BLOCK, NextEvent.BLOCK, NextEvent.CANCEL, NextEvent.CANCEL, NextEvent.INVOICE);
// Should be in OD1
checkODState("OD1");
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestBundleTransfer.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestBundleTransfer.java
index 86c91ec..7915e6e 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestBundleTransfer.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestBundleTransfer.java
@@ -1,7 +1,9 @@
/*
* Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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:
*
@@ -75,18 +77,14 @@ public class TestBundleTransfer extends TestIntegrationBase {
assertEquals(bpEntitlement.getSubscriptionBase().getCurrentPlan().getRecurringBillingPeriod(), BillingPeriod.ANNUAL);
// Move out of trials for interesting invoices adjustments
- busHandler.pushExpectedEvent(NextEvent.PHASE);
- busHandler.pushExpectedEvent(NextEvent.INVOICE);
- busHandler.pushExpectedEvent(NextEvent.PAYMENT);
+ busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
clock.addDays(40);
assertListenerStatus();
// BUNDLE TRANSFER
final Account newAccount = createAccountWithNonOsgiPaymentMethod(getAccountData(17));
- busHandler.pushExpectedEvent(NextEvent.TRANSFER);
- busHandler.pushExpectedEvent(NextEvent.INVOICE);
- busHandler.pushExpectedEvent(NextEvent.PAYMENT);
+ busHandler.pushExpectedEvents(NextEvent.TRANSFER, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
transferApi.transferBundle(account.getId(), newAccount.getId(), "externalKey", clock.getUTCNow(), false, false, callContext);
assertListenerStatus();
@@ -129,18 +127,14 @@ public class TestBundleTransfer extends TestIntegrationBase {
assertEquals(bpEntitlement.getSubscriptionBase().getCurrentPlan().getRecurringBillingPeriod(), BillingPeriod.MONTHLY);
// Move out of trials for interesting invoices adjustments
- busHandler.pushExpectedEvent(NextEvent.PHASE);
- busHandler.pushExpectedEvent(NextEvent.INVOICE);
- busHandler.pushExpectedEvent(NextEvent.PAYMENT);
+ busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
clock.addDays(32);
assertListenerStatus();
// BUNDLE TRANSFER
final Account newAccount = createAccountWithNonOsgiPaymentMethod(getAccountData(0));
- busHandler.pushExpectedEvent(NextEvent.TRANSFER);
- busHandler.pushExpectedEvent(NextEvent.INVOICE);
- busHandler.pushExpectedEvent(NextEvent.PAYMENT);
+ busHandler.pushExpectedEvents(NextEvent.TRANSFER, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
transferApi.transferBundle(account.getId(), newAccount.getId(), "externalKey", clock.getUTCNow(), false, false, callContext);
assertListenerStatus();
@@ -191,20 +185,14 @@ public class TestBundleTransfer extends TestIntegrationBase {
assertEquals(bpEntitlement.getSubscriptionBase().getCurrentPlan().getRecurringBillingPeriod(), BillingPeriod.MONTHLY);
// Move out of trials for interesting invoices adjustments
- busHandler.pushExpectedEvent(NextEvent.PHASE);
- busHandler.pushExpectedEvent(NextEvent.INVOICE);
- busHandler.pushExpectedEvent(NextEvent.PAYMENT);
+ busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
clock.addDays(32);
assertListenerStatus();
// BUNDLE TRANSFER
final Account newAccount = createAccountWithNonOsgiPaymentMethod(getAccountData(15));
- busHandler.pushExpectedEvent(NextEvent.CANCEL);
- busHandler.pushExpectedEvent(NextEvent.TRANSFER);
- busHandler.pushExpectedEvent(NextEvent.INVOICE);
- busHandler.pushExpectedEvent(NextEvent.INVOICE);
- busHandler.pushExpectedEvent(NextEvent.PAYMENT);
+ busHandler.pushExpectedEvents(NextEvent.CANCEL, NextEvent.TRANSFER, NextEvent.INVOICE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
transferApi.transferBundle(account.getId(), newAccount.getId(), "externalKey", clock.getUTCNow(), false, true, callContext);
assertListenerStatus();
@@ -256,12 +244,12 @@ public class TestBundleTransfer extends TestIntegrationBase {
// Create the add-on
final DefaultEntitlement aoEntitlement = addAOEntitlementAndCheckForCompletion(bpEntitlement.getBundleId(), aoProductName, ProductCategory.ADD_ON, term,
- NextEvent.CREATE, NextEvent.INVOICE, NextEvent.PAYMENT);
+ NextEvent.CREATE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
final Invoice secondInvoice = invoiceChecker.checkInvoice(account.getId(), 2, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 4, 1), new LocalDate(2012, 5, 1), InvoiceItemType.RECURRING, new BigDecimal("399.95")));
paymentChecker.checkPayment(account.getId(), 1, callContext, new ExpectedPaymentCheck(new LocalDate(2012, 4, 1), new BigDecimal("399.95"), TransactionStatus.SUCCESS, secondInvoice.getId(), Currency.USD));
// Move past the phase for simplicity
- busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT);
+ busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
clock.addDays(30);
assertListenerStatus();
final Invoice thirdInvoice = invoiceChecker.checkInvoice(account.getId(), 3, callContext,
@@ -270,7 +258,7 @@ public class TestBundleTransfer extends TestIntegrationBase {
paymentChecker.checkPayment(account.getId(), 2, callContext, new ExpectedPaymentCheck(new LocalDate(2012, 5, 1), new BigDecimal("1249.90"), TransactionStatus.SUCCESS, thirdInvoice.getId(), Currency.USD));
// Align the transfer on the BCD to make pro-rations easier
- busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT);
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
// Move a bit the time to make sure notifications kick in
clock.setTime(new DateTime(2012, 6, 1, 1, 0, DateTimeZone.UTC));
assertListenerStatus();
@@ -282,7 +270,7 @@ public class TestBundleTransfer extends TestIntegrationBase {
final DateTime now = clock.getUTCNow();
final LocalDate transferDay = now.toLocalDate();
- busHandler.pushExpectedEvents(NextEvent.CANCEL, NextEvent.CANCEL, NextEvent.BLOCK, NextEvent.BLOCK, NextEvent.TRANSFER, NextEvent.TRANSFER, NextEvent.INVOICE, NextEvent.INVOICE, NextEvent.PAYMENT);
+ busHandler.pushExpectedEvents(NextEvent.CANCEL, NextEvent.CANCEL, NextEvent.BLOCK, NextEvent.BLOCK, NextEvent.TRANSFER, NextEvent.TRANSFER, NextEvent.INVOICE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
final UUID newBundleId = entitlementApi.transferEntitlements(account.getId(), newAccount.getId(), bundleExternalKey, transferDay, ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestCatalogRetireElements.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestCatalogRetireElements.java
index ed65f98..3a8a532 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestCatalogRetireElements.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestCatalogRetireElements.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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
@@ -74,7 +74,7 @@ public class TestCatalogRetireElements extends TestIntegrationBase {
assertEquals(bpEntitlement.getSubscriptionBase().getCurrentPlan().getRecurringBillingPeriod(), BillingPeriod.MONTHLY);
// Move out a month.
- busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT);
+ busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
clock.addMonths(1);
assertListenerStatus();
@@ -90,7 +90,7 @@ public class TestCatalogRetireElements extends TestIntegrationBase {
}
// Move out a month and verify 'Pistol' plan continue working as expected.
- busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT);
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
clock.addMonths(1);
assertListenerStatus();
@@ -128,12 +128,12 @@ public class TestCatalogRetireElements extends TestIntegrationBase {
assertEquals(bpEntitlement.getSubscriptionBase().getCurrentPlan().getRecurringBillingPeriod(), BillingPeriod.MONTHLY);
// Move out a month.
- busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT);
+ busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
clock.addMonths(1);
assertListenerStatus();
// Move out a month.
- busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT);
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
clock.addMonths(1);
assertListenerStatus();
@@ -149,7 +149,7 @@ public class TestCatalogRetireElements extends TestIntegrationBase {
}
// Move out a month and verify 'Pistol' plan continue working as expected.
- busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT);
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
clock.addMonths(1);
assertListenerStatus();
@@ -188,12 +188,12 @@ public class TestCatalogRetireElements extends TestIntegrationBase {
assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), callContext).size(), 1);
// Move out a month.
- busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT);
+ busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
clock.addMonths(1);
assertListenerStatus();
// Move out a month.
- busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT);
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
clock.addMonths(1);
assertListenerStatus();
@@ -208,12 +208,12 @@ public class TestCatalogRetireElements extends TestIntegrationBase {
}
// Move out a month and verify 'Pistol' plan continue working as expected.
- busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT);
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
clock.addMonths(1);
assertListenerStatus();
// Move out a month.
- busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT);
+ busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
clock.addMonths(1);
assertListenerStatus();
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegration.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegration.java
index 8d7f06f..b1ff283 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegration.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegration.java
@@ -1,7 +1,9 @@
/*
* Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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:
*
@@ -99,7 +101,7 @@ public class TestIntegration extends TestIntegrationBase {
expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2012, 4, 1), new LocalDate(2012, 5, 1), InvoiceItemType.RECURRING, new BigDecimal("399.95")));
invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, callContext, expectedInvoices);
- addAOEntitlementAndCheckForCompletion(bpSubscription.getBundleId(), "Telescopic-Scope", ProductCategory.ADD_ON, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.INVOICE, NextEvent.PAYMENT);
+ addAOEntitlementAndCheckForCompletion(bpSubscription.getBundleId(), "Telescopic-Scope", ProductCategory.ADD_ON, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
Invoice invoice = invoiceChecker.checkInvoice(account.getId(), 2, callContext, expectedInvoices);
paymentChecker.checkPayment(account.getId(), 1, callContext, new ExpectedPaymentCheck(new LocalDate(2012, 4, 1), new BigDecimal("399.95"), TransactionStatus.SUCCESS, invoice.getId(), Currency.USD));
@@ -181,12 +183,12 @@ public class TestIntegration extends TestIntegrationBase {
expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2012, 3, 2), new LocalDate(2012, 3, 31), InvoiceItemType.RECURRING, new BigDecimal("561.24")));
- // Verify first next targetDate
+ // Verify first next targetDate
dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), new LocalDate(nextDate, testTimeZone), dryRun, callContext);
invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, callContext, expectedInvoices);
- setDateAndCheckForCompletion(nextDate, NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT);
+ setDateAndCheckForCompletion(nextDate, NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
invoiceChecker.checkInvoice(account.getId(), invoiceItemCount++, callContext, expectedInvoices);
invoiceChecker.checkChargedThroughDate(subscription.getId(), new LocalDate(2012, 3, 31), callContext);
expectedInvoices.clear();
@@ -211,23 +213,23 @@ public class TestIntegration extends TestIntegrationBase {
invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, callContext, expectedInvoices);
- addDaysAndCheckForCompletion(31, NextEvent.CHANGE, NextEvent.INVOICE, NextEvent.PAYMENT);
+ addDaysAndCheckForCompletion(31, NextEvent.CHANGE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
invoiceChecker.checkInvoice(account.getId(), invoiceItemCount++, callContext, expectedInvoices);
invoiceChecker.checkChargedThroughDate(subscription.getId(), secondRecurringPistolDate, callContext);
expectedInvoices.clear();
//
- // MOVE 3 * TIME AFTER NEXT BILL CYCLE DAY AND EXPECT EVENT : NextEvent.INVOICE, NextEvent.PAYMENT
+ // MOVE 3 * TIME AFTER NEXT BILL CYCLE DAY AND EXPECT EVENT : NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT
//
- addDaysAndCheckForCompletion(31, NextEvent.INVOICE, NextEvent.PAYMENT);
+ addDaysAndCheckForCompletion(31, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
invoiceChecker.checkInvoice(account.getId(), invoiceItemCount++, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 4, 30), new LocalDate(2012, 5, 31), InvoiceItemType.RECURRING, new BigDecimal("29.95")));
invoiceChecker.checkChargedThroughDate(subscription.getId(), new LocalDate(2012, 5, 31), callContext);
- addDaysAndCheckForCompletion(31, NextEvent.INVOICE, NextEvent.PAYMENT);
+ addDaysAndCheckForCompletion(31, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
invoiceChecker.checkInvoice(account.getId(), invoiceItemCount++, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 31), new LocalDate(2012, 6, 30), InvoiceItemType.RECURRING, new BigDecimal("29.95")));
invoiceChecker.checkChargedThroughDate(subscription.getId(), new LocalDate(2012, 6, 30), callContext);
- addDaysAndCheckForCompletion(31, NextEvent.INVOICE, NextEvent.PAYMENT);
+ addDaysAndCheckForCompletion(31, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
invoiceChecker.checkInvoice(account.getId(), invoiceItemCount++, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 6, 30), new LocalDate(2012, 7, 31), InvoiceItemType.RECURRING, new BigDecimal("29.95")));
invoiceChecker.checkChargedThroughDate(subscription.getId(), new LocalDate(2012, 7, 31), callContext);
@@ -280,7 +282,7 @@ public class TestIntegration extends TestIntegrationBase {
setDateAndCheckForCompletion(new DateTime(2012, 2, 28, 23, 59, 59, 0, testTimeZone));
setDateAndCheckForCompletion(new DateTime(2012, 2, 29, 23, 59, 59, 0, testTimeZone));
setDateAndCheckForCompletion(new DateTime(2012, 3, 1, 23, 59, 59, 0, testTimeZone));
- setDateAndCheckForCompletion(new DateTime(2012, 3, 2, 23, 59, 59, 0, testTimeZone), NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT);
+ setDateAndCheckForCompletion(new DateTime(2012, 3, 2, 23, 59, 59, 0, testTimeZone), NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
invoiceChecker.checkInvoice(account.getId(), invoiceItemCount++, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 3, 2),
new LocalDate(2012, 4, 2), InvoiceItemType.RECURRING, new BigDecimal("599.95")));
invoiceChecker.checkChargedThroughDate(subscription.getId(), new LocalDate(2012, 4, 2), callContext);
@@ -309,22 +311,22 @@ public class TestIntegration extends TestIntegrationBase {
//
final LocalDate firstRecurringPistolDate = subscription.getChargedThroughDate().toLocalDate();
final LocalDate secondRecurringPistolDate = firstRecurringPistolDate.plusMonths(1);
- addDaysAndCheckForCompletion(31, NextEvent.CHANGE, NextEvent.INVOICE, NextEvent.PAYMENT);
+ addDaysAndCheckForCompletion(31, NextEvent.CHANGE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
invoiceChecker.checkInvoice(account.getId(), invoiceItemCount++, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 4, 2), new LocalDate(2012, 5, 2), InvoiceItemType.RECURRING, new BigDecimal("29.95")));
invoiceChecker.checkChargedThroughDate(subscription.getId(), secondRecurringPistolDate, callContext);
//
- // MOVE 3 * TIME AFTER NEXT BILL CYCLE DAY AND EXPECT EVENT : NextEvent.INVOICE, NextEvent.PAYMENT
+ // MOVE 3 * TIME AFTER NEXT BILL CYCLE DAY AND EXPECT EVENT : NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT
//
- addDaysAndCheckForCompletion(31, NextEvent.INVOICE, NextEvent.PAYMENT);
+ addDaysAndCheckForCompletion(31, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
invoiceChecker.checkInvoice(account.getId(), invoiceItemCount++, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 2), new LocalDate(2012, 6, 2), InvoiceItemType.RECURRING, new BigDecimal("29.95")));
invoiceChecker.checkChargedThroughDate(subscription.getId(), new LocalDate(2012, 6, 2), callContext);
- addDaysAndCheckForCompletion(31, NextEvent.INVOICE, NextEvent.PAYMENT);
+ addDaysAndCheckForCompletion(31, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
invoiceChecker.checkInvoice(account.getId(), invoiceItemCount++, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 6, 2), new LocalDate(2012, 7, 2), InvoiceItemType.RECURRING, new BigDecimal("29.95")));
invoiceChecker.checkChargedThroughDate(subscription.getId(), new LocalDate(2012, 7, 2), callContext);
- addDaysAndCheckForCompletion(31, NextEvent.INVOICE, NextEvent.PAYMENT);
+ addDaysAndCheckForCompletion(31, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
invoiceChecker.checkInvoice(account.getId(), invoiceItemCount++, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 2), new LocalDate(2012, 8, 2), InvoiceItemType.RECURRING, new BigDecimal("29.95")));
invoiceChecker.checkChargedThroughDate(subscription.getId(), new LocalDate(2012, 8, 2), callContext);
@@ -377,13 +379,13 @@ public class TestIntegration extends TestIntegrationBase {
setDateAndCheckForCompletion(new DateTime(2012, 2, 28, 23, 59, 59, 0, testTimeZone));
setDateAndCheckForCompletion(new DateTime(2012, 2, 29, 23, 59, 59, 0, testTimeZone));
setDateAndCheckForCompletion(new DateTime(2012, 3, 1, 23, 59, 59, 0, testTimeZone));
- setDateAndCheckForCompletion(new DateTime(2012, 3, 2, 23, 59, 59, 0, testTimeZone), NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT);
+ setDateAndCheckForCompletion(new DateTime(2012, 3, 2, 23, 59, 59, 0, testTimeZone), NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
// PRO_RATION
invoiceChecker.checkInvoice(account.getId(), invoiceItemCount++, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 3, 2),
new LocalDate(2012, 3, 3), InvoiceItemType.RECURRING, new BigDecimal("20.69")));
invoiceChecker.checkChargedThroughDate(subscription.getId(), new LocalDate(2012, 3, 3), callContext);
- setDateAndCheckForCompletion(new DateTime(2012, 3, 3, 23, 59, 59, 0, testTimeZone), NextEvent.INVOICE, NextEvent.PAYMENT);
+ setDateAndCheckForCompletion(new DateTime(2012, 3, 3, 23, 59, 59, 0, testTimeZone), NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
invoiceChecker.checkInvoice(account.getId(), invoiceItemCount++, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 3, 3),
new LocalDate(2012, 4, 3), InvoiceItemType.RECURRING, new BigDecimal("599.95")));
invoiceChecker.checkChargedThroughDate(subscription.getId(), new LocalDate(2012, 4, 3), callContext);
@@ -399,22 +401,22 @@ public class TestIntegration extends TestIntegrationBase {
//
final LocalDate firstRecurringPistolDate = subscription.getChargedThroughDate().toLocalDate();
final LocalDate secondRecurringPistolDate = firstRecurringPistolDate.plusMonths(1);
- addDaysAndCheckForCompletion(31, NextEvent.CHANGE, NextEvent.INVOICE, NextEvent.PAYMENT);
+ addDaysAndCheckForCompletion(31, NextEvent.CHANGE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
invoiceChecker.checkInvoice(account.getId(), invoiceItemCount++, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 4, 3), new LocalDate(2012, 5, 3), InvoiceItemType.RECURRING, new BigDecimal("29.95")));
invoiceChecker.checkChargedThroughDate(subscription.getId(), secondRecurringPistolDate, callContext);
//
- // MOVE 3 * TIME AFTER NEXT BILL CYCLE DAY AND EXPECT EVENT : NextEvent.INVOICE, NextEvent.PAYMENT
+ // MOVE 3 * TIME AFTER NEXT BILL CYCLE DAY AND EXPECT EVENT : NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT
//
- addDaysAndCheckForCompletion(31, NextEvent.INVOICE, NextEvent.PAYMENT);
+ addDaysAndCheckForCompletion(31, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
invoiceChecker.checkInvoice(account.getId(), invoiceItemCount++, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 3), new LocalDate(2012, 6, 3), InvoiceItemType.RECURRING, new BigDecimal("29.95")));
invoiceChecker.checkChargedThroughDate(subscription.getId(), new LocalDate(2012, 6, 3), callContext);
- addDaysAndCheckForCompletion(31, NextEvent.INVOICE, NextEvent.PAYMENT);
+ addDaysAndCheckForCompletion(31, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
invoiceChecker.checkInvoice(account.getId(), invoiceItemCount++, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 6, 3), new LocalDate(2012, 7, 3), InvoiceItemType.RECURRING, new BigDecimal("29.95")));
invoiceChecker.checkChargedThroughDate(subscription.getId(), new LocalDate(2012, 7, 3), callContext);
- addDaysAndCheckForCompletion(31, NextEvent.INVOICE, NextEvent.PAYMENT);
+ addDaysAndCheckForCompletion(31, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
invoiceChecker.checkInvoice(account.getId(), invoiceItemCount++, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 3), new LocalDate(2012, 8, 3), InvoiceItemType.RECURRING, new BigDecimal("29.95")));
invoiceChecker.checkChargedThroughDate(subscription.getId(), new LocalDate(2012, 8, 3), callContext);
@@ -496,23 +498,18 @@ public class TestIntegration extends TestIntegrationBase {
clock.addDays(3);
final DefaultEntitlement aoEntitlement1 = addAOEntitlementAndCheckForCompletion(baseEntitlement.getBundleId(), "Telescopic-Scope", ProductCategory.ADD_ON, BillingPeriod.MONTHLY,
- NextEvent.CREATE, NextEvent.INVOICE, NextEvent.PAYMENT);
+ NextEvent.CREATE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
final DefaultEntitlement aoEntitlement2 = addAOEntitlementAndCheckForCompletion(baseEntitlement.getBundleId(), "Laser-Scope", ProductCategory.ADD_ON, BillingPeriod.MONTHLY,
- NextEvent.CREATE, NextEvent.INVOICE, NextEvent.PAYMENT);
+ NextEvent.CREATE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
// MOVE CLOCK A LITTLE BIT MORE -- EITHER STAY IN TRIAL OR GET OUT
- busHandler.pushExpectedEvent(NextEvent.PHASE);
- busHandler.pushExpectedEvent(NextEvent.PHASE);
- busHandler.pushExpectedEvent(NextEvent.PAYMENT);
- busHandler.pushExpectedEvent(NextEvent.INVOICE);
+ busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
log.info("Moving clock from" + clock.getUTCNow() + " to " + clock.getUTCNow().plusDays(28));
clock.addDays(28);// 26 / 5
assertListenerStatus();
- busHandler.pushExpectedEvent(NextEvent.PHASE);
- busHandler.pushExpectedEvent(NextEvent.INVOICE);
- busHandler.pushExpectedEvent(NextEvent.PAYMENT);
+ busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
log.info("Moving clock from" + clock.getUTCNow() + " to " + clock.getUTCNow().plusDays(3));
clock.addDays(3);// 29 / 5
assertListenerStatus();
@@ -521,8 +518,7 @@ public class TestIntegration extends TestIntegrationBase {
clock.addDays(10);// 8 / 6
assertListenerStatus();
- busHandler.pushExpectedEvent(NextEvent.INVOICE);
- busHandler.pushExpectedEvent(NextEvent.PAYMENT);
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
log.info("Moving clock from" + clock.getUTCNow() + " to " + clock.getUTCNow().plusDays(18));
clock.addDays(18);// 26 / 6
assertListenerStatus();
@@ -606,9 +602,7 @@ public class TestIntegration extends TestIntegrationBase {
//
// MOVE TIME TO AFTER TRIAL AND EXPECT BOTH EVENTS : NextEvent.PHASE NextEvent.INVOICE
//
- busHandler.pushExpectedEvent(NextEvent.PHASE);
- busHandler.pushExpectedEvent(NextEvent.INVOICE);
- busHandler.pushExpectedEvent(NextEvent.PAYMENT);
+ busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
clock.addDeltaFromReality(AT_LEAST_ONE_MONTH_MS);
assertListenerStatus();
@@ -667,9 +661,7 @@ public class TestIntegration extends TestIntegrationBase {
assertNotNull(invoices);
assertTrue(invoices.size() == 1);
- busHandler.pushExpectedEvent(NextEvent.PHASE);
- busHandler.pushExpectedEvent(NextEvent.INVOICE);
- busHandler.pushExpectedEvent(NextEvent.PAYMENT);
+ busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
clock.addDeltaFromReality(AT_LEAST_ONE_MONTH_MS);
assertListenerStatus();
invoices = invoiceUserApi.getInvoicesByAccount(accountId, callContext);
@@ -678,15 +670,12 @@ public class TestIntegration extends TestIntegrationBase {
for (int i = 0; i < 5; i++) {
log.info("============== loop number " + i + "=======================");
- busHandler.pushExpectedEvent(NextEvent.INVOICE);
- busHandler.pushExpectedEvent(NextEvent.PAYMENT);
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
clock.addDeltaFromReality(AT_LEAST_ONE_MONTH_MS);
assertListenerStatus();
}
- busHandler.pushExpectedEvent(NextEvent.INVOICE);
- busHandler.pushExpectedEvent(NextEvent.PAYMENT);
- busHandler.pushExpectedEvent(NextEvent.PHASE);
+ busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
clock.addDeltaFromReality(AT_LEAST_ONE_MONTH_MS);
assertListenerStatus();
@@ -696,8 +685,7 @@ public class TestIntegration extends TestIntegrationBase {
for (int i = 0; i <= 5; i++) {
log.info("============== second loop number " + i + "=======================");
- busHandler.pushExpectedEvent(NextEvent.INVOICE);
- busHandler.pushExpectedEvent(NextEvent.PAYMENT);
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
clock.addDeltaFromReality(AT_LEAST_ONE_MONTH_MS);
assertListenerStatus();
}
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationInvoice.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationInvoice.java
index 3d81d73..cfb35ce 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationInvoice.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationInvoice.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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
@@ -84,7 +84,7 @@ public class TestIntegrationInvoice extends TestIntegrationBase {
invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, callContext, expectedInvoices);
// Move through time and verify we get the same invoice
- busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT);
+ busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
clock.addDays(30);
assertListenerStatus();
List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), callContext);
@@ -98,7 +98,7 @@ public class TestIntegrationInvoice extends TestIntegrationBase {
// Move through time and verify we get the same invoice
- busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT);
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
clock.addMonths(1);
assertListenerStatus();
invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), callContext);
@@ -149,7 +149,7 @@ public class TestIntegrationInvoice extends TestIntegrationBase {
Invoice dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), null, dryRun, callContext);
invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, callContext, expectedInvoices);
- busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT);
+ busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
// 2014-2-1
clock.addDays(30);
assertListenerStatus();
@@ -178,7 +178,7 @@ public class TestIntegrationInvoice extends TestIntegrationBase {
dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), null, dryRun, callContext);
invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, callContext, expectedInvoices);
- busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT);
+ busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
// 2015-1-14
clock.addDays(30);
assertListenerStatus();
@@ -199,7 +199,7 @@ public class TestIntegrationInvoice extends TestIntegrationBase {
dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), null, dryRun, callContext);
invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, callContext, expectedInvoices);
- busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT);
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
// 2015-2-1
clock.addDays(18);
assertListenerStatus();
@@ -256,7 +256,7 @@ public class TestIntegrationInvoice extends TestIntegrationBase {
remove_AUTO_PAY_OFF_Tag(account.getId(), ObjectType.ACCOUNT);
- busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT);
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
clock.addDays(31);
assertListenerStatus();
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationInvoiceWithRepairLogic.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationInvoiceWithRepairLogic.java
index ff7f125..e90cf77 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationInvoiceWithRepairLogic.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationInvoiceWithRepairLogic.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014 Groupon, Inc
- * Copyright 2014 The Billing Project, LLC
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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
@@ -86,7 +86,7 @@ public class TestIntegrationInvoiceWithRepairLogic extends TestIntegrationBase {
//
// Check we get the first invoice at the phase event
//
- busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT);
+ busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
// Move the clock to 2012-05-02
clock.addDays(31);
assertListenerStatus();
@@ -201,7 +201,7 @@ public class TestIntegrationInvoiceWithRepairLogic extends TestIntegrationBase {
//
// Check we get the first invoice at the phase event
//
- busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT);
+ busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
// Move the clock to 2012-05-02
clock.addDays(28);
assertListenerStatus();
@@ -396,7 +396,7 @@ public class TestIntegrationInvoiceWithRepairLogic extends TestIntegrationBase {
assertEquals(bpEntitlement.getSubscriptionBase().getCurrentPlan().getRecurringBillingPeriod(), BillingPeriod.ANNUAL);
// Move out of trials for interesting invoices adjustments
- busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT);
+ busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
clock.addDays(30);
assertListenerStatus();
@@ -501,7 +501,7 @@ public class TestIntegrationInvoiceWithRepairLogic extends TestIntegrationBase {
assertEquals(bpEntitlement.getSubscriptionBase().getCurrentPlan().getRecurringBillingPeriod(), BillingPeriod.ANNUAL);
// Move out of trials for interesting invoices adjustments
- busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT);
+ busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
clock.addDays(30);
assertListenerStatus();
@@ -538,7 +538,7 @@ public class TestIntegrationInvoiceWithRepairLogic extends TestIntegrationBase {
final Map<UUID, BigDecimal> iias = new HashMap<UUID, BigDecimal>();
iias.put(invoice1.getInvoiceItems().get(0).getId(), new BigDecimal("197.26"));
- refundPaymentWithInvoiceItemAdjAndCheckForCompletion(account, payment1, iias, NextEvent.PAYMENT, NextEvent.INVOICE_ADJUSTMENT);
+ refundPaymentWithInvoiceItemAdjAndCheckForCompletion(account, payment1, iias, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT, NextEvent.INVOICE_ADJUSTMENT);
checkNoMoreInvoiceToGenerate(account);
}
@@ -572,7 +572,7 @@ public class TestIntegrationInvoiceWithRepairLogic extends TestIntegrationBase {
assertEquals(bpEntitlement.getSubscriptionBase().getCurrentPlan().getRecurringBillingPeriod(), BillingPeriod.ANNUAL);
// Move out of trials for interesting invoices adjustments
- busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT);
+ busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
clock.addDays(30);
assertListenerStatus();
@@ -609,7 +609,7 @@ public class TestIntegrationInvoiceWithRepairLogic extends TestIntegrationBase {
final Map<UUID, BigDecimal> iias = new HashMap<UUID, BigDecimal>();
iias.put(invoice1.getInvoiceItems().get(0).getId(), new BigDecimal("100.00"));
- refundPaymentWithInvoiceItemAdjAndCheckForCompletion(account, payment1, iias, NextEvent.PAYMENT, NextEvent.INVOICE_ADJUSTMENT);
+ refundPaymentWithInvoiceItemAdjAndCheckForCompletion(account, payment1, iias, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT, NextEvent.INVOICE_ADJUSTMENT);
checkNoMoreInvoiceToGenerate(account);
}
}
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithAutoInvoiceOffTag.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithAutoInvoiceOffTag.java
index 4039dce..403343f 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithAutoInvoiceOffTag.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithAutoInvoiceOffTag.java
@@ -1,7 +1,9 @@
/*
* Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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:
*
@@ -90,7 +92,7 @@ public class TestIntegrationWithAutoInvoiceOffTag extends TestIntegrationBase {
invoices = invoiceApi.getInvoicesByAccount(account.getId(), callContext);
assertEquals(invoices.size(), 0);
- busHandler.pushExpectedEvents(NextEvent.TAG, NextEvent.INVOICE, NextEvent.PAYMENT);
+ busHandler.pushExpectedEvents(NextEvent.TAG, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
remove_AUTO_INVOICING_OFF_Tag(account.getId(), ObjectType.ACCOUNT);
assertListenerStatus();
@@ -135,7 +137,7 @@ public class TestIntegrationWithAutoInvoiceOffTag extends TestIntegrationBase {
add_AUTO_INVOICING_OFF_Tag(bpEntitlement.getSubscriptionBase().getBundleId(), ObjectType.BUNDLE);
- busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT);
+ busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
clock.addDays(40); // DAY 40 out of trial
assertListenerStatus();
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithAutoPayOff.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithAutoPayOff.java
index 1b0123b..2338747 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithAutoPayOff.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithAutoPayOff.java
@@ -1,7 +1,9 @@
/*
* Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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:
*
@@ -88,7 +90,7 @@ public class TestIntegrationWithAutoPayOff extends TestIntegrationBase {
assertEquals(cur.getBalance(), cur.getChargedAmount());
}
- remove_AUTO_PAY_OFF_Tag(account.getId(), ObjectType.ACCOUNT, NextEvent.PAYMENT);
+ remove_AUTO_PAY_OFF_Tag(account.getId(), ObjectType.ACCOUNT, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
addDelayBceauseOfLackOfCorrectSynchro();
invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), callContext);
@@ -130,7 +132,7 @@ public class TestIntegrationWithAutoPayOff extends TestIntegrationBase {
}
paymentPlugin.makeNextPaymentFailWithError();
- remove_AUTO_PAY_OFF_Tag(account.getId(), ObjectType.ACCOUNT, NextEvent.PAYMENT_ERROR);
+ remove_AUTO_PAY_OFF_Tag(account.getId(), ObjectType.ACCOUNT, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
addDelayBceauseOfLackOfCorrectSynchro();
invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), callContext);
@@ -146,7 +148,7 @@ public class TestIntegrationWithAutoPayOff extends TestIntegrationBase {
int nbDaysBeforeRetry = paymentConfig.getPaymentFailureRetryDays().get(0);
// MOVE TIME FOR RETRY TO HAPPEN
- busHandler.pushExpectedEvents(NextEvent.PAYMENT);
+ busHandler.pushExpectedEvents(NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
clock.addDays(nbDaysBeforeRetry + 1);
assertListenerStatus();
@@ -190,7 +192,7 @@ public class TestIntegrationWithAutoPayOff extends TestIntegrationBase {
// NOW SET PLUGIN TO THROW FAILURES
paymentPlugin.makeNextPaymentFailWithError();
- remove_AUTO_PAY_OFF_Tag(account.getId(), ObjectType.ACCOUNT, NextEvent.PAYMENT_ERROR);
+ remove_AUTO_PAY_OFF_Tag(account.getId(), ObjectType.ACCOUNT, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
addDelayBceauseOfLackOfCorrectSynchro();
invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), callContext);
@@ -224,7 +226,7 @@ public class TestIntegrationWithAutoPayOff extends TestIntegrationBase {
// REMOVE AUTO_PAY_OFF -> WILL SCHEDULE A PAYMENT_RETRY
paymentPlugin.clear();
- remove_AUTO_PAY_OFF_Tag(account.getId(), ObjectType.ACCOUNT, NextEvent.PAYMENT);
+ remove_AUTO_PAY_OFF_Tag(account.getId(), ObjectType.ACCOUNT, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
addDelayBceauseOfLackOfCorrectSynchro();
invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), callContext);
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithDifferentBillingPeriods.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithDifferentBillingPeriods.java
index 4d493ec..55c5715 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithDifferentBillingPeriods.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithDifferentBillingPeriods.java
@@ -1,7 +1,9 @@
/*
* Copyright 2010-2014 Ning, Inc.
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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:
*
@@ -66,7 +68,7 @@ public class TestIntegrationWithDifferentBillingPeriods extends TestIntegrationB
assertEquals(bpEntitlement.getSubscriptionBase().getCurrentPlan().getRecurringBillingPeriod(), BillingPeriod.MONTHLY);
// Move out of trials for interesting invoices adjustments
- busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT);
+ busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
clock.addDays(31);
assertListenerStatus();
@@ -81,7 +83,7 @@ public class TestIntegrationWithDifferentBillingPeriods extends TestIntegrationB
//
clock.addDays(10);
- changeEntitlementAndCheckForCompletion(bpEntitlement, productName, BillingPeriod.ANNUAL, BillingActionPolicy.IMMEDIATE, NextEvent.CHANGE, NextEvent.INVOICE, NextEvent.PAYMENT);
+ changeEntitlementAndCheckForCompletion(bpEntitlement, productName, BillingPeriod.ANNUAL, BillingActionPolicy.IMMEDIATE, NextEvent.CHANGE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), callContext);
assertEquals(invoices.size(), 3);
@@ -121,7 +123,7 @@ public class TestIntegrationWithDifferentBillingPeriods extends TestIntegrationB
assertEquals(bpEntitlement.getSubscriptionBase().getCurrentPlan().getRecurringBillingPeriod(), BillingPeriod.MONTHLY);
// Move out of trials for interesting invoices adjustments
- busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT);
+ busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
clock.addDays(31);
assertListenerStatus();
@@ -136,7 +138,7 @@ public class TestIntegrationWithDifferentBillingPeriods extends TestIntegrationB
//
clock.addDays(10);
- changeEntitlementAndCheckForCompletion(bpEntitlement, productName, BillingPeriod.QUARTERLY, BillingActionPolicy.IMMEDIATE, NextEvent.CHANGE, NextEvent.INVOICE, NextEvent.PAYMENT);
+ changeEntitlementAndCheckForCompletion(bpEntitlement, productName, BillingPeriod.QUARTERLY, BillingActionPolicy.IMMEDIATE, NextEvent.CHANGE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), callContext);
assertEquals(invoices.size(), 3);
@@ -151,7 +153,7 @@ public class TestIntegrationWithDifferentBillingPeriods extends TestIntegrationB
invoiceChecker.checkInvoice(invoices.get(2).getId(), callContext, toBeChecked);
// Move to 1020-08-01
- busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT);
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
clock.addDays(20);
clock.addMonths(2);
assertListenerStatus();
@@ -190,7 +192,7 @@ public class TestIntegrationWithDifferentBillingPeriods extends TestIntegrationB
assertEquals(bpEntitlement.getSubscriptionBase().getCurrentPlan().getRecurringBillingPeriod(), BillingPeriod.ANNUAL);
// Move out of trials for interesting invoices adjustments
- busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT);
+ busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
clock.addDays(31);
assertListenerStatus();
@@ -216,7 +218,7 @@ public class TestIntegrationWithDifferentBillingPeriods extends TestIntegrationB
// 2012-6-4
clock.addDays(23);
- busHandler.pushExpectedEvents(NextEvent.RESUME, NextEvent.BLOCK, NextEvent.INVOICE, NextEvent.PAYMENT);
+ busHandler.pushExpectedEvents(NextEvent.RESUME, NextEvent.BLOCK, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
entitlementApi.resume(bpEntitlement.getBundleId(), clock.getUTCNow().toLocalDate(), ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
@@ -228,7 +230,7 @@ public class TestIntegrationWithDifferentBillingPeriods extends TestIntegrationB
new ExpectedInvoiceItemCheck(new LocalDate(2012, 6, 4), new LocalDate(2012, 6, 4), InvoiceItemType.CBA_ADJ, new BigDecimal("-2327.62")));
invoiceChecker.checkInvoice(invoices.get(3).getId(), callContext, toBeChecked);
- busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT);
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
clock.addYears(1);
assertListenerStatus();
@@ -265,7 +267,7 @@ public class TestIntegrationWithDifferentBillingPeriods extends TestIntegrationB
assertEquals(bpEntitlement.getSubscriptionBase().getCurrentPlan().getRecurringBillingPeriod(), BillingPeriod.ANNUAL);
// Move out of trials for interesting invoices adjustments
- busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT);
+ busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
clock.addDays(31);
assertListenerStatus();
@@ -288,7 +290,7 @@ public class TestIntegrationWithDifferentBillingPeriods extends TestIntegrationB
assertListenerStatus();
- busHandler.pushExpectedEvents(NextEvent.TAG, NextEvent.INVOICE, NextEvent.PAYMENT);
+ busHandler.pushExpectedEvents(NextEvent.TAG, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
tagUserApi.removeTag(account.getId(), ObjectType.ACCOUNT, ControlTagType.AUTO_INVOICING_OFF.getId(), callContext);
assertListenerStatus();
@@ -305,7 +307,7 @@ public class TestIntegrationWithDifferentBillingPeriods extends TestIntegrationB
new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 12), new LocalDate(2013, 5, 1), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-2327.62")));
invoiceChecker.checkInvoice(invoices.get(2).getId(), callContext, toBeChecked);
- busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT);
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
clock.addYears(1);
assertListenerStatus();
@@ -341,7 +343,7 @@ public class TestIntegrationWithDifferentBillingPeriods extends TestIntegrationB
assertEquals(bpEntitlement.getSubscriptionBase().getCurrentPlan().getRecurringBillingPeriod(), BillingPeriod.ANNUAL);
// Move out of TRIAL and verify we invioice for a full year
- busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT);
+ busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
clock.addDays(30);
assertListenerStatus();
@@ -360,7 +362,7 @@ public class TestIntegrationWithDifferentBillingPeriods extends TestIntegrationB
//
clock.addDays(73);
- changeEntitlementAndCheckForCompletion(bpEntitlement, "Assault-Rifle", BillingPeriod.ANNUAL, BillingActionPolicy.IMMEDIATE, NextEvent.CHANGE, NextEvent.INVOICE, NextEvent.PAYMENT);
+ changeEntitlementAndCheckForCompletion(bpEntitlement, "Assault-Rifle", BillingPeriod.ANNUAL, BillingActionPolicy.IMMEDIATE, NextEvent.CHANGE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), callContext);
assertEquals(invoices.size(), 3);
toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestInvoiceNotifications.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestInvoiceNotifications.java
index 25abfe5..481952d 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestInvoiceNotifications.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestInvoiceNotifications.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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
@@ -56,12 +56,12 @@ public class TestInvoiceNotifications extends TestIntegrationBase {
addDaysAndCheckForCompletion(23, NextEvent.INVOICE_NOTIFICATION);
// Move to end of trial => 2012, 5, 1
- addDaysAndCheckForCompletion(7, NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT);
+ addDaysAndCheckForCompletion(7, NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
// Next invoice is scheduled for 2012, 6, 1 so we should have a NOTIFICATION event 7 days before, on 2012, 5, 25
addDaysAndCheckForCompletion(24, NextEvent.INVOICE_NOTIFICATION);
// And then verify the invoice is correctly generated
- addDaysAndCheckForCompletion(7, NextEvent.INVOICE, NextEvent.PAYMENT);
+ addDaysAndCheckForCompletion(7, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
}
}
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestInvoicePayment.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestInvoicePayment.java
index 13a3a43..c18cd69 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestInvoicePayment.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestInvoicePayment.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2014 Groupon, Inc
- * Copyright 2014 The Billing Project, LLC
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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
@@ -62,7 +62,7 @@ public class TestInvoicePayment extends TestIntegrationBase {
assertListenerStatus();
final Invoice invoice = invoiceUserApi.getInvoice(item1.getInvoiceId(), callContext);
- final Payment payment1 = createPaymentAndCheckForCompletion(account, invoice, new BigDecimal("4.00"), account.getCurrency(), NextEvent.PAYMENT);
+ final Payment payment1 = createPaymentAndCheckForCompletion(account, invoice, new BigDecimal("4.00"), account.getCurrency(), NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
Invoice invoice1 = invoiceUserApi.getInvoice(item1.getInvoiceId(), callContext);
assertTrue(invoice1.getBalance().compareTo(new BigDecimal("6.00")) == 0);
@@ -72,7 +72,7 @@ public class TestInvoicePayment extends TestIntegrationBase {
BigDecimal accountBalance = invoiceUserApi.getAccountBalance(account.getId(), callContext);
assertTrue(accountBalance.compareTo(new BigDecimal("6.00")) == 0);
- final Payment payment2 = createPaymentAndCheckForCompletion(account, invoice, new BigDecimal("6.00"), account.getCurrency(), NextEvent.PAYMENT);
+ final Payment payment2 = createPaymentAndCheckForCompletion(account, invoice, new BigDecimal("6.00"), account.getCurrency(), NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
invoice1 = invoiceUserApi.getInvoice(item1.getInvoiceId(), callContext);
assertTrue(invoice1.getBalance().compareTo(BigDecimal.ZERO) == 0);
@@ -96,7 +96,7 @@ public class TestInvoicePayment extends TestIntegrationBase {
*/
- refundPaymentAndCheckForCompletion(account, payment1, NextEvent.PAYMENT, NextEvent.INVOICE_ADJUSTMENT);
+ refundPaymentAndCheckForCompletion(account, payment1, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
invoice1 = invoiceUserApi.getInvoice(item1.getInvoiceId(), callContext);
assertTrue(invoice1.getBalance().compareTo(new BigDecimal("4.00")) == 0);
@@ -121,7 +121,7 @@ public class TestInvoicePayment extends TestIntegrationBase {
createBaseEntitlementAndCheckForCompletion(account.getId(), "bundleKey", "Shotgun", ProductCategory.BASE, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.INVOICE);
- busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR);
+ busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
clock.addDays(30);
assertListenerStatus();
@@ -143,7 +143,7 @@ public class TestInvoicePayment extends TestIntegrationBase {
assertEquals(payments.size(), 1);
// Trigger the payment retry
- busHandler.pushExpectedEvents(NextEvent.PAYMENT);
+ busHandler.pushExpectedEvents(NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
clock.addDays(8);
assertListenerStatus();
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestPaymentRefund.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestPaymentRefund.java
index 8bae9e0..d428c8a 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestPaymentRefund.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestPaymentRefund.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014 Groupon, Inc
- * Copyright 2014 The Billing Project, LLC
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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
@@ -73,7 +73,7 @@ public class TestPaymentRefund extends TestIntegrationBase {
@Test(groups = "slow")
public void testRefundWithNoAdjustments() throws Exception {
// Although we don't adjust the invoice, the invoicing system sends an event because invoice balance changes and overdue system-- in particular-- needs to know about it.
- refundPaymentAndCheckForCompletion(account, payment, NextEvent.PAYMENT, NextEvent.INVOICE_ADJUSTMENT);
+ refundPaymentAndCheckForCompletion(account, payment, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
refundChecker.checkRefund(payment.getId(), callContext, new ExpectedRefundCheck(payment.getId(), false, new BigDecimal("233.82"), Currency.USD, initialCreationDate.toLocalDate()));
}
@@ -82,7 +82,7 @@ public class TestPaymentRefund extends TestIntegrationBase {
final Map<UUID, BigDecimal> iias = new HashMap<UUID, BigDecimal>();
iias.put(invoice.getInvoiceItems().get(0).getId(), null);
- refundPaymentWithInvoiceItemAdjAndCheckForCompletion(account, payment, iias, NextEvent.PAYMENT, NextEvent.INVOICE_ADJUSTMENT);
+ refundPaymentWithInvoiceItemAdjAndCheckForCompletion(account, payment, iias, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT, NextEvent.INVOICE_ADJUSTMENT);
refundChecker.checkRefund(payment.getId(), callContext, new ExpectedRefundCheck(payment.getId(), true, new BigDecimal("233.82"), Currency.USD, initialCreationDate.toLocalDate()));
invoice = invoiceChecker.checkInvoice(account.getId(), invoiceItemCount++, callContext,
new ExpectedInvoiceItemCheck(new LocalDate(2012, 3, 2),
@@ -93,7 +93,7 @@ public class TestPaymentRefund extends TestIntegrationBase {
@Test(groups = "slow")
public void testRefundWithInvoiceAdjustment() throws Exception {
- refundPaymentWithAdjustmentAndCheckForCompletion(account, payment, NextEvent.PAYMENT, NextEvent.INVOICE_ADJUSTMENT);
+ refundPaymentWithAdjustmentAndCheckForCompletion(account, payment, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT, NextEvent.INVOICE_ADJUSTMENT);
refundChecker.checkRefund(payment.getId(), callContext, new ExpectedRefundCheck(payment.getId(), true, new BigDecimal("233.82"), Currency.USD, initialCreationDate.toLocalDate()));
invoice = invoiceChecker.checkInvoice(account.getId(), invoiceItemCount++, callContext,
new ExpectedInvoiceItemCheck(new LocalDate(2012, 3, 2),
@@ -123,7 +123,7 @@ public class TestPaymentRefund extends TestIntegrationBase {
// No end date for the trial item (fixed price of zero), and CTD should be today (i.e. when the trial started)
invoiceChecker.checkChargedThroughDate(bpEntitlement.getId(), clock.getUTCToday(), callContext);
- setDateAndCheckForCompletion(new DateTime(2012, 3, 2, 23, 59, 59, 0, testTimeZone), NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT);
+ setDateAndCheckForCompletion(new DateTime(2012, 3, 2, 23, 59, 59, 0, testTimeZone), NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
invoice = invoiceChecker.checkInvoice(account.getId(), ++invoiceItemCount, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 3, 2),
new LocalDate(2012, 3, 31), InvoiceItemType.RECURRING, new BigDecimal("233.82")));
payment = paymentChecker.checkPayment(account.getId(), 1, callContext, new ExpectedPaymentCheck(new LocalDate(2012, 3, 2), new BigDecimal("233.82"), TransactionStatus.SUCCESS, invoice.getId(), Currency.USD));
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestPaymentWithControl.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestPaymentWithControl.java
index 2a0c774..05879f6 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestPaymentWithControl.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestPaymentWithControl.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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
@@ -53,7 +53,7 @@ import com.google.common.collect.Iterables;
public class TestPaymentWithControl extends TestIntegrationBase {
- private final static String TEST_PAYMENT_WITH_CONTROL = "TestPaymentWithControl";
+ private static final String TEST_PAYMENT_WITH_CONTROL = "TestPaymentWithControl";
private TestPaymentControlPluginApi testPaymentControlWithControl;
private List<PluginProperty> properties;
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 06fabc4..a08b05e 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
@@ -1,8 +1,9 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014 Groupon, Inc.
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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:
*
@@ -82,7 +83,7 @@ public class TestSubscription extends TestIntegrationBase {
assertEquals(bpEntitlement.getSubscriptionBase().getCurrentPlan().getRecurringBillingPeriod(), BillingPeriod.ANNUAL);
// Move out of trials for interesting invoices adjustments
- busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT);
+ busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
clock.addDays(40);
assertListenerStatus();
@@ -160,7 +161,7 @@ public class TestSubscription extends TestIntegrationBase {
assertEquals(bpEntitlement.getSubscriptionBase().getCurrentPlan().getRecurringBillingPeriod(), BillingPeriod.ANNUAL);
// Move out of trials for interesting invoices adjustments
- busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT);
+ busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
clock.addDays(40);
assertListenerStatus();
@@ -174,7 +175,7 @@ public class TestSubscription extends TestIntegrationBase {
// FORCE AN IMMEDIATE CHANGE TO TEST THE CHANGE_OF_PLAN ALIGNMENT
// (Note that, the catalog is configured to use CHANGE_OF_PLAN when moving to that plan and Not CHANGE_OF_PRICELIST which has not been implement;
// this is a bit misleading since we are changing pricelist, but in that case pricelist change has no effect)
- changeEntitlementAndCheckForCompletion(bpEntitlement, "Assault-Rifle", BillingPeriod.ANNUAL, "rescue", BillingActionPolicy.IMMEDIATE, NextEvent.CHANGE, NextEvent.INVOICE, NextEvent.PAYMENT);
+ changeEntitlementAndCheckForCompletion(bpEntitlement, "Assault-Rifle", BillingPeriod.ANNUAL, "rescue", BillingActionPolicy.IMMEDIATE, NextEvent.CHANGE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), callContext);
assertEquals(invoices.size(), 3);
toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
@@ -210,7 +211,7 @@ public class TestSubscription extends TestIntegrationBase {
specifierList.add(addOnEntitlementSpecifier1);
specifierList.add(addOnEntitlementSpecifier2);
- busHandler.pushExpectedEvents(NextEvent.CREATE, NextEvent.CREATE, NextEvent.CREATE, NextEvent.INVOICE, NextEvent.PAYMENT);
+ busHandler.pushExpectedEvents(NextEvent.CREATE, NextEvent.CREATE, NextEvent.CREATE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
final Entitlement entitlement = entitlementApi.createBaseEntitlementWithAddOns(account.getId(), externalKey, specifierList, initialDate, ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
checkNoMoreInvoiceToGenerate(account);
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 bc8a935..698b887 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
@@ -1,6 +1,6 @@
/*
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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
@@ -114,16 +114,13 @@ public class TestWithEntilementPlugin extends TestIntegrationBase {
subscriptionChecker.checkSubscriptionCreated(bpSubscription.getId(), internalCallContext);
- busHandler.pushExpectedEvent(NextEvent.PHASE);
- busHandler.pushExpectedEvent(NextEvent.INVOICE);
- busHandler.pushExpectedEvent(NextEvent.PAYMENT);
+ busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
clock.addDays(30);
assertListenerStatus();
invoiceChecker.checkInvoice(account.getId(), 2, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2012, 6, 1), InvoiceItemType.RECURRING, BigDecimal.TEN));
- busHandler.pushExpectedEvent(NextEvent.INVOICE);
- busHandler.pushExpectedEvent(NextEvent.PAYMENT);
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
clock.addMonths(1);
assertListenerStatus();
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithPriceOverride.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithPriceOverride.java
index 1371a51..e437f17 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithPriceOverride.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithPriceOverride.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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
@@ -55,7 +55,7 @@ public class TestWithPriceOverride extends TestIntegrationBase {
final List<PlanPhasePriceOverride> overrides = new ArrayList<PlanPhasePriceOverride>();
overrides.add(new DefaultPlanPhasePriceOverride("shotgun-monthly-trial", account.getCurrency(), BigDecimal.ONE, null));
- final DefaultEntitlement bpSubscription = createBaseEntitlementWithPriceOverrideAndCheckForCompletion(account.getId(), "bundleKey", "Shotgun", ProductCategory.BASE, BillingPeriod.MONTHLY, overrides, NextEvent.CREATE, NextEvent.INVOICE, NextEvent.PAYMENT);
+ final DefaultEntitlement bpSubscription = createBaseEntitlementWithPriceOverrideAndCheckForCompletion(account.getId(), "bundleKey", "Shotgun", ProductCategory.BASE, BillingPeriod.MONTHLY, overrides, NextEvent.CREATE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
// Check bundle after BP got created otherwise we get an error from auditApi.
subscriptionChecker.checkSubscriptionCreated(bpSubscription.getId(), internalCallContext);
invoiceChecker.checkInvoice(account.getId(), 1, callContext, new ExpectedInvoiceItemCheck(clock.getUTCToday(), null, InvoiceItemType.FIXED, new BigDecimal("1")));
@@ -80,16 +80,13 @@ public class TestWithPriceOverride extends TestIntegrationBase {
subscriptionChecker.checkSubscriptionCreated(bpSubscription.getId(), internalCallContext);
invoiceChecker.checkInvoice(account.getId(), 1, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 4, 1), null, InvoiceItemType.FIXED, new BigDecimal("0")));
- busHandler.pushExpectedEvent(NextEvent.PHASE);
- busHandler.pushExpectedEvent(NextEvent.INVOICE);
- busHandler.pushExpectedEvent(NextEvent.PAYMENT);
+ busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
clock.addDays(30);
assertListenerStatus();
invoiceChecker.checkInvoice(account.getId(), 2, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2012, 6, 1), InvoiceItemType.RECURRING, BigDecimal.TEN));
- busHandler.pushExpectedEvent(NextEvent.INVOICE);
- busHandler.pushExpectedEvent(NextEvent.PAYMENT);
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
clock.addMonths(1);
assertListenerStatus();
@@ -112,9 +109,7 @@ public class TestWithPriceOverride extends TestIntegrationBase {
subscriptionChecker.checkSubscriptionCreated(bpSubscription.getId(), internalCallContext);
invoiceChecker.checkInvoice(account.getId(), 1, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 4, 1), null, InvoiceItemType.FIXED, new BigDecimal("0")));
- busHandler.pushExpectedEvent(NextEvent.PHASE);
- busHandler.pushExpectedEvent(NextEvent.INVOICE);
- busHandler.pushExpectedEvent(NextEvent.PAYMENT);
+ busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
clock.addDays(30);
assertListenerStatus();
@@ -123,9 +118,7 @@ public class TestWithPriceOverride extends TestIntegrationBase {
final List<PlanPhasePriceOverride> overrides = new ArrayList<PlanPhasePriceOverride>();
overrides.add(new DefaultPlanPhasePriceOverride("shotgun-monthly-evergreen", account.getCurrency(), null, new BigDecimal("279.95")));
- busHandler.pushExpectedEvent(NextEvent.CHANGE);
- busHandler.pushExpectedEvent(NextEvent.INVOICE);
- busHandler.pushExpectedEvent(NextEvent.PAYMENT);
+ busHandler.pushExpectedEvents(NextEvent.CHANGE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
bpSubscription.changePlanOverrideBillingPolicy("Shotgun", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, overrides, new LocalDate(2012, 5, 1), BillingActionPolicy.IMMEDIATE, ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
@@ -133,8 +126,7 @@ public class TestWithPriceOverride extends TestIntegrationBase {
new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2012, 6, 1), InvoiceItemType.RECURRING, new BigDecimal("279.95")),
new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2012, 6, 1), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-249.95")));
- busHandler.pushExpectedEvent(NextEvent.INVOICE);
- busHandler.pushExpectedEvent(NextEvent.PAYMENT);
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
clock.addMonths(1);
assertListenerStatus();
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 facb5ef..07726ee 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
@@ -1,6 +1,6 @@
/*
- * Copyright 2014 Groupon, Inc
- * Copyright 2014 The Billing Project, LLC
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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
@@ -136,7 +136,7 @@ public class TestWithTaxItems extends TestIntegrationBase {
testInvoicePluginApi.addTaxItem();
// Remove AUTO_INVOICING_OFF => Invoice + Payment
- remove_AUTO_INVOICING_OFF_Tag(account.getId(), NextEvent.INVOICE, NextEvent.PAYMENT);
+ remove_AUTO_INVOICING_OFF_Tag(account.getId(), NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
invoiceChecker.checkInvoice(account.getId(), 2, callContext,
new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2012, 6, 1), InvoiceItemType.RECURRING, new BigDecimal("29.95")),
new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2012, 6, 1), InvoiceItemType.RECURRING, new BigDecimal("2.95")),
@@ -152,7 +152,7 @@ public class TestWithTaxItems extends TestIntegrationBase {
testInvoicePluginApi.addTaxItem();
// Remove AUTO_INVOICING_OFF => Invoice + Payment
- remove_AUTO_INVOICING_OFF_Tag(account.getId(), NextEvent.INVOICE, NextEvent.PAYMENT);
+ remove_AUTO_INVOICING_OFF_Tag(account.getId(), NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
invoiceChecker.checkInvoice(account.getId(), 2, callContext,
new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2012, 6, 1), InvoiceItemType.RECURRING, new BigDecimal("29.95")),
new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2012, 6, 1), InvoiceItemType.RECURRING, new BigDecimal("2.95")),
@@ -174,7 +174,7 @@ public class TestWithTaxItems extends TestIntegrationBase {
testInvoicePluginApi.addTaxItem();
// Remove AUTO_INVOICING_OFF => Invoice + Payment
- remove_AUTO_INVOICING_OFF_Tag(account.getId(), NextEvent.INVOICE, NextEvent.PAYMENT);
+ remove_AUTO_INVOICING_OFF_Tag(account.getId(), NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
invoiceChecker.checkInvoice(account.getId(), 3, callContext,
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithTimeZones.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithTimeZones.java
index 5cbd357..24fa051 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithTimeZones.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithTimeZones.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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
@@ -88,7 +88,7 @@ public class TestWithTimeZones extends TestIntegrationBase {
invoiceChecker.checkInvoice(account.getId(), 1, callContext, expectedInvoices);
expectedInvoices.clear();
- busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT);
+ busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
clock.addDays(30);
assertListenerStatus();
invoiceChecker.checkInvoice(account.getId(), 2, callContext,
@@ -99,7 +99,7 @@ public class TestWithTimeZones extends TestIntegrationBase {
for (int i = 0; i < 18; i++) {
LocalDate endDate = startDate.plusMonths(1);
- busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT);
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
clock.addMonths(1);
assertListenerStatus();
invoiceChecker.checkInvoice(account.getId(), i + 3, callContext,
@@ -134,7 +134,7 @@ public class TestWithTimeZones extends TestIntegrationBase {
final Account account = createAccountWithNonOsgiPaymentMethod(accountData);
accountChecker.checkAccount(account.getId(), accountData, callContext);
- busHandler.pushExpectedEvents(NextEvent.CREATE, NextEvent.INVOICE, NextEvent.PAYMENT);
+ busHandler.pushExpectedEvents(NextEvent.CREATE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Blowdart", ProductCategory.BASE, BillingPeriod.MONTHLY, "notrial", null);
final LocalDate effectiveDate = new LocalDate(clock.getUTCNow());
Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, "Something", ImmutableList.<PlanPhasePriceOverride>of(), effectiveDate, ImmutableList.<PluginProperty>of(), callContext);
@@ -190,7 +190,7 @@ public class TestWithTimeZones extends TestIntegrationBase {
final Account account = createAccountWithNonOsgiPaymentMethod(accountData);
accountChecker.checkAccount(account.getId(), accountData, callContext);
- busHandler.pushExpectedEvents(NextEvent.CREATE, NextEvent.INVOICE, NextEvent.PAYMENT);
+ busHandler.pushExpectedEvents(NextEvent.CREATE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Blowdart", ProductCategory.BASE, BillingPeriod.MONTHLY, "notrial", null);
final LocalDate effectiveDate = new LocalDate(clock.getUTCNow());
Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, "Something", ImmutableList.<PlanPhasePriceOverride>of(), effectiveDate, ImmutableList.<PluginProperty>of(), callContext);
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/usage/TestConsumableInArrear.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/usage/TestConsumableInArrear.java
index 034573b..54107d1 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/usage/TestConsumableInArrear.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/usage/TestConsumableInArrear.java
@@ -1,7 +1,8 @@
/*
- * Copyright 2014 The Billing Project, LLC
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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:
*
@@ -84,7 +85,7 @@ public class TestConsumableInArrear extends TestIntegrationBase {
setUsage(aoSubscription.getId(), "bullets", new LocalDate(2012, 4, 1), 99L, callContext);
setUsage(aoSubscription.getId(), "bullets", new LocalDate(2012, 4, 15), 100L, callContext);
- busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT);
+ busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
clock.addDays(30);
assertListenerStatus();
@@ -99,7 +100,7 @@ public class TestConsumableInArrear extends TestIntegrationBase {
setUsage(aoSubscription.getId(), "bullets", new LocalDate(2012, 6, 1), 50L, callContext);
setUsage(aoSubscription.getId(), "bullets", new LocalDate(2012, 6, 16), 300L, callContext);
- busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT);
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
clock.addMonths(1);
assertListenerStatus();
@@ -116,7 +117,7 @@ public class TestConsumableInArrear extends TestIntegrationBase {
setUsage(aoSubscription.getId(), "bullets", new LocalDate(2012, 7, 1), 50L, callContext);
setUsage(aoSubscription.getId(), "bullets", new LocalDate(2012, 7, 16), 300L, callContext);
- busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT);
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
clock.addMonths(1);
assertListenerStatus();
@@ -152,7 +153,7 @@ public class TestConsumableInArrear extends TestIntegrationBase {
setUsage(aoSubscription.getId(), "bullets", new LocalDate(2012, 4, 1), 99L, callContext);
setUsage(aoSubscription.getId(), "bullets", new LocalDate(2012, 4, 15), 100L, callContext);
- busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT);
+ busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
clock.addDays(30);
assertListenerStatus();
invoiceChecker.checkInvoice(account.getId(), 2, callContext,
@@ -166,7 +167,7 @@ public class TestConsumableInArrear extends TestIntegrationBase {
setUsage(aoSubscription.getId(), "bullets", new LocalDate(2012, 5, 29), 100L, callContext);
clock.addDays(27);
- busHandler.pushExpectedEvents(NextEvent.BLOCK, NextEvent.CANCEL, NextEvent.INVOICE, NextEvent.PAYMENT);
+ busHandler.pushExpectedEvents(NextEvent.BLOCK, NextEvent.CANCEL, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
aoSubscription.cancelEntitlementWithDateOverrideBillingPolicy(new LocalDate(2012, 5, 28), BillingActionPolicy.IMMEDIATE, ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
@@ -214,7 +215,7 @@ public class TestConsumableInArrear extends TestIntegrationBase {
final DefaultEntitlement aoSubscription = addAOEntitlementAndCheckForCompletion(bpSubscription.getBundleId(), "Bullets", ProductCategory.ADD_ON, BillingPeriod.NO_BILLING_PERIOD, NextEvent.CREATE);
assertListenerStatus();
- busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT);
+ busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
clock.addDays(30);
assertListenerStatus();
invoiceChecker.checkInvoice(account.getId(), 2, callContext,
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/util/InvoiceChecker.java b/beatrix/src/test/java/org/killbill/billing/beatrix/util/InvoiceChecker.java
index d3f24c2..77bc381 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/util/InvoiceChecker.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/util/InvoiceChecker.java
@@ -71,17 +71,6 @@ public class InvoiceChecker {
return invoice;
}
- public void checkRepairedInvoice(final UUID accountId, final int invoiceNb, final CallContext context, final ExpectedInvoiceItemCheck... expected) throws InvoiceApiException {
- checkRepairedInvoice(accountId, invoiceNb, context, ImmutableList.<ExpectedInvoiceItemCheck>copyOf(expected));
- }
-
- public void checkRepairedInvoice(final UUID accountId, final int invoiceNb, final CallContext context, final List<ExpectedInvoiceItemCheck> expected) throws InvoiceApiException {
- final List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(accountId, context);
- Assert.assertTrue(invoices.size() > invoiceNb);
- final Invoice invoice = invoices.get(invoiceNb - 1);
- checkInvoice(invoice.getId(), context, expected);
- }
-
public void checkInvoice(final UUID invoiceId, final CallContext context, final List<ExpectedInvoiceItemCheck> expected) throws InvoiceApiException {
final Invoice invoice = invoiceUserApi.getInvoice(invoiceId, context);
Assert.assertNotNull(invoice);
@@ -89,11 +78,9 @@ public class InvoiceChecker {
}
public void checkInvoiceNoAudits(final Invoice invoice, final CallContext context, final List<ExpectedInvoiceItemCheck> expected) throws InvoiceApiException {
-
final List<InvoiceItem> actual = invoice.getInvoiceItems();
- Assert.assertEquals(actual.size(), expected.size());
+ Assert.assertEquals(actual.size(), expected.size(), String.format("Expected items: %s, actual items: %s", expected, actual));
for (final ExpectedInvoiceItemCheck cur : expected) {
-
boolean found = false;
// First try to find exact match; this is necessary because the for loop below might encounter a similar item -- for instance
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/io/VersionedCatalogLoader.java b/catalog/src/main/java/org/killbill/billing/catalog/io/VersionedCatalogLoader.java
index 3404ec7..3b5f7de 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/io/VersionedCatalogLoader.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/io/VersionedCatalogLoader.java
@@ -1,7 +1,9 @@
/*
* Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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:
*
@@ -35,7 +37,6 @@ import org.killbill.clock.Clock;
import org.killbill.xmlloader.UriAccessor;
import org.killbill.xmlloader.XMLLoader;
-import com.google.common.io.Resources;
import com.google.inject.Inject;
public class VersionedCatalogLoader implements CatalogLoader {
@@ -80,7 +81,7 @@ public class VersionedCatalogLoader implements CatalogLoader {
}
}
- public VersionedCatalog load(final List<String> catalogXMLs, final Long tenantRecordId) throws CatalogApiException {
+ public VersionedCatalog load(final Iterable<String> catalogXMLs, final Long tenantRecordId) throws CatalogApiException {
final VersionedCatalog result = new VersionedCatalog(clock);
final URI uri;
try {
@@ -91,7 +92,9 @@ public class VersionedCatalogLoader implements CatalogLoader {
result.add(new StandaloneCatalogWithPriceOverride(catalog, priceOverride, tenantRecordId, internalCallContextFactory));
}
return result;
- } catch (Exception e) {
+ } catch (final CatalogApiException e) {
+ throw e;
+ } catch (final Exception e) {
throw new CatalogApiException(ErrorCode.CAT_INVALID_DEFAULT, "Problem encountered loading catalog ", e);
}
}
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 dffcddb..a4d2148 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
@@ -1,7 +1,9 @@
/*
* Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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:
*
@@ -17,7 +19,9 @@
package org.killbill.billing.entitlement.api;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.Collection;
+import java.util.LinkedList;
import java.util.List;
import java.util.UUID;
@@ -296,14 +300,19 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
} else {
getSubscriptionBase().cancel(callContext);
}
- } catch (SubscriptionBaseApiException e) {
+ } catch (final SubscriptionBaseApiException e) {
throw new EntitlementApiException(e);
}
final BlockingState newBlockingState = new DefaultBlockingState(getId(), BlockingStateType.SUBSCRIPTION, DefaultEntitlementApi.ENT_STATE_CANCELLED, EntitlementService.ENTITLEMENT_SERVICE_NAME, true, true, false, effectiveCancelDate);
- entitlementUtils.setBlockingStateAndPostBlockingTransitionEvent(newBlockingState, contextWithValidAccountRecordId);
+ final Collection<NotificationEvent> notificationEvents = new ArrayList<NotificationEvent>();
+ final Collection<BlockingState> addOnsBlockingStates = computeAddOnBlockingStates(effectiveCancelDate, notificationEvents, callContext, contextWithValidAccountRecordId);
- blockAddOnsIfRequired(effectiveCancelDate, callContext, contextWithValidAccountRecordId);
+ // Record the new state first, then insert the notifications to avoid race conditions
+ setBlockingStates(newBlockingState, addOnsBlockingStates, contextWithValidAccountRecordId);
+ for (final NotificationEvent notificationEvent : notificationEvents) {
+ recordFutureNotification(effectiveCancelDate, notificationEvent, contextWithValidAccountRecordId);
+ }
return entitlementApi.getEntitlementForId(getId(), callContext);
}
@@ -353,12 +362,13 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
if (getSubscriptionBase().getFutureEndDate() != null) {
try {
getSubscriptionBase().uncancel(callContext);
- } catch (SubscriptionBaseApiException e) {
+ } catch (final SubscriptionBaseApiException e) {
throw new EntitlementApiException(e);
}
}
}
+ // See also EntitlementInternalApi#cancel for the bulk API
@Override
public Entitlement cancelEntitlementWithDateOverrideBillingPolicy(final LocalDate localCancelDate, final BillingActionPolicy billingPolicy, final Iterable<PluginProperty> properties, final CallContext callContext) throws EntitlementApiException {
@@ -394,14 +404,19 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
try {
// Cancel subscription base first, to correctly compute the add-ons entitlements we need to cancel (see below)
getSubscriptionBase().cancelWithPolicy(billingPolicy, callContext);
- } catch (SubscriptionBaseApiException e) {
+ } catch (final SubscriptionBaseApiException e) {
throw new EntitlementApiException(e);
}
final BlockingState newBlockingState = new DefaultBlockingState(getId(), BlockingStateType.SUBSCRIPTION, DefaultEntitlementApi.ENT_STATE_CANCELLED, EntitlementService.ENTITLEMENT_SERVICE_NAME, true, true, false, effectiveDate);
- entitlementUtils.setBlockingStateAndPostBlockingTransitionEvent(newBlockingState, contextWithValidAccountRecordId);
+ final Collection<NotificationEvent> notificationEvents = new ArrayList<NotificationEvent>();
+ final Collection<BlockingState> addOnsBlockingStates = computeAddOnBlockingStates(effectiveDate, notificationEvents, callContext, contextWithValidAccountRecordId);
- blockAddOnsIfRequired(effectiveDate, callContext, contextWithValidAccountRecordId);
+ // Record the new state first, then insert the notifications to avoid race conditions
+ setBlockingStates(newBlockingState, addOnsBlockingStates, contextWithValidAccountRecordId);
+ for (final NotificationEvent notificationEvent : notificationEvents) {
+ recordFutureNotification(effectiveDate, notificationEvent, contextWithValidAccountRecordId);
+ }
return entitlementApi.getEntitlementForId(getId(), callContext);
}
@@ -450,23 +465,35 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
throw new EntitlementApiException(ErrorCode.SUB_CHANGE_NON_ACTIVE, getId(), getState());
}
+ final InternalCallContext context = internalCallContextFactory.createInternalCallContext(getAccountId(), callContext);
final DateTime effectiveChangeDate;
try {
- effectiveChangeDate = getSubscriptionBase().changePlan(productName, billingPeriod, priceList, overrides, callContext);
- } catch (SubscriptionBaseApiException e) {
- throw new EntitlementApiException(e);
+ effectiveChangeDate = subscriptionInternalApi.getDryRunChangePlanEffectiveDate(getSubscriptionBase(), productName, billingPeriod, priceList, null, null, context);
+ } catch (final SubscriptionBaseApiException e) {
+ throw new EntitlementApiException(e, e.getCode(), e.getMessage());
}
- final InternalCallContext context = internalCallContextFactory.createInternalCallContext(getAccountId(), callContext);
try {
checker.checkBlockedChange(getSubscriptionBase(), effectiveChangeDate, context);
- } catch (BlockingApiException e) {
+ } catch (final BlockingApiException e) {
throw new EntitlementApiException(e, e.getCode(), e.getMessage());
}
- blockAddOnsIfRequired(effectiveChangeDate, callContext, context);
+ try {
+ getSubscriptionBase().changePlan(productName, billingPeriod, priceList, overrides, callContext);
+ } catch (final SubscriptionBaseApiException e) {
+ throw new EntitlementApiException(e);
+ }
+
+ final Collection<NotificationEvent> notificationEvents = new ArrayList<NotificationEvent>();
+ final Iterable<BlockingState> addOnsBlockingStates = computeAddOnBlockingStates(effectiveChangeDate, notificationEvents, callContext, context);
+ // Record the new state first, then insert the notifications to avoid race conditions
+ setBlockingStates(addOnsBlockingStates, context);
+ for (final NotificationEvent notificationEvent : notificationEvents) {
+ recordFutureNotification(effectiveChangeDate, notificationEvent, context);
+ }
return entitlementApi.getEntitlementForId(getId(), callContext);
}
};
@@ -499,21 +526,35 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
}
final InternalCallContext context = internalCallContextFactory.createInternalCallContext(getAccountId(), callContext);
- final DateTime effectiveChangeDate = dateHelper.fromLocalDateAndReferenceTime(updatedPluginContext.getEffectiveDate(), getSubscriptionBase().getStartDate(), context);
+ final DateTime effectiveChangeDateComputed = dateHelper.fromLocalDateAndReferenceTime(updatedPluginContext.getEffectiveDate(), getSubscriptionBase().getStartDate(), context);
+
+ final DateTime effectiveChangeDate;
try {
- getSubscriptionBase().changePlanWithDate(productName, billingPeriod, priceList, overrides, effectiveChangeDate, callContext);
- } catch (SubscriptionBaseApiException e) {
- throw new EntitlementApiException(e);
+ effectiveChangeDate = subscriptionInternalApi.getDryRunChangePlanEffectiveDate(getSubscriptionBase(), productName, billingPeriod, priceList, effectiveChangeDateComputed, null, context);
+ } catch (final SubscriptionBaseApiException e) {
+ throw new EntitlementApiException(e, e.getCode(), e.getMessage());
}
try {
checker.checkBlockedChange(getSubscriptionBase(), effectiveChangeDate, context);
- } catch (BlockingApiException e) {
+ } catch (final BlockingApiException e) {
throw new EntitlementApiException(e, e.getCode(), e.getMessage());
}
+ try {
+ getSubscriptionBase().changePlanWithDate(productName, billingPeriod, priceList, overrides, effectiveChangeDate, callContext);
+ } catch (final SubscriptionBaseApiException e) {
+ throw new EntitlementApiException(e);
+ }
- blockAddOnsIfRequired(effectiveChangeDate, callContext, context);
+ final Collection<NotificationEvent> notificationEvents = new ArrayList<NotificationEvent>();
+ final Iterable<BlockingState> addOnsBlockingStates = computeAddOnBlockingStates(effectiveChangeDate, notificationEvents, callContext, context);
+
+ // Record the new state first, then insert the notifications to avoid race conditions
+ setBlockingStates(addOnsBlockingStates, context);
+ for (final NotificationEvent notificationEvent : notificationEvents) {
+ recordFutureNotification(effectiveChangeDate, notificationEvent, context);
+ }
return entitlementApi.getEntitlementForId(getId(), callContext);
}
@@ -550,18 +591,31 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
final DateTime effectiveChangeDate;
try {
- effectiveChangeDate = getSubscriptionBase().changePlanWithPolicy(productName, billingPeriod, priceList, overrides, actionPolicy, callContext);
- } catch (SubscriptionBaseApiException e) {
- throw new EntitlementApiException(e);
+ effectiveChangeDate = subscriptionInternalApi.getDryRunChangePlanEffectiveDate(getSubscriptionBase(), productName, billingPeriod, priceList, null, actionPolicy, context);
+ } catch (final SubscriptionBaseApiException e) {
+ throw new EntitlementApiException(e, e.getCode(), e.getMessage());
}
try {
checker.checkBlockedChange(getSubscriptionBase(), effectiveChangeDate, context);
- } catch (BlockingApiException e) {
+ } catch (final BlockingApiException e) {
throw new EntitlementApiException(e, e.getCode(), e.getMessage());
}
- blockAddOnsIfRequired(effectiveChangeDate, callContext, context);
+ try {
+ getSubscriptionBase().changePlanWithPolicy(productName, billingPeriod, priceList, overrides, actionPolicy, callContext);
+ } catch (final SubscriptionBaseApiException e) {
+ throw new EntitlementApiException(e);
+ }
+
+ final Collection<NotificationEvent> notificationEvents = new ArrayList<NotificationEvent>();
+ final Iterable<BlockingState> addOnsBlockingStates = computeAddOnBlockingStates(effectiveChangeDate, notificationEvents, callContext, context);
+
+ // Record the new state first, then insert the notifications to avoid race conditions
+ setBlockingStates(addOnsBlockingStates, context);
+ for (final NotificationEvent notificationEvent : notificationEvents) {
+ recordFutureNotification(effectiveChangeDate, notificationEvent, context);
+ }
return entitlementApi.getEntitlementForId(getId(), callContext);
}
@@ -573,11 +627,11 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
eventsStream = eventsStreamBuilder.refresh(eventsStream, context);
}
- public void blockAddOnsIfRequired(final DateTime effectiveDate, final TenantContext context, final InternalCallContext internalCallContext) throws EntitlementApiException {
+ public Collection<BlockingState> computeAddOnBlockingStates(final DateTime effectiveDate, final Collection<NotificationEvent> notificationEvents, final TenantContext context, final InternalCallContext internalCallContext) throws EntitlementApiException {
// Optimization - bail early
if (!ProductCategory.BASE.equals(getSubscriptionBase().getCategory())) {
// Only base subscriptions have add-ons
- return;
+ return ImmutableList.<BlockingState>of();
}
// Get the latest state from disk (we just got cancelled or changed plan)
@@ -594,14 +648,11 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
// go through the DAO (e.g. change)
final boolean isBaseEntitlementCancelled = eventsStream.isEntitlementCancelled();
final NotificationEvent notificationEvent = new EntitlementNotificationKey(getId(), getBundleId(), isBaseEntitlementCancelled ? EntitlementNotificationKeyAction.CANCEL : EntitlementNotificationKeyAction.CHANGE, effectiveDate);
- recordFutureNotification(effectiveDate, notificationEvent, internalCallContext);
- return;
+ notificationEvents.add(notificationEvent);
+ return ImmutableList.<BlockingState>of();
}
- final Collection<BlockingState> addOnsBlockingStates = eventsStream.computeAddonsBlockingStatesForNextSubscriptionBaseEvent(effectiveDate);
- for (final BlockingState addOnBlockingState : addOnsBlockingStates) {
- entitlementUtils.setBlockingStateAndPostBlockingTransitionEvent(addOnBlockingState, internalCallContext);
- }
+ return eventsStream.computeAddonsBlockingStatesForNextSubscriptionBaseEvent(effectiveDate);
}
private void recordFutureNotification(final DateTime effectiveDate,
@@ -611,18 +662,29 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
final NotificationQueue subscriptionEventQueue = notificationQueueService.getNotificationQueue(DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME,
DefaultEntitlementService.NOTIFICATION_QUEUE_NAME);
subscriptionEventQueue.recordFutureNotification(effectiveDate, notificationEvent, context.getUserToken(), context.getAccountRecordId(), context.getTenantRecordId());
- } catch (NoSuchNotificationQueue e) {
+ } catch (final NoSuchNotificationQueue e) {
throw new RuntimeException(e);
- } catch (IOException e) {
+ } catch (final IOException e) {
throw new RuntimeException(e);
}
}
+ private void setBlockingStates(final BlockingState entitlementBlockingState, final Collection<BlockingState> addOnsBlockingStates, final InternalCallContext internalCallContext) {
+ final Collection<BlockingState> states = new LinkedList<BlockingState>();
+ states.add(entitlementBlockingState);
+ states.addAll(addOnsBlockingStates);
+ setBlockingStates(states, internalCallContext);
+ }
+
+ private void setBlockingStates(final Iterable<BlockingState> blockingStates, final InternalCallContext internalCallContext) {
+ entitlementUtils.setBlockingStatesAndPostBlockingTransitionEvent(blockingStates, getBundleId(), internalCallContext);
+ }
+
//
// Unfortunately the permission checks for the entitlement api cannot *simply* rely on the KillBillShiroAopModule because some of the operations (CANCEL, CHANGE) are
// done through objects that are not injected by Guice, and so the check needs to happen explicitly.
//
- private void checkForPermissions(final Permission permission, final CallContext callContext) throws EntitlementApiException {
+ private void checkForPermissions(final Permission permission, final TenantContext callContext) throws EntitlementApiException {
//
// If authentication had been done (CorsBasicHttpAuthenticationFilter) we verify the correct permissions exist.
//
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 e8983c2..0560b0e 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
@@ -1,7 +1,9 @@
/*
* Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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:
*
@@ -17,7 +19,9 @@
package org.killbill.billing.entitlement.api;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.UUID;
import javax.inject.Inject;
@@ -59,8 +63,6 @@ import org.killbill.billing.util.callcontext.TenantContext;
import org.killbill.bus.api.PersistentBus;
import org.killbill.clock.Clock;
import org.killbill.notificationq.api.NotificationQueueService;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
@@ -70,15 +72,12 @@ import com.google.common.collect.Lists;
public class DefaultEntitlementApi extends DefaultEntitlementApiBase implements EntitlementApi {
- private static final Logger log = LoggerFactory.getLogger(DefaultEntitlementApi.class);
-
public static final String ENT_STATE_BLOCKED = "ENT_BLOCKED";
public static final String ENT_STATE_CLEAR = "ENT_CLEAR";
public static final String ENT_STATE_CANCELLED = "ENT_CANCELLED";
private final SubscriptionBaseInternalApi subscriptionBaseInternalApi;
private final SubscriptionBaseTransferApi subscriptionBaseTransferApi;
- private final AccountInternalApi accountApi;
private final Clock clock;
private final InternalCallContextFactory internalCallContextFactory;
private final BlockingChecker checker;
@@ -104,7 +103,6 @@ public class DefaultEntitlementApi extends DefaultEntitlementApiBase implements
this.internalCallContextFactory = internalCallContextFactory;
this.subscriptionBaseInternalApi = subscriptionInternalApi;
this.subscriptionBaseTransferApi = subscriptionTransferApi;
- this.accountApi = accountApi;
this.clock = clock;
this.checker = checker;
this.blockingStateDao = blockingStateDao;
@@ -119,7 +117,7 @@ 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 EntitlementSpecifier entitlementSpecifier = new DefaultEntitlementSpecifier(planPhaseSpecifier, overrides);
final List<EntitlementSpecifier> entitlementSpecifierList = new ArrayList<EntitlementSpecifier>();
entitlementSpecifierList.add(entitlementSpecifier);
final EntitlementContext pluginContext = new DefaultEntitlementContext(OperationType.CREATE_SUBSCRIPTION,
@@ -152,7 +150,7 @@ public class DefaultEntitlementApi extends DefaultEntitlementApiBase implements
return new DefaultEntitlement(subscription.getId(), eventsStreamBuilder, entitlementApi, pluginExecution,
blockingStateDao, subscriptionBaseInternalApi, checker, notificationQueueService,
entitlementUtils, dateHelper, clock, securityApi, internalCallContextFactory, callContext);
- } catch (SubscriptionBaseApiException e) {
+ } catch (final SubscriptionBaseApiException e) {
throw new EntitlementApiException(e);
}
}
@@ -216,8 +214,7 @@ public class DefaultEntitlementApi extends DefaultEntitlementApiBase implements
blockingStateDao, subscriptionBaseInternalApi, checker, notificationQueueService,
entitlementUtils, dateHelper, clock, securityApi, internalCallContextFactory, callContext);
-
- } catch (SubscriptionBaseApiException e) {
+ } catch (final SubscriptionBaseApiException e) {
throw new EntitlementApiException(e);
}
@@ -229,7 +226,7 @@ public class DefaultEntitlementApi extends DefaultEntitlementApiBase implements
@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 EntitlementSpecifier entitlementSpecifier = new DefaultEntitlementSpecifier(planPhaseSpecifier, overrides);
final List<EntitlementSpecifier> entitlementSpecifierList = new ArrayList<EntitlementSpecifier>();
entitlementSpecifierList.add(entitlementSpecifier);
final EntitlementContext pluginContext = new DefaultEntitlementContext(OperationType.CREATE_SUBSCRIPTION,
@@ -267,7 +264,7 @@ public class DefaultEntitlementApi extends DefaultEntitlementApiBase implements
return new DefaultEntitlement(subscription.getId(), eventsStreamBuilder, entitlementApi, pluginExecution,
blockingStateDao, subscriptionBaseInternalApi, checker, notificationQueueService,
entitlementUtils, dateHelper, clock, securityApi, internalCallContextFactory, callContext);
- } catch (SubscriptionBaseApiException e) {
+ } catch (final SubscriptionBaseApiException e) {
throw new EntitlementApiException(e);
}
}
@@ -285,7 +282,7 @@ public class DefaultEntitlementApi extends DefaultEntitlementApiBase implements
final InternalTenantContext contextWithValidAccountRecordId = internalCallContextFactory.createInternalTenantContext(bundle.getAccountId(), context);
final DateTime requestedDate = dateHelper.fromLocalDateAndReferenceTime(effectiveDate, baseSubscription.getStartDate(), contextWithValidAccountRecordId);
return subscriptionBaseInternalApi.getDryRunChangePlanStatus(baseSubscription.getId(), targetProductName, requestedDate, contextWithValidAccountRecordId);
- } catch (SubscriptionBaseApiException e) {
+ } catch (final SubscriptionBaseApiException e) {
throw new EntitlementApiException(e);
}
}
@@ -302,7 +299,7 @@ public class DefaultEntitlementApi extends DefaultEntitlementApiBase implements
final UUID accountId;
try {
accountId = subscriptionBaseInternalApi.getBundleFromId(bundleId, internalContext).getAccountId();
- } catch (SubscriptionBaseApiException e) {
+ } catch (final SubscriptionBaseApiException e) {
throw new EntitlementApiException(e);
}
@@ -360,15 +357,13 @@ public class DefaultEntitlementApi extends DefaultEntitlementApiBase implements
}
-
@Override
- public void setBlockingState(final UUID bundleId, final String stateName, final String serviceName, final LocalDate effectiveDate, boolean blockBilling, boolean blockEntitlement, boolean blockChange, final Iterable<PluginProperty> properties, final CallContext context)
+ public void setBlockingState(final UUID bundleId, final String stateName, final String serviceName, final LocalDate effectiveDate, final boolean blockBilling, final boolean blockEntitlement, final boolean blockChange, final Iterable<PluginProperty> properties, final CallContext context)
throws EntitlementApiException {
final InternalCallContext contextWithValidAccountRecordId = internalCallContextFactory.createInternalCallContext(bundleId, ObjectType.BUNDLE, context);
super.setBlockingState(bundleId, stateName, serviceName, effectiveDate, blockBilling, blockEntitlement, blockChange, properties, contextWithValidAccountRecordId);
}
-
@Override
public Iterable<BlockingState> getBlockingStatesForServiceAndType(final UUID blockableId, final BlockingStateType blockingStateType, final String serviceName, final TenantContext tenantContext) {
// Not implemented see #431
@@ -426,15 +421,17 @@ public class DefaultEntitlementApi extends DefaultEntitlementApiBase implements
// Block all associated subscriptions - TODO Do we want to block the bundle as well (this will add an extra STOP_ENTITLEMENT event in the bundle timeline stream)?
// Note that there is no un-transfer at the moment, so we effectively add a blocking state on disk for all subscriptions
+ final Map<BlockingState, UUID> blockingStates = new HashMap<BlockingState, UUID>();
for (final SubscriptionBase subscriptionBase : subscriptionBaseInternalApi.getSubscriptionsForBundle(baseBundle.getId(), null, contextWithValidAccountRecordId)) {
final BlockingState blockingState = new DefaultBlockingState(subscriptionBase.getId(), BlockingStateType.SUBSCRIPTION, DefaultEntitlementApi.ENT_STATE_CANCELLED, EntitlementService.ENTITLEMENT_SERVICE_NAME, true, true, false, requestedDate);
- entitlementUtils.setBlockingStateAndPostBlockingTransitionEvent(blockingState, contextWithValidAccountRecordId);
+ blockingStates.put(blockingState, subscriptionBase.getBundleId());
}
+ entitlementUtils.setBlockingStateAndPostBlockingTransitionEvent(blockingStates, contextWithValidAccountRecordId);
return newBundle.getId();
- } catch (SubscriptionBaseTransferApiException e) {
+ } catch (final SubscriptionBaseTransferApiException e) {
throw new EntitlementApiException(e);
- } catch (SubscriptionBaseApiException e) {
+ } catch (final SubscriptionBaseApiException e) {
throw new EntitlementApiException(e);
}
}
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/api/EntitlementPluginExecution.java b/entitlement/src/main/java/org/killbill/billing/entitlement/api/EntitlementPluginExecution.java
index 61f6307..82d9278 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/api/EntitlementPluginExecution.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/api/EntitlementPluginExecution.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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
@@ -17,6 +17,10 @@
package org.killbill.billing.entitlement.api;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.Callable;
+
import javax.inject.Inject;
import org.killbill.billing.ErrorCode;
@@ -41,15 +45,48 @@ public class EntitlementPluginExecution {
T doCall(final EntitlementApi entitlementApi, final EntitlementContext updatedPluginContext) throws EntitlementApiException;
}
-
@Inject
public EntitlementPluginExecution(final EntitlementApi entitlementApi, final OSGIServiceRegistration<EntitlementPluginApi> pluginRegistry) {
this.entitlementApi = entitlementApi;
this.pluginRegistry = pluginRegistry;
}
- public <T> T executeWithPlugin(final WithEntitlementPlugin<T> callback, final EntitlementContext pluginContext) throws EntitlementApiException {
+ public void executeWithPlugin(final Callable<Void> preCallbacksCallback, final List<WithEntitlementPlugin> callbacks, final Iterable<EntitlementContext> pluginContexts) throws EntitlementApiException {
+ final List<EntitlementContext> updatedPluginContexts = new LinkedList<EntitlementContext>();
+
+ try {
+ for (final EntitlementContext pluginContext : pluginContexts) {
+ final PriorEntitlementResult priorEntitlementResult = executePluginPriorCalls(pluginContext);
+ if (priorEntitlementResult != null && priorEntitlementResult.isAborted()) {
+ throw new EntitlementApiException(ErrorCode.ENT_PLUGIN_API_ABORTED);
+ }
+ updatedPluginContexts.add(new DefaultEntitlementContext(pluginContext, priorEntitlementResult));
+ }
+
+ preCallbacksCallback.call();
+ try {
+ for (int i = 0; i < updatedPluginContexts.size(); i++) {
+ final EntitlementContext updatedPluginContext = updatedPluginContexts.get(i);
+ final WithEntitlementPlugin callback = callbacks.get(i);
+
+ callback.doCall(entitlementApi, updatedPluginContext);
+ executePluginOnSuccessCalls(updatedPluginContext);
+ }
+ } catch (final EntitlementApiException e) {
+ for (final EntitlementContext updatedPluginContext : updatedPluginContexts) {
+ executePluginOnFailureCalls(updatedPluginContext);
+ }
+ throw e;
+ }
+ } catch (final EntitlementPluginApiException e) {
+ throw new EntitlementApiException(ErrorCode.ENT_PLUGIN_API_ABORTED, e.getMessage());
+ } catch (final Exception e) {
+ throw new EntitlementApiException(ErrorCode.ENT_PLUGIN_API_ABORTED, e.getMessage());
+ }
+ }
+
+ public <T> T executeWithPlugin(final WithEntitlementPlugin<T> callback, final EntitlementContext pluginContext) throws EntitlementApiException {
try {
final PriorEntitlementResult priorEntitlementResult = executePluginPriorCalls(pluginContext);
if (priorEntitlementResult != null && priorEntitlementResult.isAborted()) {
@@ -57,7 +94,7 @@ public class EntitlementPluginExecution {
}
final EntitlementContext updatedPluginContext = new DefaultEntitlementContext(pluginContext, priorEntitlementResult);
try {
- T result = callback.doCall(entitlementApi, updatedPluginContext);
+ final T result = callback.doCall(entitlementApi, updatedPluginContext);
executePluginOnSuccessCalls(updatedPluginContext);
return result;
} catch (final EntitlementApiException e) {
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 9651c95..0173d7c 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
@@ -78,6 +78,8 @@ import org.killbill.notificationq.api.NotificationQueueService.NoSuchNotificatio
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.google.common.collect.ImmutableList;
+
public class DefaultEntitlementApiBase {
private static final Logger log = LoggerFactory.getLogger(DefaultEntitlementApiBase.class);
@@ -277,9 +279,9 @@ public class DefaultEntitlementApiBase {
final SubscriptionBase baseSubscription = inputBaseSubscription == null ? subscriptionInternalApi.getBaseSubscription(bundleId, internalCallContext) : inputBaseSubscription;
final DateTime effectiveDate = dateHelper.fromLocalDateAndReferenceTime(localEffectiveDate, baseSubscription.getStartDate(), internalCallContext);
final BlockingState state = new DefaultBlockingState(bundleId, BlockingStateType.SUBSCRIPTION_BUNDLE, stateName, serviceName, blockChange, blockEntitlement, blockBilling, effectiveDate);
- entitlementUtils.setBlockingStateAndPostBlockingTransitionEvent(state, internalCallContext);
+ entitlementUtils.setBlockingStatesAndPostBlockingTransitionEvent(ImmutableList.<BlockingState>of(state), bundleId, internalCallContext);
return state.getId();
- } catch (SubscriptionBaseApiException e) {
+ } catch (final SubscriptionBaseApiException e) {
throw new EntitlementApiException(e);
}
}
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/api/svcs/DefaultEntitlementInternalApi.java b/entitlement/src/main/java/org/killbill/billing/entitlement/api/svcs/DefaultEntitlementInternalApi.java
index 52e03f4..d77e4ab 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/api/svcs/DefaultEntitlementInternalApi.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/api/svcs/DefaultEntitlementInternalApi.java
@@ -1,7 +1,9 @@
/*
* Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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:
*
@@ -16,25 +18,66 @@
package org.killbill.billing.entitlement.api.svcs;
+import java.io.IOException;
+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.UUID;
+import java.util.concurrent.Callable;
+
import javax.inject.Inject;
+import org.joda.time.DateTime;
+import org.joda.time.LocalDate;
import org.killbill.billing.account.api.AccountInternalApi;
+import org.killbill.billing.callcontext.InternalCallContext;
+import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.catalog.api.BillingActionPolicy;
+import org.killbill.billing.entitlement.DefaultEntitlementService;
import org.killbill.billing.entitlement.EntitlementInternalApi;
+import org.killbill.billing.entitlement.EntitlementService;
+import org.killbill.billing.entitlement.api.BlockingState;
+import org.killbill.billing.entitlement.api.BlockingStateType;
+import org.killbill.billing.entitlement.api.DefaultEntitlement;
+import org.killbill.billing.entitlement.api.DefaultEntitlementApi;
+import org.killbill.billing.entitlement.api.DefaultEntitlementContext;
+import org.killbill.billing.entitlement.api.Entitlement;
+import org.killbill.billing.entitlement.api.Entitlement.EntitlementState;
import org.killbill.billing.entitlement.api.EntitlementApi;
+import org.killbill.billing.entitlement.api.EntitlementApiException;
import org.killbill.billing.entitlement.api.EntitlementPluginExecution;
+import org.killbill.billing.entitlement.api.EntitlementPluginExecution.WithEntitlementPlugin;
import org.killbill.billing.entitlement.block.BlockingChecker;
import org.killbill.billing.entitlement.dao.BlockingStateDao;
import org.killbill.billing.entitlement.engine.core.EntitlementUtils;
import org.killbill.billing.entitlement.engine.core.EventsStreamBuilder;
+import org.killbill.billing.entitlement.plugin.api.EntitlementContext;
+import org.killbill.billing.entitlement.plugin.api.OperationType;
+import org.killbill.billing.junction.DefaultBlockingState;
+import org.killbill.billing.payment.api.PluginProperty;
import org.killbill.billing.security.api.SecurityApi;
+import org.killbill.billing.subscription.api.SubscriptionBase;
import org.killbill.billing.subscription.api.SubscriptionBaseInternalApi;
+import org.killbill.billing.subscription.api.user.SubscriptionBaseApiException;
+import org.killbill.billing.util.callcontext.CallContext;
import org.killbill.billing.util.callcontext.InternalCallContextFactory;
import org.killbill.bus.api.PersistentBus;
import org.killbill.clock.Clock;
+import org.killbill.notificationq.api.NotificationEvent;
+import org.killbill.notificationq.api.NotificationQueue;
import org.killbill.notificationq.api.NotificationQueueService;
+import org.killbill.notificationq.api.NotificationQueueService.NoSuchNotificationQueue;
+
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableMap;
public class DefaultEntitlementInternalApi extends DefaultEntitlementApiBase implements EntitlementInternalApi {
+ private final BlockingStateDao blockingStateDao;
+
@Inject
public DefaultEntitlementInternalApi(final PersistentBus eventBus,
final EntitlementApi entitlementApi, final EntitlementPluginExecution pluginExecution,
@@ -44,6 +87,161 @@ public class DefaultEntitlementInternalApi extends DefaultEntitlementApiBase imp
final BlockingChecker checker, final NotificationQueueService notificationQueueService,
final EventsStreamBuilder eventsStreamBuilder, final EntitlementUtils entitlementUtils, final SecurityApi securityApi) {
super(eventBus, entitlementApi, pluginExecution, internalCallContextFactory, subscriptionInternalApi, accountApi, blockingStateDao, clock, checker, notificationQueueService, eventsStreamBuilder, entitlementUtils, securityApi);
+ this.blockingStateDao = blockingStateDao;
+ }
+
+ @Override
+ public void cancel(final Iterable<Entitlement> entitlements, final LocalDate effectiveDate, final BillingActionPolicy billingPolicy, final Iterable<PluginProperty> properties, final InternalCallContext internalCallContext) throws EntitlementApiException {
+ final CallContext callContext = internalCallContextFactory.createCallContext(internalCallContext);
+
+ final ImmutableMap.Builder<BlockingState, Optional<UUID>> blockingStates = new ImmutableMap.Builder<BlockingState, Optional<UUID>>();
+ final Map<DateTime, Collection<NotificationEvent>> notificationEvents = new HashMap<DateTime, Collection<NotificationEvent>>();
+ final Collection<EntitlementContext> pluginContexts = new LinkedList<EntitlementContext>();
+ final List<WithEntitlementPlugin> callbacks = new LinkedList<WithEntitlementPlugin>();
+ final List<SubscriptionBase> subscriptions = new LinkedList<SubscriptionBase>();
+
+ for (final Entitlement entitlement : entitlements) {
+ if (entitlement.getState() == EntitlementState.CANCELLED) {
+ // If subscription has already been cancelled, we ignore and carry on
+ continue;
+ }
+
+ final EntitlementContext pluginContext = new DefaultEntitlementContext(OperationType.CANCEL_SUBSCRIPTION,
+ entitlement.getAccountId(),
+ null,
+ entitlement.getBundleId(),
+ entitlement.getExternalKey(),
+ null,
+ effectiveDate,
+ properties,
+ callContext);
+ pluginContexts.add(pluginContext);
+
+ final DefaultEntitlement defaultEntitlement = getDefaultEntitlement(entitlement, internalCallContext);
+ final WithEntitlementPlugin<Entitlement> cancelEntitlementWithPlugin = new WithDateOverrideBillingPolicyEntitlementCanceler(defaultEntitlement,
+ blockingStates,
+ notificationEvents,
+ callContext,
+ internalCallContext);
+ callbacks.add(cancelEntitlementWithPlugin);
+
+ subscriptions.add(defaultEntitlement.getSubscriptionBase());
+ }
+
+ final Callable<Void> preCallbacksCallback = new BulkSubscriptionBaseCancellation(subscriptions,
+ billingPolicy,
+ internalCallContext);
+
+ pluginExecution.executeWithPlugin(preCallbacksCallback, callbacks, pluginContexts);
+
+ // Record the new states first, then insert the notifications to avoid race conditions
+ blockingStateDao.setBlockingStatesAndPostBlockingTransitionEvent(blockingStates.build(), internalCallContext);
+ for (final DateTime effectiveDateForNotification : notificationEvents.keySet()) {
+ for (final NotificationEvent notificationEvent : notificationEvents.get(effectiveDateForNotification)) {
+ recordFutureNotification(effectiveDateForNotification, notificationEvent, internalCallContext);
+ }
+ }
+ }
+
+ private void recordFutureNotification(final DateTime effectiveDate,
+ final NotificationEvent notificationEvent,
+ final InternalCallContext context) {
+ try {
+ final NotificationQueue subscriptionEventQueue = notificationQueueService.getNotificationQueue(DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME,
+ DefaultEntitlementService.NOTIFICATION_QUEUE_NAME);
+ subscriptionEventQueue.recordFutureNotification(effectiveDate, notificationEvent, context.getUserToken(), context.getAccountRecordId(), context.getTenantRecordId());
+ } catch (final NoSuchNotificationQueue e) {
+ throw new RuntimeException(e);
+ } catch (final IOException e) {
+ throw new RuntimeException(e);
+ }
}
+ // For forward-compatibility
+ private DefaultEntitlement getDefaultEntitlement(final Entitlement entitlement, final InternalTenantContext context) throws EntitlementApiException {
+ if (entitlement instanceof DefaultEntitlement) {
+ return (DefaultEntitlement) entitlement;
+ } else {
+ // Safe cast
+ return (DefaultEntitlement) getEntitlementForId(entitlement.getId(), context);
+ }
+ }
+
+ private class BulkSubscriptionBaseCancellation implements Callable<Void> {
+
+ private final Iterable<SubscriptionBase> subscriptions;
+ private final BillingActionPolicy billingPolicy;
+ private final InternalCallContext callContext;
+
+ public BulkSubscriptionBaseCancellation(final Iterable<SubscriptionBase> subscriptions,
+ final BillingActionPolicy billingPolicy,
+ final InternalCallContext callContext) {
+ this.subscriptions = subscriptions;
+ this.billingPolicy = billingPolicy;
+ this.callContext = callContext;
+ }
+
+ @Override
+ public Void call() throws Exception {
+ try {
+ subscriptionInternalApi.cancelBaseSubscriptions(subscriptions, billingPolicy, callContext);
+ } catch (final SubscriptionBaseApiException e) {
+ throw new EntitlementApiException(e);
+ }
+
+ return null;
+ }
+ }
+
+ // Note that the implementation is similar to DefaultEntitlement#cancelEntitlementWithDateOverrideBillingPolicy but state isn't persisted on disk
+ private class WithDateOverrideBillingPolicyEntitlementCanceler implements WithEntitlementPlugin<Entitlement> {
+
+ private final DefaultEntitlement entitlement;
+ private final ImmutableMap.Builder<BlockingState, Optional<UUID>> blockingStates;
+ private final Map<DateTime, Collection<NotificationEvent>> notificationEventsWithEffectiveDate;
+ private final CallContext callContext;
+ private final InternalCallContext internalCallContext;
+
+ public WithDateOverrideBillingPolicyEntitlementCanceler(final DefaultEntitlement entitlement,
+ final ImmutableMap.Builder<BlockingState, Optional<UUID>> blockingStates,
+ final Map<DateTime, Collection<NotificationEvent>> notificationEventsWithEffectiveDate,
+ final CallContext callContext,
+ final InternalCallContext internalCallContext) {
+ this.entitlement = entitlement;
+ this.blockingStates = blockingStates;
+ this.notificationEventsWithEffectiveDate = notificationEventsWithEffectiveDate;
+ this.callContext = callContext;
+ this.internalCallContext = internalCallContext;
+ }
+
+ @Override
+ public Entitlement doCall(final EntitlementApi entitlementApi, final EntitlementContext updatedPluginContext) throws EntitlementApiException {
+ final LocalDate effectiveLocalDate = new LocalDate(updatedPluginContext.getEffectiveDate(), entitlement.getAccountTimeZone());
+ DateTime effectiveDate = dateHelper.fromLocalDateAndReferenceTime(effectiveLocalDate, entitlement.getSubscriptionBase().getStartDate(), internalCallContext);
+ // Avoid timing issues for IMM cancellations (we don't want an entitlement cancel date one second or so after the subscription cancel date or
+ // add-ons cancellations computations won't work).
+ if (effectiveDate.compareTo(entitlement.getSubscriptionBase().getEndDate()) > 0) {
+ effectiveDate = entitlement.getSubscriptionBase().getEndDate();
+ }
+
+ final BlockingState newBlockingState = new DefaultBlockingState(entitlement.getId(), BlockingStateType.SUBSCRIPTION, DefaultEntitlementApi.ENT_STATE_CANCELLED, EntitlementService.ENTITLEMENT_SERVICE_NAME, true, true, false, effectiveDate);
+ final Collection<NotificationEvent> notificationEvents = new ArrayList<NotificationEvent>();
+ final Collection<BlockingState> addOnsBlockingStates = entitlement.computeAddOnBlockingStates(effectiveDate, notificationEvents, callContext, internalCallContext);
+
+ final Optional<UUID> bundleIdOptional = Optional.<UUID>fromNullable(entitlement.getBundleId());
+ blockingStates.put(newBlockingState, bundleIdOptional);
+ for (final BlockingState blockingState : addOnsBlockingStates) {
+ blockingStates.put(blockingState, bundleIdOptional);
+ }
+
+ if (notificationEventsWithEffectiveDate.get(effectiveDate) == null) {
+ notificationEventsWithEffectiveDate.put(effectiveDate, notificationEvents);
+ } else {
+ notificationEventsWithEffectiveDate.get(effectiveDate).addAll(notificationEvents);
+ }
+
+ // Unable to return the new state (not on disk yet)
+ return null;
+ }
+ }
}
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/block/DefaultBlockingChecker.java b/entitlement/src/main/java/org/killbill/billing/entitlement/block/DefaultBlockingChecker.java
index d468dbd..d793106 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/block/DefaultBlockingChecker.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/block/DefaultBlockingChecker.java
@@ -1,7 +1,9 @@
/*
* Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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:
*
@@ -80,6 +82,16 @@ public class DefaultBlockingChecker implements BlockingChecker {
}
@Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("DefaultBlockingAggregator{");
+ sb.append("blockChange=").append(blockChange);
+ sb.append(", blockEntitlement=").append(blockEntitlement);
+ sb.append(", blockBilling=").append(blockBilling);
+ sb.append('}');
+ return sb.toString();
+ }
+
+ @Override
public boolean equals(final Object o) {
if (this == o) {
return true;
@@ -115,6 +127,8 @@ public class DefaultBlockingChecker implements BlockingChecker {
private final SubscriptionBaseInternalApi subscriptionApi;
private final BlockingStateDao dao;
+ private final StatelessBlockingChecker statelessBlockingChecker = new StatelessBlockingChecker();
+
@Inject
public DefaultBlockingChecker(final SubscriptionBaseInternalApi subscriptionApi, final BlockingStateDao dao) {
this.subscriptionApi = subscriptionApi;
@@ -126,7 +140,7 @@ public class DefaultBlockingChecker implements BlockingChecker {
try {
subscription = subscriptionApi.getSubscriptionFromId(subscriptionId, context);
return getBlockedStateSubscription(subscription, upToDate, context);
- } catch (SubscriptionBaseApiException e) {
+ } catch (final SubscriptionBaseApiException e) {
throw new BlockingApiException(e, ErrorCode.fromCode(e.getCode()));
}
}
@@ -152,7 +166,7 @@ public class DefaultBlockingChecker implements BlockingChecker {
try {
bundle = subscriptionApi.getBundleFromId(bundleId, context);
return getBlockedStateBundle(bundle, upToDate, context);
- } catch (SubscriptionBaseApiException e) {
+ } catch (final SubscriptionBaseApiException e) {
throw new BlockingApiException(e, ErrorCode.fromCode(e.getCode()));
}
}
@@ -185,15 +199,7 @@ public class DefaultBlockingChecker implements BlockingChecker {
} else {
blockableState = ImmutableList.<BlockingState>of();
}
- return getBlockedState(blockableState);
- }
-
- private DefaultBlockingAggregator getBlockedState(final Iterable<BlockingState> currentBlockableStatePerService) {
- final DefaultBlockingAggregator result = new DefaultBlockingAggregator();
- for (final BlockingState cur : currentBlockableStatePerService) {
- result.or(cur);
- }
- return result;
+ return statelessBlockingChecker.getBlockedState(blockableState);
}
@Override
@@ -209,10 +215,7 @@ public class DefaultBlockingChecker implements BlockingChecker {
@Override
public BlockingAggregator getBlockedStatus(final List<BlockingState> accountEntitlementStates, final List<BlockingState> bundleEntitlementStates, final List<BlockingState> subscriptionEntitlementStates, final InternalTenantContext internalTenantContext) {
- final DefaultBlockingAggregator result = getBlockedState(subscriptionEntitlementStates);
- result.or(getBlockedState(bundleEntitlementStates));
- result.or(getBlockedState(accountEntitlementStates));
- return result;
+ return statelessBlockingChecker.getBlockedState(accountEntitlementStates, bundleEntitlementStates, subscriptionEntitlementStates);
}
@Override
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/block/StatelessBlockingChecker.java b/entitlement/src/main/java/org/killbill/billing/entitlement/block/StatelessBlockingChecker.java
new file mode 100644
index 0000000..495c494
--- /dev/null
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/block/StatelessBlockingChecker.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2016 Groupon, Inc
+ * Copyright 2016 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.block;
+
+import org.killbill.billing.entitlement.api.BlockingState;
+import org.killbill.billing.entitlement.block.DefaultBlockingChecker.DefaultBlockingAggregator;
+
+public class StatelessBlockingChecker {
+
+ public DefaultBlockingAggregator getBlockedState(final Iterable<BlockingState> accountEntitlementStates,
+ final Iterable<BlockingState> bundleEntitlementStates,
+ final Iterable<BlockingState> subscriptionEntitlementStates) {
+ final DefaultBlockingAggregator result = getBlockedState(subscriptionEntitlementStates);
+ result.or(getBlockedState(bundleEntitlementStates));
+ result.or(getBlockedState(accountEntitlementStates));
+ return result;
+ }
+
+ public DefaultBlockingAggregator getBlockedState(final Iterable<BlockingState> currentBlockableStatePerService) {
+ final DefaultBlockingAggregator result = new DefaultBlockingAggregator();
+ for (final BlockingState cur : currentBlockableStatePerService) {
+ result.or(cur);
+ }
+ return result;
+ }
+}
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/dao/BlockingStateDao.java b/entitlement/src/main/java/org/killbill/billing/entitlement/dao/BlockingStateDao.java
index 493b519..3327f70 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/dao/BlockingStateDao.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/dao/BlockingStateDao.java
@@ -1,7 +1,9 @@
/*
* Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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:
*
@@ -17,17 +19,19 @@
package org.killbill.billing.entitlement.dao;
import java.util.List;
+import java.util.Map;
import java.util.UUID;
import org.joda.time.DateTime;
import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.callcontext.InternalTenantContext;
-import org.killbill.clock.Clock;
import org.killbill.billing.entitlement.api.BlockingState;
import org.killbill.billing.entitlement.api.BlockingStateType;
import org.killbill.billing.entitlement.api.EntitlementApiException;
import org.killbill.billing.util.entity.dao.EntityDao;
+import com.google.common.base.Optional;
+
public interface BlockingStateDao extends EntityDao<BlockingStateModelDao, BlockingState, EntitlementApiException> {
/**
@@ -60,13 +64,12 @@ public interface BlockingStateDao extends EntityDao<BlockingStateModelDao, Block
public List<BlockingState> getBlockingAllForAccountRecordId(InternalTenantContext context);
/**
- * Sets a new state for a specific service.
+ * Set new blocking states
*
- * @param state blocking state to set
- * @param clock system clock
+ * @param states blocking states to set (mapped to the associated bundle id if the blocking state type is SUBSCRIPTION)
* @param context call context
*/
- public void setBlockingState(BlockingState state, Clock clock, InternalCallContext context);
+ public void setBlockingStatesAndPostBlockingTransitionEvent(Map<BlockingState, Optional<UUID>> states, InternalCallContext context);
/**
* Unactive the blocking state
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/dao/DefaultBlockingStateDao.java b/entitlement/src/main/java/org/killbill/billing/entitlement/dao/DefaultBlockingStateDao.java
index 9b72f19..9d67470 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/dao/DefaultBlockingStateDao.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/dao/DefaultBlockingStateDao.java
@@ -1,7 +1,9 @@
/*
* Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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:
*
@@ -16,39 +18,64 @@
package org.killbill.billing.entitlement.dao;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.UUID;
import javax.annotation.Nullable;
import org.joda.time.DateTime;
import org.killbill.billing.ErrorCode;
+import org.killbill.billing.ObjectType;
import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.entitlement.DefaultEntitlementService;
+import org.killbill.billing.entitlement.EntitlementService;
+import org.killbill.billing.entitlement.api.BlockingApiException;
import org.killbill.billing.entitlement.api.BlockingState;
import org.killbill.billing.entitlement.api.BlockingStateType;
+import org.killbill.billing.entitlement.api.DefaultBlockingTransitionInternalEvent;
import org.killbill.billing.entitlement.api.EntitlementApiException;
+import org.killbill.billing.entitlement.block.BlockingChecker.BlockingAggregator;
+import org.killbill.billing.entitlement.block.StatelessBlockingChecker;
+import org.killbill.billing.entitlement.engine.core.BlockingTransitionNotificationKey;
+import org.killbill.billing.util.cache.Cachable.CacheType;
import org.killbill.billing.util.cache.CacheControllerDispatcher;
import org.killbill.billing.util.dao.NonEntityDao;
import org.killbill.billing.util.entity.dao.EntityDaoBase;
import org.killbill.billing.util.entity.dao.EntitySqlDaoTransactionWrapper;
import org.killbill.billing.util.entity.dao.EntitySqlDaoTransactionalJdbiWrapper;
import org.killbill.billing.util.entity.dao.EntitySqlDaoWrapperFactory;
+import org.killbill.bus.api.BusEvent;
+import org.killbill.bus.api.PersistentBus;
+import org.killbill.bus.api.PersistentBus.EventBusException;
import org.killbill.clock.Clock;
+import org.killbill.notificationq.api.NotificationEvent;
+import org.killbill.notificationq.api.NotificationQueue;
+import org.killbill.notificationq.api.NotificationQueueService;
+import org.killbill.notificationq.api.NotificationQueueService.NoSuchNotificationQueue;
+import org.skife.jdbi.v2.Handle;
import org.skife.jdbi.v2.IDBI;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import com.google.common.base.Function;
+import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.Ordering;
public class DefaultBlockingStateDao extends EntityDaoBase<BlockingStateModelDao, BlockingState, EntitlementApiException> implements BlockingStateDao {
+ private static final Logger log = LoggerFactory.getLogger(DefaultBlockingStateDao.class);
+
// Assume the input is blocking states for a single blockable id
private static final Ordering<BlockingStateModelDao> BLOCKING_STATE_MODEL_DAO_ORDERING = Ordering.<BlockingStateModelDao>from(new Comparator<BlockingStateModelDao>() {
@Override
@@ -77,11 +104,21 @@ public class DefaultBlockingStateDao extends EntityDaoBase<BlockingStateModelDao
});
private final Clock clock;
+ private final NotificationQueueService notificationQueueService;
+ private final PersistentBus eventBus;
+ private final CacheControllerDispatcher cacheControllerDispatcher;
+ private final NonEntityDao nonEntityDao;
+
+ private final StatelessBlockingChecker statelessBlockingChecker = new StatelessBlockingChecker();
- public DefaultBlockingStateDao(final IDBI dbi, final Clock clock,
+ public DefaultBlockingStateDao(final IDBI dbi, final Clock clock, final NotificationQueueService notificationQueueService, final PersistentBus eventBus,
final CacheControllerDispatcher cacheControllerDispatcher, final NonEntityDao nonEntityDao) {
super(new EntitySqlDaoTransactionalJdbiWrapper(dbi, clock, cacheControllerDispatcher, nonEntityDao), BlockingStateSqlDao.class);
this.clock = clock;
+ this.notificationQueueService = notificationQueueService;
+ this.eventBus = eventBus;
+ this.cacheControllerDispatcher = cacheControllerDispatcher;
+ this.nonEntityDao = nonEntityDao;
}
@Override
@@ -107,20 +144,25 @@ public class DefaultBlockingStateDao extends EntityDaoBase<BlockingStateModelDao
return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<List<BlockingState>>() {
@Override
public List<BlockingState> inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
- // Upper bound time limit is now
- final Date upTo = upToDate.toDate();
- final List<BlockingStateModelDao> models = entitySqlDaoWrapperFactory.become(BlockingStateSqlDao.class).getBlockingState(blockableId, upTo, context);
- final Collection<BlockingStateModelDao> modelsFiltered = filterBlockingStates(models, blockingStateType);
- return new ArrayList<BlockingState>(Collections2.transform(modelsFiltered, new Function<BlockingStateModelDao, BlockingState>() {
- @Override
- public BlockingState apply(@Nullable final BlockingStateModelDao src) {
- return BlockingStateModelDao.toBlockingState(src);
- }
- }));
+ final BlockingStateSqlDao sqlDao = entitySqlDaoWrapperFactory.become(BlockingStateSqlDao.class);
+ return getBlockingState(sqlDao, blockableId, blockingStateType, upToDate, context);
}
});
}
+ private List<BlockingState> getBlockingState(final BlockingStateSqlDao sqlDao, final UUID blockableId, final BlockingStateType blockingStateType, final DateTime upToDate, final InternalTenantContext context) {
+ final Date upTo = upToDate.toDate();
+ final List<BlockingStateModelDao> models = sqlDao.getBlockingState(blockableId, upTo, context);
+ final Collection<BlockingStateModelDao> modelsFiltered = filterBlockingStates(models, blockingStateType);
+ return new ArrayList<BlockingState>(Collections2.transform(modelsFiltered,
+ new Function<BlockingStateModelDao, BlockingState>() {
+ @Override
+ public BlockingState apply(@Nullable final BlockingStateModelDao src) {
+ return BlockingStateModelDao.toBlockingState(src);
+ }
+ }));
+ }
+
@Override
public List<BlockingState> getBlockingAllForAccountRecordId(final InternalTenantContext context) {
return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<List<BlockingState>>() {
@@ -139,48 +181,68 @@ public class DefaultBlockingStateDao extends EntityDaoBase<BlockingStateModelDao
}
@Override
- public void setBlockingState(final BlockingState state, final Clock clock, final InternalCallContext context) {
+ public void setBlockingStatesAndPostBlockingTransitionEvent(final Map<BlockingState, Optional<UUID>> states, final InternalCallContext context) {
transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<Void>() {
@Override
public Void inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
- final BlockingStateModelDao newBlockingStateModelDao = new BlockingStateModelDao(state, context);
-
final BlockingStateSqlDao sqlDao = entitySqlDaoWrapperFactory.become(BlockingStateSqlDao.class);
- // Get all blocking states for that blocked id and service
- final List<BlockingStateModelDao> allForBlockedItAndService = sqlDao.getBlockingHistoryForService(state.getBlockedId(), state.getService(), context);
-
- // Add the new one (we rely below on the fact that the ID for newBlockingStateModelDao is now set)
- allForBlockedItAndService.add(newBlockingStateModelDao);
-
- // Re-order what should be the final list (allForBlockedItAndService is ordered by record_id in the SQL and we just added a new state)
- final List<BlockingStateModelDao> allForBlockedItAndServiceOrdered = BLOCKING_STATE_MODEL_DAO_ORDERING.immutableSortedCopy(allForBlockedItAndService);
-
- // Go through the (ordered) stream of blocking states for that blocked id and service and check
- // if there is one or more blocking states for the same state following each others.
- // If there are, delete them, as they are not needed anymore. A picture being worth a thousand words,
- // if the current stream is: t0 S1 t1 S2 t3 S3 and we want to insert S2 at t0 < t1' < t1,
- // the final stream should be: t0 S1 t1' S2 t3 S3 (and not t0 S1 t1' S2 t1 S2 t3 S3)
- // Note that we also take care of the use case t0 S1 t1 S2 t2 S2 t3 S3 to cleanup legacy systems, although
- // it shouldn't happen anymore
- final Collection<UUID> blockingStatesToRemove = new HashSet<UUID>();
- BlockingStateModelDao prevBlockingStateModelDao = null;
- for (final BlockingStateModelDao blockingStateModelDao : allForBlockedItAndServiceOrdered) {
- if (prevBlockingStateModelDao != null && prevBlockingStateModelDao.getState().equals(blockingStateModelDao.getState())) {
- blockingStatesToRemove.add(blockingStateModelDao.getId());
+
+ for (final BlockingState state : states.keySet()) {
+ final DateTime upToDate = state.getEffectiveDate();
+ final UUID bundleId = states.get(state).orNull();
+ final BlockingAggregator previousState = getBlockedStatus(sqlDao, entitySqlDaoWrapperFactory.getHandle(), state.getBlockedId(), state.getType(), bundleId, upToDate, context);
+
+ final BlockingStateModelDao newBlockingStateModelDao = new BlockingStateModelDao(state, context);
+
+ // Get all blocking states for that blocked id and service
+ final List<BlockingStateModelDao> allForBlockedItAndService = sqlDao.getBlockingHistoryForService(state.getBlockedId(), state.getService(), context);
+
+ // Add the new one (we rely below on the fact that the ID for newBlockingStateModelDao is now set)
+ allForBlockedItAndService.add(newBlockingStateModelDao);
+
+ // Re-order what should be the final list (allForBlockedItAndService is ordered by record_id in the SQL and we just added a new state)
+ final List<BlockingStateModelDao> allForBlockedItAndServiceOrdered = BLOCKING_STATE_MODEL_DAO_ORDERING.immutableSortedCopy(allForBlockedItAndService);
+
+ // Go through the (ordered) stream of blocking states for that blocked id and service and check
+ // if there is one or more blocking states for the same state following each others.
+ // If there are, delete them, as they are not needed anymore. A picture being worth a thousand words,
+ // if the current stream is: t0 S1 t1 S2 t3 S3 and we want to insert S2 at t0 < t1' < t1,
+ // the final stream should be: t0 S1 t1' S2 t3 S3 (and not t0 S1 t1' S2 t1 S2 t3 S3)
+ // Note that we also take care of the use case t0 S1 t1 S2 t2 S2 t3 S3 to cleanup legacy systems, although
+ // it shouldn't happen anymore
+ final Collection<UUID> blockingStatesToRemove = new HashSet<UUID>();
+ BlockingStateModelDao prevBlockingStateModelDao = null;
+ for (final BlockingStateModelDao blockingStateModelDao : allForBlockedItAndServiceOrdered) {
+ if (prevBlockingStateModelDao != null && prevBlockingStateModelDao.getState().equals(blockingStateModelDao.getState())) {
+ blockingStatesToRemove.add(blockingStateModelDao.getId());
+ }
+ prevBlockingStateModelDao = blockingStateModelDao;
}
- prevBlockingStateModelDao = blockingStateModelDao;
- }
- // Delete unnecessary states (except newBlockingStateModelDao, which doesn't exist in the database)
- for (final UUID blockedId : blockingStatesToRemove) {
- if (!newBlockingStateModelDao.getId().equals(blockedId)) {
- sqlDao.unactiveEvent(blockedId.toString(), context);
+ // Delete unnecessary states (except newBlockingStateModelDao, which doesn't exist in the database)
+ for (final UUID blockedId : blockingStatesToRemove) {
+ if (!newBlockingStateModelDao.getId().equals(blockedId)) {
+ sqlDao.unactiveEvent(blockedId.toString(), context);
+ }
+ }
+
+ // Create the state, if needed
+ if (!blockingStatesToRemove.contains(newBlockingStateModelDao.getId())) {
+ sqlDao.create(newBlockingStateModelDao, context);
}
- }
- // Create the state, if needed
- if (!blockingStatesToRemove.contains(newBlockingStateModelDao.getId())) {
- sqlDao.create(newBlockingStateModelDao, context);
+ final BlockingAggregator currentState = getBlockedStatus(sqlDao, entitySqlDaoWrapperFactory.getHandle(), state.getBlockedId(), state.getType(), bundleId, upToDate, context);
+ if (previousState != null && currentState != null) {
+ recordBusOrFutureNotificationFromTransaction(entitySqlDaoWrapperFactory,
+ state.getId(),
+ state.getEffectiveDate(),
+ state.getBlockedId(),
+ state.getType(),
+ state.getService(),
+ previousState,
+ currentState,
+ context);
+ }
}
return null;
@@ -188,6 +250,93 @@ public class DefaultBlockingStateDao extends EntityDaoBase<BlockingStateModelDao
});
}
+ private BlockingAggregator getBlockedStatus(final BlockingStateSqlDao sqlDao, final Handle handle, final UUID blockableId, final BlockingStateType type, @Nullable final UUID bundleId, final DateTime upToDate, final InternalTenantContext context) throws BlockingApiException {
+ final List<BlockingState> accountBlockingStates;
+ final List<BlockingState> bundleBlockingStates;
+ final List<BlockingState> subscriptionBlockingStates;
+ if (type == BlockingStateType.SUBSCRIPTION) {
+ final UUID accountId = nonEntityDao.retrieveIdFromObjectInTransaction(context.getAccountRecordId(), ObjectType.ACCOUNT, cacheControllerDispatcher.getCacheController(CacheType.OBJECT_ID), handle);
+ accountBlockingStates = getBlockingState(sqlDao, accountId, BlockingStateType.ACCOUNT, upToDate, context);
+ bundleBlockingStates = getBlockingState(sqlDao, bundleId, BlockingStateType.SUBSCRIPTION_BUNDLE, upToDate, context);
+ subscriptionBlockingStates = getBlockingState(sqlDao, blockableId, BlockingStateType.SUBSCRIPTION, upToDate, context);
+ } else if (type == BlockingStateType.SUBSCRIPTION_BUNDLE) {
+ final UUID accountId = nonEntityDao.retrieveIdFromObjectInTransaction(context.getAccountRecordId(), ObjectType.ACCOUNT, cacheControllerDispatcher.getCacheController(CacheType.OBJECT_ID), handle);
+ accountBlockingStates = getBlockingState(sqlDao, accountId, BlockingStateType.ACCOUNT, upToDate, context);
+ bundleBlockingStates = getBlockingState(sqlDao, blockableId, BlockingStateType.SUBSCRIPTION_BUNDLE, upToDate, context);
+ subscriptionBlockingStates = ImmutableList.<BlockingState>of();
+ } else { // BlockingStateType.ACCOUNT {
+ accountBlockingStates = getBlockingState(sqlDao, blockableId, BlockingStateType.ACCOUNT, upToDate, context);
+ bundleBlockingStates = ImmutableList.<BlockingState>of();
+ subscriptionBlockingStates = ImmutableList.<BlockingState>of();
+ }
+ return statelessBlockingChecker.getBlockedState(accountBlockingStates, bundleBlockingStates, subscriptionBlockingStates);
+ }
+
+ private void recordBusOrFutureNotificationFromTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory,
+ final UUID blockingStateId,
+ final DateTime effectiveDate,
+ final UUID blockableId,
+ final BlockingStateType type,
+ final String serviceName,
+ final BlockingAggregator previousState,
+ final BlockingAggregator currentState,
+ final InternalCallContext context) {
+ final boolean isTransitionToBlockedBilling = !previousState.isBlockBilling() && currentState.isBlockBilling();
+ final boolean isTransitionToUnblockedBilling = previousState.isBlockBilling() && !currentState.isBlockBilling();
+
+ final boolean isTransitionToBlockedEntitlement = !previousState.isBlockEntitlement() && currentState.isBlockEntitlement();
+ final boolean isTransitionToUnblockedEntitlement = previousState.isBlockEntitlement() && !currentState.isBlockEntitlement();
+
+ if (effectiveDate.compareTo(clock.getUTCNow()) > 0) {
+ // Add notification entry to send the bus event at the effective date
+ final NotificationEvent notificationEvent = new BlockingTransitionNotificationKey(blockingStateId, blockableId, type,
+ isTransitionToBlockedBilling, isTransitionToUnblockedBilling,
+ isTransitionToBlockedEntitlement, isTransitionToUnblockedEntitlement);
+ recordFutureNotificationFromTransaction(entitySqlDaoWrapperFactory, effectiveDate, notificationEvent, context);
+ } else {
+ // TODO Do we want to send a DefaultEffectiveEntitlementEvent for entitlement specific blocking states?
+ // Don't post if nothing has changed for entitlement-service
+ if (!serviceName.equals(EntitlementService.ENTITLEMENT_SERVICE_NAME) || !previousState.equals(currentState)) {
+ final BusEvent event = new DefaultBlockingTransitionInternalEvent(blockableId, type,
+ isTransitionToBlockedBilling, isTransitionToUnblockedBilling,
+ isTransitionToBlockedEntitlement, isTransitionToUnblockedEntitlement,
+ context.getAccountRecordId(), context.getTenantRecordId(), context.getUserToken());
+ notifyBusFromTransaction(entitySqlDaoWrapperFactory, event);
+ } else {
+ log.debug("Skipping event for service {} and blockableId {} (previousState={}, currentState={})", serviceName, blockableId, previousState, currentState);
+ }
+
+ }
+ }
+
+ private void recordFutureNotificationFromTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory,
+ final DateTime effectiveDate,
+ final NotificationEvent notificationEvent,
+ final InternalCallContext context) {
+ try {
+ final NotificationQueue subscriptionEventQueue = notificationQueueService.getNotificationQueue(DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME,
+ DefaultEntitlementService.NOTIFICATION_QUEUE_NAME);
+ subscriptionEventQueue.recordFutureNotificationFromTransaction(entitySqlDaoWrapperFactory.getHandle().getConnection(),
+ effectiveDate,
+ notificationEvent,
+ context.getUserToken(),
+ context.getAccountRecordId(),
+ context.getTenantRecordId());
+ } catch (final NoSuchNotificationQueue e) {
+ throw new RuntimeException(e);
+ } catch (final IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private void notifyBusFromTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory, final BusEvent event) {
+ try {
+ eventBus.postFromTransaction(event, entitySqlDaoWrapperFactory.getHandle().getConnection());
+ } catch (final EventBusException e) {
+ log.warn("Failed to post event {}", e);
+ }
+ }
+
@Override
public void unactiveBlockingState(final UUID id, final InternalCallContext context) {
transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<Void>() {
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/dao/OptimizedProxyBlockingStateDao.java b/entitlement/src/main/java/org/killbill/billing/entitlement/dao/OptimizedProxyBlockingStateDao.java
index cef565e..3c228f2 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/dao/OptimizedProxyBlockingStateDao.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/dao/OptimizedProxyBlockingStateDao.java
@@ -1,7 +1,9 @@
/*
* Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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:
*
@@ -34,7 +36,9 @@ import org.killbill.billing.subscription.api.SubscriptionBaseInternalApi;
import org.killbill.billing.subscription.api.user.SubscriptionBaseBundle;
import org.killbill.billing.util.cache.CacheControllerDispatcher;
import org.killbill.billing.util.dao.NonEntityDao;
+import org.killbill.bus.api.PersistentBus;
import org.killbill.clock.Clock;
+import org.killbill.notificationq.api.NotificationQueueService;
import org.skife.jdbi.v2.IDBI;
import com.google.common.collect.ImmutableList;
@@ -42,9 +46,9 @@ import com.google.common.collect.ImmutableList;
public class OptimizedProxyBlockingStateDao extends ProxyBlockingStateDao {
public OptimizedProxyBlockingStateDao(final EventsStreamBuilder eventsStreamBuilder, final SubscriptionBaseInternalApi subscriptionBaseInternalApi,
- final IDBI dbi, final Clock clock, final CacheControllerDispatcher cacheControllerDispatcher,
- final NonEntityDao nonEntityDao) {
- super(eventsStreamBuilder, subscriptionBaseInternalApi, dbi, clock, cacheControllerDispatcher, nonEntityDao);
+ final IDBI dbi, final Clock clock, final NotificationQueueService notificationQueueService, final PersistentBus eventBus,
+ final CacheControllerDispatcher cacheControllerDispatcher, final NonEntityDao nonEntityDao) {
+ super(eventsStreamBuilder, subscriptionBaseInternalApi, dbi, clock, notificationQueueService, eventBus, cacheControllerDispatcher, nonEntityDao);
}
/**
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/dao/ProxyBlockingStateDao.java b/entitlement/src/main/java/org/killbill/billing/entitlement/dao/ProxyBlockingStateDao.java
index 92f6fb0..1defc97 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/dao/ProxyBlockingStateDao.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/dao/ProxyBlockingStateDao.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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
@@ -47,11 +47,14 @@ import org.killbill.billing.util.cache.CacheControllerDispatcher;
import org.killbill.billing.util.customfield.ShouldntHappenException;
import org.killbill.billing.util.dao.NonEntityDao;
import org.killbill.billing.util.entity.Pagination;
+import org.killbill.bus.api.PersistentBus;
import org.killbill.clock.Clock;
+import org.killbill.notificationq.api.NotificationQueueService;
import org.skife.jdbi.v2.IDBI;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Ordering;
@@ -74,8 +77,11 @@ public class ProxyBlockingStateDao implements BlockingStateDao {
final BlockingState current = iterator.next();
if (iterator.hasNext()) {
final BlockingState next = iterator.next();
- if (prev != null && current.getEffectiveDate().equals(next.getEffectiveDate()) && current.getBlockedId().equals(next.getBlockedId())) {
- // Same date, same blockable id
+ if (prev != null &&
+ current.getEffectiveDate().equals(next.getEffectiveDate()) &&
+ current.getBlockedId().equals(next.getBlockedId()) &&
+ !current.getService().equals(next.getService())) {
+ // Same date, same blockable id, different services (for same-service events, trust the total ordering)
// Make sure block billing transitions are respected first
BlockingState prevCandidate = insertTiedBlockingStatesInTheRightOrder(result, current, next, prev.isBlockBilling(), current.isBlockBilling(), next.isBlockBilling());
@@ -164,12 +170,12 @@ public class ProxyBlockingStateDao implements BlockingStateDao {
@Inject
public ProxyBlockingStateDao(final EventsStreamBuilder eventsStreamBuilder, final SubscriptionBaseInternalApi subscriptionBaseInternalApi,
- final IDBI dbi, final Clock clock,
+ final IDBI dbi, final Clock clock, final NotificationQueueService notificationQueueService, final PersistentBus eventBus,
final CacheControllerDispatcher cacheControllerDispatcher, final NonEntityDao nonEntityDao) {
this.eventsStreamBuilder = eventsStreamBuilder;
this.subscriptionInternalApi = subscriptionBaseInternalApi;
this.clock = clock;
- this.delegate = new DefaultBlockingStateDao(dbi, clock, cacheControllerDispatcher, nonEntityDao);
+ this.delegate = new DefaultBlockingStateDao(dbi, clock, notificationQueueService, eventBus, cacheControllerDispatcher, nonEntityDao);
}
@Override
@@ -229,8 +235,8 @@ public class ProxyBlockingStateDao implements BlockingStateDao {
}
@Override
- public void setBlockingState(final BlockingState state, final Clock clock, final InternalCallContext context) {
- delegate.setBlockingState(state, clock, context);
+ public void setBlockingStatesAndPostBlockingTransitionEvent(final Map<BlockingState, Optional<UUID>> states, final InternalCallContext context) {
+ delegate.setBlockingStatesAndPostBlockingTransitionEvent(states, context);
}
@Override
@@ -239,7 +245,7 @@ public class ProxyBlockingStateDao implements BlockingStateDao {
}
// Add blocking states for add-ons, which would be impacted by a future cancellation or change of their base plan
- // See DefaultEntitlement#blockAddOnsIfRequired
+ // See DefaultEntitlement#computeAddOnBlockingStates
private List<BlockingState> addBlockingStatesNotOnDisk(final List<BlockingState> blockingStatesOnDisk,
final InternalTenantContext context) {
final Collection<BlockingState> blockingStatesOnDiskCopy = new LinkedList<BlockingState>(blockingStatesOnDisk);
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/DefaultEntitlementService.java b/entitlement/src/main/java/org/killbill/billing/entitlement/DefaultEntitlementService.java
index f62b795..7e189c0 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/DefaultEntitlementService.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/DefaultEntitlementService.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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
@@ -18,10 +18,14 @@
package org.killbill.billing.entitlement;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
import java.util.UUID;
import org.joda.time.DateTime;
import org.killbill.billing.callcontext.InternalCallContext;
+import org.killbill.billing.entitlement.api.BlockingState;
import org.killbill.billing.entitlement.api.DefaultBlockingTransitionInternalEvent;
import org.killbill.billing.entitlement.api.DefaultEntitlement;
import org.killbill.billing.entitlement.api.Entitlement;
@@ -30,12 +34,14 @@ import org.killbill.billing.entitlement.dao.BlockingStateDao;
import org.killbill.billing.entitlement.engine.core.BlockingTransitionNotificationKey;
import org.killbill.billing.entitlement.engine.core.EntitlementNotificationKey;
import org.killbill.billing.entitlement.engine.core.EntitlementNotificationKeyAction;
+import org.killbill.billing.entitlement.engine.core.EntitlementUtils;
import org.killbill.billing.payment.api.PluginProperty;
import org.killbill.billing.platform.api.LifecycleHandlerType;
import org.killbill.billing.platform.api.LifecycleHandlerType.LifecycleLevel;
import org.killbill.billing.util.callcontext.CallContext;
import org.killbill.billing.util.callcontext.CallOrigin;
import org.killbill.billing.util.callcontext.InternalCallContextFactory;
+import org.killbill.billing.util.callcontext.TenantContext;
import org.killbill.billing.util.callcontext.UserType;
import org.killbill.bus.api.BusEvent;
import org.killbill.bus.api.PersistentBus;
@@ -62,6 +68,7 @@ public class DefaultEntitlementService implements EntitlementService {
private final BlockingStateDao blockingStateDao;
private final PersistentBus eventBus;
private final NotificationQueueService notificationQueueService;
+ private final EntitlementUtils entitlementUtils;
private final InternalCallContextFactory internalCallContextFactory;
private NotificationQueue entitlementEventQueue;
@@ -71,11 +78,13 @@ public class DefaultEntitlementService implements EntitlementService {
final BlockingStateDao blockingStateDao,
final PersistentBus eventBus,
final NotificationQueueService notificationQueueService,
+ final EntitlementUtils entitlementUtils,
final InternalCallContextFactory internalCallContextFactory) {
this.entitlementInternalApi = entitlementInternalApi;
this.blockingStateDao = blockingStateDao;
this.eventBus = eventBus;
this.notificationQueueService = notificationQueueService;
+ this.entitlementUtils = entitlementUtils;
this.internalCallContextFactory = internalCallContextFactory;
}
@@ -131,7 +140,7 @@ public class DefaultEntitlementService implements EntitlementService {
try {
if (EntitlementNotificationKeyAction.CHANGE.equals(entitlementNotificationKeyAction) ||
EntitlementNotificationKeyAction.CANCEL.equals(entitlementNotificationKeyAction)) {
- ((DefaultEntitlement) entitlement).blockAddOnsIfRequired(key.getEffectiveDate(), callContext, internalCallContext);
+ blockAddOnsIfRequired(key, (DefaultEntitlement) entitlement, callContext, internalCallContext);
} else if (EntitlementNotificationKeyAction.PAUSE.equals(entitlementNotificationKeyAction)) {
entitlementInternalApi.pause(key.getBundleId(), key.getEffectiveDate().toLocalDate(), ImmutableList.<PluginProperty>of(), internalCallContext);
} else if (EntitlementNotificationKeyAction.RESUME.equals(entitlementNotificationKeyAction)) {
@@ -142,6 +151,30 @@ public class DefaultEntitlementService implements EntitlementService {
}
}
+ private void blockAddOnsIfRequired(final EntitlementNotificationKey key, final DefaultEntitlement entitlement, final TenantContext callContext, final InternalCallContext internalCallContext) throws EntitlementApiException {
+ final Collection<NotificationEvent> notificationEvents = new ArrayList<NotificationEvent>();
+ final Collection<BlockingState> blockingStates = entitlement.computeAddOnBlockingStates(key.getEffectiveDate(), notificationEvents, callContext, internalCallContext);
+ // Record the new state first, then insert the notifications to avoid race conditions
+ entitlementUtils.setBlockingStatesAndPostBlockingTransitionEvent(blockingStates, entitlement.getBundleId(), internalCallContext);
+ for (final NotificationEvent notificationEvent : notificationEvents) {
+ recordFutureNotification(key.getEffectiveDate(), notificationEvent, internalCallContext);
+ }
+ }
+
+ private void recordFutureNotification(final DateTime effectiveDate,
+ final NotificationEvent notificationEvent,
+ final InternalCallContext context) {
+ try {
+ final NotificationQueue subscriptionEventQueue = notificationQueueService.getNotificationQueue(DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME,
+ DefaultEntitlementService.NOTIFICATION_QUEUE_NAME);
+ subscriptionEventQueue.recordFutureNotification(effectiveDate, notificationEvent, context.getUserToken(), context.getAccountRecordId(), context.getTenantRecordId());
+ } catch (final NoSuchNotificationQueue e) {
+ throw new RuntimeException(e);
+ } catch (final IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
private void processBlockingNotification(final BlockingTransitionNotificationKey key, final InternalCallContext internalCallContext) {
// Check if the blocking state has been deleted since
if (blockingStateDao.getById(key.getBlockingStateId(), internalCallContext) == null) {
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/EntitlementUtils.java b/entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/EntitlementUtils.java
index 6be5e37..fd44b4d 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/EntitlementUtils.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/EntitlementUtils.java
@@ -1,7 +1,9 @@
/*
* Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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:
*
@@ -16,96 +18,80 @@
package org.killbill.billing.entitlement.engine.core;
-import java.io.IOException;
-import java.util.List;
+import java.util.Map;
import java.util.UUID;
import javax.annotation.Nullable;
import javax.inject.Inject;
-import org.joda.time.DateTime;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import org.killbill.bus.api.BusEvent;
-import org.killbill.bus.api.PersistentBus;
-import org.killbill.bus.api.PersistentBus.EventBusException;
import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.callcontext.InternalTenantContext;
-import org.killbill.clock.Clock;
import org.killbill.billing.entitlement.DefaultEntitlementService;
-import org.killbill.billing.entitlement.EntitlementService;
-import org.killbill.billing.entitlement.EventsStream;
-import org.killbill.billing.entitlement.api.BlockingApiException;
import org.killbill.billing.entitlement.api.BlockingState;
import org.killbill.billing.entitlement.api.BlockingStateType;
-import org.killbill.billing.entitlement.api.DefaultBlockingTransitionInternalEvent;
import org.killbill.billing.entitlement.api.DefaultEntitlementApi;
-import org.killbill.billing.entitlement.api.Entitlement.EntitlementState;
-import org.killbill.billing.entitlement.api.EntitlementApiException;
-import org.killbill.billing.entitlement.block.BlockingChecker;
-import org.killbill.billing.entitlement.block.BlockingChecker.BlockingAggregator;
import org.killbill.billing.entitlement.dao.BlockingStateDao;
-import org.killbill.notificationq.api.NotificationEvent;
-import org.killbill.notificationq.api.NotificationQueue;
-import org.killbill.notificationq.api.NotificationQueueService;
-import org.killbill.notificationq.api.NotificationQueueService.NoSuchNotificationQueue;
import org.killbill.billing.subscription.api.SubscriptionBaseInternalApi;
-import org.killbill.billing.subscription.api.user.SubscriptionBaseBundle;
-import org.killbill.billing.util.callcontext.CallContext;
-import org.killbill.billing.util.callcontext.TenantContext;
+import org.killbill.billing.subscription.api.user.SubscriptionBaseApiException;
+import org.killbill.notificationq.api.NotificationQueueService;
+import com.google.common.base.Optional;
import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
public class EntitlementUtils {
- private static final Logger log = LoggerFactory.getLogger(EntitlementUtils.class);
+ protected final NotificationQueueService notificationQueueService;
private final BlockingStateDao dao;
- private final BlockingChecker blockingChecker;
private final SubscriptionBaseInternalApi subscriptionBaseInternalApi;
- private final PersistentBus eventBus;
- private final Clock clock;
- protected final NotificationQueueService notificationQueueService;
@Inject
- public EntitlementUtils(final BlockingStateDao dao, final BlockingChecker blockingChecker,
- final PersistentBus eventBus, final Clock clock,
+ public EntitlementUtils(final BlockingStateDao dao,
final SubscriptionBaseInternalApi subscriptionBaseInternalApi,
final NotificationQueueService notificationQueueService) {
this.dao = dao;
- this.blockingChecker = blockingChecker;
- this.eventBus = eventBus;
- this.clock = clock;
this.subscriptionBaseInternalApi = subscriptionBaseInternalApi;
this.notificationQueueService = notificationQueueService;
}
- /**
- * Wrapper around BlockingStateDao#setBlockingState which will send an event on the bus if needed
- *
- * @param state new state to store
- * @param context call context
- */
- public void setBlockingStateAndPostBlockingTransitionEvent(final BlockingState state, final InternalCallContext context) {
- final BlockingAggregator previousState = getBlockingStateFor(state.getBlockedId(), state.getType(), clock.getUTCNow(), context);
+ public void setBlockingStatesAndPostBlockingTransitionEvent(final Iterable<BlockingState> blockingStates, @Nullable final UUID bundleId, final InternalCallContext internalCallContext) {
+ final ImmutableMap.Builder<BlockingState, Optional<UUID>> states = new ImmutableMap.Builder<BlockingState, Optional<UUID>>();
+ final Optional<UUID> bundleIdOptional = Optional.<UUID>fromNullable(bundleId);
+ for (final BlockingState blockingState : blockingStates) {
+ states.put(blockingState, bundleIdOptional);
+ }
+ dao.setBlockingStatesAndPostBlockingTransitionEvent(states.build(), internalCallContext);
+ }
- dao.setBlockingState(state, clock, context);
+ public void setBlockingStateAndPostBlockingTransitionEvent(final Map<BlockingState, UUID> blockingStates, final InternalCallContext internalCallContext) {
+ final ImmutableMap.Builder<BlockingState, Optional<UUID>> states = new ImmutableMap.Builder<BlockingState, Optional<UUID>>();
+ for (final BlockingState blockingState : blockingStates.keySet()) {
+ states.put(blockingState, Optional.<UUID>fromNullable(blockingStates.get(blockingState)));
+ }
+ dao.setBlockingStatesAndPostBlockingTransitionEvent(states.build(), internalCallContext);
+ }
- final BlockingAggregator currentState = getBlockingStateFor(state.getBlockedId(), state.getType(), state.getEffectiveDate(), context);
- if (previousState != null && currentState != null) {
- postBlockingTransitionEvent(state.getId(), state.getEffectiveDate(), state.getBlockedId(), state.getType(), state.getService(), previousState, currentState, context);
+ public void setBlockingStateAndPostBlockingTransitionEvent(final BlockingState state, final InternalCallContext context) {
+ UUID bundleId = null;
+ // We only need the bundle id in case of subscriptions (at the account level, we don't need it and at the bundle level, we already have it)
+ if (state.getType() == BlockingStateType.SUBSCRIPTION) {
+ try {
+ bundleId = subscriptionBaseInternalApi.getSubscriptionFromId(state.getBlockedId(), context).getBundleId();
+ } catch (final SubscriptionBaseApiException e) {
+ throw new RuntimeException(e);
+ }
}
+ dao.setBlockingStatesAndPostBlockingTransitionEvent(ImmutableMap.<BlockingState, Optional<UUID>>of(state, Optional.<UUID>fromNullable(bundleId)), context);
}
/**
- *
- * @param externalKey the bundle externalKey
+ * @param externalKey the bundle externalKey
* @param tenantContext the context
* @return the id of the first subscription (BASE or STANDALONE) that is still active for that key
*/
- public UUID getFirstActiveSubscriptionIdForKeyOrNull(final String externalKey, final InternalTenantContext tenantContext) {
+ public UUID getFirstActiveSubscriptionIdForKeyOrNull(final String externalKey, final InternalTenantContext tenantContext) {
final Iterable<UUID> nonAddonUUIDs = subscriptionBaseInternalApi.getNonAOSubscriptionIdsForKey(externalKey, tenantContext);
return Iterables.tryFind(nonAddonUUIDs, new Predicate<UUID>() {
@@ -116,70 +102,4 @@ public class EntitlementUtils {
}
}).orNull();
}
-
-
- private BlockingAggregator getBlockingStateFor(final UUID blockableId, final BlockingStateType type, final DateTime effectiveDate, final InternalCallContext context) {
- try {
- return blockingChecker.getBlockedStatus(blockableId, type, effectiveDate, context);
- } catch (BlockingApiException e) {
- log.warn("Failed to retrieve blocking state for {} {}", blockableId, type);
- return null;
- }
- }
-
- private void postBlockingTransitionEvent(final UUID blockingStateId, final DateTime effectiveDate, final UUID blockableId, final BlockingStateType type,
- final String serviceName, final BlockingAggregator previousState, final BlockingAggregator currentState,
- final InternalCallContext context) {
- final boolean isTransitionToBlockedBilling = !previousState.isBlockBilling() && currentState.isBlockBilling();
- final boolean isTransitionToUnblockedBilling = previousState.isBlockBilling() && !currentState.isBlockBilling();
-
- final boolean isTransitionToBlockedEntitlement = !previousState.isBlockEntitlement() && currentState.isBlockEntitlement();
- final boolean isTransitionToUnblockedEntitlement = previousState.isBlockEntitlement() && !currentState.isBlockEntitlement();
-
- if (effectiveDate.compareTo(clock.getUTCNow()) > 0) {
- // Add notification entry to send the bus event at the effective date
- final NotificationEvent notificationEvent = new BlockingTransitionNotificationKey(blockingStateId, blockableId, type,
- isTransitionToBlockedBilling, isTransitionToUnblockedBilling,
- isTransitionToBlockedEntitlement, isTransitionToUnblockedEntitlement);
- recordFutureNotification(effectiveDate, notificationEvent, context);
- } else {
- // TODO Do we want to send a DefaultEffectiveEntitlementEvent for entitlement specific blocking states?
-
- // Don't post if nothing has changed for entitlement-service
- if (! serviceName.equals(EntitlementService.ENTITLEMENT_SERVICE_NAME) || ! previousState.equals(currentState)) {
- final BusEvent event = new DefaultBlockingTransitionInternalEvent(blockableId, type,
- isTransitionToBlockedBilling, isTransitionToUnblockedBilling,
- isTransitionToBlockedEntitlement, isTransitionToUnblockedEntitlement,
- context.getAccountRecordId(), context.getTenantRecordId(), context.getUserToken());
- postBusEvent(event);
- } else {
- System.out.println("********** SKIPPING EVENT ");
- }
-
- }
- }
-
- private void postBusEvent(final BusEvent event) {
- try {
- // TODO STEPH Ideally we would like to post from transaction when we inserted the new blocking state, but new state would have to be recalculated from transaction which is
- // difficult without the help of BlockingChecker -- which itself relies on dao. Other alternative is duplicating the logic, or refactoring the DAO to export higher level api.
- eventBus.post(event);
- } catch (EventBusException e) {
- log.warn("Failed to post event {}", e);
- }
- }
-
- private void recordFutureNotification(final DateTime effectiveDate,
- final NotificationEvent notificationEvent,
- final InternalCallContext context) {
- try {
- final NotificationQueue subscriptionEventQueue = notificationQueueService.getNotificationQueue(DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME,
- DefaultEntitlementService.NOTIFICATION_QUEUE_NAME);
- subscriptionEventQueue.recordFutureNotification(effectiveDate, notificationEvent, context.getUserToken(), context.getAccountRecordId(), context.getTenantRecordId());
- } catch (NoSuchNotificationQueue e) {
- throw new RuntimeException(e);
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
}
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/EventsStreamBuilder.java b/entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/EventsStreamBuilder.java
index afd51c8..b090a03 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/EventsStreamBuilder.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/EventsStreamBuilder.java
@@ -1,7 +1,9 @@
/*
* Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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:
*
@@ -53,7 +55,9 @@ import org.killbill.billing.util.cache.CacheControllerDispatcher;
import org.killbill.billing.util.callcontext.InternalCallContextFactory;
import org.killbill.billing.util.callcontext.TenantContext;
import org.killbill.billing.util.dao.NonEntityDao;
+import org.killbill.bus.api.PersistentBus;
import org.killbill.clock.Clock;
+import org.killbill.notificationq.api.NotificationQueueService;
import org.skife.jdbi.v2.IDBI;
import com.google.common.base.Objects;
@@ -75,6 +79,7 @@ public class EventsStreamBuilder {
@Inject
public EventsStreamBuilder(final AccountInternalApi accountInternalApi, final SubscriptionBaseInternalApi subscriptionInternalApi,
final BlockingChecker checker, final IDBI dbi, final Clock clock,
+ final NotificationQueueService notificationQueueService, final PersistentBus eventBus,
final CacheControllerDispatcher cacheControllerDispatcher,
final NonEntityDao nonEntityDao,
final InternalCallContextFactory internalCallContextFactory) {
@@ -84,8 +89,8 @@ public class EventsStreamBuilder {
this.clock = clock;
this.internalCallContextFactory = internalCallContextFactory;
- this.defaultBlockingStateDao = new DefaultBlockingStateDao(dbi, clock, cacheControllerDispatcher, nonEntityDao);
- this.blockingStateDao = new OptimizedProxyBlockingStateDao(this, subscriptionInternalApi, dbi, clock, cacheControllerDispatcher, nonEntityDao);
+ this.defaultBlockingStateDao = new DefaultBlockingStateDao(dbi, clock, notificationQueueService, eventBus, cacheControllerDispatcher, nonEntityDao);
+ this.blockingStateDao = new OptimizedProxyBlockingStateDao(this, subscriptionInternalApi, dbi, clock, notificationQueueService, eventBus, cacheControllerDispatcher, nonEntityDao);
}
public EventsStream refresh(final EventsStream eventsStream, final TenantContext tenantContext) throws EntitlementApiException {
diff --git a/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultEntitlement.java b/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultEntitlement.java
index f7e4372..e2a2bf1 100644
--- a/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultEntitlement.java
+++ b/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultEntitlement.java
@@ -1,7 +1,9 @@
/*
* Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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:
*
@@ -18,13 +20,11 @@ package org.killbill.billing.entitlement.api;
import org.joda.time.DateTime;
import org.joda.time.LocalDate;
-import org.killbill.billing.payment.api.PluginProperty;
-import org.killbill.billing.subscription.api.user.SubscriptionBaseApiException;
-import org.testng.annotations.Test;
-
+import org.killbill.billing.ErrorCode;
import org.killbill.billing.account.api.Account;
import org.killbill.billing.account.api.AccountApiException;
import org.killbill.billing.api.TestApiListener.NextEvent;
+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;
@@ -32,10 +32,14 @@ import org.killbill.billing.catalog.api.ProductCategory;
import org.killbill.billing.entitlement.EntitlementTestSuiteWithEmbeddedDB;
import org.killbill.billing.entitlement.api.Entitlement.EntitlementActionPolicy;
import org.killbill.billing.entitlement.api.Entitlement.EntitlementState;
+import org.killbill.billing.payment.api.PluginProperty;
+import org.killbill.billing.subscription.api.user.SubscriptionBaseApiException;
+import org.testng.annotations.Test;
import com.google.common.collect.ImmutableList;
import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.fail;
public class TestDefaultEntitlement extends EntitlementTestSuiteWithEmbeddedDB {
@@ -274,4 +278,53 @@ public class TestDefaultEntitlement extends EntitlementTestSuiteWithEmbeddedDB {
final Subscription subscription = subscriptionApi.getSubscriptionForEntitlementId(entitlement.getBaseEntitlementId(), callContext);
assertEquals(subscription.getBillingEndDate(), new LocalDate(2013, 8, 7));
}
+
+ @Test(groups = "slow", description = "https://github.com/killbill/killbill/issues/452")
+ public void testBlockedEntitlementChange() throws AccountApiException, EntitlementApiException {
+ final LocalDate initialDate = new LocalDate(2013, 8, 7);
+ clock.setDay(initialDate);
+
+ final Account account = accountApi.createAccount(getAccountData(7), callContext);
+
+ final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Shotgun", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
+
+ // Create entitlement and check each field
+ testListener.pushExpectedEvent(NextEvent.CREATE);
+ final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, initialDate, ImmutableList.<PluginProperty>of(), callContext);
+ assertListenerStatus();
+
+ clock.addDays(1);
+ assertListenerStatus();
+
+ testListener.pushExpectedEvent(NextEvent.BLOCK);
+ entitlementApi.setBlockingState(entitlement.getBundleId(), "MY_BLOCK", "test", clock.getUTCToday(), false, false, true, ImmutableList.<PluginProperty>of(), callContext);
+ assertListenerStatus();
+
+ try {
+ entitlement.changePlan("Assault-Rifle", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null, ImmutableList.<PluginProperty>of(), callContext);
+ fail();
+ } catch (final EntitlementApiException e) {
+ assertEquals(e.getCode(), ErrorCode.BLOCK_BLOCKED_ACTION.getCode());
+ final Entitlement latestEntitlement = entitlementApi.getEntitlementForId(entitlement.getId(), callContext);
+ assertEquals(latestEntitlement.getLastActivePlan().getProduct().getName(), "Shotgun");
+ }
+
+ try {
+ entitlement.changePlanWithDate("Assault-Rifle", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null, clock.getUTCToday(), ImmutableList.<PluginProperty>of(), callContext);
+ fail();
+ } catch (final EntitlementApiException e) {
+ assertEquals(e.getCode(), ErrorCode.BLOCK_BLOCKED_ACTION.getCode());
+ final Entitlement latestEntitlement = entitlementApi.getEntitlementForId(entitlement.getId(), callContext);
+ assertEquals(latestEntitlement.getLastActivePlan().getProduct().getName(), "Shotgun");
+ }
+
+ try {
+ entitlement.changePlanOverrideBillingPolicy("Assault-Rifle", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null, clock.getUTCToday(), BillingActionPolicy.IMMEDIATE, ImmutableList.<PluginProperty>of(), callContext);
+ fail();
+ } catch (final EntitlementApiException e) {
+ assertEquals(e.getCode(), ErrorCode.BLOCK_BLOCKED_ACTION.getCode());
+ final Entitlement latestEntitlement = entitlementApi.getEntitlementForId(entitlement.getId(), callContext);
+ assertEquals(latestEntitlement.getLastActivePlan().getProduct().getName(), "Shotgun");
+ }
+ }
}
diff --git a/entitlement/src/test/java/org/killbill/billing/entitlement/block/TestBlockingChecker.java b/entitlement/src/test/java/org/killbill/billing/entitlement/block/TestBlockingChecker.java
index 03fabcb..bb480fa 100644
--- a/entitlement/src/test/java/org/killbill/billing/entitlement/block/TestBlockingChecker.java
+++ b/entitlement/src/test/java/org/killbill/billing/entitlement/block/TestBlockingChecker.java
@@ -1,7 +1,9 @@
/*
* Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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:
*
@@ -18,11 +20,6 @@ package org.killbill.billing.entitlement.block;
import java.util.UUID;
-import org.mockito.Mockito;
-import org.testng.Assert;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-
import org.killbill.billing.account.api.Account;
import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.entitlement.EntitlementTestSuiteNoDB;
@@ -34,6 +31,13 @@ import org.killbill.billing.junction.DefaultBlockingState;
import org.killbill.billing.subscription.api.SubscriptionBase;
import org.killbill.billing.subscription.api.user.SubscriptionBaseApiException;
import org.killbill.billing.subscription.api.user.SubscriptionBaseBundle;
+import org.mockito.Mockito;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableMap;
public class TestBlockingChecker extends EntitlementTestSuiteNoDB {
@@ -68,20 +72,19 @@ public class TestBlockingChecker extends EntitlementTestSuiteNoDB {
((MockBlockingStateDao) blockingStateDao).clear();
}
-
private void setStateBundle(final boolean bC, final boolean bE, final boolean bB) {
- final BlockingState bundleState = new DefaultBlockingState(bundle.getId(), BlockingStateType.SUBSCRIPTION_BUNDLE,"state", "test-service", bC, bE, bB, clock.getUTCNow());
- blockingStateDao.setBlockingState(bundleState, clock, internalCallContext);
+ final BlockingState bundleState = new DefaultBlockingState(bundle.getId(), BlockingStateType.SUBSCRIPTION_BUNDLE, "state", "test-service", bC, bE, bB, clock.getUTCNow());
+ blockingStateDao.setBlockingStatesAndPostBlockingTransitionEvent(ImmutableMap.<BlockingState, Optional<UUID>>of(bundleState, Optional.<UUID>absent()), internalCallContext);
}
private void setStateAccount(final boolean bC, final boolean bE, final boolean bB) {
final BlockingState accountState = new DefaultBlockingState(account.getId(), BlockingStateType.ACCOUNT, "state", "test-service", bC, bE, bB, clock.getUTCNow());
- blockingStateDao.setBlockingState(accountState, clock, internalCallContext);
+ blockingStateDao.setBlockingStatesAndPostBlockingTransitionEvent(ImmutableMap.<BlockingState, Optional<UUID>>of(accountState, Optional.<UUID>absent()), internalCallContext);
}
private void setStateSubscription(final boolean bC, final boolean bE, final boolean bB) {
final BlockingState subscriptionState = new DefaultBlockingState(subscription.getId(), BlockingStateType.SUBSCRIPTION, "state", "test-service", bC, bE, bB, clock.getUTCNow());
- blockingStateDao.setBlockingState(subscriptionState, clock, internalCallContext);
+ blockingStateDao.setBlockingStatesAndPostBlockingTransitionEvent(ImmutableMap.<BlockingState, Optional<UUID>>of(subscriptionState, Optional.<UUID>of(subscription.getBundleId())), internalCallContext);
}
@Test(groups = "fast")
diff --git a/entitlement/src/test/java/org/killbill/billing/entitlement/dao/MockBlockingStateDao.java b/entitlement/src/test/java/org/killbill/billing/entitlement/dao/MockBlockingStateDao.java
index e36f469..bc51a98 100644
--- a/entitlement/src/test/java/org/killbill/billing/entitlement/dao/MockBlockingStateDao.java
+++ b/entitlement/src/test/java/org/killbill/billing/entitlement/dao/MockBlockingStateDao.java
@@ -1,7 +1,9 @@
/*
* Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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:
*
@@ -27,13 +29,13 @@ import javax.annotation.Nullable;
import org.joda.time.DateTime;
import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.callcontext.InternalTenantContext;
-import org.killbill.clock.Clock;
import org.killbill.billing.entitlement.api.BlockingState;
import org.killbill.billing.entitlement.api.BlockingStateType;
import org.killbill.billing.entitlement.api.EntitlementApiException;
import org.killbill.billing.util.entity.dao.MockEntityDaoBase;
import com.google.common.base.Objects;
+import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableList;
@@ -83,16 +85,18 @@ public class MockBlockingStateDao extends MockEntityDaoBase<BlockingStateModelDa
}
@Override
- public synchronized void setBlockingState(final BlockingState state, final Clock clock, final InternalCallContext context) {
- if (blockingStates.get(state.getBlockedId()) == null) {
- blockingStates.put(state.getBlockedId(), new ArrayList<BlockingState>());
- }
- blockingStates.get(state.getBlockedId()).add(state);
+ public synchronized void setBlockingStatesAndPostBlockingTransitionEvent(final Map<BlockingState, Optional<UUID>> states, final InternalCallContext context) {
+ for (final BlockingState state : states.keySet()) {
+ if (blockingStates.get(state.getBlockedId()) == null) {
+ blockingStates.put(state.getBlockedId(), new ArrayList<BlockingState>());
+ }
+ blockingStates.get(state.getBlockedId()).add(state);
- if (blockingStatesPerAccountRecordId.get(context.getAccountRecordId()) == null) {
- blockingStatesPerAccountRecordId.put(context.getAccountRecordId(), new ArrayList<BlockingState>());
+ if (blockingStatesPerAccountRecordId.get(context.getAccountRecordId()) == null) {
+ blockingStatesPerAccountRecordId.put(context.getAccountRecordId(), new ArrayList<BlockingState>());
+ }
+ blockingStatesPerAccountRecordId.get(context.getAccountRecordId()).add(state);
}
- blockingStatesPerAccountRecordId.get(context.getAccountRecordId()).add(state);
}
@Override
diff --git a/entitlement/src/test/java/org/killbill/billing/entitlement/dao/TestBlockingDao.java b/entitlement/src/test/java/org/killbill/billing/entitlement/dao/TestBlockingDao.java
index c78fd59..f078f2f 100644
--- a/entitlement/src/test/java/org/killbill/billing/entitlement/dao/TestBlockingDao.java
+++ b/entitlement/src/test/java/org/killbill/billing/entitlement/dao/TestBlockingDao.java
@@ -1,7 +1,9 @@
/*
* Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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:
*
@@ -20,6 +22,7 @@ import java.util.List;
import java.util.UUID;
import org.joda.time.LocalDate;
+import org.killbill.billing.api.TestApiListener.NextEvent;
import org.testng.Assert;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
@@ -30,6 +33,9 @@ import org.killbill.billing.entitlement.api.BlockingState;
import org.killbill.billing.entitlement.api.BlockingStateType;
import org.killbill.billing.junction.DefaultBlockingState;
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableMap;
+
public class TestBlockingDao extends EntitlementTestSuiteWithEmbeddedDB {
@BeforeMethod(groups = "slow")
@@ -52,14 +58,18 @@ public class TestBlockingDao extends EntitlementTestSuiteWithEmbeddedDB {
clock.setDay(new LocalDate(2012, 4, 1));
+ testListener.pushExpectedEvent(NextEvent.BLOCK);
final BlockingState state1 = new DefaultBlockingState(uuid, BlockingStateType.ACCOUNT, overdueStateName, service, blockChange, blockEntitlement, blockBilling, clock.getUTCNow());
- blockingStateDao.setBlockingState(state1, clock, internalCallContext);
+ blockingStateDao.setBlockingStatesAndPostBlockingTransitionEvent(ImmutableMap.<BlockingState, Optional<UUID>>of(state1, Optional.<UUID>absent()), internalCallContext);
+ assertListenerStatus();
clock.addDays(1);
+ testListener.pushExpectedEvent(NextEvent.BLOCK);
final String overdueStateName2 = "NoReallyThisCantGoOn";
final BlockingState state2 = new DefaultBlockingState(uuid, BlockingStateType.ACCOUNT, overdueStateName2, service, blockChange, blockEntitlement, blockBilling, clock.getUTCNow());
- blockingStateDao.setBlockingState(state2, clock, internalCallContext);
+ blockingStateDao.setBlockingStatesAndPostBlockingTransitionEvent(ImmutableMap.<BlockingState, Optional<UUID>>of(state2, Optional.<UUID>absent()), internalCallContext);
+ assertListenerStatus();
Assert.assertEquals(blockingStateDao.getBlockingStateForService(uuid, BlockingStateType.ACCOUNT, service, internalCallContext).getStateName(), state2.getStateName());
@@ -80,15 +90,20 @@ public class TestBlockingDao extends EntitlementTestSuiteWithEmbeddedDB {
final boolean blockEntitlement = false;
final boolean blockBilling = false;
+ testListener.pushExpectedEvent(NextEvent.BLOCK);
final BlockingState state1 = new DefaultBlockingState(uuid, BlockingStateType.ACCOUNT, overdueStateName, service1, blockChange, blockEntitlement, blockBilling, clock.getUTCNow());
- blockingStateDao.setBlockingState(state1, clock, internalCallContext);
+ blockingStateDao.setBlockingStatesAndPostBlockingTransitionEvent(ImmutableMap.<BlockingState, Optional<UUID>>of(state1, Optional.<UUID>absent()), internalCallContext);
+ assertListenerStatus();
+
clock.setDeltaFromReality(1000 * 3600 * 24);
final String service2 = "TEST2";
+ testListener.pushExpectedEvent(NextEvent.BLOCK);
final String overdueStateName2 = "NoReallyThisCantGoOn";
final BlockingState state2 = new DefaultBlockingState(uuid, BlockingStateType.ACCOUNT, overdueStateName2, service2, blockChange, blockEntitlement, blockBilling, clock.getUTCNow());
- blockingStateDao.setBlockingState(state2, clock, internalCallContext);
+ blockingStateDao.setBlockingStatesAndPostBlockingTransitionEvent(ImmutableMap.<BlockingState, Optional<UUID>>of(state2, Optional.<UUID>absent()), internalCallContext);
+ assertListenerStatus();
final List<BlockingState> history2 = blockingStateDao.getBlockingAllForAccountRecordId(internalCallContext);
Assert.assertEquals(history2.size(), 2);
diff --git a/entitlement/src/test/java/org/killbill/billing/entitlement/dao/TestDefaultBlockingStateDao.java b/entitlement/src/test/java/org/killbill/billing/entitlement/dao/TestDefaultBlockingStateDao.java
index 32f84a7..62a2919 100644
--- a/entitlement/src/test/java/org/killbill/billing/entitlement/dao/TestDefaultBlockingStateDao.java
+++ b/entitlement/src/test/java/org/killbill/billing/entitlement/dao/TestDefaultBlockingStateDao.java
@@ -1,7 +1,9 @@
/*
* Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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:
*
@@ -38,7 +40,9 @@ import org.killbill.billing.entitlement.api.BlockingStateType;
import org.killbill.billing.entitlement.api.Entitlement;
import org.killbill.billing.junction.DefaultBlockingState;
+import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
public class TestDefaultBlockingStateDao extends EntitlementTestSuiteWithEmbeddedDB {
@@ -72,7 +76,7 @@ public class TestDefaultBlockingStateDao extends EntitlementTestSuiteWithEmbedde
// Set a state
final DateTime stateDateTime = new DateTime(2013, 5, 6, 10, 11, 12, DateTimeZone.UTC);
final BlockingState blockingState = new DefaultBlockingState(entitlement.getId(), type, state, service, false, false, false, stateDateTime);
- blockingStateDao.setBlockingState(blockingState, clock, internalCallContext);
+ blockingStateDao.setBlockingStatesAndPostBlockingTransitionEvent(ImmutableMap.<BlockingState, Optional<UUID>>of(blockingState, Optional.<UUID>of(entitlement.getBundleId())), internalCallContext);
Assert.assertEquals(blockingStateDao.getBlockingAllForAccountRecordId(internalCallContext).size(), 1);
}
@@ -95,7 +99,7 @@ public class TestDefaultBlockingStateDao extends EntitlementTestSuiteWithEmbedde
// Set a state for service A
final DateTime stateDateTime = new DateTime(2013, 5, 6, 10, 11, 12, DateTimeZone.UTC);
final BlockingState blockingState1 = new DefaultBlockingState(blockableId, type, state, serviceA, false, false, false, stateDateTime);
- blockingStateDao.setBlockingState(blockingState1, clock, internalCallContext);
+ blockingStateDao.setBlockingStatesAndPostBlockingTransitionEvent(ImmutableMap.<BlockingState, Optional<UUID>>of(blockingState1, Optional.<UUID>absent()), internalCallContext);
final List<BlockingState> blockingStates1 = blockingStateDao.getBlockingAllForAccountRecordId(internalCallContext);
Assert.assertEquals(blockingStates1.size(), 1);
Assert.assertEquals(blockingStates1.get(0).getBlockedId(), blockableId);
@@ -104,7 +108,7 @@ public class TestDefaultBlockingStateDao extends EntitlementTestSuiteWithEmbedde
Assert.assertEquals(blockingStates1.get(0).getEffectiveDate(), stateDateTime);
// Set the same state again - no change
- blockingStateDao.setBlockingState(blockingState1, clock, internalCallContext);
+ blockingStateDao.setBlockingStatesAndPostBlockingTransitionEvent(ImmutableMap.<BlockingState, Optional<UUID>>of(blockingState1, Optional.<UUID>absent()), internalCallContext);
final List<BlockingState> blockingStates2 = blockingStateDao.getBlockingAllForAccountRecordId(internalCallContext);
Assert.assertEquals(blockingStates2.size(), 1);
Assert.assertEquals(blockingStates2.get(0).getBlockedId(), blockableId);
@@ -114,7 +118,7 @@ public class TestDefaultBlockingStateDao extends EntitlementTestSuiteWithEmbedde
// Set the state for service B
final BlockingState blockingState2 = new DefaultBlockingState(blockableId, type, state, serviceB, false, false, false, stateDateTime);
- blockingStateDao.setBlockingState(blockingState2, clock, internalCallContext);
+ blockingStateDao.setBlockingStatesAndPostBlockingTransitionEvent(ImmutableMap.<BlockingState, Optional<UUID>>of(blockingState2, Optional.<UUID>absent()), internalCallContext);
final List<BlockingState> blockingStates3 = blockingStateDao.getBlockingAllForAccountRecordId(internalCallContext);
Assert.assertEquals(blockingStates3.size(), 2);
Assert.assertEquals(blockingStates3.get(0).getBlockedId(), blockableId);
@@ -129,7 +133,7 @@ public class TestDefaultBlockingStateDao extends EntitlementTestSuiteWithEmbedde
// Set the state for service A in the future - there should be no change (already effective)
final DateTime stateDateTime2 = new DateTime(2013, 6, 6, 10, 11, 12, DateTimeZone.UTC);
final BlockingState blockingState3 = new DefaultBlockingState(blockableId, type, state, serviceA, false, false, false, stateDateTime2);
- blockingStateDao.setBlockingState(blockingState3, clock, internalCallContext);
+ blockingStateDao.setBlockingStatesAndPostBlockingTransitionEvent(ImmutableMap.<BlockingState, Optional<UUID>>of(blockingState3, Optional.<UUID>absent()), internalCallContext);
final List<BlockingState> blockingStates4 = blockingStateDao.getBlockingAllForAccountRecordId(internalCallContext);
Assert.assertEquals(blockingStates4.size(), 2);
Assert.assertEquals(blockingStates4.get(0).getBlockedId(), blockableId);
@@ -144,7 +148,7 @@ public class TestDefaultBlockingStateDao extends EntitlementTestSuiteWithEmbedde
// Set the state for service A in the past - the new effective date should be respected
final DateTime stateDateTime3 = new DateTime(2013, 2, 6, 10, 11, 12, DateTimeZone.UTC);
final BlockingState blockingState4 = new DefaultBlockingState(blockableId, type, state, serviceA, false, false, false, stateDateTime3);
- blockingStateDao.setBlockingState(blockingState4, clock, internalCallContext);
+ blockingStateDao.setBlockingStatesAndPostBlockingTransitionEvent(ImmutableMap.<BlockingState, Optional<UUID>>of(blockingState4, Optional.<UUID>absent()), internalCallContext);
final List<BlockingState> blockingStates5 = blockingStateDao.getBlockingAllForAccountRecordId(internalCallContext);
Assert.assertEquals(blockingStates5.size(), 2);
Assert.assertEquals(blockingStates5.get(0).getBlockedId(), blockableId);
@@ -159,7 +163,7 @@ public class TestDefaultBlockingStateDao extends EntitlementTestSuiteWithEmbedde
// Set a new state for service A
final DateTime state2DateTime = new DateTime(2013, 12, 6, 10, 11, 12, DateTimeZone.UTC);
final BlockingState blockingState5 = new DefaultBlockingState(blockableId, type, state2, serviceA, false, false, false, state2DateTime);
- blockingStateDao.setBlockingState(blockingState5, clock, internalCallContext);
+ blockingStateDao.setBlockingStatesAndPostBlockingTransitionEvent(ImmutableMap.<BlockingState, Optional<UUID>>of(blockingState5, Optional.<UUID>absent()), internalCallContext);
final List<BlockingState> blockingStates6 = blockingStateDao.getBlockingAllForAccountRecordId(internalCallContext);
Assert.assertEquals(blockingStates6.size(), 3);
Assert.assertEquals(blockingStates6.get(0).getBlockedId(), blockableId);
diff --git a/entitlement/src/test/java/org/killbill/billing/entitlement/dao/TestProxyBlockingStateDao.java b/entitlement/src/test/java/org/killbill/billing/entitlement/dao/TestProxyBlockingStateDao.java
new file mode 100644
index 0000000..549f02b
--- /dev/null
+++ b/entitlement/src/test/java/org/killbill/billing/entitlement/dao/TestProxyBlockingStateDao.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2016 Groupon, Inc
+ * Copyright 2016 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.dao;
+
+import java.util.List;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.killbill.billing.entitlement.EntitlementTestSuiteNoDB;
+import org.killbill.billing.entitlement.api.BlockingState;
+import org.killbill.billing.entitlement.api.BlockingStateType;
+import org.killbill.billing.junction.DefaultBlockingState;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+
+public class TestProxyBlockingStateDao extends EntitlementTestSuiteNoDB {
+
+ @Test(groups = "fast", description = "https://github.com/killbill/killbill/issues/174")
+ public void testComparisonSameEffectiveDate() throws Exception {
+ final UUID blockedId = UUID.randomUUID();
+ final BlockingStateType blockingStateType = BlockingStateType.ACCOUNT;
+ final String service = "test";
+ final DateTime effectiveDate = clock.getUTCNow();
+
+ final BlockingState bs1 = new DefaultBlockingState(UUID.randomUUID(),
+ blockedId,
+ blockingStateType,
+ "OD1",
+ service,
+ false,
+ false,
+ false,
+ effectiveDate.minusDays(10),
+ effectiveDate,
+ effectiveDate,
+ 1L);
+ final BlockingState bs2 = new DefaultBlockingState(UUID.randomUUID(),
+ blockedId,
+ blockingStateType,
+ "OD2",
+ service,
+ true,
+ true,
+ true,
+ effectiveDate.minusDays(5),
+ effectiveDate,
+ effectiveDate,
+ 2L);
+ final BlockingState bs3 = new DefaultBlockingState(UUID.randomUUID(),
+ blockedId,
+ blockingStateType,
+ "OD3",
+ service,
+ true,
+ true,
+ true,
+ effectiveDate,
+ effectiveDate,
+ effectiveDate,
+ 3L);
+ final BlockingState bs4 = new DefaultBlockingState(UUID.randomUUID(),
+ blockedId,
+ blockingStateType,
+ DefaultBlockingState.CLEAR_STATE_NAME,
+ service,
+ false,
+ false,
+ false,
+ effectiveDate,
+ effectiveDate,
+ effectiveDate,
+ 4L);
+
+ verifySortedCopy(bs1, bs2, bs3, bs4, bs1, bs2, bs3, bs4);
+ verifySortedCopy(bs1, bs2, bs3, bs4, bs1, bs3, bs2, bs4);
+ verifySortedCopy(bs1, bs2, bs3, bs4, bs2, bs3, bs1, bs4);
+ verifySortedCopy(bs1, bs2, bs3, bs4, bs2, bs1, bs3, bs4);
+ verifySortedCopy(bs1, bs2, bs3, bs4, bs3, bs1, bs2, bs4);
+ verifySortedCopy(bs1, bs2, bs3, bs4, bs3, bs2, bs1, bs4);
+ }
+
+ private void verifySortedCopy(final BlockingState bs1, final BlockingState bs2, final BlockingState bs3, final BlockingState bs4,
+ final BlockingState a, final BlockingState b, final BlockingState c, final BlockingState d) {
+ final List<BlockingState> sortedCopy = ProxyBlockingStateDao.sortedCopy(ImmutableList.<BlockingState>of(a, b, c, d));
+ Assert.assertEquals(sortedCopy.get(0).getStateName(), bs1.getStateName());
+ Assert.assertEquals(sortedCopy.get(1).getStateName(), bs2.getStateName());
+ Assert.assertEquals(sortedCopy.get(2).getStateName(), bs3.getStateName());
+ Assert.assertEquals(sortedCopy.get(3).getStateName(), bs4.getStateName());
+ }
+}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/api/DefaultInvoicePaymentErrorEvent.java b/invoice/src/main/java/org/killbill/billing/invoice/api/DefaultInvoicePaymentErrorEvent.java
new file mode 100644
index 0000000..e0b3f08
--- /dev/null
+++ b/invoice/src/main/java/org/killbill/billing/invoice/api/DefaultInvoicePaymentErrorEvent.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2016 Groupon, Inc
+ * Copyright 2016 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.math.BigDecimal;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.killbill.billing.catalog.api.Currency;
+import org.killbill.billing.events.InvoicePaymentErrorInternalEvent;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class DefaultInvoicePaymentErrorEvent extends DefaultInvoicePaymentInternalEvent implements InvoicePaymentErrorInternalEvent {
+
+ public DefaultInvoicePaymentErrorEvent(@JsonProperty("accountId") final UUID accountId,
+ @JsonProperty("paymentId") final UUID paymentId,
+ @JsonProperty("type") final InvoicePaymentType type,
+ @JsonProperty("invoiceId") final UUID invoiceId,
+ @JsonProperty("paymentDate") final DateTime paymentDate,
+ @JsonProperty("amount") final BigDecimal amount,
+ @JsonProperty("currency") final Currency currency,
+ @JsonProperty("linkedInvoicePaymentId") final UUID linkedInvoicePaymentId,
+ @JsonProperty("paymentCookieId") final String paymentCookieId,
+ @JsonProperty("processedCurrency") final Currency processedCurrency,
+ @JsonProperty("searchKey1") final Long searchKey1,
+ @JsonProperty("searchKey2") final Long searchKey2,
+ @JsonProperty("userToken") final UUID userToken) {
+ super(accountId, paymentId, type, invoiceId, paymentDate, amount, currency, linkedInvoicePaymentId, paymentCookieId, processedCurrency, searchKey1, searchKey2, userToken);
+ }
+
+ @JsonIgnore
+ @Override
+ public BusInternalEventType getBusEventType() {
+ return BusInternalEventType.INVOICE_PAYMENT_ERROR;
+ }
+
+ @Override
+ protected Class getInvoicePaymentInternalEventClass() {
+ return DefaultInvoicePaymentErrorEvent.class;
+ }
+}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/api/DefaultInvoicePaymentInfoEvent.java b/invoice/src/main/java/org/killbill/billing/invoice/api/DefaultInvoicePaymentInfoEvent.java
new file mode 100644
index 0000000..1014c3c
--- /dev/null
+++ b/invoice/src/main/java/org/killbill/billing/invoice/api/DefaultInvoicePaymentInfoEvent.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2016 Groupon, Inc
+ * Copyright 2016 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.math.BigDecimal;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.killbill.billing.catalog.api.Currency;
+import org.killbill.billing.events.InvoicePaymentInfoInternalEvent;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class DefaultInvoicePaymentInfoEvent extends DefaultInvoicePaymentInternalEvent implements InvoicePaymentInfoInternalEvent {
+
+ public DefaultInvoicePaymentInfoEvent(@JsonProperty("accountId") final UUID accountId,
+ @JsonProperty("paymentId") final UUID paymentId,
+ @JsonProperty("type") final InvoicePaymentType type,
+ @JsonProperty("invoiceId") final UUID invoiceId,
+ @JsonProperty("paymentDate") final DateTime paymentDate,
+ @JsonProperty("amount") final BigDecimal amount,
+ @JsonProperty("currency") final Currency currency,
+ @JsonProperty("linkedInvoicePaymentId") final UUID linkedInvoicePaymentId,
+ @JsonProperty("paymentCookieId") final String paymentCookieId,
+ @JsonProperty("processedCurrency") final Currency processedCurrency,
+ @JsonProperty("searchKey1") final Long searchKey1,
+ @JsonProperty("searchKey2") final Long searchKey2,
+ @JsonProperty("userToken") final UUID userToken) {
+ super(accountId, paymentId, type, invoiceId, paymentDate, amount, currency, linkedInvoicePaymentId, paymentCookieId, processedCurrency, searchKey1, searchKey2, userToken);
+ }
+
+ @JsonIgnore
+ @Override
+ public BusInternalEventType getBusEventType() {
+ return BusInternalEventType.INVOICE_PAYMENT_INFO;
+ }
+
+ @Override
+ protected Class getInvoicePaymentInternalEventClass() {
+ return DefaultInvoicePaymentInfoEvent.class;
+ }
+}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/api/DefaultInvoicePaymentInternalEvent.java b/invoice/src/main/java/org/killbill/billing/invoice/api/DefaultInvoicePaymentInternalEvent.java
new file mode 100644
index 0000000..1c5c7cf
--- /dev/null
+++ b/invoice/src/main/java/org/killbill/billing/invoice/api/DefaultInvoicePaymentInternalEvent.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright 2016 Groupon, Inc
+ * Copyright 2016 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.math.BigDecimal;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.killbill.billing.catalog.api.Currency;
+import org.killbill.billing.events.BusEventBase;
+import org.killbill.billing.events.InvoicePaymentInternalEvent;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public abstract class DefaultInvoicePaymentInternalEvent extends BusEventBase implements InvoicePaymentInternalEvent {
+
+ private final UUID accountId;
+ private final UUID paymentId;
+ private final InvoicePaymentType type;
+ private final UUID invoiceId;
+ private final DateTime paymentDate;
+ private final BigDecimal amount;
+ private final Currency currency;
+ private final UUID linkedInvoicePaymentId;
+ private final String paymentCookieId;
+ private final Currency processedCurrency;
+
+ @JsonCreator
+ public DefaultInvoicePaymentInternalEvent(@JsonProperty("accountId") final UUID accountId,
+ @JsonProperty("paymentId") final UUID paymentId,
+ @JsonProperty("type") final InvoicePaymentType type,
+ @JsonProperty("invoiceId") final UUID invoiceId,
+ @JsonProperty("paymentDate") final DateTime paymentDate,
+ @JsonProperty("amount") final BigDecimal amount,
+ @JsonProperty("currency") final Currency currency,
+ @JsonProperty("linkedInvoicePaymentId") final UUID linkedInvoicePaymentId,
+ @JsonProperty("paymentCookieId") final String paymentCookieId,
+ @JsonProperty("processedCurrency") final Currency processedCurrency,
+ @JsonProperty("searchKey1") final Long searchKey1,
+ @JsonProperty("searchKey2") final Long searchKey2,
+ @JsonProperty("userToken") final UUID userToken) {
+ super(searchKey1, searchKey2, userToken);
+ this.accountId = accountId;
+ this.paymentId = paymentId;
+ this.type = type;
+ this.invoiceId = invoiceId;
+ this.paymentDate = paymentDate;
+ this.amount = amount;
+ this.currency = currency;
+ this.linkedInvoicePaymentId = linkedInvoicePaymentId;
+ this.paymentCookieId = paymentCookieId;
+ this.processedCurrency = processedCurrency;
+ }
+
+ @Override
+ public UUID getAccountId() {
+ return accountId;
+ }
+
+ @Override
+ public UUID getPaymentId() {
+ return paymentId;
+ }
+
+ public InvoicePaymentType getType() {
+ return type;
+ }
+
+ @Override
+ public UUID getInvoiceId() {
+ return invoiceId;
+ }
+
+ @Override
+ public DateTime getPaymentDate() {
+ return paymentDate;
+ }
+
+ @Override
+ public BigDecimal getAmount() {
+ return amount;
+ }
+
+ @Override
+ public Currency getCurrency() {
+ return currency;
+ }
+
+ @Override
+ public UUID getLinkedInvoicePaymentId() {
+ return linkedInvoicePaymentId;
+ }
+
+ @Override
+ public String getPaymentCookieId() {
+ return paymentCookieId;
+ }
+
+ @Override
+ public Currency getProcessedCurrency() {
+ return processedCurrency;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder();
+ sb.append(getInvoicePaymentInternalEventClass().toString());
+ sb.append(" {accountId=").append(accountId);
+ sb.append(", paymentId=").append(paymentId);
+ sb.append(", type=").append(type);
+ sb.append(", invoiceId=").append(invoiceId);
+ sb.append(", paymentDate=").append(paymentDate);
+ sb.append(", amount=").append(amount);
+ sb.append(", currency=").append(currency);
+ sb.append(", linkedInvoicePaymentId=").append(linkedInvoicePaymentId);
+ sb.append(", paymentCookieId='").append(paymentCookieId).append('\'');
+ sb.append(", processedCurrency=").append(processedCurrency);
+ 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 DefaultInvoicePaymentInternalEvent that = (DefaultInvoicePaymentInternalEvent) o;
+
+ if (accountId != null ? !accountId.equals(that.accountId) : that.accountId != null) {
+ return false;
+ }
+ if (paymentId != null ? !paymentId.equals(that.paymentId) : that.paymentId != null) {
+ return false;
+ }
+ if (type != that.type) {
+ return false;
+ }
+ if (invoiceId != null ? !invoiceId.equals(that.invoiceId) : that.invoiceId != null) {
+ return false;
+ }
+ if (paymentDate != null ? paymentDate.compareTo(that.paymentDate) != 0 : that.paymentDate != null) {
+ return false;
+ }
+ if (amount != null ? amount.compareTo(that.amount) != 0 : that.amount != null) {
+ return false;
+ }
+ if (currency != that.currency) {
+ return false;
+ }
+ if (linkedInvoicePaymentId != null ? !linkedInvoicePaymentId.equals(that.linkedInvoicePaymentId) : that.linkedInvoicePaymentId != null) {
+ return false;
+ }
+ if (paymentCookieId != null ? !paymentCookieId.equals(that.paymentCookieId) : that.paymentCookieId != null) {
+ return false;
+ }
+ return processedCurrency == that.processedCurrency;
+
+ }
+
+ @Override
+ public int hashCode() {
+ int result = super.hashCode();
+ result = 31 * result + (accountId != null ? accountId.hashCode() : 0);
+ result = 31 * result + (paymentId != null ? paymentId.hashCode() : 0);
+ result = 31 * result + (type != null ? type.hashCode() : 0);
+ result = 31 * result + (invoiceId != null ? invoiceId.hashCode() : 0);
+ result = 31 * result + (paymentDate != null ? paymentDate.hashCode() : 0);
+ result = 31 * result + (amount != null ? amount.hashCode() : 0);
+ result = 31 * result + (currency != null ? currency.hashCode() : 0);
+ result = 31 * result + (linkedInvoicePaymentId != null ? linkedInvoicePaymentId.hashCode() : 0);
+ result = 31 * result + (paymentCookieId != null ? paymentCookieId.hashCode() : 0);
+ result = 31 * result + (processedCurrency != null ? processedCurrency.hashCode() : 0);
+ return result;
+ }
+
+ protected abstract Class getInvoicePaymentInternalEventClass();
+}
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 9f86bc9..f22f497 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
@@ -31,12 +31,15 @@ import javax.annotation.Nullable;
import org.joda.time.DateTime;
import org.joda.time.LocalDate;
import org.killbill.billing.ErrorCode;
+import org.killbill.billing.ObjectType;
import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.catalog.api.Currency;
import org.killbill.billing.entity.EntityPersistenceException;
import org.killbill.billing.invoice.InvoiceDispatcher.FutureAccountNotifications;
import org.killbill.billing.invoice.InvoiceDispatcher.FutureAccountNotifications.SubscriptionNotification;
+import org.killbill.billing.invoice.api.DefaultInvoicePaymentErrorEvent;
+import org.killbill.billing.invoice.api.DefaultInvoicePaymentInfoEvent;
import org.killbill.billing.invoice.api.Invoice;
import org.killbill.billing.invoice.api.InvoiceApiException;
import org.killbill.billing.invoice.api.InvoiceItemType;
@@ -44,6 +47,7 @@ import org.killbill.billing.invoice.api.InvoicePaymentType;
import org.killbill.billing.invoice.api.user.DefaultInvoiceAdjustmentEvent;
import org.killbill.billing.invoice.notification.NextBillingDatePoster;
import org.killbill.billing.util.UUIDs;
+import org.killbill.billing.util.cache.Cachable.CacheType;
import org.killbill.billing.util.cache.CacheControllerDispatcher;
import org.killbill.billing.util.callcontext.InternalCallContextFactory;
import org.killbill.billing.util.config.InvoiceConfig;
@@ -54,6 +58,7 @@ import org.killbill.billing.util.entity.dao.EntityDaoBase;
import org.killbill.billing.util.entity.dao.EntitySqlDaoTransactionWrapper;
import org.killbill.billing.util.entity.dao.EntitySqlDaoTransactionalJdbiWrapper;
import org.killbill.billing.util.entity.dao.EntitySqlDaoWrapperFactory;
+import org.killbill.bus.api.BusEvent;
import org.killbill.bus.api.PersistentBus;
import org.killbill.bus.api.PersistentBus.EventBusException;
import org.killbill.clock.Clock;
@@ -95,6 +100,8 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
private final CBADao cbaDao;
private final InvoiceConfig invoiceConfig;
private final Clock clock;
+ private final CacheControllerDispatcher cacheControllerDispatcher;
+ private final NonEntityDao nonEntityDao;
@Inject
public DefaultInvoiceDao(final IDBI dbi,
@@ -115,6 +122,8 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
this.invoiceDaoHelper = invoiceDaoHelper;
this.cbaDao = cbaDao;
this.clock = clock;
+ this.cacheControllerDispatcher = cacheControllerDispatcher;
+ this.nonEntityDao = nonEntityDao;
}
@Override
@@ -504,8 +513,10 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
cbaDao.addCBAComplexityFromTransaction(invoice, entitySqlDaoWrapperFactory, context);
- // Notify the bus since the balance of the invoice changed
- notifyBusOfInvoiceAdjustment(entitySqlDaoWrapperFactory, invoice.getId(), invoice.getAccountId(), context.getUserToken(), context);
+ if (isInvoiceAdjusted) {
+ notifyBusOfInvoiceAdjustment(entitySqlDaoWrapperFactory, invoice.getId(), invoice.getAccountId(), context.getUserToken(), context);
+ }
+ notifyBusOfInvoicePayment(entitySqlDaoWrapperFactory, refund, invoice.getAccountId(), context.getUserToken(), context);
return refund;
}
@@ -558,7 +569,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
cbaDao.addCBAComplexityFromTransaction(payment.getInvoiceId(), entitySqlDaoWrapperFactory, context);
- notifyBusOfInvoiceAdjustment(entitySqlDaoWrapperFactory, payment.getInvoiceId(), accountId, context.getUserToken(), context);
+ notifyBusOfInvoicePayment(entitySqlDaoWrapperFactory, chargeBack, accountId, context.getUserToken(), context);
return chargeBack;
}
@@ -667,18 +678,26 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
@Override
public Void inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
final InvoicePaymentSqlDao transactional = entitySqlDaoWrapperFactory.become(InvoicePaymentSqlDao.class);
- final List<InvoicePaymentModelDao> invoicePayments = transactional.getInvoicePayments(invoicePayment.getPaymentId().toString(), context);
- final InvoicePaymentModelDao existingAttempt = Iterables.tryFind(invoicePayments, new Predicate<InvoicePaymentModelDao>() {
- @Override
- public boolean apply(final InvoicePaymentModelDao input) {
- return input.getType() == InvoicePaymentType.ATTEMPT;
+
+ // If the payment id is null, the payment wasn't attempted (e.g. no payment method). We don't record an attempt but send an event nonetheless (e.g. for Overdue)
+ if (invoicePayment.getPaymentId() != null) {
+ final List<InvoicePaymentModelDao> invoicePayments = transactional.getInvoicePayments(invoicePayment.getPaymentId().toString(), context);
+ final InvoicePaymentModelDao existingAttempt = Iterables.tryFind(invoicePayments, new Predicate<InvoicePaymentModelDao>() {
+ @Override
+ public boolean apply(final InvoicePaymentModelDao input) {
+ return input.getType() == InvoicePaymentType.ATTEMPT;
+ }
+ }).orNull();
+ if (existingAttempt == null) {
+ transactional.create(invoicePayment, context);
+ } else if (!existingAttempt.getSuccess() && invoicePayment.getSuccess()) {
+ transactional.updateAttempt(existingAttempt.getRecordId(), invoicePayment.getPaymentDate().toDate(), invoicePayment.getAmount(), invoicePayment.getCurrency(), invoicePayment.getProcessedCurrency(), context);
}
- }).orNull();
- if (existingAttempt == null) {
- transactional.create(invoicePayment, context);
- } else if (!existingAttempt.getSuccess() && invoicePayment.getSuccess()) {
- transactional.updateAttempt(existingAttempt.getRecordId(), invoicePayment.getPaymentDate().toDate(), invoicePayment.getAmount(), invoicePayment.getCurrency(), invoicePayment.getProcessedCurrency(), context);
}
+
+ final UUID accountId = nonEntityDao.retrieveIdFromObjectInTransaction(context.getAccountRecordId(), ObjectType.ACCOUNT, cacheControllerDispatcher.getCacheController(CacheType.OBJECT_ID), entitySqlDaoWrapperFactory.getHandle());
+ notifyBusOfInvoicePayment(entitySqlDaoWrapperFactory, invoicePayment, accountId, context.getUserToken(), context);
+
return null;
}
});
@@ -833,6 +852,45 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
}
}
+ private void notifyBusOfInvoicePayment(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory, final InvoicePaymentModelDao invoicePaymentModelDao,
+ final UUID accountId, final UUID userToken, final InternalCallContext context) {
+ final BusEvent busEvent;
+ if (invoicePaymentModelDao.getSuccess() == Boolean.TRUE) {
+ busEvent = new DefaultInvoicePaymentInfoEvent(accountId,
+ invoicePaymentModelDao.getPaymentId(),
+ invoicePaymentModelDao.getType(),
+ invoicePaymentModelDao.getInvoiceId(),
+ invoicePaymentModelDao.getPaymentDate(),
+ invoicePaymentModelDao.getAmount(),
+ invoicePaymentModelDao.getCurrency(),
+ invoicePaymentModelDao.getLinkedInvoicePaymentId(),
+ invoicePaymentModelDao.getPaymentCookieId(),
+ invoicePaymentModelDao.getProcessedCurrency(),
+ context.getAccountRecordId(),
+ context.getTenantRecordId(),
+ userToken);
+ } else {
+ busEvent = new DefaultInvoicePaymentErrorEvent(accountId,
+ invoicePaymentModelDao.getPaymentId(),
+ invoicePaymentModelDao.getType(),
+ invoicePaymentModelDao.getInvoiceId(),
+ invoicePaymentModelDao.getPaymentDate(),
+ invoicePaymentModelDao.getAmount(),
+ invoicePaymentModelDao.getCurrency(),
+ invoicePaymentModelDao.getLinkedInvoicePaymentId(),
+ invoicePaymentModelDao.getPaymentCookieId(),
+ invoicePaymentModelDao.getProcessedCurrency(),
+ context.getAccountRecordId(),
+ context.getTenantRecordId(),
+ userToken);
+ }
+ try {
+ eventBus.postFromTransaction(busEvent, entitySqlDaoWrapperFactory.getHandle().getConnection());
+ } catch (final EventBusException e) {
+ log.warn("Failed to post invoice payment event for invoice " + invoicePaymentModelDao.getInvoiceId(), e);
+ }
+ }
+
private void createInvoiceItemFromTransaction(final InvoiceItemSqlDao invoiceItemSqlDao, final InvoiceItemModelDao invoiceItemModelDao, final InternalCallContext context) throws EntityPersistenceException, InvoiceApiException {
// There is no efficient way to retrieve an invoice item given an ID today (and invoice plugins can put item adjustments
// on a different invoice than the original item), so it's easier to do the check in the DAO rather than in the API layer
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceDaoHelper.java b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceDaoHelper.java
index f6b6619..e80ffa1 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceDaoHelper.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceDaoHelper.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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
@@ -42,6 +42,8 @@ import org.killbill.billing.tag.TagInternalApi;
import org.killbill.billing.util.entity.dao.EntitySqlDaoWrapperFactory;
import org.killbill.billing.util.tag.ControlTagType;
import org.killbill.billing.util.tag.Tag;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import com.google.common.base.Objects;
import com.google.common.base.Predicate;
@@ -51,6 +53,8 @@ import com.google.common.collect.Iterables;
public class InvoiceDaoHelper {
+ private static final Logger log = LoggerFactory.getLogger(InvoiceDaoHelper.class);
+
private final TagInternalApi tagInternalApi;
@Inject
@@ -164,6 +168,7 @@ public class InvoiceDaoHelper {
public List<InvoiceModelDao> getUnpaidInvoicesByAccountFromTransaction(final UUID accountId, final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory, final LocalDate upToDate, final InternalTenantContext context) {
final List<InvoiceModelDao> invoices = getAllInvoicesByAccountFromTransaction(entitySqlDaoWrapperFactory, context);
+ log.debug("Found invoices={} for accountId={}", invoices, accountId);
return getUnpaidInvoicesByAccountFromTransaction(invoices, upToDate);
}
@@ -171,7 +176,9 @@ public class InvoiceDaoHelper {
final Collection<InvoiceModelDao> unpaidInvoices = Collections2.filter(invoices, new Predicate<InvoiceModelDao>() {
@Override
public boolean apply(final InvoiceModelDao in) {
- return (InvoiceModelDaoHelper.getBalance(in).compareTo(BigDecimal.ZERO) >= 1) && (upToDate == null || !in.getTargetDate().isAfter(upToDate));
+ final BigDecimal balance = InvoiceModelDaoHelper.getBalance(in);
+ log.debug("Computed balance={} for invoice={}", balance, in);
+ return (balance.compareTo(BigDecimal.ZERO) >= 1) && (upToDate == null || !in.getTargetDate().isAfter(upToDate));
}
});
return new ArrayList<InvoiceModelDao>(unpaidInvoices);
@@ -252,6 +259,7 @@ public class InvoiceDaoHelper {
for (final InvoiceModelDao invoice : invoices) {
// Make sure to set invoice items to a non-null value
final List<InvoiceItemModelDao> invoiceItemsForInvoice = Objects.firstNonNull(invoiceItemsPerInvoiceId.get(invoice.getId()), ImmutableList.<InvoiceItemModelDao>of());
+ log.debug("Found items={} for invoice={}", invoiceItemsForInvoice, invoice);
invoice.addInvoiceItems(invoiceItemsForInvoice);
}
}
@@ -271,6 +279,7 @@ public class InvoiceDaoHelper {
for (final InvoiceModelDao invoice : invoices) {
// Make sure to set payments to a non-null value
final List<InvoicePaymentModelDao> invoicePaymentsForInvoice = Objects.firstNonNull(invoicePaymentsPerInvoiceId.get(invoice.getId()), ImmutableList.<InvoicePaymentModelDao>of());
+ log.debug("Found payments={} for invoice={}", invoicePaymentsForInvoice, invoice);
invoice.addPayments(invoicePaymentsForInvoice);
for (final InvoicePaymentModelDao invoicePayment : invoicePaymentsForInvoice) {
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceModelDao.java b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceModelDao.java
index 0316d8d..bf3b32e 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceModelDao.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceModelDao.java
@@ -1,7 +1,9 @@
/*
* Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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:
*
@@ -170,14 +172,17 @@ public class InvoiceModelDao extends EntityModelDaoBase implements EntityModelDa
@Override
public String toString() {
- final StringBuilder sb = new StringBuilder();
- sb.append("InvoiceModelDao");
- sb.append("{accountId=").append(accountId);
+ final StringBuilder sb = new StringBuilder("InvoiceModelDao{");
+ sb.append("accountId=").append(accountId);
sb.append(", invoiceNumber=").append(invoiceNumber);
sb.append(", invoiceDate=").append(invoiceDate);
sb.append(", targetDate=").append(targetDate);
sb.append(", currency=").append(currency);
sb.append(", migrated=").append(migrated);
+ sb.append(", invoiceItems=").append(invoiceItems);
+ sb.append(", invoicePayments=").append(invoicePayments);
+ sb.append(", processedCurrency=").append(processedCurrency);
+ sb.append(", isWrittenOff=").append(isWrittenOff);
sb.append('}');
return sb.toString();
}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoicePaymentModelDao.java b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoicePaymentModelDao.java
index e05448f..53cbf16 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoicePaymentModelDao.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoicePaymentModelDao.java
@@ -1,7 +1,9 @@
/*
* Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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:
*
@@ -148,16 +150,17 @@ public class InvoicePaymentModelDao extends EntityModelDaoBase implements Entity
@Override
public String toString() {
- final StringBuilder sb = new StringBuilder();
- sb.append("InvoicePaymentModelDao");
- sb.append("{type=").append(type);
+ final StringBuilder sb = new StringBuilder("InvoicePaymentModelDao{");
+ sb.append("type=").append(type);
sb.append(", invoiceId=").append(invoiceId);
sb.append(", paymentId=").append(paymentId);
sb.append(", paymentDate=").append(paymentDate);
sb.append(", amount=").append(amount);
sb.append(", currency=").append(currency);
- sb.append(", paymentCookieId=").append(paymentCookieId);
+ sb.append(", processedCurrency=").append(processedCurrency);
+ sb.append(", paymentCookieId='").append(paymentCookieId).append('\'');
sb.append(", linkedInvoicePaymentId=").append(linkedInvoicePaymentId);
+ sb.append(", success=").append(success);
sb.append('}');
return sb.toString();
}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/template/formatters/DefaultInvoiceItemFormatter.java b/invoice/src/main/java/org/killbill/billing/invoice/template/formatters/DefaultInvoiceItemFormatter.java
index 5516c8c..c6185a9 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/template/formatters/DefaultInvoiceItemFormatter.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/template/formatters/DefaultInvoiceItemFormatter.java
@@ -89,7 +89,7 @@ public class DefaultInvoiceItemFormatter implements InvoiceItemFormatter {
@Override
public String getDescription() {
- return Strings.nullToEmpty(item.getDescription());
+ return Strings.nullToEmpty(translator.getTranslation(item.getDescription()));
}
@Override
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceDao.java b/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceDao.java
index 74d938e..6a16b88 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceDao.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceDao.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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
@@ -1467,17 +1467,17 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
fixedItem1.getStartDate(), fixedItem1.getAmount(),
fixedItem1.getCurrency());
+ invoiceUtil.createInvoice(invoice1, true, context);
+ invoiceUtil.createInvoiceItem(fixedItem1, context);
+ invoiceUtil.createInvoiceItem(repairAdjInvoiceItem, context);
+ invoiceUtil.createInvoiceItem(creditBalanceAdjInvoiceItem1, context);
+
final UUID paymentId = UUID.randomUUID();
final DefaultInvoicePayment defaultInvoicePayment = new DefaultInvoicePayment(InvoicePaymentType.ATTEMPT, paymentId, invoice1.getId(), clock.getUTCNow().plusDays(12), new BigDecimal("10.0"),
Currency.USD, Currency.USD, true);
invoiceDao.notifyOfPayment(new InvoicePaymentModelDao(defaultInvoicePayment), context);
- invoiceUtil.createInvoice(invoice1, true, context);
- invoiceUtil.createInvoiceItem(fixedItem1, context);
- invoiceUtil.createInvoiceItem(repairAdjInvoiceItem, context);
- invoiceUtil.createInvoiceItem(creditBalanceAdjInvoiceItem1, context);
-
// Create invoice 2
// Scenario: single item
// * $5 item
diff --git a/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBillingApi.java b/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBillingApi.java
index 63438b7..5b4748b 100644
--- a/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBillingApi.java
+++ b/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBillingApi.java
@@ -1,7 +1,9 @@
/*
* Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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:
*
@@ -30,7 +32,6 @@ import org.killbill.billing.account.api.AccountApiException;
import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.catalog.MockCatalog;
import org.killbill.billing.catalog.api.BillingAlignment;
-import org.killbill.billing.catalog.api.BillingMode;
import org.killbill.billing.catalog.api.CatalogApiException;
import org.killbill.billing.catalog.api.Currency;
import org.killbill.billing.catalog.api.InternationalPrice;
@@ -38,6 +39,7 @@ import org.killbill.billing.catalog.api.Plan;
import org.killbill.billing.catalog.api.PlanPhase;
import org.killbill.billing.catalog.api.PriceList;
import org.killbill.billing.catalog.api.PriceListSet;
+import org.killbill.billing.entitlement.api.BlockingState;
import org.killbill.billing.entitlement.api.BlockingStateType;
import org.killbill.billing.entitlement.api.Entitlement.EntitlementState;
import org.killbill.billing.entitlement.dao.MockBlockingStateDao;
@@ -60,7 +62,9 @@ import org.testng.Assert;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
+import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNull;
@@ -181,8 +185,9 @@ public class TestBillingApi extends JunctionTestSuiteNoDB {
final Account account = createAccount(32);
- blockingStateDao.setBlockingState(new DefaultBlockingState(bunId, BlockingStateType.SUBSCRIPTION_BUNDLE, DISABLED_BUNDLE, "test", true, true, true, now.plusDays(1)), clock, internalCallContext);
- blockingStateDao.setBlockingState(new DefaultBlockingState(bunId, BlockingStateType.SUBSCRIPTION_BUNDLE, CLEAR_BUNDLE, "test", false, false, false, now.plusDays(2)), clock, internalCallContext);
+ final BlockingState blockingState1 = new DefaultBlockingState(bunId, BlockingStateType.SUBSCRIPTION_BUNDLE, DISABLED_BUNDLE, "test", true, true, true, now.plusDays(1));
+ final BlockingState blockingState2 = new DefaultBlockingState(bunId, BlockingStateType.SUBSCRIPTION_BUNDLE, CLEAR_BUNDLE, "test", false, false, false, now.plusDays(2));
+ blockingStateDao.setBlockingStatesAndPostBlockingTransitionEvent(ImmutableMap.<BlockingState, Optional<UUID>>of(blockingState1, Optional.<UUID>absent(), blockingState2, Optional.<UUID>absent()), internalCallContext);
final SortedSet<BillingEvent> events = billingInternalApi.getBillingEventsForAccountAndUpdateAccountBCD(account.getId(), null, internalCallContext);
diff --git a/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBlockingCalculator.java b/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBlockingCalculator.java
index c0abd20..8c26b09 100644
--- a/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBlockingCalculator.java
+++ b/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBlockingCalculator.java
@@ -1,7 +1,9 @@
/*
* Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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,10 +31,6 @@ import java.util.UUID;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.LocalDate;
-import org.mockito.Mockito;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-
import org.killbill.billing.account.api.Account;
import org.killbill.billing.catalog.MockPlan;
import org.killbill.billing.catalog.MockPlanPhase;
@@ -49,6 +47,12 @@ import org.killbill.billing.junction.JunctionTestSuiteNoDB;
import org.killbill.billing.junction.plumbing.billing.BlockingCalculator.DisabledDuration;
import org.killbill.billing.subscription.api.SubscriptionBase;
import org.killbill.billing.subscription.api.SubscriptionBaseTransitionType;
+import org.mockito.Mockito;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableMap;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
@@ -113,11 +117,12 @@ public class TestBlockingCalculator extends JunctionTestSuiteNoDB {
billingEvents.add(C);
billingEvents.add(D);
- final List<BlockingState> blockingStates = new ArrayList<BlockingState>();
- blockingStates.add(new DefaultBlockingState(bundleId1, BlockingStateType.SUBSCRIPTION_BUNDLE, DISABLED_BUNDLE, "test", true, true, true, now));
- blockingStates.add(new DefaultBlockingState(bundleId1, BlockingStateType.SUBSCRIPTION_BUNDLE, CLEAR_BUNDLE, "test", false, false, false, now.plusDays(2)));
+ final BlockingState blockingState1 = new DefaultBlockingState(bundleId1, BlockingStateType.SUBSCRIPTION_BUNDLE, DISABLED_BUNDLE, "test", true, true, true, now);
+ final BlockingState blockingState2 = new DefaultBlockingState(bundleId1, BlockingStateType.SUBSCRIPTION_BUNDLE, CLEAR_BUNDLE, "test", false, false, false, now.plusDays(2));
- setBlockingStates(blockingStates);
+ blockingStateDao.setBlockingStatesAndPostBlockingTransitionEvent(ImmutableMap.<BlockingState, Optional<UUID>>of(blockingState1, Optional.<UUID>absent(),
+ blockingState2, Optional.<UUID>absent()),
+ internalCallContext);
blockingCalculator.insertBlockingEvents(billingEvents, new HashSet<UUID>(), internalCallContext);
@@ -743,13 +748,16 @@ public class TestBlockingCalculator extends JunctionTestSuiteNoDB {
billingEvents.add(phase);
billingEvents.add(upgrade);
- final List<BlockingState> blockingEvents = new ArrayList<BlockingState>();
- blockingEvents.add(new DefaultBlockingState(bundleId1, BlockingStateType.SUBSCRIPTION_BUNDLE, DISABLED_BUNDLE, "test", true, false, false, new LocalDate(2012, 7, 5).toDateTimeAtStartOfDay(DateTimeZone.UTC)));
- blockingEvents.add(new DefaultBlockingState(bundleId1, BlockingStateType.SUBSCRIPTION_BUNDLE, DISABLED_BUNDLE, "test", true, true, true, new LocalDate(2012, 7, 15).toDateTimeAtStartOfDay(DateTimeZone.UTC)));
- blockingEvents.add(new DefaultBlockingState(bundleId1, BlockingStateType.SUBSCRIPTION_BUNDLE, DISABLED_BUNDLE, "test", true, true, true, new LocalDate(2012, 7, 24).toDateTimeAtStartOfDay(DateTimeZone.UTC)));
- blockingEvents.add(new DefaultBlockingState(bundleId1, BlockingStateType.SUBSCRIPTION_BUNDLE, CLEAR_BUNDLE, "test", false, false, false, new LocalDate(2012, 7, 25).toDateTimeAtStartOfDay(DateTimeZone.UTC)));
+ final BlockingState blockingState1 = new DefaultBlockingState(bundleId1, BlockingStateType.SUBSCRIPTION_BUNDLE, DISABLED_BUNDLE, "test", true, false, false, new LocalDate(2012, 7, 5).toDateTimeAtStartOfDay(DateTimeZone.UTC));
+ final BlockingState blockingState2 = new DefaultBlockingState(bundleId1, BlockingStateType.SUBSCRIPTION_BUNDLE, DISABLED_BUNDLE, "test", true, true, true, new LocalDate(2012, 7, 15).toDateTimeAtStartOfDay(DateTimeZone.UTC));
+ final BlockingState blockingState3 = new DefaultBlockingState(bundleId1, BlockingStateType.SUBSCRIPTION_BUNDLE, DISABLED_BUNDLE, "test", true, true, true, new LocalDate(2012, 7, 24).toDateTimeAtStartOfDay(DateTimeZone.UTC));
+ final BlockingState blockingState4 = new DefaultBlockingState(bundleId1, BlockingStateType.SUBSCRIPTION_BUNDLE, CLEAR_BUNDLE, "test", false, false, false, new LocalDate(2012, 7, 25).toDateTimeAtStartOfDay(DateTimeZone.UTC));
- setBlockingStates(blockingEvents);
+ blockingStateDao.setBlockingStatesAndPostBlockingTransitionEvent(ImmutableMap.<BlockingState, Optional<UUID>>of(blockingState1, Optional.<UUID>absent(),
+ blockingState2, Optional.<UUID>absent(),
+ blockingState3, Optional.<UUID>absent(),
+ blockingState4, Optional.<UUID>absent()),
+ internalCallContext);
blockingCalculator.insertBlockingEvents(billingEvents, new HashSet<UUID>(), internalCallContext);
@@ -766,10 +774,4 @@ public class TestBlockingCalculator extends JunctionTestSuiteNoDB {
assertEquals(events.get(4).getEffectiveDate(), new LocalDate(2012, 7, 25).toDateTimeAtStartOfDay(DateTimeZone.UTC));
assertEquals(events.get(4).getTransitionType(), SubscriptionBaseTransitionType.CHANGE);
}
-
- private void setBlockingStates(final List<BlockingState> blockingStates) {
- for (final BlockingState blockingState : blockingStates) {
- blockingStateDao.setBlockingState(blockingState, clock, internalCallContext);
- }
- }
}
diff --git a/junction/src/test/java/org/killbill/billing/junction/TestDefaultBlockingState.java b/junction/src/test/java/org/killbill/billing/junction/TestDefaultBlockingState.java
new file mode 100644
index 0000000..2bc9e33
--- /dev/null
+++ b/junction/src/test/java/org/killbill/billing/junction/TestDefaultBlockingState.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2016 Groupon, Inc
+ * Copyright 2016 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.junction;
+
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.killbill.billing.entitlement.api.BlockingState;
+import org.killbill.billing.entitlement.api.BlockingStateType;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+public class TestDefaultBlockingState extends JunctionTestSuiteNoDB {
+
+ @Test(groups = "fast", description = "https://github.com/killbill/killbill/issues/174")
+ public void testComparisonSameEffectiveDate() throws Exception {
+ final DateTime effectiveDate = clock.getUTCNow();
+ final BlockingState bs1 = new DefaultBlockingState(UUID.randomUUID(),
+ UUID.randomUUID(),
+ BlockingStateType.ACCOUNT,
+ "OD3",
+ "test",
+ true,
+ true,
+ true,
+ effectiveDate,
+ effectiveDate,
+ effectiveDate,
+ 3L);
+ final BlockingState bs2 = new DefaultBlockingState(UUID.randomUUID(),
+ UUID.randomUUID(),
+ BlockingStateType.ACCOUNT,
+ DefaultBlockingState.CLEAR_STATE_NAME,
+ "test",
+ false,
+ false,
+ false,
+ effectiveDate,
+ effectiveDate,
+ effectiveDate,
+ 4L);
+ final BlockingState bs3 = new DefaultBlockingState(UUID.randomUUID(),
+ UUID.randomUUID(),
+ BlockingStateType.ACCOUNT,
+ "OD1",
+ "test",
+ true,
+ true,
+ true,
+ effectiveDate.plusMillis(1),
+ effectiveDate,
+ effectiveDate,
+ 5L);
+ Assert.assertTrue(bs1.compareTo(bs2) < 0);
+ Assert.assertTrue(bs1.compareTo(bs3) < 0);
+ Assert.assertTrue(bs2.compareTo(bs1) > 0);
+ Assert.assertTrue(bs2.compareTo(bs3) < 0);
+ Assert.assertTrue(bs3.compareTo(bs2) > 0);
+ Assert.assertTrue(bs3.compareTo(bs1) > 0);
+ }
+}
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/api/DefaultOverdueInternalApi.java b/overdue/src/main/java/org/killbill/billing/overdue/api/DefaultOverdueInternalApi.java
index d676cf9..81c4002 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/api/DefaultOverdueInternalApi.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/api/DefaultOverdueInternalApi.java
@@ -1,7 +1,9 @@
/*
* Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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:
*
@@ -16,8 +18,11 @@
package org.killbill.billing.overdue.api;
+import java.util.UUID;
+
+import javax.inject.Named;
+
import org.killbill.billing.ErrorCode;
-import org.killbill.billing.ObjectType;
import org.killbill.billing.account.api.ImmutableAccountData;
import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.callcontext.InternalTenantContext;
@@ -27,14 +32,21 @@ import org.killbill.billing.overdue.OverdueInternalApi;
import org.killbill.billing.overdue.OverdueService;
import org.killbill.billing.overdue.caching.OverdueConfigCache;
import org.killbill.billing.overdue.config.DefaultOverdueConfig;
+import org.killbill.billing.overdue.config.DefaultOverdueState;
import org.killbill.billing.overdue.config.api.BillingState;
import org.killbill.billing.overdue.config.api.OverdueException;
import org.killbill.billing.overdue.config.api.OverdueStateSet;
+import org.killbill.billing.overdue.glue.DefaultOverdueModule;
+import org.killbill.billing.overdue.notification.OverdueAsyncBusNotificationKey;
+import org.killbill.billing.overdue.notification.OverdueAsyncBusNotificationKey.OverdueAsyncBusNotificationAction;
+import org.killbill.billing.overdue.notification.OverdueAsyncBusNotifier;
+import org.killbill.billing.overdue.notification.OverduePoster;
import org.killbill.billing.overdue.wrapper.OverdueWrapper;
import org.killbill.billing.overdue.wrapper.OverdueWrapperFactory;
import org.killbill.billing.util.callcontext.CallContext;
import org.killbill.billing.util.callcontext.InternalCallContextFactory;
import org.killbill.billing.util.callcontext.TenantContext;
+import org.killbill.clock.Clock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -42,20 +54,26 @@ import com.google.inject.Inject;
public class DefaultOverdueInternalApi implements OverdueInternalApi {
- Logger log = LoggerFactory.getLogger(DefaultOverdueInternalApi.class);
+ private static final Logger log = LoggerFactory.getLogger(DefaultOverdueInternalApi.class);
private final OverdueWrapperFactory factory;
private final BlockingInternalApi accessApi;
- private final InternalCallContextFactory internalCallContextFactory;
+ private final Clock clock;
+ private final OverduePoster asyncPoster;
private final OverdueConfigCache overdueConfigCache;
+ private final InternalCallContextFactory internalCallContextFactory;
@Inject
public DefaultOverdueInternalApi(final OverdueWrapperFactory factory,
final BlockingInternalApi accessApi,
+ final Clock clock,
+ @Named(DefaultOverdueModule.OVERDUE_NOTIFIER_ASYNC_BUS_NAMED) final OverduePoster asyncPoster,
final OverdueConfigCache overdueConfigCache,
final InternalCallContextFactory internalCallContextFactory) {
this.factory = factory;
this.accessApi = accessApi;
+ this.clock = clock;
+ this.asyncPoster = asyncPoster;
this.overdueConfigCache = overdueConfigCache;
this.internalCallContextFactory = internalCallContextFactory;
}
@@ -69,7 +87,7 @@ public class DefaultOverdueInternalApi implements OverdueInternalApi {
final OverdueConfig overdueConfig = overdueConfigCache.getOverdueConfig(internalTenantContext);
final OverdueStateSet states = ((DefaultOverdueConfig) overdueConfig).getOverdueStatesAccount();
return states.findState(stateName);
- } catch (OverdueApiException e) {
+ } catch (final OverdueApiException e) {
throw new OverdueException(e, ErrorCode.OVERDUE_CAT_ERROR_ENCOUNTERED, overdueable.getId(), overdueable.getClass().getSimpleName());
}
}
@@ -84,15 +102,43 @@ public class DefaultOverdueInternalApi implements OverdueInternalApi {
}
@Override
- public OverdueState refreshOverdueStateFor(final ImmutableAccountData blockable, final CallContext context) throws OverdueException, OverdueApiException {
- log.info("Refresh of blockable {} ({}) requested", blockable.getId(), blockable.getClass());
- final InternalCallContext internalCallContext = createInternalCallContext(blockable, context);
- final OverdueWrapper wrapper = factory.createOverdueWrapperFor(blockable, internalCallContext);
- return wrapper.refresh(internalCallContext);
+ public void scheduleOverdueRefresh(final UUID accountId, final InternalCallContext internalCallContext) {
+ insertBusEventIntoNotificationQueue(accountId, OverdueAsyncBusNotificationAction.REFRESH, internalCallContext);
+ }
+
+ @Override
+ public void scheduleOverdueClear(final UUID accountId, final InternalCallContext internalCallContext) {
+ insertBusEventIntoNotificationQueue(accountId, OverdueAsyncBusNotificationAction.CLEAR, internalCallContext);
+ }
+
+ private void insertBusEventIntoNotificationQueue(final UUID accountId, final OverdueAsyncBusNotificationAction action, final InternalCallContext callContext) {
+ final boolean shouldInsertNotification = shouldInsertNotification(callContext);
+
+ if (shouldInsertNotification) {
+ final OverdueAsyncBusNotificationKey notificationKey = new OverdueAsyncBusNotificationKey(accountId, action);
+ asyncPoster.insertOverdueNotification(accountId, clock.getUTCNow(), OverdueAsyncBusNotifier.OVERDUE_ASYNC_BUS_NOTIFIER_QUEUE, notificationKey, callContext);
+ }
}
- private InternalCallContext createInternalCallContext(final ImmutableAccountData blockable, final CallContext context) {
- return internalCallContextFactory.createInternalCallContext(blockable.getId(), ObjectType.ACCOUNT, context);
+ // Optimization: don't bother running the Overdue machinery if it's disabled
+ private boolean shouldInsertNotification(final InternalTenantContext internalTenantContext) {
+ OverdueConfig overdueConfig;
+ try {
+ overdueConfig = overdueConfigCache.getOverdueConfig(internalTenantContext);
+ } catch (final OverdueApiException e) {
+ log.warn("Failed to extract overdue config for tenant " + internalTenantContext.getTenantRecordId());
+ overdueConfig = null;
+ }
+ if (overdueConfig == null || overdueConfig.getOverdueStatesAccount() == null || overdueConfig.getOverdueStatesAccount().getStates() == null) {
+ return false;
+ }
+
+ for (final DefaultOverdueState state : ((DefaultOverdueConfig) overdueConfig).getOverdueStatesAccount().getStates()) {
+ if (state.getConditionEvaluation() != null) {
+ return true;
+ }
+ }
+ return false;
}
@Override
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/applicator/OverdueStateApplicator.java b/overdue/src/main/java/org/killbill/billing/overdue/applicator/OverdueStateApplicator.java
index e2b3604..2c942f6 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/applicator/OverdueStateApplicator.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/applicator/OverdueStateApplicator.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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
@@ -39,6 +39,7 @@ import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.catalog.api.BillingActionPolicy;
import org.killbill.billing.catalog.api.ProductCategory;
+import org.killbill.billing.entitlement.EntitlementInternalApi;
import org.killbill.billing.entitlement.api.BlockingApiException;
import org.killbill.billing.entitlement.api.BlockingStateType;
import org.killbill.billing.entitlement.api.Entitlement;
@@ -91,6 +92,7 @@ public class OverdueStateApplicator {
private final PersistentBus bus;
private final AccountInternalApi accountApi;
private final EntitlementApi entitlementApi;
+ private final EntitlementInternalApi entitlementInternalApi;
private final OverdueEmailGenerator overdueEmailGenerator;
private final TagInternalApi tagApi;
private final EmailSender emailSender;
@@ -100,6 +102,7 @@ public class OverdueStateApplicator {
public OverdueStateApplicator(final BlockingInternalApi accessApi,
final AccountInternalApi accountApi,
final EntitlementApi entitlementApi,
+ final EntitlementInternalApi entitlementInternalApi,
final Clock clock,
@Named(DefaultOverdueModule.OVERDUE_NOTIFIER_CHECK_NAMED) final OverduePoster checkPoster,
final OverdueEmailGenerator overdueEmailGenerator,
@@ -111,6 +114,7 @@ public class OverdueStateApplicator {
this.blockingApi = accessApi;
this.accountApi = accountApi;
this.entitlementApi = entitlementApi;
+ this.entitlementInternalApi = entitlementInternalApi;
this.clock = clock;
this.checkPoster = checkPoster;
this.overdueEmailGenerator = overdueEmailGenerator;
@@ -124,13 +128,12 @@ public class OverdueStateApplicator {
final ImmutableAccountData account, final OverdueState previousOverdueState,
final OverdueState nextOverdueState, final InternalCallContext context) throws OverdueException, OverdueApiException {
try {
-
if (isAccountTaggedWith_OVERDUE_ENFORCEMENT_OFF(context)) {
- log.debug("OverdueStateApplicator:apply returns because account (recordId = " + context.getAccountRecordId() + ") is set with OVERDUE_ENFORCEMENT_OFF ");
+ log.debug("OverdueStateApplicator: apply returns because account (recordId={}) is set with OVERDUE_ENFORCEMENT_OFF", context.getAccountRecordId());
return;
}
- log.debug("OverdueStateApplicator:apply <enter> : time = " + clock.getUTCNow() + ", previousState = " + previousOverdueState.getName() + ", nextState = " + nextOverdueState);
+ log.debug("OverdueStateApplicator: time={}, previousState={}, nextState={}, billingState={}", clock.getUTCNow(), previousOverdueState, nextOverdueState, billingState);
final OverdueState firstOverdueState = overdueStateSet.getFirstState();
final boolean conditionForNextNotfication = !nextOverdueState.isClearState() ||
@@ -141,10 +144,9 @@ public class OverdueStateApplicator {
final Period reevaluationInterval = getReevaluationInterval(overdueStateSet, nextOverdueState);
// If there is no configuration in the config, we assume this is because the overdue conditions are not time based and so there is nothing to retry
if (reevaluationInterval == null) {
- log.debug("OverdueStateApplicator <notificationQ> : Missing InitialReevaluationInterval from config, NOT inserting notification for account " + account.getId());
-
+ log.debug("OverdueStateApplicator <notificationQ>: missing InitialReevaluationInterval from config, NOT inserting notification for account {}", account.getId());
} else {
- log.debug("OverdueStateApplicator <notificationQ> : inserting notification for account " + account.getId() + ", time = " + clock.getUTCNow().plus(reevaluationInterval));
+ log.debug("OverdueStateApplicator <notificationQ>: inserting notification for account={}, time={}", account.getId(), clock.getUTCNow().plus(reevaluationInterval));
createFutureNotification(account, clock.getUTCNow().plus(reevaluationInterval), context);
}
} else if (nextOverdueState.isClearState()) {
@@ -152,6 +154,7 @@ public class OverdueStateApplicator {
}
if (previousOverdueState.getName().equals(nextOverdueState.getName())) {
+ log.debug("OverdueStateApplicator is no-op: previousState={}, nextState={}", previousOverdueState, nextOverdueState);
return;
}
@@ -314,15 +317,10 @@ public class OverdueStateApplicator {
final List<Entitlement> toBeCancelled = new LinkedList<Entitlement>();
computeEntitlementsToCancel(account, toBeCancelled, callContext);
- for (final Entitlement cur : toBeCancelled) {
- try {
- cur.cancelEntitlementWithDateOverrideBillingPolicy(new LocalDate(clock.getUTCNow(), account.getTimeZone()), actionPolicy, ImmutableList.<PluginProperty>of(), callContext);
- } catch (final EntitlementApiException e) {
- // If subscription has already been cancelled, there is nothing to do so we can ignore
- if (e.getCode() != ErrorCode.SUB_CANCEL_BAD_STATE.getCode()) {
- throw new OverdueException(e);
- }
- }
+ try {
+ entitlementInternalApi.cancel(toBeCancelled, new LocalDate(clock.getUTCNow(), account.getTimeZone()), actionPolicy, ImmutableList.<PluginProperty>of(), context);
+ } catch (final EntitlementApiException e) {
+ throw new OverdueException(e);
}
} catch (final EntitlementApiException e) {
throw new OverdueException(e);
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultDuration.java b/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultDuration.java
index dc259ff..5528e14 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultDuration.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultDuration.java
@@ -1,7 +1,9 @@
/*
* Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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:
*
@@ -36,17 +38,11 @@ public class DefaultDuration extends ValidatingConfig<DefaultOverdueConfig> impl
@XmlElement(required = false)
private Integer number = -1;
- /* (non-Javadoc)
- * @see org.killbill.billing.catalog.IDuration#getUnit()
- */
@Override
public TimeUnit getUnit() {
return unit;
}
- /* (non-Javadoc)
- * @see org.killbill.billing.catalog.IDuration#getLength()
- */
@Override
public int getNumber() {
return number;
@@ -108,5 +104,12 @@ public class DefaultDuration extends ValidatingConfig<DefaultOverdueConfig> impl
return this;
}
-
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("DefaultDuration{");
+ sb.append("unit=").append(unit);
+ sb.append(", number=").append(number);
+ sb.append('}');
+ return sb.toString();
+ }
}
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultOverdueCondition.java b/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultOverdueCondition.java
index ada7650..ee30861 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultOverdueCondition.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultOverdueCondition.java
@@ -1,7 +1,9 @@
/*
* Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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:
*
@@ -18,6 +20,7 @@ package org.killbill.billing.overdue.config;
import java.math.BigDecimal;
import java.net.URI;
+import java.util.Arrays;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
@@ -135,4 +138,16 @@ public class DefaultOverdueCondition extends ValidatingConfig<DefaultOverdueConf
public ControlTagType getControlTagType() {
return controlTag;
}
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("DefaultOverdueCondition{");
+ sb.append("numberOfUnpaidInvoicesEqualsOrExceeds=").append(numberOfUnpaidInvoicesEqualsOrExceeds);
+ sb.append(", totalUnpaidInvoiceBalanceEqualsOrExceeds=").append(totalUnpaidInvoiceBalanceEqualsOrExceeds);
+ sb.append(", timeSinceEarliestUnpaidInvoiceEqualsOrExceeds=").append(timeSinceEarliestUnpaidInvoiceEqualsOrExceeds);
+ sb.append(", responseForLastFailedPayment=").append(Arrays.toString(responseForLastFailedPayment));
+ sb.append(", controlTag=").append(controlTag);
+ sb.append('}');
+ return sb.toString();
+ }
}
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultOverdueState.java b/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultOverdueState.java
index 156ccec..9d33ddd 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultOverdueState.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultOverdueState.java
@@ -1,7 +1,9 @@
/*
* Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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:
*
@@ -171,4 +173,20 @@ public class DefaultOverdueState extends ValidatingConfig<DefaultOverdueConfig>
public EmailNotification getEmailNotification() {
return enterStateEmailNotification;
}
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("DefaultOverdueState{");
+ sb.append("condition=").append(condition);
+ sb.append(", name='").append(name).append('\'');
+ sb.append(", externalMessage='").append(externalMessage).append('\'');
+ sb.append(", blockChanges=").append(blockChanges);
+ sb.append(", disableEntitlement=").append(disableEntitlement);
+ sb.append(", subscriptionCancellationPolicy=").append(subscriptionCancellationPolicy);
+ sb.append(", isClearState=").append(isClearState);
+ sb.append(", autoReevaluationInterval=").append(autoReevaluationInterval);
+ sb.append(", enterStateEmailNotification=").append(enterStateEmailNotification);
+ sb.append('}');
+ return sb.toString();
+ }
}
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/listener/OverdueListener.java b/overdue/src/main/java/org/killbill/billing/overdue/listener/OverdueListener.java
index fa2e581..8f1515f 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/listener/OverdueListener.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/listener/OverdueListener.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014 Groupon, Inc
- * Copyright 2014 The Billing Project, LLC
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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
@@ -20,33 +20,19 @@ package org.killbill.billing.overdue.listener;
import java.util.UUID;
-import javax.inject.Named;
-
import org.killbill.billing.ObjectType;
import org.killbill.billing.callcontext.InternalCallContext;
-import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.events.ControlTagCreationInternalEvent;
import org.killbill.billing.events.ControlTagDeletionInternalEvent;
import org.killbill.billing.events.InvoiceAdjustmentInternalEvent;
-import org.killbill.billing.events.PaymentErrorInternalEvent;
-import org.killbill.billing.events.PaymentInfoInternalEvent;
-import org.killbill.billing.overdue.OverdueService;
-import org.killbill.billing.overdue.api.OverdueApiException;
-import org.killbill.billing.overdue.api.OverdueConfig;
-import org.killbill.billing.overdue.caching.OverdueConfigCache;
-import org.killbill.billing.overdue.config.DefaultOverdueConfig;
-import org.killbill.billing.overdue.config.DefaultOverdueState;
-import org.killbill.billing.overdue.glue.DefaultOverdueModule;
-import org.killbill.billing.overdue.notification.OverdueAsyncBusNotificationKey;
-import org.killbill.billing.overdue.notification.OverdueAsyncBusNotificationKey.OverdueAsyncBusNotificationAction;
-import org.killbill.billing.overdue.notification.OverdueAsyncBusNotifier;
-import org.killbill.billing.overdue.notification.OverduePoster;
+import org.killbill.billing.events.InvoicePaymentErrorInternalEvent;
+import org.killbill.billing.events.InvoicePaymentInfoInternalEvent;
+import org.killbill.billing.overdue.OverdueInternalApi;
import org.killbill.billing.util.callcontext.CallOrigin;
import org.killbill.billing.util.callcontext.InternalCallContextFactory;
import org.killbill.billing.util.callcontext.UserType;
import org.killbill.billing.util.tag.ControlTagType;
import org.killbill.bus.api.BusEvent;
-import org.killbill.clock.Clock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -56,21 +42,15 @@ import com.google.inject.Inject;
public class OverdueListener {
- private final InternalCallContextFactory internalCallContextFactory;
- private final OverduePoster asyncPoster;
- private final Clock clock;
- private final OverdueConfigCache overdueConfigCache;
-
private static final Logger log = LoggerFactory.getLogger(OverdueListener.class);
+ private final OverdueInternalApi overdueInternalApi;
+ private final InternalCallContextFactory internalCallContextFactory;
+
@Inject
- public OverdueListener(final Clock clock,
- @Named(DefaultOverdueModule.OVERDUE_NOTIFIER_ASYNC_BUS_NAMED) final OverduePoster asyncPoster,
- final OverdueConfigCache overdueConfigCache,
+ public OverdueListener(final OverdueInternalApi overdueInternalApi,
final InternalCallContextFactory internalCallContextFactory) {
- this.asyncPoster = asyncPoster;
- this.clock = clock;
- this.overdueConfigCache = overdueConfigCache;
+ this.overdueInternalApi = overdueInternalApi;
this.internalCallContextFactory = internalCallContextFactory;
}
@@ -78,7 +58,8 @@ public class OverdueListener {
@Subscribe
public void handle_OVERDUE_ENFORCEMENT_OFF_Insert(final ControlTagCreationInternalEvent event) {
if (event.getTagDefinition().getName().equals(ControlTagType.OVERDUE_ENFORCEMENT_OFF.toString()) && event.getObjectType() == ObjectType.ACCOUNT) {
- insertBusEventIntoNotificationQueue(event.getObjectId(), event, OverdueAsyncBusNotificationAction.CLEAR, event.getSearchKey2());
+ final InternalCallContext callContext = createCallContext(event.getUserToken(), event.getSearchKey1(), event.getSearchKey2());
+ overdueInternalApi.scheduleOverdueClear(event.getObjectId(), callContext);
}
}
@@ -86,60 +67,34 @@ public class OverdueListener {
@Subscribe
public void handle_OVERDUE_ENFORCEMENT_OFF_Removal(final ControlTagDeletionInternalEvent event) {
if (event.getTagDefinition().getName().equals(ControlTagType.OVERDUE_ENFORCEMENT_OFF.toString()) && event.getObjectType() == ObjectType.ACCOUNT) {
- insertBusEventIntoNotificationQueue(event.getObjectId(), event, OverdueAsyncBusNotificationAction.REFRESH, event.getSearchKey2());
+ insertBusEventIntoNotificationQueue(event.getObjectId(), event);
}
}
@AllowConcurrentEvents
@Subscribe
- public void handlePaymentInfoEvent(final PaymentInfoInternalEvent event) {
- log.debug("Received PaymentInfo event {}", event);
- insertBusEventIntoNotificationQueue(event.getAccountId(), event, OverdueAsyncBusNotificationAction.REFRESH, event.getSearchKey2());
+ public void handlePaymentInfoEvent(final InvoicePaymentInfoInternalEvent event) {
+ log.debug("Received InvoicePaymentInfo event {}", event);
+ insertBusEventIntoNotificationQueue(event.getAccountId(), event);
}
@AllowConcurrentEvents
@Subscribe
- public void handlePaymentErrorEvent(final PaymentErrorInternalEvent event) {
- log.debug("Received PaymentError event {}", event);
- insertBusEventIntoNotificationQueue(event.getAccountId(), event, OverdueAsyncBusNotificationAction.REFRESH, event.getSearchKey2());
+ public void handlePaymentErrorEvent(final InvoicePaymentErrorInternalEvent event) {
+ log.debug("Received InvoicePaymentError event {}", event);
+ insertBusEventIntoNotificationQueue(event.getAccountId(), event);
}
@AllowConcurrentEvents
@Subscribe
public void handleInvoiceAdjustmentEvent(final InvoiceAdjustmentInternalEvent event) {
log.debug("Received InvoiceAdjustment event {}", event);
- insertBusEventIntoNotificationQueue(event.getAccountId(), event, OverdueAsyncBusNotificationAction.REFRESH, event.getSearchKey2());
+ insertBusEventIntoNotificationQueue(event.getAccountId(), event);
}
- private void insertBusEventIntoNotificationQueue(final UUID accountId, final BusEvent event, final OverdueAsyncBusNotificationAction action, final Long tenantRecordId) {
- final InternalTenantContext tenantContext = internalCallContextFactory.createInternalTenantContext(tenantRecordId, null);
- final boolean shouldInsertNotification = shouldInsertNotification(tenantContext);
-
- if (shouldInsertNotification) {
- final OverdueAsyncBusNotificationKey notificationKey = new OverdueAsyncBusNotificationKey(accountId, action);
- asyncPoster.insertOverdueNotification(accountId, clock.getUTCNow(), OverdueAsyncBusNotifier.OVERDUE_ASYNC_BUS_NOTIFIER_QUEUE, notificationKey, createCallContext(event.getUserToken(), event.getSearchKey1(), event.getSearchKey2()));
- }
- }
-
- // Optimization: don't bother running the Overdue machinery if it's disabled
- private boolean shouldInsertNotification(final InternalTenantContext internalTenantContext) {
- OverdueConfig overdueConfig;
- try {
- overdueConfig = overdueConfigCache.getOverdueConfig(internalTenantContext);
- } catch (OverdueApiException e) {
- log.warn("Failed to extract overdue config for tenant " + internalTenantContext.getTenantRecordId());
- overdueConfig = null;
- }
- if (overdueConfig == null || overdueConfig.getOverdueStatesAccount() == null || overdueConfig.getOverdueStatesAccount().getStates() == null) {
- return false;
- }
-
- for (final DefaultOverdueState state : ((DefaultOverdueConfig) overdueConfig).getOverdueStatesAccount().getStates()) {
- if (state.getConditionEvaluation() != null) {
- return true;
- }
- }
- return false;
+ private void insertBusEventIntoNotificationQueue(final UUID accountId, final BusEvent event) {
+ final InternalCallContext callContext = createCallContext(event.getUserToken(), event.getSearchKey1(), event.getSearchKey2());
+ overdueInternalApi.scheduleOverdueRefresh(accountId, callContext);
}
private InternalCallContext createCallContext(final UUID userToken, final Long accountRecordId, final Long tenantRecordId) {
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/notification/OverdueAsyncBusNotifier.java b/overdue/src/main/java/org/killbill/billing/overdue/notification/OverdueAsyncBusNotifier.java
index 9aca856..be98671 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/notification/OverdueAsyncBusNotifier.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/notification/OverdueAsyncBusNotifier.java
@@ -1,7 +1,9 @@
/*
* Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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:
*
@@ -19,18 +21,13 @@ package org.killbill.billing.overdue.notification;
import java.util.UUID;
import org.joda.time.DateTime;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import org.killbill.billing.callcontext.InternalCallContext;
-import org.killbill.notificationq.api.NotificationEvent;
-import org.killbill.notificationq.api.NotificationQueueService;
import org.killbill.billing.overdue.OverdueProperties;
import org.killbill.billing.overdue.listener.OverdueDispatcher;
-import org.killbill.billing.overdue.listener.OverdueListener;
-import org.killbill.billing.util.callcontext.CallOrigin;
import org.killbill.billing.util.callcontext.InternalCallContextFactory;
-import org.killbill.billing.util.callcontext.UserType;
+import org.killbill.notificationq.api.NotificationEvent;
+import org.killbill.notificationq.api.NotificationQueueService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import com.google.inject.Inject;
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/notification/OverdueCheckNotifier.java b/overdue/src/main/java/org/killbill/billing/overdue/notification/OverdueCheckNotifier.java
index 22417cd..bd644dd 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/notification/OverdueCheckNotifier.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/notification/OverdueCheckNotifier.java
@@ -1,7 +1,9 @@
/*
* Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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:
*
@@ -19,15 +21,13 @@ package org.killbill.billing.overdue.notification;
import java.util.UUID;
import org.joda.time.DateTime;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import org.killbill.notificationq.api.NotificationEvent;
-import org.killbill.notificationq.api.NotificationQueueService;
import org.killbill.billing.overdue.OverdueProperties;
import org.killbill.billing.overdue.listener.OverdueDispatcher;
-import org.killbill.billing.overdue.listener.OverdueListener;
import org.killbill.billing.util.callcontext.InternalCallContextFactory;
+import org.killbill.notificationq.api.NotificationEvent;
+import org.killbill.notificationq.api.NotificationQueueService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import com.google.inject.Inject;
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/wrapper/OverdueWrapper.java b/overdue/src/main/java/org/killbill/billing/overdue/wrapper/OverdueWrapper.java
index 0b45601..a7c14f8 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/wrapper/OverdueWrapper.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/wrapper/OverdueWrapper.java
@@ -1,7 +1,9 @@
/*
* Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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,12 +31,24 @@ import org.killbill.billing.overdue.calculator.BillingStateCalculator;
import org.killbill.billing.overdue.config.api.BillingState;
import org.killbill.billing.overdue.config.api.OverdueException;
import org.killbill.billing.overdue.config.api.OverdueStateSet;
+import org.killbill.billing.util.globallocker.LockerType;
import org.killbill.clock.Clock;
+import org.killbill.commons.locker.GlobalLock;
+import org.killbill.commons.locker.GlobalLocker;
+import org.killbill.commons.locker.LockFailedException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
public class OverdueWrapper {
+ private static final Logger log = LoggerFactory.getLogger(OverdueWrapper.class);
+
+ // Should we introduce a config?
+ private static final int MAX_LOCK_RETRIES = 50;
+
private final ImmutableAccountData overdueable;
private final BlockingInternalApi api;
+ private final GlobalLocker locker;
private final Clock clock;
private final OverdueStateSet overdueStateSet;
private final BillingStateCalculator billingStateCalcuator;
@@ -43,12 +57,14 @@ public class OverdueWrapper {
public OverdueWrapper(final ImmutableAccountData overdueable,
final BlockingInternalApi api,
final OverdueStateSet overdueStateSet,
+ final GlobalLocker locker,
final Clock clock,
final BillingStateCalculator billingStateCalcuator,
final OverdueStateApplicator overdueStateApplicator) {
this.overdueable = overdueable;
this.overdueStateSet = overdueStateSet;
this.api = api;
+ this.locker = locker;
this.clock = clock;
this.billingStateCalcuator = billingStateCalcuator;
this.overdueStateApplicator = overdueStateApplicator;
@@ -59,6 +75,23 @@ public class OverdueWrapper {
return overdueStateSet.getClearState();
}
+ GlobalLock lock = null;
+ try {
+ lock = locker.lockWithNumberOfTries(LockerType.ACCNT_INV_PAY.toString(), overdueable.getId().toString(), MAX_LOCK_RETRIES);
+
+ return refreshWithLock(context);
+ } catch (final LockFailedException e) {
+ // Not good!
+ log.error(String.format("Failed to process overdue for account %s", overdueable.getId()), e);
+ } finally {
+ if (lock != null) {
+ lock.release();
+ }
+ }
+ return null;
+ }
+
+ private OverdueState refreshWithLock(final InternalCallContext context) throws OverdueException, OverdueApiException {
final BillingState billingState = billingState(context);
final String previousOverdueStateName = api.getBlockingStateForService(overdueable.getId(), BlockingStateType.ACCOUNT, OverdueService.OVERDUE_SERVICE_NAME, context).getStateName();
@@ -71,6 +104,22 @@ public class OverdueWrapper {
}
public void clear(final InternalCallContext context) throws OverdueException, OverdueApiException {
+ GlobalLock lock = null;
+ try {
+ lock = locker.lockWithNumberOfTries(LockerType.ACCNT_INV_PAY.toString(), overdueable.getId().toString(), MAX_LOCK_RETRIES);
+
+ clearWithLock(context);
+ } catch (final LockFailedException e) {
+ // Not good!
+ log.error(String.format("Failed to clear overdue for account %s", overdueable.getId()), e);
+ } finally {
+ if (lock != null) {
+ lock.release();
+ }
+ }
+ }
+
+ private void clearWithLock(final InternalCallContext context) throws OverdueException, OverdueApiException {
final String previousOverdueStateName = api.getBlockingStateForService(overdueable.getId(), BlockingStateType.ACCOUNT, OverdueService.OVERDUE_SERVICE_NAME, context).getStateName();
final OverdueState previousOverdueState = overdueStateSet.findState(previousOverdueStateName);
overdueStateApplicator.clear(overdueable, previousOverdueState, overdueStateSet.getClearState(), context);
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/wrapper/OverdueWrapperFactory.java b/overdue/src/main/java/org/killbill/billing/overdue/wrapper/OverdueWrapperFactory.java
index 9c51f85..bec4c53 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/wrapper/OverdueWrapperFactory.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/wrapper/OverdueWrapperFactory.java
@@ -1,7 +1,9 @@
/*
* Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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:
*
@@ -35,6 +37,7 @@ import org.killbill.billing.overdue.config.DefaultOverdueStateSet;
import org.killbill.billing.overdue.config.api.OverdueException;
import org.killbill.billing.overdue.config.api.OverdueStateSet;
import org.killbill.clock.Clock;
+import org.killbill.commons.locker.GlobalLocker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -48,11 +51,14 @@ public class OverdueWrapperFactory {
private final BillingStateCalculator billingStateCalculator;
private final OverdueStateApplicator overdueStateApplicator;
private final BlockingInternalApi api;
+ private final GlobalLocker locker;
private final Clock clock;
private final OverdueConfigCache overdueConfigCache;
@Inject
- public OverdueWrapperFactory(final BlockingInternalApi api, final Clock clock,
+ public OverdueWrapperFactory(final BlockingInternalApi api,
+ final GlobalLocker locker,
+ final Clock clock,
final BillingStateCalculator billingStateCalculator,
final OverdueStateApplicator overdueStateApplicatorBundle,
final OverdueConfigCache overdueConfigCache,
@@ -61,24 +67,20 @@ public class OverdueWrapperFactory {
this.overdueStateApplicator = overdueStateApplicatorBundle;
this.accountApi = accountApi;
this.api = api;
+ this.locker = locker;
this.clock = clock;
this.overdueConfigCache = overdueConfigCache;
}
- @SuppressWarnings("unchecked")
public OverdueWrapper createOverdueWrapperFor(final ImmutableAccountData blockable, final InternalTenantContext context) throws OverdueException {
- return (OverdueWrapper) new OverdueWrapper(blockable, api, getOverdueStateSet(context),
- clock, billingStateCalculator, overdueStateApplicator);
+ return new OverdueWrapper(blockable, api, getOverdueStateSet(context), locker, clock, billingStateCalculator, overdueStateApplicator);
}
- @SuppressWarnings("unchecked")
public OverdueWrapper createOverdueWrapperFor(final UUID id, final InternalTenantContext context) throws OverdueException {
-
try {
final ImmutableAccountData account = accountApi.getImmutableAccountDataById(id, context);
- return new OverdueWrapper(account, api, getOverdueStateSet(context),
- clock, billingStateCalculator, overdueStateApplicator);
- } catch (AccountApiException e) {
+ return new OverdueWrapper(account, api, getOverdueStateSet(context), locker, clock, billingStateCalculator, overdueStateApplicator);
+ } catch (final AccountApiException e) {
throw new OverdueException(e);
}
}
@@ -104,7 +106,7 @@ public class OverdueWrapperFactory {
} else {
return ((DefaultOverdueConfig) overdueConfig).getOverdueStatesAccount();
}
- } catch (OverdueApiException e) {
+ } catch (final OverdueApiException e) {
throw new OverdueException(e);
}
}
diff --git a/overdue/src/test/java/org/killbill/billing/overdue/glue/TestOverdueModule.java b/overdue/src/test/java/org/killbill/billing/overdue/glue/TestOverdueModule.java
index d858aa1..142f39d 100644
--- a/overdue/src/test/java/org/killbill/billing/overdue/glue/TestOverdueModule.java
+++ b/overdue/src/test/java/org/killbill/billing/overdue/glue/TestOverdueModule.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014 Groupon, Inc
- * Copyright 2014 The Billing Project, LLC
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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
@@ -18,6 +18,15 @@
package org.killbill.billing.overdue.glue;
+import java.util.List;
+import java.util.UUID;
+
+import org.killbill.billing.callcontext.InternalCallContext;
+import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.entitlement.api.BlockingState;
+import org.killbill.billing.entitlement.api.BlockingStateType;
+import org.killbill.billing.junction.BlockingInternalApi;
+import org.killbill.billing.junction.DefaultBlockingState;
import org.killbill.billing.mock.glue.MockAccountModule;
import org.killbill.billing.mock.glue.MockEntitlementModule;
import org.killbill.billing.mock.glue.MockInvoiceModule;
@@ -25,7 +34,6 @@ import org.killbill.billing.mock.glue.MockTagModule;
import org.killbill.billing.mock.glue.MockTenantModule;
import org.killbill.billing.overdue.TestOverdueHelper;
import org.killbill.billing.overdue.applicator.OverdueBusListenerTester;
-import org.killbill.billing.overdue.caching.EhCacheOverdueConfigCache;
import org.killbill.billing.overdue.caching.MockOverdueConfigCache;
import org.killbill.billing.overdue.caching.OverdueCacheInvalidationCallback;
import org.killbill.billing.overdue.caching.OverdueConfigCache;
@@ -37,6 +45,8 @@ import org.killbill.billing.util.glue.AuditModule;
import org.killbill.billing.util.glue.CacheModule;
import org.killbill.billing.util.glue.CallContextModule;
import org.killbill.billing.util.glue.CustomFieldModule;
+import org.killbill.billing.util.glue.MemoryGlobalLockerModule;
+import org.killbill.clock.ClockMock;
import com.google.inject.name.Names;
@@ -56,15 +66,12 @@ public class TestOverdueModule extends DefaultOverdueModule {
install(new CustomFieldModule(configSource));
install(new EmailModule(configSource));
install(new MockAccountModule(configSource));
- install(new MockEntitlementModule(configSource));
+ install(new MockEntitlementModule(configSource, new ApplicatorBlockingApi()));
install(new MockInvoiceModule(configSource));
install(new MockTagModule(configSource));
install(new TemplateModule(configSource));
install(new MockTenantModule(configSource));
-
-
- // We can't use the dumb mocks in MockJunctionModule here
- install(new ApplicatorMockJunctionModule(configSource));
+ install(new MemoryGlobalLockerModule(configSource));
bind(OverdueBusListenerTester.class).asEagerSingleton();
bind(TestOverdueHelper.class).asEagerSingleton();
@@ -75,4 +82,31 @@ public class TestOverdueModule extends DefaultOverdueModule {
bind(CacheInvalidationCallback.class).annotatedWith(Names.named(OVERDUE_INVALIDATION_CALLBACK)).to(OverdueCacheInvalidationCallback.class).asEagerSingleton();
}
+ public static class ApplicatorBlockingApi implements BlockingInternalApi {
+
+ private BlockingState blockingState;
+
+ public BlockingState getBlockingState() {
+ return blockingState;
+ }
+
+ @Override
+ public BlockingState getBlockingStateForService(final UUID blockableId, final BlockingStateType blockingStateType, final String serviceName, final InternalTenantContext context) {
+ if (blockingState != null && blockingState.getBlockedId().equals(blockableId)) {
+ return blockingState;
+ } else {
+ return DefaultBlockingState.getClearState(blockingStateType, serviceName, new ClockMock());
+ }
+ }
+
+ @Override
+ public List<BlockingState> getBlockingAllForAccount(final InternalTenantContext context) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setBlockingState(final BlockingState state, final InternalCallContext context) {
+ blockingState = state;
+ }
+ }
}
diff --git a/overdue/src/test/java/org/killbill/billing/overdue/TestOverdueHelper.java b/overdue/src/test/java/org/killbill/billing/overdue/TestOverdueHelper.java
index 0e74a22..3f09ee6 100644
--- a/overdue/src/test/java/org/killbill/billing/overdue/TestOverdueHelper.java
+++ b/overdue/src/test/java/org/killbill/billing/overdue/TestOverdueHelper.java
@@ -1,7 +1,9 @@
/*
* Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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:
*
@@ -25,6 +27,7 @@ import org.joda.time.DateTimeZone;
import org.joda.time.LocalDate;
import org.killbill.billing.account.api.ImmutableAccountData;
import org.killbill.billing.overdue.api.OverdueState;
+import org.killbill.billing.overdue.glue.TestOverdueModule.ApplicatorBlockingApi;
import org.mockito.Mockito;
import org.testng.Assert;
@@ -32,7 +35,6 @@ import org.killbill.billing.account.api.AccountApiException;
import org.killbill.billing.invoice.api.Invoice;
import org.killbill.billing.invoice.api.InvoiceItem;
import org.killbill.billing.entitlement.api.BlockingState;
-import org.killbill.billing.overdue.glue.ApplicatorMockJunctionModule.ApplicatorBlockingApi;
import org.killbill.billing.subscription.api.user.SubscriptionBaseApiException;
import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.account.api.AccountInternalApi;
diff --git a/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentGatewayApi.java b/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentGatewayApi.java
index ad1b38e..ec17b19 100644
--- a/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentGatewayApi.java
+++ b/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentGatewayApi.java
@@ -148,7 +148,7 @@ public class DefaultPaymentGatewayApi extends DefaultApiBase implements PaymentG
null, null, true, paymentControlPluginNames, properties, callContext);
} catch (final PaymentControlApiException e) {
- throw new PaymentApiException(ErrorCode.PAYMENT_PLUGIN_EXCEPTION, e);
+ throw new PaymentApiException(e, ErrorCode.PAYMENT_PLUGIN_EXCEPTION, e);
}
try {
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/PaymentGatewayProcessor.java b/payment/src/main/java/org/killbill/billing/payment/core/PaymentGatewayProcessor.java
index 3ca5b2e..53ffd63 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/PaymentGatewayProcessor.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/PaymentGatewayProcessor.java
@@ -92,7 +92,7 @@ public class PaymentGatewayProcessor extends ProcessorBase {
final GatewayNotification result = plugin.processNotification(notification, properties, callContext);
return PluginDispatcher.createPluginDispatcherReturnType(result == null ? new DefaultNoOpGatewayNotification() : result);
} catch (final PaymentPluginApiException e) {
- throw new PaymentApiException(ErrorCode.PAYMENT_PLUGIN_EXCEPTION, e.getErrorMessage());
+ throw new PaymentApiException(e, ErrorCode.PAYMENT_PLUGIN_EXCEPTION, e.getErrorMessage());
}
}
}, paymentPluginNotificationDispatcher);
@@ -101,7 +101,7 @@ public class PaymentGatewayProcessor extends ProcessorBase {
try {
return plugin.processNotification(notification, properties, callContext);
} catch (final PaymentPluginApiException e) {
- throw new PaymentApiException(ErrorCode.PAYMENT_PLUGIN_EXCEPTION, e.getErrorMessage());
+ throw new PaymentApiException(e, ErrorCode.PAYMENT_PLUGIN_EXCEPTION, e.getErrorMessage());
}
}
}
@@ -123,7 +123,7 @@ public class PaymentGatewayProcessor extends ProcessorBase {
} catch (final RuntimeException e) {
throw new PaymentApiException(e, ErrorCode.PAYMENT_INTERNAL_ERROR, Objects.firstNonNull(e.getMessage(), ""));
} catch (final PaymentPluginApiException e) {
- throw new PaymentApiException(ErrorCode.PAYMENT_PLUGIN_EXCEPTION, e.getErrorMessage());
+ throw new PaymentApiException(e, ErrorCode.PAYMENT_PLUGIN_EXCEPTION, e.getErrorMessage());
}
}
}, paymentPluginFormDispatcher);
@@ -132,7 +132,7 @@ public class PaymentGatewayProcessor extends ProcessorBase {
try {
return plugin.buildFormDescriptor(account.getId(), customFields, properties, callContext);
} catch (final PaymentPluginApiException e) {
- throw new PaymentApiException(ErrorCode.PAYMENT_PLUGIN_EXCEPTION, e.getErrorMessage());
+ throw new PaymentApiException(e, ErrorCode.PAYMENT_PLUGIN_EXCEPTION, e.getErrorMessage());
}
}
}
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/PluginControlPaymentProcessor.java b/payment/src/main/java/org/killbill/billing/payment/core/PluginControlPaymentProcessor.java
index 96a6eda..85ba858 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/PluginControlPaymentProcessor.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/PluginControlPaymentProcessor.java
@@ -205,6 +205,10 @@ public class PluginControlPaymentProcessor extends ProcessorBase {
final CallContext callContext = buildCallContext(internalCallContext);
final State state = paymentControlStateMachineHelper.getState(attempt.getStateName());
+
+ log.debug("Retrying attemptId={}, paymentExternalKey={}, transactionExternalKey={}. paymentControlPluginNames={}, now={}",
+ attemptId, attempt.getPaymentExternalKey(), attempt.getTransactionExternalKey(), paymentControlPluginNames, clock.getUTCNow());
+
pluginControlledPaymentAutomatonRunner.run(state,
false,
attempt.getTransactionType(),
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PaymentOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PaymentOperation.java
index 1240635..4b11c6b 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PaymentOperation.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PaymentOperation.java
@@ -206,7 +206,7 @@ public abstract class PaymentOperation extends OperationCallbackBase<PaymentTran
return paymentStateContext.getOverridePluginOperationResult();
}
} catch (final PaymentPluginApiException e) {
- throw new PaymentApiException(ErrorCode.PAYMENT_PLUGIN_EXCEPTION, e.getErrorMessage());
+ throw new PaymentApiException(e, ErrorCode.PAYMENT_PLUGIN_EXCEPTION, e.getErrorMessage());
}
}
diff --git a/payment/src/main/java/org/killbill/billing/payment/invoice/InvoicePaymentControlPluginApi.java b/payment/src/main/java/org/killbill/billing/payment/invoice/InvoicePaymentControlPluginApi.java
index ce2913e..86b22f2 100644
--- a/payment/src/main/java/org/killbill/billing/payment/invoice/InvoicePaymentControlPluginApi.java
+++ b/payment/src/main/java/org/killbill/billing/payment/invoice/InvoicePaymentControlPluginApi.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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
@@ -100,8 +100,9 @@ public final class InvoicePaymentControlPluginApi implements PaymentControlPlugi
private final Logger log = LoggerFactory.getLogger(InvoicePaymentControlPluginApi.class);
@Inject
- public InvoicePaymentControlPluginApi(final PaymentConfig paymentConfig, final InvoiceInternalApi invoiceApi, final TagUserApi tagApi, final PaymentDao paymentDao,
- final InvoicePaymentControlDao invoicePaymentControlDao,
+ public InvoicePaymentControlPluginApi(final PaymentConfig paymentConfig,
+ final InvoiceInternalApi invoiceApi, final TagUserApi tagApi,
+ final PaymentDao paymentDao, final InvoicePaymentControlDao invoicePaymentControlDao,
@Named(PaymentModule.RETRYABLE_NAMED) final RetryServiceScheduler retryServiceScheduler,
final InternalCallContextFactory internalCallContextFactory, final Clock clock) {
this.paymentConfig = paymentConfig;
@@ -153,6 +154,7 @@ public final class InvoicePaymentControlPluginApi implements PaymentControlPlugi
if (existingInvoicePayment != null && existingInvoicePayment.isSuccess()) {
log.info("onSuccessCall was already completed for payment purchase: " + paymentControlContext.getPaymentId());
} else {
+ log.debug("Notifying invoice of successful payment: id={}, amount={}, currency={}, invoiceId={}", paymentControlContext.getPaymentId(), paymentControlContext.getAmount(), paymentControlContext.getCurrency(), invoiceId);
invoiceApi.notifyOfPayment(invoiceId,
paymentControlContext.getAmount(),
paymentControlContext.getCurrency(),
@@ -191,6 +193,7 @@ public final class InvoicePaymentControlPluginApi implements PaymentControlPlugi
} catch (final InvoiceApiException e) {
log.error("InvoicePaymentControlPluginApi onSuccessCall failed for attemptId = " + paymentControlContext.getAttemptPaymentId() + ", transactionType = " + transactionType, e);
}
+
return new DefaultOnSuccessPaymentControlResult();
}
@@ -198,34 +201,37 @@ public final class InvoicePaymentControlPluginApi implements PaymentControlPlugi
public OnFailurePaymentControlResult onFailureCall(final PaymentControlContext paymentControlContext, final Iterable<PluginProperty> pluginProperties) throws PaymentControlApiException {
final InternalCallContext internalContext = internalCallContextFactory.createInternalCallContext(paymentControlContext.getAccountId(), paymentControlContext);
final TransactionType transactionType = paymentControlContext.getTransactionType();
+
+ DateTime nextRetryDate = null;
switch (transactionType) {
case PURCHASE:
final UUID invoiceId = getInvoiceId(pluginProperties);
- if (paymentControlContext.getPaymentId() != null) {
- try {
- invoiceApi.notifyOfPayment(invoiceId,
- paymentControlContext.getAmount(),
- paymentControlContext.getCurrency(),
- // processed currency may be null so we use currency; processed currency will be updated if/when payment succeeds
- paymentControlContext.getCurrency(),
- paymentControlContext.getPaymentId(),
- paymentControlContext.getCreatedDate(),
- false,
- internalContext);
- } catch (InvoiceApiException e) {
- log.error("InvoicePaymentControlPluginApi onFailureCall failed ton update invoice for attemptId = " + paymentControlContext.getAttemptPaymentId() + ", transactionType = " + transactionType, e);
- }
+ try {
+ log.debug("Notifying invoice of failed payment: id={}, amount={}, currency={}, invoiceId={}", paymentControlContext.getPaymentId(), paymentControlContext.getAmount(), paymentControlContext.getCurrency(), invoiceId);
+ invoiceApi.notifyOfPayment(invoiceId,
+ paymentControlContext.getAmount(),
+ paymentControlContext.getCurrency(),
+ // processed currency may be null so we use currency; processed currency will be updated if/when payment succeeds
+ paymentControlContext.getCurrency(),
+ paymentControlContext.getPaymentId(),
+ paymentControlContext.getCreatedDate(),
+ false,
+ internalContext);
+ } catch (final InvoiceApiException e) {
+ log.error("InvoicePaymentControlPluginApi onFailureCall failed ton update invoice for attemptId = " + paymentControlContext.getAttemptPaymentId() + ", transactionType = " + transactionType, e);
}
- final DateTime nextRetryDate = computeNextRetryDate(paymentControlContext.getPaymentExternalKey(), paymentControlContext.isApiPayment(), internalContext);
- return new DefaultFailureCallResult(nextRetryDate);
+ nextRetryDate = computeNextRetryDate(paymentControlContext.getPaymentExternalKey(), paymentControlContext.isApiPayment(), internalContext);
+ break;
case REFUND:
case CHARGEBACK:
- // We don't retry REFUND, CHARGEBACK
- return new DefaultFailureCallResult(null);
+ // We don't retry REFUND, CHARGEBACK
+ break;
default:
throw new IllegalStateException("Unexpected transactionType " + transactionType);
}
+
+ return new DefaultFailureCallResult(nextRetryDate);
}
public void process_AUTO_PAY_OFF_removal(final UUID accountId, final InternalCallContext internalCallContext) {
@@ -407,6 +413,7 @@ public final class InvoicePaymentControlPluginApi implements PaymentControlPlugi
try {
retryInDays = retryDays.get(retryCount);
result = nextRetryDate.plusDays(retryInDays);
+ log.debug("Next retryDate={}, retryInDays={}, retryCount={}, now={}", result, retryInDays, retryCount, clock.getUTCNow());
} catch (final NumberFormatException ex) {
log.error("Could not get retry day for retry count {}", retryCount);
}
@@ -427,6 +434,7 @@ public final class InvoicePaymentControlPluginApi implements PaymentControlPlugi
nbSec = nbSec * paymentConfig.getPluginFailureRetryMultiplier();
}
result = clock.getUTCNow().plusSeconds(nbSec);
+ log.debug("Next retryDate={}, retryAttempt={}, now={}", result, retryAttempt, clock.getUTCNow());
}
return result;
}
diff --git a/payment/src/main/java/org/killbill/billing/payment/retry/BaseRetryService.java b/payment/src/main/java/org/killbill/billing/payment/retry/BaseRetryService.java
index a5ec4ce..7757b65 100644
--- a/payment/src/main/java/org/killbill/billing/payment/retry/BaseRetryService.java
+++ b/payment/src/main/java/org/killbill/billing/payment/retry/BaseRetryService.java
@@ -116,6 +116,7 @@ public abstract class BaseRetryService implements RetryService {
final NotificationQueue retryQueue = notificationQueueService.getNotificationQueue(DefaultPaymentService.SERVICE_NAME, getQueueName());
final NotificationEvent key = new PaymentRetryNotificationKey(attemptId, paymentControlPluginNames);
if (retryQueue != null) {
+ log.debug("Scheduling retry timeOfRetry={}, key={}", timeOfRetry, key);
if (transactionalDao == null) {
retryQueue.recordFutureNotification(timeOfRetry, key, context.getUserToken(), context.getAccountRecordId(), context.getTenantRecordId());
} else {
diff --git a/payment/src/main/java/org/killbill/billing/payment/retry/PaymentRetryNotificationKey.java b/payment/src/main/java/org/killbill/billing/payment/retry/PaymentRetryNotificationKey.java
index d2569fd..eb4d38a 100644
--- a/payment/src/main/java/org/killbill/billing/payment/retry/PaymentRetryNotificationKey.java
+++ b/payment/src/main/java/org/killbill/billing/payment/retry/PaymentRetryNotificationKey.java
@@ -1,7 +1,9 @@
/*
* Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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:
*
@@ -43,4 +45,13 @@ public class PaymentRetryNotificationKey implements NotificationEvent {
public List<String> getPaymentControlPluginNames() {
return paymentControlPluginNames;
}
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("PaymentRetryNotificationKey{");
+ sb.append("attemptId=").append(attemptId);
+ sb.append(", paymentControlPluginNames=").append(paymentControlPluginNames);
+ sb.append('}');
+ return sb.toString();
+ }
}
diff --git a/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApi.java b/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApi.java
index 2fc2626..d5c1aa5 100644
--- a/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApi.java
+++ b/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApi.java
@@ -876,7 +876,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
final String pluginName = mockPaymentProviderPlugin.PLUGIN_NAME;
- mockPaymentProviderPlugin.makePluginWaitSomeMilliseconds(1100);
+ mockPaymentProviderPlugin.makePluginWaitSomeMilliseconds((int) (paymentConfig.getPaymentPluginTimeout().getMillis() + 100));
SpyLogger spyLogger = withSpyLogger(OperationCallbackBase.class, new Callable<Void>() {
diff --git a/payment/src/test/java/org/killbill/billing/payment/dispatcher/TestPluginDispatcher.java b/payment/src/test/java/org/killbill/billing/payment/dispatcher/TestPluginDispatcher.java
index ad324a9..06077cc 100644
--- a/payment/src/test/java/org/killbill/billing/payment/dispatcher/TestPluginDispatcher.java
+++ b/payment/src/test/java/org/killbill/billing/payment/dispatcher/TestPluginDispatcher.java
@@ -53,7 +53,7 @@ public class TestPluginDispatcher extends PaymentTestSuiteNoDB {
voidPluginDispatcher.dispatchWithTimeout(new Callable<PluginDispatcherReturnType<Void>>() {
@Override
public PluginDispatcherReturnType<Void> call() throws Exception {
- Thread.sleep(1000);
+ Thread.sleep(paymentConfig.getPaymentPluginTimeout().getMillis() + 100);
return null;
}
}, 100, TimeUnit.MILLISECONDS);
diff --git a/payment/src/test/java/org/killbill/billing/payment/glue/MockEntitlementModuleForPayment.java b/payment/src/test/java/org/killbill/billing/payment/glue/MockEntitlementModuleForPayment.java
new file mode 100644
index 0000000..ae2c6aa
--- /dev/null
+++ b/payment/src/test/java/org/killbill/billing/payment/glue/MockEntitlementModuleForPayment.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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.payment.glue;
+
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.entitlement.api.BlockingState;
+import org.killbill.billing.entitlement.api.BlockingStateType;
+import org.killbill.billing.junction.BlockingInternalApi;
+import org.killbill.billing.junction.DefaultBlockingState;
+import org.killbill.billing.mock.glue.MockEntitlementModule;
+import org.killbill.billing.platform.api.KillbillConfigSource;
+import org.mockito.Mockito;
+
+import com.google.common.collect.ImmutableList;
+import com.google.inject.Module;
+
+public class MockEntitlementModuleForPayment extends MockEntitlementModule {
+
+ public MockEntitlementModuleForPayment(final KillbillConfigSource configSource) {
+ super(configSource);
+ }
+
+ @Override
+ public void installBlockingApi() {
+ super.installBlockingApi();
+
+ final BlockingState blockingState = new DefaultBlockingState(null, BlockingStateType.ACCOUNT, DefaultBlockingState.CLEAR_STATE_NAME, "test", false, false, false, new DateTime(DateTimeZone.UTC));
+ Mockito.when(blockingApi.getBlockingAllForAccount(Mockito.<InternalTenantContext>any())).thenReturn(ImmutableList.<BlockingState>of(blockingState));
+ Mockito.when(blockingApi.getBlockingStateForService(Mockito.<UUID>any(), Mockito.<BlockingStateType>any(), Mockito.anyString(), Mockito.<InternalTenantContext>any())).thenReturn(blockingState);
+ }
+}
diff --git a/payment/src/test/java/org/killbill/billing/payment/glue/TestPaymentModule.java b/payment/src/test/java/org/killbill/billing/payment/glue/TestPaymentModule.java
index 2b1ca1f..c5d16c5 100644
--- a/payment/src/test/java/org/killbill/billing/payment/glue/TestPaymentModule.java
+++ b/payment/src/test/java/org/killbill/billing/payment/glue/TestPaymentModule.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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
@@ -73,6 +73,7 @@ public class TestPaymentModule extends PaymentModule {
install(new MockTenantModule(configSource));
install(new CacheModule(configSource));
install(new CallContextModule(configSource));
+
installExternalApis();
bind(TestPaymentHelper.class).asEagerSingleton();
}
diff --git a/payment/src/test/java/org/killbill/billing/payment/TestJanitor.java b/payment/src/test/java/org/killbill/billing/payment/TestJanitor.java
index d81caa8..f62e802 100644
--- a/payment/src/test/java/org/killbill/billing/payment/TestJanitor.java
+++ b/payment/src/test/java/org/killbill/billing/payment/TestJanitor.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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
@@ -24,6 +24,7 @@ import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.Callable;
+import java.util.concurrent.TimeUnit;
import org.joda.time.LocalDate;
import org.killbill.billing.account.api.Account;
@@ -47,7 +48,6 @@ import org.killbill.billing.payment.dao.PaymentAttemptModelDao;
import org.killbill.billing.payment.dao.PaymentTransactionModelDao;
import org.killbill.billing.payment.glue.DefaultPaymentService;
import org.killbill.billing.payment.invoice.InvoicePaymentControlPluginApi;
-import org.killbill.billing.payment.plugin.api.PaymentPluginApiException;
import org.killbill.billing.payment.plugin.api.PaymentPluginStatus;
import org.killbill.billing.payment.plugin.api.PaymentTransactionInfoPlugin;
import org.killbill.billing.payment.provider.DefaultNoOpPaymentInfoPlugin;
@@ -135,10 +135,14 @@ public class TestJanitor extends PaymentTestSuiteWithEmbeddedDB {
eventBus.register(testListener);
mockPaymentProviderPlugin.clear();
account = testHelper.createTestAccount("bobo@gmail.com", true);
+
+ testListener.assertListenerStatus();
}
@AfterMethod(groups = "slow")
public void afterMethod() throws Exception {
+ testListener.assertListenerStatus();
+
janitor.stop();
eventBus.unregister(handler);
eventBus.unregister(testListener);
@@ -153,7 +157,9 @@ public class TestJanitor extends PaymentTestSuiteWithEmbeddedDB {
final UUID bundleId = UUID.randomUUID();
final LocalDate now = clock.getUTCToday();
+ testListener.pushExpectedEvent(NextEvent.INVOICE);
final Invoice invoice = testHelper.createTestInvoice(account, now, Currency.USD);
+ testListener.assertListenerStatus();
final String paymentExternalKey = invoice.getId().toString();
final String transactionExternalKey = "wouf wouf";
@@ -169,8 +175,10 @@ public class TestJanitor extends PaymentTestSuiteWithEmbeddedDB {
new BigDecimal("1.0"),
Currency.USD));
+ testListener.pushExpectedEvent(NextEvent.PAYMENT);
final Payment payment = paymentApi.createPurchaseWithPaymentControl(account, account.getPaymentMethodId(), null, requestedAmount, Currency.USD, paymentExternalKey, transactionExternalKey,
createPropertiesForInvoice(invoice), INVOICE_PAYMENT, callContext);
+ testListener.assertListenerStatus();
assertEquals(payment.getTransactions().size(), 1);
assertEquals(payment.getTransactions().get(0).getTransactionStatus(), TransactionStatus.SUCCESS);
assertEquals(payment.getTransactions().get(0).getTransactionType(), TransactionType.PURCHASE);
@@ -204,7 +212,9 @@ public class TestJanitor extends PaymentTestSuiteWithEmbeddedDB {
final UUID bundleId = UUID.randomUUID();
final LocalDate now = clock.getUTCToday();
+ testListener.pushExpectedEvent(NextEvent.INVOICE);
final Invoice invoice = testHelper.createTestInvoice(account, now, Currency.USD);
+ testListener.assertListenerStatus();
final String paymentExternalKey = invoice.getId().toString();
final String transactionExternalKey = "craboom";
@@ -221,8 +231,10 @@ public class TestJanitor extends PaymentTestSuiteWithEmbeddedDB {
Currency.USD);
invoice.addInvoiceItem(invoiceItem);
+ testListener.pushExpectedEvent(NextEvent.PAYMENT);
final Payment payment = paymentApi.createPurchaseWithPaymentControl(account, account.getPaymentMethodId(), null, requestedAmount, Currency.USD, paymentExternalKey, transactionExternalKey,
createPropertiesForInvoice(invoice), INVOICE_PAYMENT, callContext);
+ testListener.assertListenerStatus();
final List<PluginProperty> refundProperties = new ArrayList<PluginProperty>();
final HashMap<UUID, BigDecimal> uuidBigDecimalHashMap = new HashMap<UUID, BigDecimal>();
@@ -230,8 +242,10 @@ public class TestJanitor extends PaymentTestSuiteWithEmbeddedDB {
final PluginProperty refundIdsProp = new PluginProperty(InvoicePaymentControlPluginApi.PROP_IPCD_REFUND_IDS_WITH_AMOUNT_KEY, uuidBigDecimalHashMap, false);
refundProperties.add(refundIdsProp);
+ testListener.pushExpectedEvent(NextEvent.PAYMENT);
final Payment payment2 = paymentApi.createRefundWithPaymentControl(account, payment.getId(), null, Currency.USD, transactionExternalKey2,
refundProperties, INVOICE_PAYMENT, callContext);
+ testListener.assertListenerStatus();
assertEquals(payment2.getTransactions().size(), 2);
PaymentTransaction refundTransaction = payment2.getTransactions().get(1);
@@ -280,8 +294,10 @@ public class TestJanitor extends PaymentTestSuiteWithEmbeddedDB {
testListener.assertListenerStatus();
// Move clock for notification to be processed
+ testListener.pushExpectedEvent(NextEvent.PAYMENT);
clock.addDeltaFromReality(5 * 60 * 1000);
assertNotificationsCompleted(internalCallContext, 5);
+ testListener.assertListenerStatus();
final Payment updatedPayment = paymentApi.getPayment(payment.getId(), false, ImmutableList.<PluginProperty>of(), callContext);
assertEquals(updatedPayment.getTransactions().get(0).getTransactionStatus(), TransactionStatus.SUCCESS);
@@ -314,8 +330,10 @@ public class TestJanitor extends PaymentTestSuiteWithEmbeddedDB {
Assert.assertEquals(paymentTransactionHistoryBeforeJanitor.size(), 3);
// Move clock for notification to be processed
+ testListener.pushExpectedEvent(NextEvent.PAYMENT_ERROR);
clock.addDeltaFromReality(5 * 60 * 1000);
assertNotificationsCompleted(internalCallContext, 5);
+ testListener.assertListenerStatus();
// Proves the Janitor ran (and updated the transaction)
final List<PaymentTransactionModelDao> paymentTransactionHistoryAfterJanitor = getPaymentTransactionHistory(transactionExternalKey);
@@ -391,16 +409,18 @@ public class TestJanitor extends PaymentTestSuiteWithEmbeddedDB {
testListener.assertListenerStatus();
// Move clock for notification to be processed
+ testListener.pushExpectedEvent(NextEvent.PAYMENT);
clock.addDeltaFromReality(5 * 60 * 1000);
assertNotificationsCompleted(internalCallContext, 5);
+ testListener.assertListenerStatus();
final Payment updatedPayment = paymentApi.getPayment(payment.getId(), false, ImmutableList.<PluginProperty>of(), callContext);
Assert.assertEquals(updatedPayment.getTransactions().get(0).getTransactionStatus(), TransactionStatus.SUCCESS);
}
// The test will check that when a PENDING entry stays PENDING, we go through all our retries and evebtually give up (no infinite loop of retries)
@Test(groups = "slow")
- public void testPendingEntriesThatDontMove() throws PaymentApiException, EventBusException, NoSuchNotificationQueue, PaymentPluginApiException, InterruptedException {
+ public void testPendingEntriesThatDontMove() throws Exception {
final BigDecimal requestedAmount = BigDecimal.TEN;
final String paymentExternalKey = "haha";
@@ -446,7 +466,12 @@ public class TestJanitor extends PaymentTestSuiteWithEmbeddedDB {
Assert.assertEquals(updatedPayment.getTransactions().get(0).getTransactionStatus(), TransactionStatus.PENDING);
}
- assertEquals(getPendingNotificationCnt(internalCallContext), 0);
+ await().atMost(5, TimeUnit.SECONDS).until(new Callable<Boolean>() {
+ @Override
+ public Boolean call() throws Exception {
+ return getPendingNotificationCnt(internalCallContext) == 0;
+ }
+ });
}
diff --git a/payment/src/test/resources/payment.properties b/payment/src/test/resources/payment.properties
index 7314dfa..8b77678 100644
--- a/payment/src/test/resources/payment.properties
+++ b/payment/src/test/resources/payment.properties
@@ -1,4 +1,4 @@
org.killbill.payment.failure.retry.start.sec=3600
org.killbill.payment.failure.retry.multiplier=1
org.killbill.payment.failure.retry.max.attempts=3
-org.killbill.payment.plugin.timeout=1s
+org.killbill.payment.plugin.timeout=2s
pom.xml 2(+1 -1)
diff --git a/pom.xml b/pom.xml
index 894eeaa..a90c5fa 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.79</version>
+ <version>0.80</version>
</parent>
<artifactId>killbill</artifactId>
<version>0.16.1-SNAPSHOT</version>
diff --git a/profiles/killbill/src/main/java/org/killbill/billing/server/security/TenantFilter.java b/profiles/killbill/src/main/java/org/killbill/billing/server/security/TenantFilter.java
index a0af60a..3c6c6ce 100644
--- a/profiles/killbill/src/main/java/org/killbill/billing/server/security/TenantFilter.java
+++ b/profiles/killbill/src/main/java/org/killbill/billing/server/security/TenantFilter.java
@@ -147,8 +147,10 @@ public class TenantFilter implements Filter {
return shouldContinue;
}
+
+
private boolean isPermissionRequest(final String path, final String httpMethod) {
- return path != null && path.startsWith(JaxrsResource.SECURITY_PATH) && "GET".equals(httpMethod);
+ return path != null && path.startsWith(JaxrsResource.SECURITY_PATH);
}
private boolean isTenantCreationRequest(final String path, final String httpMethod) {
@@ -163,6 +165,7 @@ public class TenantFilter implements Filter {
return "OPTIONS".equals(httpMethod);
}
+
private boolean isNotKbNorPluginResourceRequest(final String path, final String httpMethod) {
return !isPluginRequest(path) && !isKbApiRequest(path) && "GET".equals(httpMethod);
}
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/server/dao/TestEmbeddedDBFactory.java b/profiles/killbill/src/test/java/org/killbill/billing/server/dao/TestEmbeddedDBFactory.java
index cfc7184..8faae41 100644
--- a/profiles/killbill/src/test/java/org/killbill/billing/server/dao/TestEmbeddedDBFactory.java
+++ b/profiles/killbill/src/test/java/org/killbill/billing/server/dao/TestEmbeddedDBFactory.java
@@ -40,6 +40,10 @@ public class TestEmbeddedDBFactory extends KillbillTestSuite {
Assert.assertEquals(h2EmbeddedDb.getDBEngine(), DBEngine.H2);
checkEmbeddedDb(h2EmbeddedDb);
+ final EmbeddedDB postgresEmbeddedDb = EmbeddedDBFactory.get(createDaoConfig("jdbc:postgresql://127.0.0.1:5432/killbill", "root", "root"));
+ Assert.assertEquals(postgresEmbeddedDb.getDBEngine(), DBEngine.POSTGRESQL);
+ checkEmbeddedDb(postgresEmbeddedDb);
+
final EmbeddedDB genericEmbeddedDb = EmbeddedDBFactory.get(createDaoConfig("jdbc:derby://localhost:1527/killbill;collation=TERRITORY_BASED:PRIMARY", "root", "root"));
Assert.assertEquals(genericEmbeddedDb.getDBEngine(), DBEngine.GENERIC);
checkEmbeddedDb(genericEmbeddedDb);
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 a4fefd0..b803399 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
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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
@@ -22,6 +22,7 @@ import java.util.List;
import java.util.UUID;
import org.joda.time.DateTime;
+import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.catalog.api.BillingActionPolicy;
import org.killbill.billing.catalog.api.BillingPeriod;
@@ -31,7 +32,6 @@ import org.killbill.billing.catalog.api.Plan;
import org.killbill.billing.catalog.api.PlanChangeResult;
import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
-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;
@@ -63,10 +63,17 @@ public interface SubscriptionBaseApiService {
public boolean cancelWithPolicy(DefaultSubscriptionBase subscription, BillingActionPolicy policy, CallContext context)
throws SubscriptionBaseApiException;
+ public boolean cancelWithPolicyNoValidation(Iterable<DefaultSubscriptionBase> subscriptions, BillingActionPolicy policy, InternalCallContext context)
+ throws SubscriptionBaseApiException;
+
public boolean uncancel(DefaultSubscriptionBase subscription, CallContext context)
throws SubscriptionBaseApiException;
// Return the effective date of the change
+ public DateTime dryRunChangePlan(DefaultSubscriptionBase subscription, String productName, BillingPeriod term,
+ String priceList, DateTime requestedDate, BillingActionPolicy policy, TenantContext context) throws SubscriptionBaseApiException;
+
+ // Return the effective date of the change
public DateTime changePlan(DefaultSubscriptionBase subscription, String productName, BillingPeriod term,
String priceList, List<PlanPhasePriceOverride> overrides, CallContext context)
throws SubscriptionBaseApiException;
@@ -81,7 +88,7 @@ public interface SubscriptionBaseApiService {
String priceList, List<PlanPhasePriceOverride> overrides, BillingActionPolicy policy, CallContext context)
throws SubscriptionBaseApiException;
- public int cancelAddOnsIfRequired(final Product baseProduct, final UUID bundleId, final DateTime effectiveDate, final CallContext context) throws CatalogApiException;
+ public int cancelAddOnsIfRequiredOnBasePlanEvent(final DefaultSubscriptionBase subscription, final SubscriptionBaseEvent event, final CallContext context) throws CatalogApiException;
public PlanChangeResult getPlanChangeResult(final DefaultSubscriptionBase subscription, final String productName,
final BillingPeriod term, final String priceList, final DateTime effectiveDate, TenantContext 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 64c4d39..4b17ee0 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
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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
@@ -32,6 +32,7 @@ import org.killbill.billing.ErrorCode;
import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.catalog.api.BillingActionPolicy;
+import org.killbill.billing.catalog.api.BillingPeriod;
import org.killbill.billing.catalog.api.Catalog;
import org.killbill.billing.catalog.api.CatalogApiException;
import org.killbill.billing.catalog.api.CatalogService;
@@ -215,6 +216,23 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
}
@Override
+ public void cancelBaseSubscriptions(final Iterable<SubscriptionBase> subscriptions, final BillingActionPolicy policy, final InternalCallContext context) throws SubscriptionBaseApiException {
+ apiService.cancelWithPolicyNoValidation(Iterables.<SubscriptionBase, DefaultSubscriptionBase>transform(subscriptions,
+ new Function<SubscriptionBase, DefaultSubscriptionBase>() {
+ @Override
+ public DefaultSubscriptionBase apply(final SubscriptionBase subscriptionBase) {
+ try {
+ return getDefaultSubscriptionBase(subscriptionBase, context);
+ } catch (final CatalogApiException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }),
+ policy,
+ context);
+ }
+
+ @Override
public SubscriptionBaseBundle createBundleForAccount(final UUID accountId, final String bundleKey, final InternalCallContext context) throws SubscriptionBaseApiException {
final List<SubscriptionBaseBundle> existingBundles = dao.getSubscriptionBundlesForKey(bundleKey, context);
@@ -405,6 +423,18 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
}
@Override
+ public DateTime getDryRunChangePlanEffectiveDate(final SubscriptionBase subscription,
+ final String productName,
+ final BillingPeriod term,
+ final String priceList,
+ final DateTime requestedDateWithMs,
+ final BillingActionPolicy requestedPolicy,
+ final InternalTenantContext context) throws SubscriptionBaseApiException {
+ final TenantContext tenantContext = internalCallContextFactory.createTenantContext(context);
+ return apiService.dryRunChangePlan((DefaultSubscriptionBase) subscription, productName, term, priceList, requestedDateWithMs, requestedPolicy, tenantContext);
+ }
+
+ @Override
public List<EntitlementAOStatusDryRun> getDryRunChangePlanStatus(final UUID subscriptionId, @Nullable final String baseProductName, final DateTime requestedDate, final InternalTenantContext context) throws SubscriptionBaseApiException {
try {
final SubscriptionBase subscription = dao.getSubscriptionFromId(subscriptionId, context);
@@ -627,4 +657,14 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
}
}));
}
+
+ // For forward-compatibility
+ private DefaultSubscriptionBase getDefaultSubscriptionBase(final SubscriptionBase subscriptionBase, final InternalTenantContext context) throws CatalogApiException {
+ if (subscriptionBase instanceof DefaultSubscriptionBase) {
+ return (DefaultSubscriptionBase) subscriptionBase;
+ } else {
+ // Safe cast, see above
+ return (DefaultSubscriptionBase) dao.getSubscriptionFromId(subscriptionBase.getId(), 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 0805f55..b12935f 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
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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,12 +19,15 @@
package org.killbill.billing.subscription.api.user;
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.UUID;
+import javax.annotation.Nullable;
+
import org.joda.time.DateTime;
import org.killbill.billing.ErrorCode;
import org.killbill.billing.ObjectType;
@@ -74,6 +77,7 @@ import org.killbill.clock.DefaultClock;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.inject.Inject;
@@ -220,11 +224,11 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
subscription.getCurrentPhase().getPhaseType());
try {
- final InternalTenantContext internalCallContext = createTenantContextFromBundleId(subscription.getBundleId(), context);
+ final InternalCallContext internalCallContext = createCallContextFromBundleId(subscription.getBundleId(), context);
final BillingActionPolicy policy = catalogService.getFullCatalog(internalCallContext).planCancelPolicy(planPhase, now);
final DateTime effectiveDate = subscription.getPlanChangeEffectiveDate(policy);
- return doCancelPlan(subscription, now, effectiveDate, context);
+ return doCancelPlan(ImmutableMap.<DefaultSubscriptionBase, DateTime>of(subscription, effectiveDate), now, internalCallContext);
} catch (final CatalogApiException e) {
throw new SubscriptionBaseApiException(e);
}
@@ -238,7 +242,9 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
}
final DateTime now = clock.getUTCNow();
final DateTime effectiveDate = (requestedDateWithMs != null) ? DefaultClock.truncateMs(requestedDateWithMs) : now;
- return doCancelPlan(subscription, now, effectiveDate, context);
+
+ final InternalCallContext internalCallContext = createCallContextFromBundleId(subscription.getBundleId(), context);
+ return doCancelPlan(ImmutableMap.<DefaultSubscriptionBase, DateTime>of(subscription, effectiveDate), now, internalCallContext);
}
@Override
@@ -247,30 +253,51 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
if (currentState != null && currentState != EntitlementState.ACTIVE) {
throw new SubscriptionBaseApiException(ErrorCode.SUB_CANCEL_BAD_STATE, subscription.getId(), currentState);
}
+
+ final InternalCallContext internalCallContext = createCallContextFromBundleId(subscription.getBundleId(), context);
+ return cancelWithPolicyNoValidation(ImmutableList.<DefaultSubscriptionBase>of(subscription), policy, internalCallContext);
+ }
+
+ @Override
+ public boolean cancelWithPolicyNoValidation(final Iterable<DefaultSubscriptionBase> subscriptions, final BillingActionPolicy policy, final InternalCallContext context) throws SubscriptionBaseApiException {
+ final Map<DefaultSubscriptionBase, DateTime> subscriptionsWithEffectiveDate = new HashMap<DefaultSubscriptionBase, DateTime>();
final DateTime now = clock.getUTCNow();
- final DateTime effectiveDate = subscription.getPlanChangeEffectiveDate(policy);
- return doCancelPlan(subscription, now, effectiveDate, context);
+ for (final DefaultSubscriptionBase subscription : subscriptions) {
+ final DateTime effectiveDate = subscription.getPlanChangeEffectiveDate(policy);
+ subscriptionsWithEffectiveDate.put(subscription, effectiveDate);
+ }
+
+ return doCancelPlan(subscriptionsWithEffectiveDate, now, context);
}
- private boolean doCancelPlan(final DefaultSubscriptionBase subscription, final DateTime now, final DateTime effectiveDate, final CallContext context) throws SubscriptionBaseApiException {
+ private boolean doCancelPlan(final Map<DefaultSubscriptionBase, DateTime> subscriptions, final DateTime now, final InternalCallContext internalCallContext) throws SubscriptionBaseApiException {
+ final List<DefaultSubscriptionBase> subscriptionsToBeCancelled = new LinkedList<DefaultSubscriptionBase>();
+ final List<SubscriptionBaseEvent> cancelEvents = new LinkedList<SubscriptionBaseEvent>();
+
try {
- validateEffectiveDate(subscription, effectiveDate);
+ for (final DefaultSubscriptionBase subscription : subscriptions.keySet()) {
+ final DateTime effectiveDate = subscriptions.get(subscription);
+ validateEffectiveDate(subscription, effectiveDate);
- final InternalCallContext internalCallContext = createCallContextFromBundleId(subscription.getBundleId(), context);
- final List<SubscriptionBaseEvent> cancelEvents = getEventsOnCancelPlan(subscription, effectiveDate, now, false, internalCallContext);
- // cancelEvents will contain only one item
- dao.cancelSubscription(subscription, cancelEvents.get(0), internalCallContext, 0);
- final Catalog fullCatalog = catalogService.getFullCatalog(internalCallContext);
- subscription.rebuildTransitions(dao.getEventsForSubscription(subscription.getId(), internalCallContext), fullCatalog);
+ subscriptionsToBeCancelled.add(subscription);
+ cancelEvents.addAll(getEventsOnCancelPlan(subscription, effectiveDate, now, false, internalCallContext));
+
+ if (subscription.getCategory() == ProductCategory.BASE) {
+ subscriptionsToBeCancelled.addAll(computeAddOnsToCancel(cancelEvents, null, subscription.getBundleId(), effectiveDate, internalCallContext));
+ }
+ }
- if (subscription.getCategory() == ProductCategory.BASE) {
- final Product baseProduct = (subscription.getState() == null || subscription.getState() == EntitlementState.CANCELLED) ? null : subscription.getCurrentPlan().getProduct();
- cancelAddOnsIfRequired(baseProduct, subscription.getBundleId(), effectiveDate, context);
+ dao.cancelSubscriptions(subscriptionsToBeCancelled, cancelEvents, internalCallContext);
+
+ boolean allSubscriptionsCancelled = true;
+ for (final DefaultSubscriptionBase subscription : subscriptions.keySet()) {
+ final Catalog fullCatalog = catalogService.getFullCatalog(internalCallContext);
+ subscription.rebuildTransitions(dao.getEventsForSubscription(subscription.getId(), internalCallContext), fullCatalog);
+ allSubscriptionsCancelled = allSubscriptionsCancelled && (subscription.getState() == EntitlementState.CANCELLED);
}
- final boolean isImmediate = subscription.getState() == EntitlementState.CANCELLED;
- return isImmediate;
+ return allSubscriptionsCancelled;
} catch (final CatalogApiException e) {
throw new SubscriptionBaseApiException(e);
}
@@ -312,6 +339,31 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
}
@Override
+ public DateTime dryRunChangePlan(final DefaultSubscriptionBase subscription,
+ final String productName,
+ final BillingPeriod term,
+ final String priceList,
+ @Nullable final DateTime requestedDateWithMs,
+ @Nullable final BillingActionPolicy requestedPolicy,
+ final TenantContext context) throws SubscriptionBaseApiException {
+ final DateTime now = clock.getUTCNow();
+
+ BillingActionPolicy policyMaybeNull = requestedPolicy;
+ if (requestedDateWithMs == null && requestedPolicy == null) {
+ final PlanChangeResult planChangeResult = getPlanChangeResult(subscription, productName, term, priceList, now, context);
+ policyMaybeNull = planChangeResult.getPolicy();
+ }
+
+ if (policyMaybeNull != null) {
+ return subscription.getPlanChangeEffectiveDate(policyMaybeNull);
+ } else if (requestedDateWithMs != null) {
+ return DefaultClock.truncateMs(requestedDateWithMs);
+ } else {
+ return now;
+ }
+ }
+
+ @Override
public DateTime changePlan(final DefaultSubscriptionBase subscription, final String productName, final BillingPeriod term,
final String priceList, final List<PlanPhasePriceOverride> overrides, final CallContext context) throws SubscriptionBaseApiException {
final DateTime now = clock.getUTCNow();
@@ -319,47 +371,48 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
validateEntitlementState(subscription);
final PlanChangeResult planChangeResult = getPlanChangeResult(subscription, productName, term, priceList, now, context);
- final DateTime effectiveDate = subscription.getPlanChangeEffectiveDate(planChangeResult.getPolicy());
+ final DateTime effectiveDate = dryRunChangePlan(subscription, productName, term, priceList, null, planChangeResult.getPolicy(), context);
validateEffectiveDate(subscription, effectiveDate);
try {
- return doChangePlan(subscription, productName, term, planChangeResult.getNewPriceList().getName(), overrides, now, effectiveDate, context);
+ doChangePlan(subscription, productName, term, planChangeResult.getNewPriceList().getName(), overrides, effectiveDate, context);
} catch (final CatalogApiException e) {
throw new SubscriptionBaseApiException(e);
}
+
+ return effectiveDate;
}
@Override
public DateTime changePlanWithRequestedDate(final DefaultSubscriptionBase subscription, final String productName, final BillingPeriod term,
final String priceList, final List<PlanPhasePriceOverride> overrides,
final DateTime requestedDateWithMs, final CallContext context) throws SubscriptionBaseApiException {
- final DateTime now = clock.getUTCNow();
- final DateTime effectiveDate = (requestedDateWithMs != null) ? DefaultClock.truncateMs(requestedDateWithMs) : now;
-
+ final DateTime effectiveDate = dryRunChangePlan(subscription, productName, term, priceList, requestedDateWithMs, null, context);
validateEffectiveDate(subscription, effectiveDate);
validateEntitlementState(subscription);
try {
- return doChangePlan(subscription, productName, term, priceList, overrides, now, effectiveDate, context);
+ doChangePlan(subscription, productName, term, priceList, overrides, effectiveDate, context);
} catch (final CatalogApiException e) {
throw new SubscriptionBaseApiException(e);
}
+
+ return effectiveDate;
}
@Override
public DateTime changePlanWithPolicy(final DefaultSubscriptionBase subscription, final String productName, final BillingPeriod term,
- final String priceList, final List<PlanPhasePriceOverride> overrides, final BillingActionPolicy policy, final CallContext context)
- throws SubscriptionBaseApiException {
- final DateTime now = clock.getUTCNow();
-
+ final String priceList, final List<PlanPhasePriceOverride> overrides, final BillingActionPolicy policy, final CallContext context) throws SubscriptionBaseApiException {
validateEntitlementState(subscription);
- final DateTime effectiveDate = subscription.getPlanChangeEffectiveDate(policy);
+ final DateTime effectiveDate = dryRunChangePlan(subscription, productName, term, priceList, null, policy, context);
try {
- return doChangePlan(subscription, productName, term, priceList, overrides, now, effectiveDate, context);
+ doChangePlan(subscription, productName, term, priceList, overrides, effectiveDate, context);
} catch (final CatalogApiException e) {
throw new SubscriptionBaseApiException(e);
}
+
+ return effectiveDate;
}
@Override
@@ -389,14 +442,13 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
return planChangeResult;
}
- private DateTime doChangePlan(final DefaultSubscriptionBase subscription,
- final String newProductName,
- final BillingPeriod newBillingPeriod,
- final String newPriceList,
- final List<PlanPhasePriceOverride> overrides,
- final DateTime now,
- final DateTime effectiveDate,
- final CallContext context) throws SubscriptionBaseApiException, CatalogApiException {
+ private void doChangePlan(final DefaultSubscriptionBase subscription,
+ final String newProductName,
+ final BillingPeriod newBillingPeriod,
+ final String newPriceList,
+ final List<PlanPhasePriceOverride> overrides,
+ final DateTime effectiveDate,
+ final CallContext context) throws SubscriptionBaseApiException, CatalogApiException {
final InternalCallContext internalCallContext = createCallContextFromBundleId(subscription.getBundleId(), context);
final PlanPhasePriceOverridesWithCallContext overridesWithContext = new DefaultPlanPhasePriceOverridesWithCallContext(overrides, context);
final Plan newPlan = catalogService.getFullCatalog(internalCallContext).createOrFindPlan(newProductName, newBillingPeriod, newPriceList, overridesWithContext, effectiveDate, subscription.getStartDate());
@@ -404,15 +456,15 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
if (newPlan.getProduct().getCategory() != subscription.getCategory()) {
throw new SubscriptionBaseApiException(ErrorCode.SUB_CHANGE_INVALID, subscription.getId());
}
- final List<SubscriptionBaseEvent> changeEvents = getEventsOnChangePlan(subscription, newPlan, newPriceList, effectiveDate, now, false, internalCallContext);
- dao.changePlan(subscription, changeEvents, internalCallContext);
- subscription.rebuildTransitions(dao.getEventsForSubscription(subscription.getId(), internalCallContext), catalogService.getFullCatalog(internalCallContext));
- if (subscription.getCategory() == ProductCategory.BASE) {
- final Product baseProduct = (subscription.getState() == EntitlementState.CANCELLED) ? null : subscription.getCurrentPlan().getProduct();
- cancelAddOnsIfRequired(baseProduct, subscription.getBundleId(), effectiveDate, context);
- }
- return effectiveDate;
+ final List<DefaultSubscriptionBase> addOnSubscriptionsToBeCancelled = new ArrayList<DefaultSubscriptionBase>();
+ final List<SubscriptionBaseEvent> addOnCancelEvents = new ArrayList<SubscriptionBaseEvent>();
+ final List<SubscriptionBaseEvent> changeEvents = getEventsOnChangePlan(subscription, newPlan, newPriceList, effectiveDate, true, addOnSubscriptionsToBeCancelled, addOnCancelEvents, internalCallContext);
+
+ dao.changePlan(subscription, changeEvents, addOnSubscriptionsToBeCancelled, addOnCancelEvents, internalCallContext);
+
+ final Catalog fullCatalog = catalogService.getFullCatalog(internalCallContext);
+ subscription.rebuildTransitions(dao.getEventsForSubscription(subscription.getId(), internalCallContext), fullCatalog);
}
@Override
@@ -449,6 +501,20 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
public List<SubscriptionBaseEvent> getEventsOnChangePlan(final DefaultSubscriptionBase subscription, final Plan newPlan,
final String newPriceList, final DateTime effectiveDate, final DateTime processedDate,
final boolean addCancellationAddOnForEventsIfRequired, final InternalTenantContext internalTenantContext) throws CatalogApiException, SubscriptionBaseApiException {
+ final Collection<DefaultSubscriptionBase> addOnSubscriptionsToBeCancelled = new ArrayList<DefaultSubscriptionBase>();
+ final List<SubscriptionBaseEvent> addOnCancelEvents = new ArrayList<SubscriptionBaseEvent>();
+
+ final List<SubscriptionBaseEvent> changeEvents = getEventsOnChangePlan(subscription, newPlan, newPriceList, effectiveDate, addCancellationAddOnForEventsIfRequired, addOnSubscriptionsToBeCancelled, addOnCancelEvents, internalTenantContext);
+ changeEvents.addAll(addOnCancelEvents);
+ return changeEvents;
+ }
+
+ private List<SubscriptionBaseEvent> getEventsOnChangePlan(final DefaultSubscriptionBase subscription, final Plan newPlan,
+ final String newPriceList, final DateTime effectiveDate,
+ final boolean addCancellationAddOnForEventsIfRequired,
+ final Collection<DefaultSubscriptionBase> addOnSubscriptionsToBeCancelled,
+ final List<SubscriptionBaseEvent> addOnCancelEvents,
+ final InternalTenantContext internalTenantContext) throws CatalogApiException, SubscriptionBaseApiException {
final TimedPhase currentTimedPhase = planAligner.getCurrentTimedPhaseOnChange(subscription, newPlan, newPriceList, effectiveDate, internalTenantContext);
final SubscriptionBaseEvent changeEvent = new ApiEventChange(new ApiEventBuilder()
@@ -475,7 +541,7 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
if (subscription.getCategory() == ProductCategory.BASE && addCancellationAddOnForEventsIfRequired) {
final Product currentBaseProduct = changeEvent.getEffectiveDate().compareTo(clock.getUTCNow()) <= 0 ? newPlan.getProduct() : subscription.getCurrentPlan().getProduct();
- addCancellationAddOnForEventsIfRequired(changeEvents, currentBaseProduct, subscription.getBundleId(), effectiveDate, internalTenantContext);
+ addOnSubscriptionsToBeCancelled.addAll(addCancellationAddOnForEventsIfRequired(addOnCancelEvents, currentBaseProduct, subscription.getBundleId(), effectiveDate, internalTenantContext));
}
return changeEvents;
}
@@ -498,20 +564,26 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
return cancelEvents;
}
- public int cancelAddOnsIfRequired(final Product baseProduct, final UUID bundleId, final DateTime effectiveDate, final CallContext context) throws CatalogApiException {
+ @Override
+ public int cancelAddOnsIfRequiredOnBasePlanEvent(final DefaultSubscriptionBase subscription, final SubscriptionBaseEvent event, final CallContext context) throws CatalogApiException {
+ final Product baseProduct = (subscription.getState() == EntitlementState.CANCELLED) ? null : subscription.getCurrentPlan().getProduct();
+
+ final List<SubscriptionBaseEvent> cancelEvents = new LinkedList<SubscriptionBaseEvent>();
+ final InternalCallContext internalCallContext = createCallContextFromBundleId(subscription.getBundleId(), context);
+ final List<DefaultSubscriptionBase> subscriptionsToBeCancelled = computeAddOnsToCancel(cancelEvents, baseProduct, subscription.getBundleId(), event.getEffectiveDate(), internalCallContext);
+ dao.cancelSubscriptionsOnBasePlanEvent(subscription, event, subscriptionsToBeCancelled, cancelEvents, internalCallContext);
+
+ return subscriptionsToBeCancelled.size();
+ }
+
+ private List<DefaultSubscriptionBase> computeAddOnsToCancel(final List<SubscriptionBaseEvent> cancelEvents, final Product baseProduct, final UUID bundleId, final DateTime effectiveDate, final InternalCallContext internalCallContext) throws CatalogApiException {
// If cancellation/change occur in the future, there is nothing to do
final DateTime now = clock.getUTCNow();
if (effectiveDate.compareTo(now) > 0) {
- return 0;
+ return ImmutableList.<DefaultSubscriptionBase>of();
+ } else {
+ return addCancellationAddOnForEventsIfRequired(cancelEvents, baseProduct, bundleId, effectiveDate, internalCallContext);
}
-
- final List<SubscriptionBaseEvent> cancelEvents = new LinkedList<SubscriptionBaseEvent>();
- final InternalCallContext internalCallContext = createCallContextFromBundleId(bundleId, context);
- final List<DefaultSubscriptionBase> subscriptionsToBeCancelled = addCancellationAddOnForEventsIfRequired(cancelEvents, baseProduct, bundleId, effectiveDate, internalCallContext);
- if (!subscriptionsToBeCancelled.isEmpty()) {
- dao.cancelSubscriptions(subscriptionsToBeCancelled, cancelEvents, internalCallContext);
- }
- return subscriptionsToBeCancelled.size();
}
private List<DefaultSubscriptionBase> addCancellationAddOnForEventsIfRequired(final List<SubscriptionBaseEvent> events, final Product baseProduct, final UUID bundleId,
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/engine/core/DefaultSubscriptionBaseService.java b/subscription/src/main/java/org/killbill/billing/subscription/engine/core/DefaultSubscriptionBaseService.java
index 8b4fbe8..18dd082 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/engine/core/DefaultSubscriptionBaseService.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/engine/core/DefaultSubscriptionBaseService.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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
@@ -23,10 +23,7 @@ import java.util.UUID;
import org.joda.time.DateTime;
import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.catalog.api.CatalogApiException;
-import org.killbill.billing.catalog.api.Product;
import org.killbill.billing.catalog.api.ProductCategory;
-import org.killbill.billing.entitlement.api.Entitlement.EntitlementState;
-import org.killbill.billing.events.EffectiveSubscriptionInternalEvent;
import org.killbill.billing.platform.api.LifecycleHandlerType;
import org.killbill.billing.platform.api.LifecycleHandlerType.LifecycleLevel;
import org.killbill.billing.subscription.alignment.PlanAligner;
@@ -41,12 +38,12 @@ import org.killbill.billing.subscription.events.SubscriptionBaseEvent;
import org.killbill.billing.subscription.events.SubscriptionBaseEvent.EventType;
import org.killbill.billing.subscription.events.phase.PhaseEvent;
import org.killbill.billing.subscription.events.phase.PhaseEventData;
-import org.killbill.billing.subscription.events.user.ApiEvent;
import org.killbill.billing.subscription.exceptions.SubscriptionBaseError;
import org.killbill.billing.util.callcontext.CallContext;
import org.killbill.billing.util.callcontext.CallOrigin;
import org.killbill.billing.util.callcontext.InternalCallContextFactory;
import org.killbill.billing.util.callcontext.UserType;
+import org.killbill.bus.api.BusEvent;
import org.killbill.bus.api.PersistentBus;
import org.killbill.bus.api.PersistentBus.EventBusException;
import org.killbill.clock.Clock;
@@ -74,9 +71,10 @@ public class DefaultSubscriptionBaseService implements EventListener, Subscripti
private final PersistentBus eventBus;
private final NotificationQueueService notificationQueueService;
private final InternalCallContextFactory internalCallContextFactory;
- private NotificationQueue subscriptionEventQueue;
private final SubscriptionBaseApiService apiService;
+ private NotificationQueue subscriptionEventQueue;
+
@Inject
public DefaultSubscriptionBaseService(final Clock clock, final SubscriptionDao dao, final PlanAligner planAligner,
final PersistentBus eventBus,
@@ -159,22 +157,24 @@ public class DefaultSubscriptionBaseService implements EventListener, Subscripti
return;
}
- //
- // Do any internal processing on that event before we send the event to the bus
- //
- int theRealSeqId = seqId;
+ boolean eventSent = false;
if (event.getType() == EventType.PHASE) {
- onPhaseEvent(subscription, context);
+ eventSent = onPhaseEvent(subscription, event, context);
} else if (event.getType() == EventType.API_USER && subscription.getCategory() == ProductCategory.BASE) {
final CallContext callContext = internalCallContextFactory.createCallContext(context);
- theRealSeqId = onBasePlanEvent(subscription, (ApiEvent) event, callContext);
+ eventSent = onBasePlanEvent(subscription, event, callContext);
}
- final SubscriptionBaseTransitionData transition = (subscription.getTransitionFromEvent(event, theRealSeqId));
- final EffectiveSubscriptionInternalEvent busEvent = new DefaultEffectiveSubscriptionEvent(transition, subscription.getAlignStartDate(),
- context.getUserToken(),
- context.getAccountRecordId(), context.getTenantRecordId());
- eventBus.post(busEvent);
+ if (!eventSent) {
+ // Methods above invoking the DAO will send this event directly from the transaction
+ final SubscriptionBaseTransitionData transition = subscription.getTransitionFromEvent(event, seqId);
+ final BusEvent busEvent = new DefaultEffectiveSubscriptionEvent(transition,
+ subscription.getAlignStartDate(),
+ context.getUserToken(),
+ context.getAccountRecordId(),
+ context.getTenantRecordId());
+ eventBus.post(busEvent);
+ }
} catch (final EventBusException e) {
log.warn("Failed to post subscription event " + event, e);
} catch (final CatalogApiException e) {
@@ -182,7 +182,7 @@ public class DefaultSubscriptionBaseService implements EventListener, Subscripti
}
}
- private void onPhaseEvent(final DefaultSubscriptionBase subscription, final InternalCallContext context) {
+ private boolean onPhaseEvent(final DefaultSubscriptionBase subscription, final SubscriptionBaseEvent readyPhaseEvent, final InternalCallContext context) {
try {
final DateTime now = clock.getUTCNow();
final TimedPhase nextTimedPhase = planAligner.getNextTimedPhase(subscription, now, context);
@@ -191,15 +191,18 @@ public class DefaultSubscriptionBaseService implements EventListener, Subscripti
nextTimedPhase.getPhase().getName(), nextTimedPhase.getStartPhase()) :
null;
if (nextPhaseEvent != null) {
- dao.createNextPhaseEvent(subscription, nextPhaseEvent, context);
+ dao.createNextPhaseEvent(subscription, readyPhaseEvent, nextPhaseEvent, context);
+ return true;
}
} catch (final SubscriptionBaseError e) {
log.error(String.format("Failed to insert next phase for subscription %s", subscription.getId()), e);
}
+
+ return false;
}
- private int onBasePlanEvent(final DefaultSubscriptionBase baseSubscription, final ApiEvent event, final CallContext context) throws CatalogApiException {
- final Product baseProduct = (baseSubscription.getState() == EntitlementState.CANCELLED) ? null : baseSubscription.getCurrentPlan().getProduct();
- return apiService.cancelAddOnsIfRequired(baseProduct, baseSubscription.getBundleId(), event.getEffectiveDate(), context);
+ private boolean onBasePlanEvent(final DefaultSubscriptionBase baseSubscription, final SubscriptionBaseEvent event, final CallContext context) throws CatalogApiException {
+ apiService.cancelAddOnsIfRequiredOnBasePlanEvent(baseSubscription, event, context);
+ return true;
}
}
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 c1e403b..1e6c118 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
@@ -45,7 +45,6 @@ import org.killbill.billing.catalog.api.Plan;
import org.killbill.billing.catalog.api.ProductCategory;
import org.killbill.billing.entitlement.api.SubscriptionApiException;
import org.killbill.billing.entity.EntityPersistenceException;
-import org.killbill.billing.events.EffectiveSubscriptionInternalEvent;
import org.killbill.billing.subscription.api.SubscriptionBase;
import org.killbill.billing.subscription.api.SubscriptionBaseTransitionType;
import org.killbill.billing.subscription.api.migration.AccountMigrationData;
@@ -86,6 +85,7 @@ import org.killbill.billing.util.entity.dao.EntityDaoBase;
import org.killbill.billing.util.entity.dao.EntitySqlDaoTransactionWrapper;
import org.killbill.billing.util.entity.dao.EntitySqlDaoTransactionalJdbiWrapper;
import org.killbill.billing.util.entity.dao.EntitySqlDaoWrapperFactory;
+import org.killbill.bus.api.BusEvent;
import org.killbill.bus.api.PersistentBus;
import org.killbill.bus.api.PersistentBus.EventBusException;
import org.killbill.clock.Clock;
@@ -405,20 +405,21 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
}
@Override
- public void createNextPhaseEvent(final DefaultSubscriptionBase subscription, final SubscriptionBaseEvent nextPhase, final InternalCallContext context) {
+ public void createNextPhaseEvent(final DefaultSubscriptionBase subscription, final SubscriptionBaseEvent readyPhaseEvent, final SubscriptionBaseEvent nextPhaseEvent, final InternalCallContext context) {
transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<Void>() {
@Override
public Void inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
final SubscriptionEventSqlDao transactional = entitySqlDaoWrapperFactory.become(SubscriptionEventSqlDao.class);
final UUID subscriptionId = subscription.getId();
cancelNextPhaseEventFromTransaction(subscriptionId, entitySqlDaoWrapperFactory, context);
- transactional.create(new SubscriptionEventModelDao(nextPhase), context);
+ transactional.create(new SubscriptionEventModelDao(nextPhaseEvent), context);
recordFutureNotificationFromTransaction(entitySqlDaoWrapperFactory,
- nextPhase.getEffectiveDate(),
- new SubscriptionNotificationKey(nextPhase.getId()), context);
+ nextPhaseEvent.getEffectiveDate(),
+ new SubscriptionNotificationKey(nextPhaseEvent.getId()), context);
- // Notify the Bus of the requested change
- notifyBusOfRequestedChange(entitySqlDaoWrapperFactory, subscription, nextPhase, SubscriptionBaseTransitionType.PHASE, context);
+ // Notify the Bus
+ notifyBusOfRequestedChange(entitySqlDaoWrapperFactory, subscription, nextPhaseEvent, SubscriptionBaseTransitionType.PHASE, context);
+ notifyBusOfEffectiveImmediateChange(entitySqlDaoWrapperFactory, subscription, readyPhaseEvent, 0, context);
return null;
}
@@ -561,32 +562,37 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
}
@Override
- public void cancelSubscriptions(final List<DefaultSubscriptionBase> subscriptions, final List<SubscriptionBaseEvent> cancelEvents, final InternalCallContext context) {
-
+ public void cancelSubscriptionsOnBasePlanEvent(final DefaultSubscriptionBase subscription, final SubscriptionBaseEvent event, final List<DefaultSubscriptionBase> subscriptions, final List<SubscriptionBaseEvent> cancelEvents, final InternalCallContext context) {
transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<Void>() {
@Override
public Void inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
- for (int i = 0; i < subscriptions.size(); i++) {
- final DefaultSubscriptionBase subscription = subscriptions.get(i);
- final SubscriptionBaseEvent cancelEvent = cancelEvents.get(i);
- cancelSubscriptionFromTransaction(subscription, cancelEvent, entitySqlDaoWrapperFactory, context, i);
- }
+ cancelSubscriptionsFromTransaction(entitySqlDaoWrapperFactory, subscriptions, cancelEvents, context);
+ // Make sure to always send the event, even if there were no subscriptions to cancel
+ notifyBusOfEffectiveImmediateChange(entitySqlDaoWrapperFactory, subscription, event, subscriptions.size(), context);
return null;
}
});
}
@Override
- public void cancelSubscription(final DefaultSubscriptionBase subscription, final SubscriptionBaseEvent cancelEvent, final InternalCallContext context, final int seqId) {
+ public void cancelSubscriptions(final List<DefaultSubscriptionBase> subscriptions, final List<SubscriptionBaseEvent> cancelEvents, final InternalCallContext context) {
transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<Void>() {
@Override
public Void inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
- cancelSubscriptionFromTransaction(subscription, cancelEvent, entitySqlDaoWrapperFactory, context, seqId);
+ cancelSubscriptionsFromTransaction(entitySqlDaoWrapperFactory, subscriptions, cancelEvents, context);
return null;
}
});
}
+ private void cancelSubscriptionsFromTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory, final List<DefaultSubscriptionBase> subscriptions, final List<SubscriptionBaseEvent> cancelEvents, final InternalCallContext context) throws EntityPersistenceException {
+ for (int i = 0; i < subscriptions.size(); i++) {
+ final DefaultSubscriptionBase subscription = subscriptions.get(i);
+ final SubscriptionBaseEvent cancelEvent = cancelEvents.get(i);
+ cancelSubscriptionFromTransaction(subscription, cancelEvent, entitySqlDaoWrapperFactory, context, subscriptions.size() - i - 1);
+ }
+ }
+
@Override
public void uncancelSubscription(final DefaultSubscriptionBase subscription, final List<SubscriptionBaseEvent> uncancelEvents, final InternalCallContext context) {
transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<Void>() {
@@ -629,11 +635,10 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
}
@Override
- public void changePlan(final DefaultSubscriptionBase subscription, final List<SubscriptionBaseEvent> changeEvents, final InternalCallContext context) {
+ public void changePlan(final DefaultSubscriptionBase subscription, final List<SubscriptionBaseEvent> changeEvents, final List<DefaultSubscriptionBase> subscriptionsToBeCancelled, final List<SubscriptionBaseEvent> cancelEvents, final InternalCallContext context) {
transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<Void>() {
@Override
public Void inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
-
final SubscriptionEventSqlDao transactional = entitySqlDaoWrapperFactory.become(SubscriptionEventSqlDao.class);
final UUID subscriptionId = subscription.getId();
@@ -641,11 +646,9 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
changeEvents,
entitySqlDaoWrapperFactory,
context);
-
cancelFutureEventsFromTransaction(subscriptionId, changeEvents.get(0).getEffectiveDate(), entitySqlDaoWrapperFactory, context);
for (final SubscriptionBaseEvent cur : changeEventsTweakedWithMigrateBilling) {
-
transactional.create(new SubscriptionEventModelDao(cur), context);
final boolean isBusEvent = cur.getEffectiveDate().compareTo(clock.getUTCNow()) <= 0 && (cur.getType() == EventType.API_USER);
@@ -656,6 +659,9 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
final SubscriptionBaseEvent finalEvent = changeEventsTweakedWithMigrateBilling.get(changeEvents.size() - 1);
notifyBusOfRequestedChange(entitySqlDaoWrapperFactory, subscription, finalEvent, SubscriptionBaseTransitionType.CHANGE, context);
+ // Cancel associated add-ons
+ cancelSubscriptionsFromTransaction(entitySqlDaoWrapperFactory, subscriptionsToBeCancelled, cancelEvents, context);
+
return null;
}
});
@@ -1054,12 +1060,12 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
}
//
- // Either records a notfication or sends a bus event is operation is immediate
+ // Either records a notification or sends a bus event if operation is immediate
//
private void recordBusOrFutureNotificationFromTransaction(final DefaultSubscriptionBase subscription, final SubscriptionBaseEvent event, final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory, final boolean busEvent,
final int seqId, final InternalCallContext context) {
if (busEvent) {
- notifyBusOfEffectiveImmediateChange(entitySqlDaoWrapperFactory, subscription, event, seqId, context);
+ rebuildSubscriptionAndNotifyBusOfEffectiveImmediateChange(entitySqlDaoWrapperFactory, subscription, event, seqId, context);
} else {
recordFutureNotificationFromTransaction(entitySqlDaoWrapperFactory,
event.getEffectiveDate(),
@@ -1068,25 +1074,29 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
}
}
- //
- // Sends bus notification for event on effecfive date-- only used for operation that happen immediately:
- // - CREATE,
- // - IMM CANCEL or CHANGE
- //
- private void notifyBusOfEffectiveImmediateChange(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory, final DefaultSubscriptionBase subscription,
- final SubscriptionBaseEvent immediateEvent, final int seqId, final InternalCallContext context) {
+ // Sends bus notification for event on effective date -- only used for operation that happen immediately
+ private void rebuildSubscriptionAndNotifyBusOfEffectiveImmediateChange(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory, final DefaultSubscriptionBase subscription,
+ final SubscriptionBaseEvent immediateEvent, final int seqId, final InternalCallContext context) {
try {
final DefaultSubscriptionBase upToDateSubscription = createSubscriptionWithNewEvent(subscription, immediateEvent, context);
+ notifyBusOfEffectiveImmediateChange(entitySqlDaoWrapperFactory, upToDateSubscription, immediateEvent, seqId, context);
+ } catch (final CatalogApiException e) {
+ log.warn("Failed to post effective event for subscription " + subscription.getId(), e);
+ }
+ }
- final SubscriptionBaseTransitionData transition = upToDateSubscription.getTransitionFromEvent(immediateEvent, seqId);
- final EffectiveSubscriptionInternalEvent busEvent = new DefaultEffectiveSubscriptionEvent(transition, upToDateSubscription.getAlignStartDate(),
- context.getUserToken(),
- context.getAccountRecordId(), context.getTenantRecordId());
+ private void notifyBusOfEffectiveImmediateChange(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory, final DefaultSubscriptionBase subscription,
+ final SubscriptionBaseEvent immediateEvent, final int seqId, final InternalCallContext context) {
+ try {
+ final SubscriptionBaseTransitionData transition = subscription.getTransitionFromEvent(immediateEvent, seqId);
+ final BusEvent busEvent = new DefaultEffectiveSubscriptionEvent(transition,
+ subscription.getAlignStartDate(),
+ context.getUserToken(),
+ context.getAccountRecordId(),
+ context.getTenantRecordId());
eventBus.postFromTransaction(busEvent, entitySqlDaoWrapperFactory.getHandle().getConnection());
- } catch (EventBusException e) {
- log.warn("Failed to post effective event for subscription " + subscription.getId(), e);
- } catch (CatalogApiException e) {
+ } catch (final EventBusException e) {
log.warn("Failed to post effective event for subscription " + subscription.getId(), e);
}
}
@@ -1095,7 +1105,7 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
final SubscriptionBaseEvent nextEvent, final SubscriptionBaseTransitionType transitionType, final InternalCallContext context) {
try {
eventBus.postFromTransaction(new DefaultRequestedSubscriptionEvent(subscription, nextEvent, transitionType, context.getAccountRecordId(), context.getTenantRecordId(), context.getUserToken()), entitySqlDaoWrapperFactory.getHandle().getConnection());
- } catch (EventBusException e) {
+ } catch (final EventBusException e) {
log.warn("Failed to post requested change event for subscription " + subscription.getId(), e);
}
}
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 893c5d4..487fae3 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
@@ -69,7 +69,7 @@ public interface SubscriptionDao extends EntityDao<SubscriptionBundleModelDao, S
public void updateChargedThroughDate(DefaultSubscriptionBase subscription, InternalCallContext context);
// Event apis
- public void createNextPhaseEvent(DefaultSubscriptionBase subscription, SubscriptionBaseEvent nextPhase, InternalCallContext context);
+ public void createNextPhaseEvent(DefaultSubscriptionBase subscription, SubscriptionBaseEvent readyPhaseEvent, SubscriptionBaseEvent nextPhase, InternalCallContext context);
public SubscriptionBaseEvent getEventById(UUID eventId, InternalTenantContext context);
@@ -86,13 +86,13 @@ public interface SubscriptionDao extends EntityDao<SubscriptionBundleModelDao, S
public void recreateSubscription(DefaultSubscriptionBase subscription, List<SubscriptionBaseEvent> recreateEvents, InternalCallContext context);
- public void cancelSubscription(DefaultSubscriptionBase subscription, SubscriptionBaseEvent cancelEvent, InternalCallContext context, int cancelSeq);
+ public void cancelSubscriptionsOnBasePlanEvent(DefaultSubscriptionBase subscription, SubscriptionBaseEvent event, List<DefaultSubscriptionBase> subscriptions, List<SubscriptionBaseEvent> cancelEvents, InternalCallContext context);
public void cancelSubscriptions(List<DefaultSubscriptionBase> subscriptions, List<SubscriptionBaseEvent> cancelEvents, InternalCallContext context);
public void uncancelSubscription(DefaultSubscriptionBase subscription, List<SubscriptionBaseEvent> uncancelEvents, InternalCallContext context);
- public void changePlan(DefaultSubscriptionBase subscription, List<SubscriptionBaseEvent> changeEvents, InternalCallContext context);
+ public void changePlan(DefaultSubscriptionBase subscription, List<SubscriptionBaseEvent> changeEvents, List<DefaultSubscriptionBase> subscriptionsToBeCancelled, List<SubscriptionBaseEvent> cancelEvents, InternalCallContext context);
public void migrate(UUID accountId, AccountMigrationData data, InternalCallContext context);
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 af1970f..f7947f5 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
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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
@@ -42,9 +42,11 @@ import org.killbill.billing.subscription.api.migration.AccountMigrationData;
import org.killbill.billing.subscription.api.migration.AccountMigrationData.BundleMigrationData;
import org.killbill.billing.subscription.api.migration.AccountMigrationData.SubscriptionMigrationData;
import org.killbill.billing.subscription.api.transfer.TransferCancelData;
+import org.killbill.billing.subscription.api.user.DefaultEffectiveSubscriptionEvent;
import org.killbill.billing.subscription.api.user.DefaultSubscriptionBase;
import org.killbill.billing.subscription.api.user.DefaultSubscriptionBaseBundle;
import org.killbill.billing.subscription.api.user.SubscriptionBaseBundle;
+import org.killbill.billing.subscription.api.user.SubscriptionBaseTransitionData;
import org.killbill.billing.subscription.api.user.SubscriptionBuilder;
import org.killbill.billing.subscription.engine.core.DefaultSubscriptionBaseService;
import org.killbill.billing.subscription.engine.core.SubscriptionNotificationKey;
@@ -57,6 +59,9 @@ import org.killbill.billing.util.entity.DefaultPagination;
import org.killbill.billing.util.entity.Pagination;
import org.killbill.billing.util.entity.dao.EntitySqlDaoWrapperFactory;
import org.killbill.billing.util.entity.dao.MockEntityDaoBase;
+import org.killbill.bus.api.BusEvent;
+import org.killbill.bus.api.PersistentBus;
+import org.killbill.bus.api.PersistentBus.EventBusException;
import org.killbill.clock.Clock;
import org.killbill.notificationq.api.NotificationEvent;
import org.killbill.notificationq.api.NotificationQueue;
@@ -78,18 +83,21 @@ public class MockSubscriptionDaoMemory extends MockEntityDaoBase<SubscriptionBun
private final MockNonEntityDao mockNonEntityDao;
private final Clock clock;
private final NotificationQueueService notificationQueueService;
+ private final PersistentBus eventBus;
private final CatalogService catalogService;
@Inject
public MockSubscriptionDaoMemory(final MockNonEntityDao mockNonEntityDao,
final Clock clock,
final NotificationQueueService notificationQueueService,
+ final PersistentBus eventBus,
final CatalogService catalogService) {
super();
this.mockNonEntityDao = mockNonEntityDao;
this.clock = clock;
this.catalogService = catalogService;
this.notificationQueueService = notificationQueueService;
+ this.eventBus = eventBus;
this.bundles = new ArrayList<SubscriptionBaseBundle>();
this.subscriptions = new ArrayList<SubscriptionBase>();
this.events = new TreeSet<SubscriptionBaseEvent>();
@@ -305,9 +313,10 @@ public class MockSubscriptionDaoMemory extends MockEntityDaoBase<SubscriptionBun
}
@Override
- public void createNextPhaseEvent(final DefaultSubscriptionBase subscription, final SubscriptionBaseEvent nextPhase, final InternalCallContext context) {
+ public void createNextPhaseEvent(final DefaultSubscriptionBase subscription, final SubscriptionBaseEvent readyPhaseEvent, final SubscriptionBaseEvent nextPhase, final InternalCallContext context) {
cancelNextPhaseEvent(subscription.getId(), context);
insertEvent(nextPhase, context);
+ notifyBusOfEffectiveImmediateChange(subscription, readyPhaseEvent, 0, context);
}
private SubscriptionBase buildSubscription(final DefaultSubscriptionBase in, final InternalTenantContext context) {
@@ -341,25 +350,23 @@ public class MockSubscriptionDaoMemory extends MockEntityDaoBase<SubscriptionBun
}
@Override
- public void cancelSubscription(final DefaultSubscriptionBase subscription, final SubscriptionBaseEvent cancelEvent,
- final InternalCallContext context, final int seqId) {
- synchronized (events) {
- cancelNextPhaseEvent(subscription.getId(), context);
- insertEvent(cancelEvent, context);
- }
+ public void cancelSubscriptionsOnBasePlanEvent(final DefaultSubscriptionBase subscription, final SubscriptionBaseEvent event, final List<DefaultSubscriptionBase> subscriptions, final List<SubscriptionBaseEvent> cancelEvents, final InternalCallContext context) {
+ cancelSubscriptions(subscriptions, cancelEvents, context);
+ notifyBusOfEffectiveImmediateChange(subscription, event, subscriptions.size(), context);
}
@Override
public void cancelSubscriptions(final List<DefaultSubscriptionBase> subscriptions, final List<SubscriptionBaseEvent> cancelEvents, final InternalCallContext context) {
synchronized (events) {
for (int i = 0; i < subscriptions.size(); i++) {
- cancelSubscription(subscriptions.get(i), cancelEvents.get(i), context, 0);
+ cancelNextPhaseEvent(subscriptions.get(i).getId(), context);
+ insertEvent(cancelEvents.get(i), context);
}
}
}
@Override
- public void changePlan(final DefaultSubscriptionBase subscription, final List<SubscriptionBaseEvent> changeEvents, final InternalCallContext context) {
+ public void changePlan(final DefaultSubscriptionBase subscription, final List<SubscriptionBaseEvent> changeEvents, final List<DefaultSubscriptionBase> subscriptionsToBeCancelled, final List<SubscriptionBaseEvent> cancelEvents, final InternalCallContext context) {
synchronized (events) {
cancelNextChangeEvent(subscription.getId());
cancelNextPhaseEvent(subscription.getId(), context);
@@ -368,6 +375,8 @@ public class MockSubscriptionDaoMemory extends MockEntityDaoBase<SubscriptionBun
recordFutureNotificationFromTransaction(null, cur.getEffectiveDate(), new SubscriptionNotificationKey(cur.getId()), context);
}
}
+
+ cancelSubscriptions(subscriptionsToBeCancelled, cancelEvents, context);
}
private void insertEvent(final SubscriptionBaseEvent event, final InternalCallContext context) {
@@ -499,6 +508,21 @@ public class MockSubscriptionDaoMemory extends MockEntityDaoBase<SubscriptionBun
}
}
+ private void notifyBusOfEffectiveImmediateChange(final DefaultSubscriptionBase subscription, final SubscriptionBaseEvent immediateEvent, final int seqId, final InternalCallContext context) {
+ try {
+ final SubscriptionBaseTransitionData transition = subscription.getTransitionFromEvent(immediateEvent, seqId);
+ final BusEvent busEvent = new DefaultEffectiveSubscriptionEvent(transition,
+ subscription.getAlignStartDate(),
+ context.getUserToken(),
+ context.getAccountRecordId(),
+ context.getTenantRecordId());
+
+ eventBus.post(busEvent);
+ } catch (final EventBusException e) {
+ log.warn("Failed to post effective event for subscription " + subscription.getId(), e);
+ }
+ }
+
@Override
public Iterable<SubscriptionBaseEvent> getFutureEventsForAccount(final InternalTenantContext context) {
return null;
diff --git a/util/src/test/java/org/killbill/billing/api/TestApiListener.java b/util/src/test/java/org/killbill/billing/api/TestApiListener.java
index a5815c4..690707a 100644
--- a/util/src/test/java/org/killbill/billing/api/TestApiListener.java
+++ b/util/src/test/java/org/killbill/billing/api/TestApiListener.java
@@ -1,7 +1,9 @@
/*
* Copyright 2010-2011 Ning, Inc.
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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:
*
@@ -18,34 +20,35 @@ package org.killbill.billing.api;
import java.util.Iterator;
import java.util.List;
+import java.util.Map;
import java.util.Stack;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
-import org.joda.time.DateTime;
-import org.killbill.billing.events.BroadcastInternalEvent;
-import org.killbill.billing.events.InvoiceNotificationInternalEvent;
-import org.skife.jdbi.v2.Handle;
-import org.skife.jdbi.v2.IDBI;
-import org.skife.jdbi.v2.tweak.HandleCallback;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.testng.Assert;
-
import org.killbill.billing.events.BlockingTransitionInternalEvent;
+import org.killbill.billing.events.BroadcastInternalEvent;
import org.killbill.billing.events.CustomFieldEvent;
import org.killbill.billing.events.EffectiveEntitlementInternalEvent;
import org.killbill.billing.events.EffectiveSubscriptionInternalEvent;
import org.killbill.billing.events.InvoiceAdjustmentInternalEvent;
import org.killbill.billing.events.InvoiceCreationInternalEvent;
+import org.killbill.billing.events.InvoiceNotificationInternalEvent;
+import org.killbill.billing.events.InvoicePaymentErrorInternalEvent;
+import org.killbill.billing.events.InvoicePaymentInfoInternalEvent;
import org.killbill.billing.events.PaymentErrorInternalEvent;
import org.killbill.billing.events.PaymentInfoInternalEvent;
import org.killbill.billing.events.PaymentPluginErrorInternalEvent;
import org.killbill.billing.events.RepairSubscriptionInternalEvent;
import org.killbill.billing.events.TagDefinitionInternalEvent;
import org.killbill.billing.events.TagInternalEvent;
+import org.skife.jdbi.v2.Handle;
+import org.skife.jdbi.v2.IDBI;
+import org.skife.jdbi.v2.tweak.HandleCallback;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
import com.google.common.base.Joiner;
import com.google.common.eventbus.Subscribe;
@@ -78,6 +81,12 @@ public class TestApiListener {
}
public void assertListenerStatus() {
+ // Bail early
+ if (isListenerFailed) {
+ log.error(listenerFailedMsg);
+ Assert.fail(listenerFailedMsg);
+ }
+
try {
assertTrue(isCompleted(DELAY));
} catch (final Exception e) {
@@ -107,6 +116,8 @@ public class TestApiListener {
INVOICE,
INVOICE_NOTIFICATION,
INVOICE_ADJUSTMENT,
+ INVOICE_PAYMENT,
+ INVOICE_PAYMENT_ERROR,
PAYMENT,
PAYMENT_ERROR,
PAYMENT_PLUGIN_ERROR,
@@ -242,6 +253,20 @@ public class TestApiListener {
}
@Subscribe
+ public void handleInvoicePaymentEvents(final InvoicePaymentInfoInternalEvent event) {
+ log.info(String.format("Got InvoicePaymentInfo event %s", event.toString()));
+ assertEqualsNicely(NextEvent.INVOICE_PAYMENT);
+ notifyIfStackEmpty();
+ }
+
+ @Subscribe
+ public void handleInvoicePaymentErrorEvents(final InvoicePaymentErrorInternalEvent event) {
+ log.info(String.format("Got InvoicePaymentError event %s", event.toString()));
+ assertEqualsNicely(NextEvent.INVOICE_PAYMENT_ERROR);
+ notifyIfStackEmpty();
+ }
+
+ @Subscribe
public void handlePaymentEvents(final PaymentInfoInternalEvent event) {
log.info(String.format("Got PaymentInfo event %s", event.toString()));
assertEqualsNicely(NextEvent.PAYMENT);
@@ -288,39 +313,52 @@ public class TestApiListener {
public boolean isCompleted(final long timeout) {
synchronized (this) {
- if (completed) {
- return completed;
- }
long waitTimeMs = timeout;
do {
try {
- final DateTime before = new DateTime();
- wait(500);
+ final long before = System.currentTimeMillis();
+ wait(100);
if (completed) {
// TODO PIERRE Kludge alert!
// When we arrive here, we got notified by the current thread (Bus listener) that we received
// all expected events. But other handlers might still be processing them.
// Since there is only one bus thread, and that the test thread waits for all events to be processed,
// we're guaranteed that all are processed when the bus events table is empty.
+ // We also need to wait for in-processing notifications (see https://github.com/killbill/killbill/issues/475).
+ // This is really similar to TestResource#waitForNotificationToComplete.
await().atMost(timeout, TimeUnit.MILLISECONDS).until(new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
- final long inProcessingBusEvents = idbi.withHandle(new HandleCallback<Long>() {
+ final long inProcessing = idbi.withHandle(new HandleCallback<Long>() {
@Override
public Long withHandle(final Handle handle) throws Exception {
- return (Long) handle.select("select count(distinct record_id) count from bus_events").get(0).get("count");
+ return (Long) handle.select("select count(distinct record_id) count from bus_events").get(0).get("count") +
+ // We assume all ready notifications have been picked up
+ (Long) handle.select("select count(distinct record_id) count from notifications where processing_state = 'IN_PROCESSING'").get(0).get("count");
}
});
- log.debug("Events still in processing: " + inProcessingBusEvents);
- return inProcessingBusEvents == 0;
+ log.debug("Events still in processing: {}", inProcessing);
+ return inProcessing == 0;
}
});
return completed;
}
- final DateTime after = new DateTime();
- waitTimeMs -= after.getMillis() - before.getMillis();
+ final long after = System.currentTimeMillis();
+ waitTimeMs -= (after - before);
} catch (final Exception ignore) {
- log.error("isCompleted got interrupted ", ignore);
+ final StringBuilder errorBuilder = new StringBuilder("isCompleted got interrupted. Exception: ").append(ignore)
+ .append("\nRemaining bus events:\n");
+ idbi.withHandle(new HandleCallback<Void>() {
+ @Override
+ public Void withHandle(final Handle handle) throws Exception {
+ final List<Map<String, Object>> busEvents = handle.select("select * from bus_events");
+ for (final Map<String, Object> busEvent : busEvents) {
+ errorBuilder.append(busEvent).append("\n");
+ }
+ return null;
+ }
+ });
+ log.error(errorBuilder.toString());
return false;
}
} while (waitTimeMs > 0 && !completed);
diff --git a/util/src/test/java/org/killbill/billing/mock/glue/MockEntitlementModule.java b/util/src/test/java/org/killbill/billing/mock/glue/MockEntitlementModule.java
index 7387ab3..eb68eac 100644
--- a/util/src/test/java/org/killbill/billing/mock/glue/MockEntitlementModule.java
+++ b/util/src/test/java/org/killbill/billing/mock/glue/MockEntitlementModule.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014 Groupon, Inc
- * Copyright 2014 The Billing Project, LLC
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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
@@ -29,13 +29,19 @@ import org.mockito.Mockito;
public class MockEntitlementModule extends KillBillModule implements EntitlementModule {
- private final BlockingInternalApi blockingApi = Mockito.mock(BlockingInternalApi.class);
private final EntitlementApi entitlementApi = Mockito.mock(EntitlementApi.class);
private final EntitlementInternalApi entitlementInternalApi = Mockito.mock(EntitlementInternalApi.class);
private final SubscriptionApi subscriptionApi = Mockito.mock(SubscriptionApi.class);
+ protected final BlockingInternalApi blockingApi;
+
public MockEntitlementModule(final KillbillConfigSource configSource) {
+ this(configSource, Mockito.mock(BlockingInternalApi.class));
+ }
+
+ public MockEntitlementModule(final KillbillConfigSource configSource, final BlockingInternalApi blockingApi) {
super(configSource);
+ this.blockingApi = blockingApi;
}
@Override
@@ -43,6 +49,7 @@ public class MockEntitlementModule extends KillBillModule implements Entitlement
installBlockingStateDao();
installBlockingApi();
installEntitlementApi();
+ installEntitlementInternalApi();
installBlockingChecker();
}
@@ -52,7 +59,7 @@ public class MockEntitlementModule extends KillBillModule implements Entitlement
@Override
public void installBlockingApi() {
- //bind(BlockingInternalApi.class).toInstance(blockingApi);
+ bind(BlockingInternalApi.class).toInstance(blockingApi);
}
@Override