killbill-memoizeit
Changes
beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationDryRunInvoice.java 476(+476 -0)
Details
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationDryRunInvoice.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationDryRunInvoice.java
new file mode 100644
index 0000000..b128ecd
--- /dev/null
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationDryRunInvoice.java
@@ -0,0 +1,476 @@
+/*
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 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.beatrix.integration;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.joda.time.DateTime;
+import org.joda.time.LocalDate;
+import org.killbill.billing.ErrorCode;
+import org.killbill.billing.account.api.Account;
+import org.killbill.billing.api.TestApiListener.NextEvent;
+import org.killbill.billing.beatrix.util.InvoiceChecker.ExpectedInvoiceItemCheck;
+import org.killbill.billing.catalog.DefaultPlanPhasePriceOverride;
+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.catalog.api.PriceListSet;
+import org.killbill.billing.catalog.api.ProductCategory;
+import org.killbill.billing.entitlement.api.DefaultEntitlement;
+import org.killbill.billing.entitlement.api.Entitlement;
+import org.killbill.billing.entitlement.api.SubscriptionEventType;
+import org.killbill.billing.invoice.api.DryRunArguments;
+import org.killbill.billing.invoice.api.DryRunType;
+import org.killbill.billing.invoice.api.Invoice;
+import org.killbill.billing.invoice.api.InvoiceApiException;
+import org.killbill.billing.invoice.api.InvoiceItemType;
+import org.killbill.billing.payment.api.PluginProperty;
+import org.killbill.billing.subscription.api.user.DefaultSubscriptionBase;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+
+import static com.tc.util.Assert.fail;
+import static org.testng.Assert.assertEquals;
+
+public class TestIntegrationDryRunInvoice extends TestIntegrationBase {
+
+ //
+ // Basic test with one subscription that verifies the behavior of using invoice dryRun api with no date
+ //
+ @Test(groups = "slow")
+ public void testDryRunWithNoTargetDate() throws Exception {
+ final int billingDay = 14;
+ final DateTime initialCreationDate = new DateTime(2015, 5, 15, 0, 0, 0, 0, testTimeZone);
+ // set clock to the initial start date
+ clock.setTime(initialCreationDate);
+
+ log.info("Beginning test with BCD of " + billingDay);
+ final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(billingDay));
+
+ int invoiceItemCount = 1;
+
+ //
+ // CREATE SUBSCRIPTION AND EXPECT BOTH EVENTS: NextEvent.CREATE, NextEvent.BLOCK NextEvent.INVOICE
+ //
+ DefaultEntitlement baseEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "bundleKey", "Shotgun", ProductCategory.BASE, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
+ DefaultSubscriptionBase subscription = subscriptionDataFromSubscription(baseEntitlement.getSubscriptionBase());
+ invoiceChecker.checkInvoice(account.getId(), invoiceItemCount++, callContext, new ExpectedInvoiceItemCheck(initialCreationDate.toLocalDate(), null, InvoiceItemType.FIXED, new BigDecimal("0")));
+ // No end date for the trial item (fixed price of zero), and CTD should be today (i.e. when the trial started)
+ invoiceChecker.checkChargedThroughDate(subscription.getId(), clock.getUTCToday(), callContext);
+
+ final List<ExpectedInvoiceItemCheck> expectedInvoices = new ArrayList<ExpectedInvoiceItemCheck>();
+ expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 6, 14), new LocalDate(2015, 7, 14), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
+
+ // This will verify that the upcoming Phase is found and the invoice is generated at the right date, with correct items
+ DryRunArguments dryRun = new TestDryRunArguments(DryRunType.UPCOMING_INVOICE);
+ Invoice dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), null, dryRun, callContext);
+ invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, callContext, expectedInvoices);
+
+ // Move through time and verify we get the same invoice
+ busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+ clock.addDays(30);
+ assertListenerStatus();
+ List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoiceChecker.checkInvoice(invoices.get(1).getId(), callContext, expectedInvoices);
+ expectedInvoices.clear();
+
+ // This will verify that the upcoming invoice notification is found and the invoice is generated at the right date, with correct items
+ expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 7, 14), new LocalDate(2015, 8, 14), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
+ dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), null, dryRun, callContext);
+ invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, callContext, expectedInvoices);
+
+ // Move through time and verify we get the same invoice
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+ clock.addMonths(1);
+ assertListenerStatus();
+ invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoiceChecker.checkInvoice(invoices.get(2).getId(), callContext, expectedInvoices);
+ expectedInvoices.clear();
+
+ // One more time, this will verify that the upcoming invoice notification is found and the invoice is generated at the right date, with correct items
+ expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 8, 14), new LocalDate(2015, 9, 14), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
+ dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), null, dryRun, callContext);
+ invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, callContext, expectedInvoices);
+ }
+
+ //
+ // More sophisticated test with two non aligned subscriptions that verifies the behavior of using invoice dryRun api with no date
+ // - The first subscription is an annual (SUBSCRIPTION aligned) whose billingDate is the first (we start on Jan 2nd to take into account the 30 days trial)
+ // - The second subscription is a monthly (ACCOUNT aligned) whose billingDate is the 14 (we start on Dec 15 to also take into account the 30 days trial)
+ //
+ // The test verifies that the dryRun invoice with supplied date will always take into account the 'closest' invoice that should be generated
+ //
+ @Test(groups = "slow")
+ public void testDryRunWithNoTargetDateAndMultipleNonAlignedSubscriptions() throws Exception {
+ // Set in such a way that annual billing date will be the 1st
+ final DateTime initialCreationDate = new DateTime(2014, 1, 2, 0, 0, 0, 0, testTimeZone);
+ clock.setTime(initialCreationDate);
+
+ // billing date for the monthly
+ final int billingDay = 14;
+
+ final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(billingDay));
+
+ int invoiceItemCount = 1;
+
+ // Create ANNUAL BP
+ DefaultEntitlement baseEntitlementAnnual = createBaseEntitlementAndCheckForCompletion(account.getId(), "bundleKeyAnnual", "Shotgun", ProductCategory.BASE, BillingPeriod.ANNUAL, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
+ DefaultSubscriptionBase subscriptionAnnual = subscriptionDataFromSubscription(baseEntitlementAnnual.getSubscriptionBase());
+ invoiceChecker.checkInvoice(account.getId(), invoiceItemCount++, callContext, new ExpectedInvoiceItemCheck(initialCreationDate.toLocalDate(), null, InvoiceItemType.FIXED, new BigDecimal("0")));
+ // No end date for the trial item (fixed price of zero), and CTD should be today (i.e. when the trial started)
+ invoiceChecker.checkChargedThroughDate(subscriptionAnnual.getId(), clock.getUTCToday(), callContext);
+
+ // Verify next dryRun invoice and then move the clock to that date to also verify real invoice is the same
+ final List<ExpectedInvoiceItemCheck> expectedInvoices = new ArrayList<ExpectedInvoiceItemCheck>();
+ expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2014, 2, 1), new LocalDate(2015, 2, 1), InvoiceItemType.RECURRING, new BigDecimal("2399.95")));
+
+ DryRunArguments dryRun = new TestDryRunArguments(DryRunType.UPCOMING_INVOICE);
+ Invoice dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), null, dryRun, callContext);
+ invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, callContext, expectedInvoices);
+
+ busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+ // 2014-2-1
+ clock.addDays(30);
+ assertListenerStatus();
+ invoiceChecker.checkInvoice(account.getId(), invoiceItemCount++, callContext, expectedInvoices);
+ expectedInvoices.clear();
+
+ // Since we only have one subscription next dryRun will show the annual
+ expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 2, 1), new LocalDate(2016, 2, 1), InvoiceItemType.RECURRING, new BigDecimal("2399.95")));
+ dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), null, dryRun, callContext);
+ invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, callContext, expectedInvoices);
+ expectedInvoices.clear();
+
+ // 2014-12-15
+ final DateTime secondSubscriptionCreationDate = new DateTime(2014, 12, 15, 0, 0, 0, 0, testTimeZone);
+ clock.setTime(secondSubscriptionCreationDate);
+
+ // Create the monthly
+ DefaultEntitlement baseEntitlementMonthly = createBaseEntitlementAndCheckForCompletion(account.getId(), "bundleKey", "Shotgun", ProductCategory.BASE, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
+ DefaultSubscriptionBase subscriptionMonthly = subscriptionDataFromSubscription(baseEntitlementMonthly.getSubscriptionBase());
+ invoiceChecker.checkInvoice(account.getId(), invoiceItemCount++, callContext, new ExpectedInvoiceItemCheck(secondSubscriptionCreationDate.toLocalDate(), null, InvoiceItemType.FIXED, new BigDecimal("0")));
+ // No end date for the trial item (fixed price of zero), and CTD should be today (i.e. when the trial started)
+ invoiceChecker.checkChargedThroughDate(subscriptionMonthly.getId(), clock.getUTCToday(), callContext);
+
+ // Verify next dryRun invoice and then move the clock to that date to also verify real invoice is the same
+ expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 1, 14), new LocalDate(2015, 2, 14), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
+ dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), null, dryRun, callContext);
+ invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, callContext, expectedInvoices);
+
+ busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+ // 2015-1-14
+ clock.addDays(30);
+ assertListenerStatus();
+ invoiceChecker.checkInvoice(account.getId(), invoiceItemCount++, callContext, expectedInvoices);
+ expectedInvoices.clear();
+
+ // We test first the next expected invoice for a specific subscription: We can see the targetDate is 2015-2-14 and not 2015-2-1
+ final DryRunArguments dryRunWIthSubscription = new TestDryRunArguments(DryRunType.UPCOMING_INVOICE, null, null, null, null, null, null, subscriptionMonthly.getId(), null, null, null);
+ expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 2, 14), new LocalDate(2015, 3, 14), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
+ dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), null, dryRunWIthSubscription, callContext);
+ assertEquals(dryRunInvoice.getTargetDate(), new LocalDate(2015, 2, 14));
+ invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, callContext, expectedInvoices);
+ expectedInvoices.clear();
+
+ // Then we test first the next expected invoice at the account level
+ expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 2, 1), new LocalDate(2016, 2, 1), InvoiceItemType.RECURRING, new BigDecimal("2399.95")));
+ dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), null, dryRun, callContext);
+ invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, callContext, expectedInvoices);
+
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+ // 2015-2-1
+ clock.addDays(18);
+ assertListenerStatus();
+ invoiceChecker.checkInvoice(account.getId(), invoiceItemCount++, callContext, expectedInvoices);
+ expectedInvoices.clear();
+
+ expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 2, 14), new LocalDate(2015, 3, 14), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
+ dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), null, dryRun, callContext);
+ invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, callContext, expectedInvoices);
+ }
+
+ @Test(groups = "slow")
+ public void testDryRunWithPendingSubscription() throws Exception {
+
+ final LocalDate initialDate = new LocalDate(2017, 4, 1);
+ clock.setDay(initialDate);
+
+ // Create account with non BCD to force junction BCD logic to activate
+ final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(null));
+
+ final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Shotgun", BillingPeriod.ANNUAL, PriceListSet.DEFAULT_PRICELIST_NAME, null);
+
+ final LocalDate futureDate = new LocalDate(2017, 5, 1);
+
+ // No CREATE event as this is set in the future
+ final Entitlement createdEntitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, futureDate, futureDate, false, ImmutableList.<PluginProperty>of(), callContext);
+ assertEquals(createdEntitlement.getState(), Entitlement.EntitlementState.PENDING);
+ assertEquals(createdEntitlement.getEffectiveStartDate().compareTo(futureDate), 0);
+ assertEquals(createdEntitlement.getEffectiveEndDate(), null);
+ assertListenerStatus();
+
+ // Generate a dryRun invoice on the billing startDate
+ final DryRunArguments dryRunArguments1 = new TestDryRunArguments(DryRunType.TARGET_DATE);
+ final Invoice dryRunInvoice1 = invoiceUserApi.triggerInvoiceGeneration(createdEntitlement.getAccountId(), futureDate, dryRunArguments1, callContext);
+ assertEquals(dryRunInvoice1.getInvoiceItems().size(), 1);
+ assertEquals(dryRunInvoice1.getInvoiceItems().get(0).getInvoiceItemType(), InvoiceItemType.FIXED);
+ assertEquals(dryRunInvoice1.getInvoiceItems().get(0).getAmount().compareTo(BigDecimal.ZERO), 0);
+ assertEquals(dryRunInvoice1.getInvoiceItems().get(0).getStartDate(), futureDate);
+ assertEquals(dryRunInvoice1.getInvoiceItems().get(0).getPlanName(), "shotgun-annual");
+
+ // Generate a dryRun invoice with a plan change
+ final DryRunArguments dryRunArguments = new TestDryRunArguments(DryRunType.SUBSCRIPTION_ACTION, "Pistol", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null,
+ SubscriptionEventType.CHANGE, createdEntitlement.getId(), createdEntitlement.getBundleId(), futureDate, BillingActionPolicy.IMMEDIATE);
+
+ // First one day prior subscription starts
+ try {
+ invoiceUserApi.triggerInvoiceGeneration(createdEntitlement.getAccountId(), futureDate.minusDays(1), dryRunArguments, callContext);
+ fail("Should fail to trigger dryRun invoice prior subscription starts");
+ } catch (final InvoiceApiException e) {
+ assertEquals(e.getCode(), ErrorCode.INVOICE_NOTHING_TO_DO.getCode());
+ }
+
+ // Second, on the startDate
+ final Invoice dryRunInvoice2 = invoiceUserApi.triggerInvoiceGeneration(createdEntitlement.getAccountId(), futureDate, dryRunArguments, callContext);
+ assertEquals(dryRunInvoice2.getInvoiceItems().size(), 1);
+ assertEquals(dryRunInvoice2.getInvoiceItems().get(0).getInvoiceItemType(), InvoiceItemType.FIXED);
+ assertEquals(dryRunInvoice2.getInvoiceItems().get(0).getAmount().compareTo(BigDecimal.ZERO), 0);
+ assertEquals(dryRunInvoice2.getInvoiceItems().get(0).getStartDate(), futureDate);
+ assertEquals(dryRunInvoice2.getInvoiceItems().get(0).getPlanName(), "pistol-monthly");
+
+ // Check BCD is not yet set
+ final Account refreshedAccount1 = accountUserApi.getAccountById(account.getId(), callContext);
+ assertEquals(refreshedAccount1.getBillCycleDayLocal(), new Integer(0));
+
+ busHandler.pushExpectedEvents(NextEvent.INVOICE);
+ final Invoice realInvoice = invoiceUserApi.triggerInvoiceGeneration(createdEntitlement.getAccountId(), futureDate, null, callContext);
+ assertListenerStatus();
+
+ assertEquals(realInvoice.getInvoiceItems().size(), 1);
+ assertEquals(realInvoice.getInvoiceItems().get(0).getInvoiceItemType(), InvoiceItemType.FIXED);
+ assertEquals(realInvoice.getInvoiceItems().get(0).getAmount().compareTo(BigDecimal.ZERO), 0);
+ assertEquals(realInvoice.getInvoiceItems().get(0).getStartDate(), futureDate);
+ assertEquals(realInvoice.getInvoiceItems().get(0).getPlanName(), "shotgun-annual");
+
+ // Check BCD is now set
+ final Account refreshedAccount2 = accountUserApi.getAccountById(account.getId(), callContext);
+ assertEquals(refreshedAccount2.getBillCycleDayLocal(), new Integer(31));
+
+ // Move clock past startDate to check nothing happens
+ busHandler.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK, NextEvent.NULL_INVOICE);
+ clock.addDays(31);
+ assertListenerStatus();
+
+ // Move clock after PHASE event
+ busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.INVOICE_PAYMENT, NextEvent.PAYMENT);
+ clock.addMonths(12);
+ assertListenerStatus();
+ }
+
+ @Test(groups = "slow")
+ public void testDryRunWithPendingCancelledSubscription() throws Exception {
+
+ final LocalDate initialDate = new LocalDate(2017, 4, 1);
+ clock.setDay(initialDate);
+
+ // Create account with non BCD to force junction BCD logic to activate
+ final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(null));
+
+ final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("pistol-monthly-notrial", null);
+
+ final LocalDate futureDate = new LocalDate(2017, 5, 1);
+
+ // No CREATE event as this is set in the future
+ final Entitlement createdEntitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, futureDate, futureDate, false, ImmutableList.<PluginProperty>of(), callContext);
+ assertEquals(createdEntitlement.getState(), Entitlement.EntitlementState.PENDING);
+ assertEquals(createdEntitlement.getEffectiveStartDate().compareTo(futureDate), 0);
+ assertEquals(createdEntitlement.getEffectiveEndDate(), null);
+ assertListenerStatus();
+
+ // Generate an invoice using a future targetDate
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.INVOICE_PAYMENT, NextEvent.PAYMENT);
+ final Invoice firstInvoice = invoiceUserApi.triggerInvoiceGeneration(createdEntitlement.getAccountId(), futureDate, null, callContext);
+ assertListenerStatus();
+
+ assertEquals(firstInvoice.getInvoiceItems().size(), 1);
+ assertEquals(firstInvoice.getInvoiceItems().get(0).getInvoiceItemType(), InvoiceItemType.RECURRING);
+ assertEquals(firstInvoice.getInvoiceItems().get(0).getAmount().compareTo(new BigDecimal("19.95")), 0);
+ assertEquals(firstInvoice.getInvoiceItems().get(0).getStartDate(), futureDate);
+ assertEquals(firstInvoice.getInvoiceItems().get(0).getPlanName(), "pistol-monthly-notrial");
+
+ // Cancel subscription on its pending startDate
+ createdEntitlement.cancelEntitlementWithDate(futureDate, true, ImmutableList.<PluginProperty>of(), callContext);
+
+ // Move to startDate/cancel Date
+ busHandler.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK, NextEvent.CANCEL, NextEvent.BLOCK, NextEvent.NULL_INVOICE, NextEvent.INVOICE);
+ clock.addMonths(1);
+ assertListenerStatus();
+
+ final List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ assertEquals(invoices.size(), 2);
+
+ final List<ExpectedInvoiceItemCheck> toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
+ new ExpectedInvoiceItemCheck(new LocalDate(2017, 5, 1), new LocalDate(2017, 6, 1), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-19.95")),
+ new ExpectedInvoiceItemCheck(new LocalDate(2017, 5, 1), new LocalDate(2017, 5, 1), InvoiceItemType.CBA_ADJ, new BigDecimal("19.95")));
+ invoiceChecker.checkInvoice(invoices.get(1).getId(), callContext, toBeChecked);
+ }
+
+ @Test(groups = "slow", description = "https://github.com/killbill/killbill/issues/783")
+ public void testIntegrationWithRecurringFreePlan() throws Exception {
+ final DateTime initialCreationDate = new DateTime(2017, 1, 1, 0, 0, 0, 0, testTimeZone);
+ // set clock to the initial start date
+ clock.setTime(initialCreationDate);
+
+ final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(1));
+
+ final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Blowdart", BillingPeriod.MONTHLY, "notrial", null);
+
+ // Price override of $0
+ final List<PlanPhasePriceOverride> overrides = new ArrayList<PlanPhasePriceOverride>();
+ overrides.add(new DefaultPlanPhasePriceOverride("blowdart-monthly-notrial-evergreen", account.getCurrency(), null, BigDecimal.ZERO));
+ busHandler.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
+ final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, "bundleExternalKey", overrides, null, null, false, ImmutableList.<PluginProperty>of(), callContext);
+ assertListenerStatus();
+
+ invoiceChecker.checkInvoice(account.getId(), 1, callContext,
+ new ExpectedInvoiceItemCheck(new LocalDate(2017, 1, 1), new LocalDate(2017, 2, 1), InvoiceItemType.RECURRING, BigDecimal.ZERO));
+
+ // 2017-02-01
+ busHandler.pushExpectedEvents(NextEvent.INVOICE);
+ clock.addMonths(1);
+ assertListenerStatus();
+
+ invoiceChecker.checkInvoice(account.getId(), 2, callContext,
+ new ExpectedInvoiceItemCheck(new LocalDate(2017, 2, 1), new LocalDate(2017, 3, 1), InvoiceItemType.RECURRING, BigDecimal.ZERO));
+
+ // Do the change mid-month so the repair triggers the bug in https://github.com/killbill/killbill/issues/783
+ entitlement.changePlanWithDate(spec, ImmutableList.<PlanPhasePriceOverride>of(), new LocalDate("2017-02-15"), ImmutableList.<PluginProperty>of(), callContext);
+ assertListenerStatus();
+
+ // 2017-02-15
+ busHandler.pushExpectedEvents(NextEvent.CHANGE, NextEvent.INVOICE, NextEvent.INVOICE_PAYMENT, NextEvent.PAYMENT);
+ clock.addDays(15);
+ assertListenerStatus();
+
+ // Note: no repair
+ invoiceChecker.checkInvoice(account.getId(), 2, callContext,
+ new ExpectedInvoiceItemCheck(new LocalDate(2017, 2, 1), new LocalDate(2017, 3, 1), InvoiceItemType.RECURRING, BigDecimal.ZERO));
+
+ invoiceChecker.checkInvoice(account.getId(), 3, callContext,
+ new ExpectedInvoiceItemCheck(new LocalDate(2017, 2, 1), new LocalDate(2017, 2, 15), InvoiceItemType.RECURRING, BigDecimal.ZERO),
+ new ExpectedInvoiceItemCheck(new LocalDate(2017, 2, 15), new LocalDate(2017, 3, 1), InvoiceItemType.RECURRING, new BigDecimal("14.98")));
+
+ // 2017-03-01
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.INVOICE_PAYMENT, NextEvent.PAYMENT);
+ clock.addDays(15);
+ assertListenerStatus();
+
+ invoiceChecker.checkInvoice(account.getId(), 4, callContext,
+ new ExpectedInvoiceItemCheck(new LocalDate(2017, 3, 1), new LocalDate(2017, 4, 1), InvoiceItemType.RECURRING, new BigDecimal("29.95")));
+
+ // 2017-04-01
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.INVOICE_PAYMENT, NextEvent.PAYMENT);
+ clock.addMonths(1);
+ assertListenerStatus();
+
+ invoiceChecker.checkInvoice(account.getId(), 5, callContext,
+ new ExpectedInvoiceItemCheck(new LocalDate(2017, 4, 1), new LocalDate(2017, 5, 1), InvoiceItemType.RECURRING, new BigDecimal("29.95")));
+ }
+
+ @Test(groups = "slow", description = "See https://github.com/killbill/killbill/issues/774")
+ public void testDryRunTargetDateWithIntermediateInvoice() throws Exception {
+ final DateTime initialCreationDate = new DateTime(2014, 1, 2, 0, 0, 0, 0, testTimeZone);
+ clock.setTime(initialCreationDate);
+
+ // billing date for the monthly
+ final int billingDay = 14;
+
+ final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(billingDay));
+
+ // Create first ANNUAL BP -> BCD = 1
+ createBaseEntitlementAndCheckForCompletion(account.getId(), "bundleKeyAnnual1", "Shotgun", ProductCategory.BASE, BillingPeriod.ANNUAL, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
+
+ // 2014-1-4
+ clock.addDays(2);
+ // Create second ANNUAL BP -> BCD = 3
+ createBaseEntitlementAndCheckForCompletion(account.getId(), "bundleKeyAnnual2", "Pistol", ProductCategory.BASE, BillingPeriod.ANNUAL, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
+
+ // 2014-2-1
+ busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+ clock.addDays(28);
+ assertListenerStatus();
+
+ // 2014-2-3
+ busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+ clock.addDays(2);
+ assertListenerStatus();
+
+
+ // 2014-12-15
+ final DateTime monthlySubscriptionCreationDate = new DateTime(2014, 12, 15, 0, 0, 0, 0, testTimeZone);
+ clock.setTime(monthlySubscriptionCreationDate);
+
+ // Create the monthly
+ createBaseEntitlementAndCheckForCompletion(account.getId(), "bundleKey", "Shotgun", ProductCategory.BASE, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
+
+ // 2015-1-14
+ busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+ clock.addDays(30);
+ assertListenerStatus();
+
+ final List<ExpectedInvoiceItemCheck> expectedInvoices = new ArrayList<ExpectedInvoiceItemCheck>();
+
+ // At this point (2015-1-14), we have 3 pending invoice notifications:
+ //
+ // - The 1st ANNUAL on 2015-2-1
+ // - The 2nd ANNUAL on 2015-2-3
+ // - The MONTHLY on 2015-2-14
+ //
+ // 1. We verify that a DryRunType.TARGET_DATE for 2015-2-14 leads to an invoice that **only** contains the MONTHLY item (fix for #774)
+ //
+ final DryRunArguments dryRun = new TestDryRunArguments(DryRunType.TARGET_DATE, null, null, null, null, null, null, null, null, null, null);
+ expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 2, 14), new LocalDate(2015, 3, 14), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
+ Invoice dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), new LocalDate(2015, 2, 14), dryRun, callContext);
+ assertEquals(dryRunInvoice.getTargetDate(), new LocalDate(2015, 2, 14));
+ invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, callContext, expectedInvoices);
+ expectedInvoices.clear();
+
+
+ // 2. We verify that a DryRunType.TARGET_DATE for 2015-2-3 leads to an invoice that **only** contains the 2nd ANNUAL item (fix for #774)
+ //
+ expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 2, 3), new LocalDate(2016, 2, 3), InvoiceItemType.RECURRING, new BigDecimal("199.95")));
+ dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), new LocalDate(2015, 2, 3), dryRun, callContext);
+ assertEquals(dryRunInvoice.getTargetDate(), new LocalDate(2015, 2, 3));
+ invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, callContext, expectedInvoices);
+ expectedInvoices.clear();
+
+
+ // 3. We verify that UPCOMING_INVOICE leads to next invoice fo the ANNUAL
+ expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 2, 1), new LocalDate(2016, 2, 1), InvoiceItemType.RECURRING, new BigDecimal("2399.95")));
+ dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), null, new TestDryRunArguments(DryRunType.UPCOMING_INVOICE), callContext);
+ assertEquals(dryRunInvoice.getTargetDate(), new LocalDate(2015, 2, 1));
+ invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, callContext, expectedInvoices);
+ expectedInvoices.clear();
+
+ }
+
+}
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 1cbad77..61475fc 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
@@ -25,25 +25,18 @@ import java.util.UUID;
import org.joda.time.DateTime;
import org.joda.time.LocalDate;
-import org.killbill.billing.ErrorCode;
import org.killbill.billing.ObjectType;
import org.killbill.billing.account.api.Account;
import org.killbill.billing.api.TestApiListener.NextEvent;
import org.killbill.billing.beatrix.util.InvoiceChecker.ExpectedInvoiceItemCheck;
import org.killbill.billing.catalog.DefaultPlanPhasePriceOverride;
-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.catalog.api.PriceListSet;
import org.killbill.billing.catalog.api.ProductCategory;
import org.killbill.billing.entitlement.api.DefaultEntitlement;
import org.killbill.billing.entitlement.api.Entitlement;
-import org.killbill.billing.entitlement.api.SubscriptionEventType;
-import org.killbill.billing.invoice.api.DryRunArguments;
-import org.killbill.billing.invoice.api.DryRunType;
import org.killbill.billing.invoice.api.Invoice;
-import org.killbill.billing.invoice.api.InvoiceApiException;
import org.killbill.billing.invoice.api.InvoiceItem;
import org.killbill.billing.invoice.api.InvoiceItemType;
import org.killbill.billing.invoice.model.ExternalChargeInvoiceItem;
@@ -54,170 +47,11 @@ import org.testng.annotations.Test;
import com.google.common.collect.ImmutableList;
-import static com.tc.util.Assert.fail;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;
public class TestIntegrationInvoice extends TestIntegrationBase {
- //
- // Basic test with one subscription that verifies the behavior of using invoice dryRun api with no date
- //
- @Test(groups = "slow")
- public void testDryRunWithNoTargetDate() throws Exception {
- final int billingDay = 14;
- final DateTime initialCreationDate = new DateTime(2015, 5, 15, 0, 0, 0, 0, testTimeZone);
- // set clock to the initial start date
- clock.setTime(initialCreationDate);
-
- log.info("Beginning test with BCD of " + billingDay);
- final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(billingDay));
-
- int invoiceItemCount = 1;
-
- //
- // CREATE SUBSCRIPTION AND EXPECT BOTH EVENTS: NextEvent.CREATE, NextEvent.BLOCK NextEvent.INVOICE
- //
- DefaultEntitlement baseEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "bundleKey", "Shotgun", ProductCategory.BASE, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
- DefaultSubscriptionBase subscription = subscriptionDataFromSubscription(baseEntitlement.getSubscriptionBase());
- invoiceChecker.checkInvoice(account.getId(), invoiceItemCount++, callContext, new ExpectedInvoiceItemCheck(initialCreationDate.toLocalDate(), null, InvoiceItemType.FIXED, new BigDecimal("0")));
- // No end date for the trial item (fixed price of zero), and CTD should be today (i.e. when the trial started)
- invoiceChecker.checkChargedThroughDate(subscription.getId(), clock.getUTCToday(), callContext);
-
- final List<ExpectedInvoiceItemCheck> expectedInvoices = new ArrayList<ExpectedInvoiceItemCheck>();
- expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 6, 14), new LocalDate(2015, 7, 14), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
-
- // This will verify that the upcoming Phase is found and the invoice is generated at the right date, with correct items
- DryRunArguments dryRun = new TestDryRunArguments(DryRunType.UPCOMING_INVOICE);
- Invoice dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), null, dryRun, callContext);
- invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, callContext, expectedInvoices);
-
- // Move through time and verify we get the same invoice
- busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
- clock.addDays(30);
- assertListenerStatus();
- List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
- invoiceChecker.checkInvoice(invoices.get(1).getId(), callContext, expectedInvoices);
- expectedInvoices.clear();
-
- // This will verify that the upcoming invoice notification is found and the invoice is generated at the right date, with correct items
- expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 7, 14), new LocalDate(2015, 8, 14), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
- dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), null, dryRun, callContext);
- invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, callContext, expectedInvoices);
-
-
- // Move through time and verify we get the same invoice
- busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
- clock.addMonths(1);
- assertListenerStatus();
- invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
- invoiceChecker.checkInvoice(invoices.get(2).getId(), callContext, expectedInvoices);
- expectedInvoices.clear();
-
- // One more time, this will verify that the upcoming invoice notification is found and the invoice is generated at the right date, with correct items
- expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 8, 14), new LocalDate(2015, 9, 14), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
- dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), null, dryRun, callContext);
- invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, callContext, expectedInvoices);
- }
-
- //
- // More sophisticated test with two non aligned subscriptions that verifies the behavior of using invoice dryRun api with no date
- // - The first subscription is an annual (SUBSCRIPTION aligned) whose billingDate is the first (we start on Jan 2nd to take into account the 30 days trial)
- // - The second subscription is a monthly (ACCOUNT aligned) whose billingDate is the 14 (we start on Dec 15 to also take into account the 30 days trial)
- //
- // The test verifies that the dryRun invoice with supplied date will always take into account the 'closest' invoice that should be generated
- //
- @Test(groups = "slow")
- public void testDryRunWithNoTargetDateAndMultipleNonAlignedSubscriptions() throws Exception {
- // Set in such a way that annual billing date will be the 1st
- final DateTime initialCreationDate = new DateTime(2014, 1, 2, 0, 0, 0, 0, testTimeZone);
- clock.setTime(initialCreationDate);
-
- // billing date for the monthly
- final int billingDay = 14;
-
- final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(billingDay));
-
- int invoiceItemCount = 1;
-
- // Create ANNUAL BP
- DefaultEntitlement baseEntitlementAnnual = createBaseEntitlementAndCheckForCompletion(account.getId(), "bundleKeyAnnual", "Shotgun", ProductCategory.BASE, BillingPeriod.ANNUAL, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
- DefaultSubscriptionBase subscriptionAnnual = subscriptionDataFromSubscription(baseEntitlementAnnual.getSubscriptionBase());
- invoiceChecker.checkInvoice(account.getId(), invoiceItemCount++, callContext, new ExpectedInvoiceItemCheck(initialCreationDate.toLocalDate(), null, InvoiceItemType.FIXED, new BigDecimal("0")));
- // No end date for the trial item (fixed price of zero), and CTD should be today (i.e. when the trial started)
- invoiceChecker.checkChargedThroughDate(subscriptionAnnual.getId(), clock.getUTCToday(), callContext);
-
-
- // Verify next dryRun invoice and then move the clock to that date to also verify real invoice is the same
- final List<ExpectedInvoiceItemCheck> expectedInvoices = new ArrayList<ExpectedInvoiceItemCheck>();
- expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2014, 2, 1), new LocalDate(2015, 2, 1), InvoiceItemType.RECURRING, new BigDecimal("2399.95")));
-
- DryRunArguments dryRun = new TestDryRunArguments(DryRunType.UPCOMING_INVOICE);
- Invoice dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), null, dryRun, callContext);
- invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, callContext, expectedInvoices);
-
- busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
- // 2014-2-1
- clock.addDays(30);
- assertListenerStatus();
- invoiceChecker.checkInvoice(account.getId(), invoiceItemCount++, callContext, expectedInvoices);
- expectedInvoices.clear();
-
- // Since we only have one subscription next dryRun will show the annual
- expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 2, 1), new LocalDate(2016, 2, 1), InvoiceItemType.RECURRING, new BigDecimal("2399.95")));
- dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), null, dryRun, callContext);
- invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, callContext, expectedInvoices);
- expectedInvoices.clear();
-
- // 2014-12-15
- final DateTime secondSubscriptionCreationDate = new DateTime(2014, 12, 15, 0, 0, 0, 0, testTimeZone);
- clock.setTime(secondSubscriptionCreationDate);
-
- // Create the monthly
- DefaultEntitlement baseEntitlementMonthly = createBaseEntitlementAndCheckForCompletion(account.getId(), "bundleKey", "Shotgun", ProductCategory.BASE, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
- DefaultSubscriptionBase subscriptionMonthly = subscriptionDataFromSubscription(baseEntitlementMonthly.getSubscriptionBase());
- invoiceChecker.checkInvoice(account.getId(), invoiceItemCount++, callContext, new ExpectedInvoiceItemCheck(secondSubscriptionCreationDate.toLocalDate(), null, InvoiceItemType.FIXED, new BigDecimal("0")));
- // No end date for the trial item (fixed price of zero), and CTD should be today (i.e. when the trial started)
- invoiceChecker.checkChargedThroughDate(subscriptionMonthly.getId(), clock.getUTCToday(), callContext);
-
- // Verify next dryRun invoice and then move the clock to that date to also verify real invoice is the same
- expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 1, 14), new LocalDate(2015, 2, 14), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
- dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), null, dryRun, callContext);
- invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, callContext, expectedInvoices);
-
- busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
- // 2015-1-14
- clock.addDays(30);
- assertListenerStatus();
- invoiceChecker.checkInvoice(account.getId(), invoiceItemCount++, callContext, expectedInvoices);
- expectedInvoices.clear();
-
-
- // We test first the next expected invoice for a specific subscription: We can see the targetDate is 2015-2-14 and not 2015-2-1
- final DryRunArguments dryRunWIthSubscription = new TestDryRunArguments(DryRunType.UPCOMING_INVOICE, null, null, null, null, null, null, subscriptionMonthly.getId(), null, null, null);
- expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 2, 14), new LocalDate(2015, 3, 14), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
- dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), null, dryRunWIthSubscription, callContext);
- assertEquals(dryRunInvoice.getTargetDate(), new LocalDate(2015, 2, 14));
- invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, callContext, expectedInvoices);
- expectedInvoices.clear();
-
- // Then we test first the next expected invoice at the account level
- expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 2, 1), new LocalDate(2016, 2, 1), InvoiceItemType.RECURRING, new BigDecimal("2399.95")));
- dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), null, dryRun, callContext);
- invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, callContext, expectedInvoices);
-
- busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
- // 2015-2-1
- clock.addDays(18);
- assertListenerStatus();
- invoiceChecker.checkInvoice(account.getId(), invoiceItemCount++, callContext, expectedInvoices);
- expectedInvoices.clear();
-
- expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 2, 14), new LocalDate(2015, 3, 14), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
- dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), null, dryRun, callContext);
- invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, callContext, expectedInvoices);
- }
-
@Test(groups = "slow")
public void testApplyCreditOnExistingBalance() throws Exception {
final DateTime initialCreationDate = new DateTime(2015, 5, 15, 0, 0, 0, 0, testTimeZone);
@@ -267,7 +101,6 @@ public class TestIntegrationInvoice extends TestIntegrationBase {
final BigDecimal accountBalance3 = invoiceUserApi.getAccountBalance(account.getId(), callContext);
assertTrue(accountBalance3.compareTo(BigDecimal.ZERO) == 0);
-
final List<Payment> payments = paymentApi.getAccountPayments(account.getId(), false, false, ImmutableList.<PluginProperty>of(), callContext);
assertEquals(payments.size(), 1);
@@ -288,7 +121,7 @@ public class TestIntegrationInvoice extends TestIntegrationBase {
int invoiceItemCount = 1;
- DefaultEntitlement baseEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "bundleKey", "Shotgun", ProductCategory.BASE, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
+ DefaultEntitlement baseEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "bundleKey", "Shotgun", ProductCategory.BASE, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
DefaultSubscriptionBase subscription = subscriptionDataFromSubscription(baseEntitlement.getSubscriptionBase());
final List<ExpectedInvoiceItemCheck> expectedInvoices = new ArrayList<ExpectedInvoiceItemCheck>();
@@ -335,8 +168,7 @@ public class TestIntegrationInvoice extends TestIntegrationBase {
assertEquals(accountPayments.get(2).getPurchasedAmount(), new BigDecimal("10.00"));
}
-
- @Test(groups = "slow", description= "See https://github.com/killbill/killbill/issues/127#issuecomment-292445089")
+ @Test(groups = "slow", description = "See https://github.com/killbill/killbill/issues/127#issuecomment-292445089")
public void testIntegrationWithBCDLargerThanEndMonth() throws Exception {
final int billingDay = 31;
@@ -368,142 +200,6 @@ public class TestIntegrationInvoice extends TestIntegrationBase {
assertListenerStatus();
}
- @Test(groups = "slow")
- public void testDryRunWithPendingSubscription() throws Exception {
-
- final LocalDate initialDate = new LocalDate(2017, 4, 1);
- clock.setDay(initialDate);
-
- // Create account with non BCD to force junction BCD logic to activate
- final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(null));
-
- final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Shotgun", BillingPeriod.ANNUAL, PriceListSet.DEFAULT_PRICELIST_NAME, null);
-
- final LocalDate futureDate = new LocalDate(2017, 5, 1);
-
- // No CREATE event as this is set in the future
- final Entitlement createdEntitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, futureDate, futureDate, false, ImmutableList.<PluginProperty>of(), callContext);
- assertEquals(createdEntitlement.getState(), Entitlement.EntitlementState.PENDING);
- assertEquals(createdEntitlement.getEffectiveStartDate().compareTo(futureDate), 0);
- assertEquals(createdEntitlement.getEffectiveEndDate(), null);
- assertListenerStatus();
-
- // Generate a dryRun invoice on the billing startDate
- final DryRunArguments dryRunArguments1 = new TestDryRunArguments(DryRunType.TARGET_DATE);
- final Invoice dryRunInvoice1 = invoiceUserApi.triggerInvoiceGeneration(createdEntitlement.getAccountId(), futureDate, dryRunArguments1, callContext);
- assertEquals(dryRunInvoice1.getInvoiceItems().size(), 1);
- assertEquals(dryRunInvoice1.getInvoiceItems().get(0).getInvoiceItemType(), InvoiceItemType.FIXED);
- assertEquals(dryRunInvoice1.getInvoiceItems().get(0).getAmount().compareTo(BigDecimal.ZERO), 0);
- assertEquals(dryRunInvoice1.getInvoiceItems().get(0).getStartDate(), futureDate);
- assertEquals(dryRunInvoice1.getInvoiceItems().get(0).getPlanName(), "shotgun-annual");
-
-
- // Generate a dryRun invoice with a plan change
- final DryRunArguments dryRunArguments = new TestDryRunArguments(DryRunType.SUBSCRIPTION_ACTION, "Pistol", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null,
- SubscriptionEventType.CHANGE, createdEntitlement.getId(), createdEntitlement.getBundleId(), futureDate, BillingActionPolicy.IMMEDIATE);
-
- // First one day prior subscription starts
- try {
- invoiceUserApi.triggerInvoiceGeneration(createdEntitlement.getAccountId(), futureDate.minusDays(1), dryRunArguments, callContext);
- fail("Should fail to trigger dryRun invoice prior subscription starts");
- } catch (final InvoiceApiException e) {
- assertEquals(e.getCode(),ErrorCode.INVOICE_NOTHING_TO_DO.getCode());
- }
-
- // Second, on the startDate
- final Invoice dryRunInvoice2 = invoiceUserApi.triggerInvoiceGeneration(createdEntitlement.getAccountId(), futureDate, dryRunArguments, callContext);
- assertEquals(dryRunInvoice2.getInvoiceItems().size(), 1);
- assertEquals(dryRunInvoice2.getInvoiceItems().get(0).getInvoiceItemType(), InvoiceItemType.FIXED);
- assertEquals(dryRunInvoice2.getInvoiceItems().get(0).getAmount().compareTo(BigDecimal.ZERO), 0);
- assertEquals(dryRunInvoice2.getInvoiceItems().get(0).getStartDate(), futureDate);
- assertEquals(dryRunInvoice2.getInvoiceItems().get(0).getPlanName(), "pistol-monthly");
-
-
- // Check BCD is not yet set
- final Account refreshedAccount1 = accountUserApi.getAccountById(account.getId(), callContext);
- assertEquals(refreshedAccount1.getBillCycleDayLocal(), new Integer(0));
-
-
- busHandler.pushExpectedEvents(NextEvent.INVOICE);
- final Invoice realInvoice = invoiceUserApi.triggerInvoiceGeneration(createdEntitlement.getAccountId(), futureDate, null, callContext);
- assertListenerStatus();
-
- assertEquals(realInvoice.getInvoiceItems().size(), 1);
- assertEquals(realInvoice.getInvoiceItems().get(0).getInvoiceItemType(), InvoiceItemType.FIXED);
- assertEquals(realInvoice.getInvoiceItems().get(0).getAmount().compareTo(BigDecimal.ZERO), 0);
- assertEquals(realInvoice.getInvoiceItems().get(0).getStartDate(), futureDate);
- assertEquals(realInvoice.getInvoiceItems().get(0).getPlanName(), "shotgun-annual");
-
- // Check BCD is now set
- final Account refreshedAccount2 = accountUserApi.getAccountById(account.getId(), callContext);
- assertEquals(refreshedAccount2.getBillCycleDayLocal(), new Integer(31));
-
-
- // Move clock past startDate to check nothing happens
- busHandler.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK, NextEvent.NULL_INVOICE);
- clock.addDays(31);
- assertListenerStatus();
-
- // Move clock after PHASE event
- busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.INVOICE_PAYMENT, NextEvent.PAYMENT);
- clock.addMonths(12);
- assertListenerStatus();
- }
-
-
- @Test(groups = "slow")
- public void testDryRunWithPendingCancelledSubscription() throws Exception {
-
- final LocalDate initialDate = new LocalDate(2017, 4, 1);
- clock.setDay(initialDate);
-
- // Create account with non BCD to force junction BCD logic to activate
- final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(null));
-
- final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("pistol-monthly-notrial", null);
-
- final LocalDate futureDate = new LocalDate(2017, 5, 1);
-
- // No CREATE event as this is set in the future
- final Entitlement createdEntitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, futureDate, futureDate, false, ImmutableList.<PluginProperty>of(), callContext);
- assertEquals(createdEntitlement.getState(), Entitlement.EntitlementState.PENDING);
- assertEquals(createdEntitlement.getEffectiveStartDate().compareTo(futureDate), 0);
- assertEquals(createdEntitlement.getEffectiveEndDate(), null);
- assertListenerStatus();
-
- // Generate an invoice using a future targetDate
- busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.INVOICE_PAYMENT, NextEvent.PAYMENT);
- final Invoice firstInvoice = invoiceUserApi.triggerInvoiceGeneration(createdEntitlement.getAccountId(), futureDate, null, callContext);
- assertListenerStatus();
-
- assertEquals(firstInvoice.getInvoiceItems().size(), 1);
- assertEquals(firstInvoice.getInvoiceItems().get(0).getInvoiceItemType(), InvoiceItemType.RECURRING);
- assertEquals(firstInvoice.getInvoiceItems().get(0).getAmount().compareTo(new BigDecimal("19.95")), 0);
- assertEquals(firstInvoice.getInvoiceItems().get(0).getStartDate(), futureDate);
- assertEquals(firstInvoice.getInvoiceItems().get(0).getPlanName(), "pistol-monthly-notrial");
-
-
- // Cancel subscription on its pending startDate
- createdEntitlement.cancelEntitlementWithDate(futureDate, true, ImmutableList.<PluginProperty>of(), callContext);
-
- // Move to startDate/cancel Date
- busHandler.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK, NextEvent.CANCEL, NextEvent.BLOCK, NextEvent.NULL_INVOICE, NextEvent.INVOICE);
- clock.addMonths(1);
- assertListenerStatus();
-
- final List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
- assertEquals(invoices.size(), 2);
-
- final List<ExpectedInvoiceItemCheck> toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
- new ExpectedInvoiceItemCheck(new LocalDate(2017, 5, 1), new LocalDate(2017, 6, 1), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-19.95")),
- new ExpectedInvoiceItemCheck(new LocalDate(2017, 5, 1), new LocalDate(2017, 5, 1), InvoiceItemType.CBA_ADJ, new BigDecimal("19.95")));
- invoiceChecker.checkInvoice(invoices.get(1).getId(), callContext, toBeChecked);
-
-
-
-
- }
-
@Test(groups = "slow", description = "https://github.com/killbill/killbill/issues/783")
public void testIntegrationWithRecurringFreePlan() throws Exception {
final DateTime initialCreationDate = new DateTime(2017, 1, 1, 0, 0, 0, 0, testTimeZone);
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java b/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java
index b2b0d48..77bd532 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java
@@ -305,9 +305,7 @@ public class InvoiceDispatcher {
} else {
final Iterable<UUID> filteredSubscriptionIdsForDryRun = getFilteredSubscriptionIdsForDryRun(dryRunArguments, billingEvents);
- final List<LocalDate> candidateTargetDates = (inputTargetDate != null) ?
- ImmutableList.<LocalDate>of(inputTargetDate) :
- getUpcomingInvoiceCandidateDates(filteredSubscriptionIdsForDryRun, context);
+ final List<LocalDate> candidateTargetDates = getUpcomingInvoiceCandidateDates(filteredSubscriptionIdsForDryRun, context);
if (dryRunArguments.getDryRunType() == DryRunType.UPCOMING_INVOICE) {
invoice = processDryRun_UPCOMING_INVOICE_Invoice(accountId, candidateTargetDates, billingEvents, existingInvoices, context);
@@ -348,6 +346,7 @@ public class InvoiceDispatcher {
private Invoice processDryRun_TARGET_DATE_Invoice(final UUID accountId, final LocalDate targetDate, final List<LocalDate> upcomingTargetDates, final BillingEventSet billingEvents, final List<Invoice> existingInvoices, final InternalCallContext context) throws InvoiceApiException {
+ // Look for first invoice notification date prior our targetDate
LocalDate prevLocalDate = null;
for (final LocalDate cur : upcomingTargetDates) {
if (cur.compareTo(targetDate) < 0) {
@@ -355,6 +354,7 @@ public class InvoiceDispatcher {
}
}
+ // Generate a dryRun invoice for such date if required in such a way that dryRun invoice on our targetDate only contains items that we expect to see
Invoice additionalInvoice = null;
if (prevLocalDate != null) {
additionalInvoice = processAccountWithLockAndInputTargetDate(accountId, prevLocalDate, billingEvents, existingInvoices, true, context);
@@ -364,7 +364,9 @@ public class InvoiceDispatcher {
new ImmutableList.Builder().addAll(existingInvoices).add(additionalInvoice).build() :
existingInvoices;
- return processAccountWithLockAndInputTargetDate(accountId, targetDate, billingEvents, augmentedExistingInvoices, true, context);
+ final Invoice targetInvoice = processAccountWithLockAndInputTargetDate(accountId, targetDate, billingEvents, augmentedExistingInvoices, true, context);
+ // If our targetDate -- user specified -- did not align with any boundary, we return previous 'additionalInvoice' invoice
+ return targetInvoice != null ? targetInvoice : additionalInvoice;
}
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceDispatcher.java b/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceDispatcher.java
index 5a86cc3..d4cfd26 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceDispatcher.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceDispatcher.java
@@ -37,8 +37,6 @@ import org.killbill.billing.catalog.api.Currency;
import org.killbill.billing.catalog.api.PhaseType;
import org.killbill.billing.catalog.api.Plan;
import org.killbill.billing.catalog.api.PlanPhase;
-import org.killbill.billing.invoice.InvoiceDispatcher.FutureAccountNotifications;
-import org.killbill.billing.invoice.InvoiceDispatcher.FutureAccountNotifications.SubscriptionNotification;
import org.killbill.billing.invoice.TestInvoiceHelper.DryRunFutureDateArguments;
import org.killbill.billing.invoice.api.DryRunArguments;
import org.killbill.billing.invoice.api.Invoice;
@@ -64,13 +62,13 @@ import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
public class TestInvoiceDispatcher extends InvoiceTestSuiteWithEmbeddedDB {
private Account account;
private SubscriptionBase subscription;
private InternalCallContext context;
+ private InvoiceDispatcher dispatcher;
@Override
@BeforeMethod(groups = "slow")
@@ -79,6 +77,15 @@ public class TestInvoiceDispatcher extends InvoiceTestSuiteWithEmbeddedDB {
account = invoiceUtil.createAccount(callContext);
subscription = invoiceUtil.createSubscription();
context = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
+
+ final InvoiceNotifier invoiceNotifier = new NullInvoiceNotifier();
+ dispatcher = new InvoiceDispatcher(generator, accountApi, billingApi, subscriptionApi, invoiceDao,
+ internalCallContextFactory, invoiceNotifier, invoicePluginDispatcher, locker, busService.getBus(),
+ notificationQueueService, invoiceConfig, clock, parkedAccountsManager);
+
+ Mockito.when(subscriptionApi.getFutureNotificationsForAccount(Mockito.<InternalCallContext>any())).thenReturn(ImmutableList.<DateTime>of());
+
+
}
@Test(groups = "slow")
@@ -100,9 +107,6 @@ public class TestInvoiceDispatcher extends InvoiceTestSuiteWithEmbeddedDB {
final LocalDate target = internalCallContext.toLocalDate(effectiveDate);
final InvoiceNotifier invoiceNotifier = new NullInvoiceNotifier();
- final InvoiceDispatcher dispatcher = new InvoiceDispatcher(generator, accountApi, billingApi, subscriptionApi, invoiceDao,
- internalCallContextFactory, invoiceNotifier, invoicePluginDispatcher, locker, busService.getBus(),
- null, invoiceConfig, clock, parkedAccountsManager);
Invoice invoice = dispatcher.processAccountFromNotificationOrBusEvent(accountId, target, new DryRunFutureDateArguments(), context);
Assert.assertNotNull(invoice);
@@ -144,9 +148,6 @@ public class TestInvoiceDispatcher extends InvoiceTestSuiteWithEmbeddedDB {
final LocalDate target = internalCallContext.toLocalDate(effectiveDate);
final InvoiceNotifier invoiceNotifier = new NullInvoiceNotifier();
- final InvoiceDispatcher dispatcher = new InvoiceDispatcher(generator, accountApi, billingApi, subscriptionApi, invoiceDao,
- internalCallContextFactory, invoiceNotifier, invoicePluginDispatcher, locker, busService.getBus(),
- null, invoiceConfig, clock, parkedAccountsManager);
// Verify initial tags state for account
Assert.assertTrue(tagUserApi.getTagsForAccount(accountId, true, callContext).isEmpty());
@@ -291,10 +292,6 @@ public class TestInvoiceDispatcher extends InvoiceTestSuiteWithEmbeddedDB {
31, BillingMode.IN_ADVANCE, "CHANGE", 3L, SubscriptionBaseTransitionType.CHANGE));
Mockito.when(billingApi.getBillingEventsForAccountAndUpdateAccountBCD(Mockito.<UUID>any(), Mockito.<DryRunArguments>any(), Mockito.<InternalCallContext>any())).thenReturn(events);
- final InvoiceNotifier invoiceNotifier = new NullInvoiceNotifier();
- final InvoiceDispatcher dispatcher = new InvoiceDispatcher(generator, accountApi, billingApi, subscriptionApi, invoiceDao,
- internalCallContextFactory, invoiceNotifier, invoicePluginDispatcher, locker, busService.getBus(),
- null, invoiceConfig, clock, parkedAccountsManager);
final Invoice invoice = dispatcher.processAccountFromNotificationOrBusEvent(account.getId(), new LocalDate("2012-07-30"), null, context);
Assert.assertNotNull(invoice);
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceHelper.java b/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceHelper.java
index e1df2df..f0cec38 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceHelper.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceHelper.java
@@ -86,6 +86,7 @@ import org.killbill.billing.util.currency.KillBillMoney;
import org.killbill.billing.util.dao.NonEntityDao;
import org.killbill.clock.Clock;
import org.killbill.commons.locker.GlobalLocker;
+import org.killbill.notificationq.api.NotificationQueueService;
import org.mockito.Mockito;
import org.skife.jdbi.v2.IDBI;
import org.testng.Assert;
@@ -163,6 +164,7 @@ public class TestInvoiceHelper {
private final MutableInternalCallContext internalCallContext;
private final InternalCallContextFactory internalCallContextFactory;
private final InvoiceConfig invoiceConfig;
+ private final NotificationQueueService notificationQueueService;
// Low level SqlDao used by the tests to directly insert rows
private final InvoicePaymentSqlDao invoicePaymentSqlDao;
private final InvoiceItemSqlDao invoiceItemSqlDao;
@@ -172,7 +174,7 @@ public class TestInvoiceHelper {
@Inject
public TestInvoiceHelper(final InvoiceGenerator generator, final IDBI dbi,
final BillingInternalApi billingApi, final AccountInternalApi accountApi, final ImmutableAccountInternalApi immutableAccountApi, final InvoicePluginDispatcher invoicePluginDispatcher, final AccountUserApi accountUserApi, final SubscriptionBaseInternalApi subscriptionApi, final BusService busService,
- final InvoiceDao invoiceDao, final GlobalLocker locker, final Clock clock, final NonEntityDao nonEntityDao, final CacheControllerDispatcher cacheControllerDispatcher, final MutableInternalCallContext internalCallContext, final InvoiceConfig invoiceConfig,
+ final InvoiceDao invoiceDao, final GlobalLocker locker, final Clock clock, final NonEntityDao nonEntityDao, final NotificationQueueService notificationQueueService, final MutableInternalCallContext internalCallContext, final InvoiceConfig invoiceConfig,
final ParkedAccountsManager parkedAccountsManager, final InternalCallContextFactory internalCallContextFactory) {
this.generator = generator;
this.billingApi = billingApi;
@@ -186,6 +188,7 @@ public class TestInvoiceHelper {
this.locker = locker;
this.clock = clock;
this.nonEntityDao = nonEntityDao;
+ this.notificationQueueService = notificationQueueService;
this.parkedAccountsManager = parkedAccountsManager;
this.internalCallContext = internalCallContext;
this.internalCallContextFactory = internalCallContextFactory;
@@ -214,8 +217,9 @@ public class TestInvoiceHelper {
final InvoiceNotifier invoiceNotifier = new NullInvoiceNotifier();
final InvoiceDispatcher dispatcher = new InvoiceDispatcher(generator, accountApi, billingApi, subscriptionApi,
invoiceDao, internalCallContextFactory, invoiceNotifier, invoicePluginDispatcher, locker, busService.getBus(),
- null, invoiceConfig, clock, parkedAccountsManager);
+ notificationQueueService, invoiceConfig, clock, parkedAccountsManager);
+ Mockito.when(subscriptionApi.getFutureNotificationsForAccount(Mockito.<InternalCallContext>any())).thenReturn(ImmutableList.<DateTime>of());
Invoice invoice = dispatcher.processAccountFromNotificationOrBusEvent(account.getId(), targetDate, new DryRunFutureDateArguments(), internalCallContext);
Assert.assertNotNull(invoice);