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 beb7246..7b6b8da 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
@@ -46,13 +46,16 @@ import org.killbill.billing.invoice.api.InvoiceItem;
import org.killbill.billing.invoice.api.InvoiceItemType;
import org.killbill.billing.invoice.api.InvoicePayment;
import org.killbill.billing.invoice.model.ExternalChargeInvoiceItem;
+import org.killbill.billing.overdue.config.DefaultOverdueConfig;
import org.killbill.billing.overdue.wrapper.OverdueWrapper;
import org.killbill.billing.payment.api.Payment;
import org.killbill.billing.payment.api.PluginProperty;
+import org.killbill.xmlloader.XMLLoader;
import org.testng.Assert;
import org.testng.annotations.Test;
import com.google.common.collect.ImmutableList;
+import com.google.common.io.Resources;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;
@@ -949,6 +952,151 @@ public class TestOverdueIntegration extends TestOverdueBase {
checkODState(OverdueWrapper.CLEAR_STATE_NAME);
}
+ @Test(groups = "slow", description = "Test overdue state with number of unpaid invoices condition")
+ public void testOverdueStateWithNumberOfUnpaidInvoicesCondition() throws Exception {
+ // 2012-05-01T00:03:42.000Z
+ clock.setTime(new DateTime(2012, 5, 1, 0, 3, 42, 0));
+
+ final DefaultOverdueConfig config = XMLLoader.getObjectFromString(Resources.getResource("overdueWithNumberOfUnpaidInvoicesCondition.xml").toExternalForm(), DefaultOverdueConfig.class);
+ overdueConfigCache.loadDefaultOverdueConfig(config);
+
+ setupAccount();
+
+ paymentPlugin.makeAllInvoicesFailWithError(true);
+
+ createBaseEntitlementAndCheckForCompletion(account.getId(), "externalKey", productName, ProductCategory.BASE,
+ term, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
+
+ // 2012-05-31 => DAY 30 have to get out of trial before first payment
+ addMonthsAndCheckForCompletion(1, NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
+ // Verify that number of unpaid invoices is 1
+ Assert.assertEquals(invoiceUserApi.getUnpaidInvoicesByAccountId(account.getId(), clock.getUTCToday(), callContext).size(), 1);
+ // Should still be in clear state
+ checkODState(OverdueWrapper.CLEAR_STATE_NAME);
+
+ // Add 1 month
+ addMonthsAndCheckForCompletion(1, NextEvent.INVOICE, NextEvent.BLOCK, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
+ // Verify that number of unpaid invoices is 2
+ Assert.assertEquals(invoiceUserApi.getUnpaidInvoicesByAccountId(account.getId(), clock.getUTCToday(), callContext).size(), 2);
+ // Now we should be in OD1
+ checkODState("OD1");
+
+ // Add 1 month
+ addMonthsAndCheckForCompletion(1, NextEvent.INVOICE, NextEvent.BLOCK, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR,
+ NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR,
+ NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
+ // Verify that number of unpaid invoices is 3
+ Assert.assertEquals(invoiceUserApi.getUnpaidInvoicesByAccountId(account.getId(), clock.getUTCToday(), callContext).size(), 3);
+ // Now we should be in OD2
+ checkODState("OD2");
+
+ // Add 1 month
+ addMonthsAndCheckForCompletion(1, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR,
+ NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR,
+ NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR,
+ NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
+ // Verify that number of unpaid invoices is 4
+ Assert.assertEquals(invoiceUserApi.getUnpaidInvoicesByAccountId(account.getId(), clock.getUTCToday(), callContext).size(), 4);
+ // We should still be in OD2
+ checkODState("OD2");
+
+ // Add 1 month
+ addMonthsAndCheckForCompletion(1, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR,
+ NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR,
+ NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR,
+ NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR,
+ NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR, NextEvent.TAG, NextEvent.BLOCK);
+ // Verify that number of unpaid invoices is 5
+ Assert.assertEquals(invoiceUserApi.getUnpaidInvoicesByAccountId(account.getId(), clock.getUTCToday(), callContext).size(), 5);
+ // Now we should be in OD3
+ checkODState("OD3");
+
+ // Get all unpaid invoices and pay them to clear the overdue state
+ paymentPlugin.makeAllInvoicesFailWithError(false);
+ List<Invoice> unpaidInvoices = getUnpaidInvoicesOrderFromRecent();
+ createPaymentAndCheckForCompletion(account, unpaidInvoices.get(0), NextEvent.BLOCK, NextEvent.TAG, NextEvent.NULL_INVOICE, NextEvent.NULL_INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+ createPaymentAndCheckForCompletion(account, unpaidInvoices.get(1), NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+ createPaymentAndCheckForCompletion(account, unpaidInvoices.get(2), NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT, NextEvent.BLOCK);
+ createPaymentAndCheckForCompletion(account, unpaidInvoices.get(3), NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT, NextEvent.BLOCK);
+ createPaymentAndCheckForCompletion(account, unpaidInvoices.get(4), NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+
+ // We should be clear now
+ checkODState(OverdueWrapper.CLEAR_STATE_NAME);
+ }
+
+ @Test(groups = "slow", description = "Test overdue state with total unpaid invoice balance condition")
+ public void testOverdueStateWithTotalUnpaidInvoiceBalanceCondition() throws Exception {
+ // 2012-05-01T00:03:42.000Z
+ clock.setTime(new DateTime(2012, 5, 1, 0, 3, 42, 0));
+
+ final DefaultOverdueConfig config = XMLLoader.getObjectFromString(Resources.getResource("overdueWithTotalUnpaidInvoiceBalanceCondition.xml").toExternalForm(), DefaultOverdueConfig.class);
+ overdueConfigCache.loadDefaultOverdueConfig(config);
+
+ setupAccount();
+
+ paymentPlugin.makeAllInvoicesFailWithError(true);
+
+ createBaseEntitlementAndCheckForCompletion(account.getId(), "externalKey", productName, ProductCategory.BASE,
+ term, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
+
+ // 2012-05-31 => DAY 30 have to get out of trial before first payment
+ addMonthsAndCheckForCompletion(1, NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
+
+ // Amount balance should be USD 249.95
+ assertEquals(invoiceUserApi.getAccountBalance(account.getId(), callContext).compareTo(BigDecimal.valueOf(249.95)), 0);
+ // Should still be in clear state
+ checkODState(OverdueWrapper.CLEAR_STATE_NAME);
+
+ // Add 1 month
+ addMonthsAndCheckForCompletion(1, NextEvent.INVOICE, NextEvent.BLOCK, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
+ // Amount balance should be USD 499.90
+ assertEquals(invoiceUserApi.getAccountBalance(account.getId(), callContext).compareTo(BigDecimal.valueOf(499.90)), 0);
+ // Now we should be in OD1
+ checkODState("OD1");
+
+ // Add 1 month
+ addMonthsAndCheckForCompletion(1, NextEvent.INVOICE, NextEvent.BLOCK, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR,
+ NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR,
+ NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
+ // Amount balance should be USD 749.85
+ assertEquals(invoiceUserApi.getAccountBalance(account.getId(), callContext).compareTo(BigDecimal.valueOf(749.85)), 0);
+ // Now we should be in OD2
+ checkODState("OD2");
+
+ // Add 1 month
+ addMonthsAndCheckForCompletion(1, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR,
+ NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR,
+ NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR,
+ NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
+ // Amount balance should be USD 999.80
+ assertEquals(invoiceUserApi.getAccountBalance(account.getId(), callContext).compareTo(BigDecimal.valueOf(999.80)), 0);
+ // We should still be in OD2
+ checkODState("OD2");
+
+ // Add 1 month
+ addMonthsAndCheckForCompletion(1, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR,
+ NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR,
+ NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR,
+ NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR,
+ NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR, NextEvent.TAG, NextEvent.BLOCK);
+ // Amount balance should be USD 1249.75
+ assertEquals(invoiceUserApi.getAccountBalance(account.getId(), callContext).compareTo(BigDecimal.valueOf(1249.75)), 0);
+ // Now we should be in OD3
+ checkODState("OD3");
+
+ // Get all unpaid invoices and pay them to clear the overdue state
+ paymentPlugin.makeAllInvoicesFailWithError(false);
+ List<Invoice> unpaidInvoices = getUnpaidInvoicesOrderFromRecent();
+ createPaymentAndCheckForCompletion(account, unpaidInvoices.get(0), NextEvent.BLOCK, NextEvent.TAG, NextEvent.NULL_INVOICE, NextEvent.NULL_INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+ createPaymentAndCheckForCompletion(account, unpaidInvoices.get(1), NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+ createPaymentAndCheckForCompletion(account, unpaidInvoices.get(2), NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT, NextEvent.BLOCK);
+ createPaymentAndCheckForCompletion(account, unpaidInvoices.get(3), NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT, NextEvent.BLOCK);
+ createPaymentAndCheckForCompletion(account, unpaidInvoices.get(4), NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+
+ // We should be clear now
+ checkODState(OverdueWrapper.CLEAR_STATE_NAME);
+ }
+
private void allowPaymentsAndResetOverdueToClearByPayingAllUnpaidInvoices(final boolean extraPayment) {
// Reset plugin so payments should now succeed
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestOverdue.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestOverdue.java
index dccf9c2..6b13996 100644
--- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestOverdue.java
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestOverdue.java
@@ -76,7 +76,23 @@ public class TestOverdue extends TestJaxrsBase {
// Post external payments, paying the most recent invoice first: this is to avoid a race condition where
// a refresh overdue notification kicks in after the first payment, which makes the account goes CLEAR and
// triggers an AUTO_INVOICE_OFF tag removal (hence adjustment of the other invoices balance).
- postExternalPayments(accountJson);
+ final Invoices invoicesForAccount = killBillClient.getInvoicesForAccount(accountJson.getAccountId(), requestOptions);
+ final List<Invoice> mostRecentInvoiceFirst = Ordering.<Invoice>from(new Comparator<Invoice>() {
+ @Override
+ public int compare(final Invoice invoice1, final Invoice invoice2) {
+ return invoice1.getInvoiceDate().compareTo(invoice2.getInvoiceDate());
+ }
+ }).reverse().sortedCopy(invoicesForAccount);
+ for (final Invoice invoice : mostRecentInvoiceFirst) {
+ if (invoice.getBalance().compareTo(BigDecimal.ZERO) > 0) {
+
+ final InvoicePayment invoicePayment = new InvoicePayment();
+ invoicePayment.setPurchasedAmount(invoice.getAmount());
+ invoicePayment.setAccountId(accountJson.getAccountId());
+ invoicePayment.setTargetInvoiceId(invoice.getInvoiceId());
+ killBillClient.createInvoicePayment(invoicePayment, true, requestOptions);
+ }
+ }
// Wait a bit for overdue to pick up the payment events...
crappyWaitForLackOfProperSynchonization();
@@ -163,128 +179,4 @@ public class TestOverdue extends TestJaxrsBase {
// This account is expected to move to OD1 state because it matches with exclusion controlTag defined
Assert.assertEquals(killBillClient.getOverdueStateForAccount(accountJsonNoTag.getAccountId(), requestOptions).getName(), "OD1");
}
-
- @Test(groups = "slow", description = "Allow overdue condition by number of unpaid invoices defined in overdue config xml file")
- public void testOverdueStatusWithNumberOfUnpaidInvoicesCondition() throws Exception {
- final String overdueConfigPath = Resources.getResource("overdueWithNumberOfUnpaidInvoicesCondition.xml").getPath();
- killBillClient.uploadXMLOverdueConfig(overdueConfigPath, requestOptions);
-
- // Create an account without a payment method
- final Account accountJson = createAccountNoPMBundleAndSubscriptionAndWaitForFirstInvoice();
-
- // Get the invoices
- // 2 invoices but look for the non zero dollar one
- assertEquals(killBillClient.getInvoicesForAccount(accountJson.getAccountId(), requestOptions).size(), 2);
-
- // We're still clear - see the configuration
- Assert.assertTrue(killBillClient.getOverdueStateForAccount(accountJson.getAccountId(), requestOptions).getIsClearState());
-
- clock.addMonths(1);
- crappyWaitForLackOfProperSynchonization();
- // 3 invoices, 2 unpaid, must be inside OD1
- assertEquals(killBillClient.getInvoicesForAccount(accountJson.getAccountId(), requestOptions).size(), 3);
- Assert.assertEquals(killBillClient.getOverdueStateForAccount(accountJson.getAccountId(), requestOptions).getName(), "OD1");
-
- clock.addMonths(1);
- crappyWaitForLackOfProperSynchonization();
- // 4 invoices, 3 unpaid, must be inside OD2
- assertEquals(killBillClient.getInvoicesForAccount(accountJson.getAccountId(), requestOptions).size(), 4);
- Assert.assertEquals(killBillClient.getOverdueStateForAccount(accountJson.getAccountId(), requestOptions).getName(), "OD2");
-
- clock.addMonths(1);
- crappyWaitForLackOfProperSynchonization();
- // 5 invoices, 4 unpaid, must be still in OD2
- assertEquals(killBillClient.getInvoicesForAccount(accountJson.getAccountId(), requestOptions).size(), 5);
- Assert.assertEquals(killBillClient.getOverdueStateForAccount(accountJson.getAccountId(), requestOptions).getName(), "OD2");
-
- clock.addMonths(1);
- crappyWaitForLackOfProperSynchonization();
- // 6 invoices, 5 unpaid, must be inside OD3
- assertEquals(killBillClient.getInvoicesForAccount(accountJson.getAccountId(), requestOptions).size(), 6);
- Assert.assertEquals(killBillClient.getOverdueStateForAccount(accountJson.getAccountId(), requestOptions).getName(), "OD3");
-
- // Post external payments, paying the most recent invoice first: this is to avoid a race condition where
- // a refresh overdue notification kicks in after the first payment, which makes the account goes CLEAR and
- // triggers an AUTO_INVOICE_OFF tag removal (hence adjustment of the other invoices balance).
- postExternalPayments(accountJson);
-
- // Wait a bit for overdue to pick up the payment events...
- crappyWaitForLackOfProperSynchonization();
-
- // Verify we're in clear state
- Assert.assertTrue(killBillClient.getOverdueStateForAccount(accountJson.getAccountId(), requestOptions).getIsClearState());
- }
-
- @Test(groups = "slow", description = "Allow overdue condition by total unpaid invoice balance defined in overdue config xml file")
- public void testOverdueStatusWithTotalUnpaidInvoiceBalanceCondition() throws Exception {
- final String overdueConfigPath = Resources.getResource("overdueWithTotalUnpaidInvoiceBalanceCondition.xml").getPath();
- killBillClient.uploadXMLOverdueConfig(overdueConfigPath, requestOptions);
-
- // Create an account without a payment method
- final Account accountJson = createAccountNoPMBundleAndSubscriptionAndWaitForFirstInvoice();
-
- // Amount balance should be USD 249.95
- assertEquals(killBillClient.getAccount(accountJson.getAccountId(), true, false, requestOptions).getAccountBalance().compareTo(BigDecimal.valueOf(249.95)), 0);
- // We're still clear - see the configuration
- Assert.assertTrue(killBillClient.getOverdueStateForAccount(accountJson.getAccountId(), requestOptions).getIsClearState());
-
- clock.addMonths(1);
- crappyWaitForLackOfProperSynchonization();
- // Amount balance should be USD 499.90
- assertEquals(killBillClient.getAccount(accountJson.getAccountId(), true, false, requestOptions).getAccountBalance().compareTo(BigDecimal.valueOf(499.90)), 0);
- // State must be inside OD1
- Assert.assertEquals(killBillClient.getOverdueStateForAccount(accountJson.getAccountId(), requestOptions).getName(), "OD1");
-
- clock.addMonths(1);
- crappyWaitForLackOfProperSynchonization();
- // Amount balance should be USD 749.85
- assertEquals(killBillClient.getAccount(accountJson.getAccountId(), true, false, requestOptions).getAccountBalance().compareTo(BigDecimal.valueOf(749.85)), 0);
- // State must be inside OD2
- Assert.assertEquals(killBillClient.getOverdueStateForAccount(accountJson.getAccountId(), requestOptions).getName(), "OD2");
-
- clock.addMonths(1);
- crappyWaitForLackOfProperSynchonization();
- // Amount balance should be USD 999.80
- assertEquals(killBillClient.getAccount(accountJson.getAccountId(), true, false, requestOptions).getAccountBalance().compareTo(BigDecimal.valueOf(999.80)), 0);
- // State must be still OD2
- Assert.assertEquals(killBillClient.getOverdueStateForAccount(accountJson.getAccountId(), requestOptions).getName(), "OD2");
-
- clock.addMonths(1);
- crappyWaitForLackOfProperSynchonization();
- // Amount balance should be USD 1249.75
- assertEquals(killBillClient.getAccount(accountJson.getAccountId(), true, false, requestOptions).getAccountBalance().compareTo(BigDecimal.valueOf(1249.75)), 0);
- // State must be inside OD3
- Assert.assertEquals(killBillClient.getOverdueStateForAccount(accountJson.getAccountId(), requestOptions).getName(), "OD3");
-
- // Post external payments, paying the most recent invoice first: this is to avoid a race condition where
- // a refresh overdue notification kicks in after the first payment, which makes the account goes CLEAR and
- // triggers an AUTO_INVOICE_OFF tag removal (hence adjustment of the other invoices balance).
- postExternalPayments(accountJson);
-
- // Wait a bit for overdue to pick up the payment events...
- crappyWaitForLackOfProperSynchonization();
-
- // Verify we're in clear state
- Assert.assertTrue(killBillClient.getOverdueStateForAccount(accountJson.getAccountId(), requestOptions).getIsClearState());
- }
-
- private void postExternalPayments(final Account accountJson) throws KillBillClientException {
- final Invoices invoicesForAccount = killBillClient.getInvoicesForAccount(accountJson.getAccountId(), requestOptions);
- final List<Invoice> mostRecentInvoiceFirst = Ordering.<Invoice>from(new Comparator<Invoice>() {
- @Override
- public int compare(final Invoice invoice1, final Invoice invoice2) {
- return invoice1.getInvoiceDate().compareTo(invoice2.getInvoiceDate());
- }
- }).reverse().sortedCopy(invoicesForAccount);
- for (final Invoice invoice : mostRecentInvoiceFirst) {
- if (invoice.getBalance().compareTo(BigDecimal.ZERO) > 0) {
-
- final InvoicePayment invoicePayment = new InvoicePayment();
- invoicePayment.setPurchasedAmount(invoice.getAmount());
- invoicePayment.setAccountId(accountJson.getAccountId());
- invoicePayment.setTargetInvoiceId(invoice.getInvoiceId());
- killBillClient.createInvoicePayment(invoicePayment, true, requestOptions);
- }
- }
- }
}