/* * Copyright 2010-2013 Ning, Inc. * 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.beatrix.integration.overdue; import java.math.BigDecimal; import org.joda.time.DateTime; import org.joda.time.LocalDate; import org.killbill.billing.api.TestApiListener.NextEvent; import org.killbill.billing.beatrix.util.InvoiceChecker.ExpectedInvoiceItemCheck; import org.killbill.billing.catalog.api.BillingPeriod; import org.killbill.billing.catalog.api.ProductCategory; import org.killbill.billing.entitlement.api.DefaultEntitlement; import org.killbill.billing.entitlement.api.Entitlement.EntitlementState; import org.killbill.billing.entitlement.api.SubscriptionBundle; import org.killbill.billing.invoice.api.InvoiceItemType; import org.killbill.billing.overdue.wrapper.OverdueWrapper; import org.killbill.billing.subscription.api.SubscriptionBase; import org.testng.annotations.Test; import static org.testng.Assert.assertTrue; @Test(groups = "slow") public class TestOverdueWithSubscriptionCancellation extends TestOverdueBase { @Override public String getOverdueConfig() { final String configXml = "<overdueConfig>" + " <accountOverdueStates>" + " <initialReevaluationInterval>" + " <unit>DAYS</unit><number>5</number>" + " </initialReevaluationInterval>" + " <state name=\"OD1\">" + " <condition>" + " <timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>" + " <unit>DAYS</unit><number>5</number>" + " </timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>" + " </condition>" + " <externalMessage>Reached OD1</externalMessage>" + " <blockChanges>true</blockChanges>" + " <disableEntitlementAndChangesBlocked>false</disableEntitlementAndChangesBlocked>" + " <subscriptionCancellationPolicy>IMMEDIATE</subscriptionCancellationPolicy>" + " <autoReevaluationInterval>" + " <unit>DAYS</unit><number>5</number>" + " </autoReevaluationInterval>" + " </state>" + " </accountOverdueStates>" + "</overdueConfig>"; return configXml; } @Test(groups = "slow") public void testCheckSubscriptionCancellation() throws Exception { clock.setTime(new DateTime(2012, 5, 1, 0, 3, 42, 0)); setupAccount(); // Set next invoice to fail and create subscription paymentPlugin.makeAllInvoicesFailWithError(true); final DefaultEntitlement baseEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "externalKey", productName, ProductCategory.BASE, term, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE); bundle = subscriptionApi.getSubscriptionBundle(baseEntitlement.getBundleId(), callContext); 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); final DefaultEntitlement addOn1 = addAOEntitlementAndCheckForCompletion(baseEntitlement.getBundleId(), "Holster", ProductCategory.ADD_ON, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE); final DefaultEntitlement addOn2 = addAOEntitlementAndCheckForCompletion(baseEntitlement.getBundleId(), "Holster", ProductCategory.ADD_ON, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE); // Cancel addOn1 one day after clock.addDays(1); cancelEntitlementAndCheckForCompletion(addOn1, NextEvent.BLOCK, NextEvent.CANCEL, NextEvent.NULL_INVOICE); // DAY 30 have to get out of trial before first payment addDaysAndCheckForCompletion(29, NextEvent.PHASE, NextEvent.PHASE, NextEvent.NULL_INVOICE, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR); invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 6, 30), callContext); // Should still be in clear state checkODState(OverdueWrapper.CLEAR_STATE_NAME); // DAY 36 -- 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 base plan and addOn2 addDaysAndCheckForCompletion(6, NextEvent.BLOCK, NextEvent.BLOCK, NextEvent.BLOCK, NextEvent.CANCEL, NextEvent.CANCEL, NextEvent.INVOICE); // Should be in OD1 checkODState("OD1"); final SubscriptionBase cancelledBaseSubscription = ((DefaultEntitlement) entitlementApi.getEntitlementForId(baseEntitlement.getId(), callContext)).getSubscriptionBase(); assertTrue(cancelledBaseSubscription.getState() == EntitlementState.CANCELLED); final SubscriptionBase cancelledAddon1= ((DefaultEntitlement) entitlementApi.getEntitlementForId(addOn1.getId(), callContext)).getSubscriptionBase(); assertTrue(cancelledAddon1.getState() == EntitlementState.CANCELLED); } @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)); setupAccount(); // Set next invoice to fail and create subscription paymentPlugin.makeAllInvoicesFailWithError(true); final DefaultEntitlement baseEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "externalKey", productName, ProductCategory.BASE, term, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE); bundle = subscriptionApi.getSubscriptionBundle(baseEntitlement.getBundleId(), callContext); 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); final DefaultEntitlement baseEntitlement2 = createBaseEntitlementAndCheckForCompletion(account.getId(), "externalKey2", productName, ProductCategory.BASE, term, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE); final SubscriptionBundle bundle2 = subscriptionApi.getSubscriptionBundle(baseEntitlement.getBundleId(), callContext); invoiceChecker.checkInvoice(account.getId(), 2, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), null, InvoiceItemType.FIXED, new BigDecimal("0"))); invoiceChecker.checkChargedThroughDate(baseEntitlement2.getId(), new LocalDate(2012, 5, 1), callContext); final DefaultEntitlement baseEntitlement3 = createBaseEntitlementAndCheckForCompletion(account.getId(), "externalKey3", productName, ProductCategory.BASE, term, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE); final SubscriptionBundle bundle3 = subscriptionApi.getSubscriptionBundle(baseEntitlement.getBundleId(), callContext); invoiceChecker.checkInvoice(account.getId(), 3, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), null, InvoiceItemType.FIXED, new BigDecimal("0"))); invoiceChecker.checkChargedThroughDate(baseEntitlement2.getId(), new LocalDate(2012, 5, 1), callContext); // Cancel bundle 2 one day after (2012-05-02) clock.addDays(1); cancelEntitlementAndCheckForCompletion(baseEntitlement2, NextEvent.BLOCK, NextEvent.CANCEL, NextEvent.NULL_INVOICE); final SubscriptionBase cancelledBaseSubscription2 = ((DefaultEntitlement) entitlementApi.getEntitlementForId(baseEntitlement2.getId(), callContext)).getSubscriptionBase(); 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.NULL_INVOICE, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR); invoiceChecker.checkInvoice(account.getId(), 4, 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, 6, 30), InvoiceItemType.RECURRING, new BigDecimal("249.95"))); invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 6, 30), callContext); // Should still be in clear state checkODState(OverdueWrapper.CLEAR_STATE_NAME); // 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); // Should be in OD1 checkODState("OD1"); final SubscriptionBase cancelledBaseSubscription = ((DefaultEntitlement) entitlementApi.getEntitlementForId(baseEntitlement.getId(), callContext)).getSubscriptionBase(); assertTrue(cancelledBaseSubscription.getState() == EntitlementState.CANCELLED); final SubscriptionBase cancelledBaseEntitlement3 = ((DefaultEntitlement) entitlementApi.getEntitlementForId(baseEntitlement3.getId(), callContext)).getSubscriptionBase(); assertTrue(cancelledBaseEntitlement3.getState() == EntitlementState.CANCELLED); } }