Details
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueBase.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueBase.java
index f7fb6b3..ff36bb8 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueBase.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueBase.java
@@ -21,26 +21,18 @@ package org.killbill.billing.beatrix.integration.overdue;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.UUID;
-import java.util.concurrent.Callable;
import org.killbill.billing.account.api.Account;
import org.killbill.billing.beatrix.integration.BeatrixIntegrationModule;
import org.killbill.billing.beatrix.integration.TestIntegrationBase;
import org.killbill.billing.catalog.api.BillingPeriod;
-import org.killbill.billing.entitlement.api.BlockingState;
-import org.killbill.billing.entitlement.api.BlockingStateType;
import org.killbill.billing.entitlement.api.SubscriptionBundle;
-import org.killbill.billing.overdue.OverdueService;
import org.killbill.billing.overdue.config.DefaultOverdueConfig;
-import org.killbill.billing.overdue.wrapper.OverdueWrapper;
import org.killbill.billing.payment.api.PaymentMethodPlugin;
import org.killbill.billing.payment.api.TestPaymentMethodPluginBase;
import org.killbill.xmlloader.XMLLoader;
-import org.testng.Assert;
import org.testng.annotations.BeforeMethod;
-import static com.jayway.awaitility.Awaitility.await;
-import static java.util.concurrent.TimeUnit.SECONDS;
import static org.testng.Assert.assertNotNull;
public abstract class TestOverdueBase extends TestIntegrationBase {
@@ -78,26 +70,4 @@ public abstract class TestOverdueBase extends TestIntegrationBase {
protected void checkODState(final String expected) {
checkODState(expected, account.getId());
}
-
- protected void checkODState(final String expected, final UUID accountId) {
- try {
- // This will test the overdue notification queue: when we move the clock, the overdue system
- // should get notified to refresh its state.
- // Calling explicitly refresh here (overdueApi.refreshOverdueStateFor(account)) would not fully
- // test overdue.
- // Since we're relying on the notification queue, we may need to wait a bit (hence await()).
- await().atMost(10, SECONDS).until(new Callable<Boolean>() {
- @Override
- public Boolean call() throws Exception {
- final BlockingState blockingStateForService = blockingApi.getBlockingStateForService(accountId, BlockingStateType.ACCOUNT, OverdueService.OVERDUE_SERVICE_NAME, internalCallContext);
- final String stateName = blockingStateForService != null ? blockingStateForService.getStateName() : OverdueWrapper.CLEAR_STATE_NAME;
- return expected.equals(stateName);
- }
- });
- } catch (final Exception e) {
- final BlockingState blockingStateForService = blockingApi.getBlockingStateForService(accountId, BlockingStateType.ACCOUNT, OverdueService.OVERDUE_SERVICE_NAME, internalCallContext);
- final String stateName = blockingStateForService != null ? blockingStateForService.getStateName() : OverdueWrapper.CLEAR_STATE_NAME;
- Assert.assertEquals(stateName, expected, "Got exception: " + e.toString());
- }
- }
}
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationBase.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationBase.java
index bab10ab..0fdb3c7 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationBase.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationBase.java
@@ -24,6 +24,7 @@ import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.UUID;
+import java.util.concurrent.Callable;
import javax.annotation.Nullable;
import javax.inject.Inject;
@@ -56,6 +57,8 @@ import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
import org.killbill.billing.catalog.api.PlanSpecifier;
import org.killbill.billing.catalog.api.PriceListSet;
import org.killbill.billing.catalog.api.ProductCategory;
+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.Entitlement;
import org.killbill.billing.entitlement.api.EntitlementApi;
@@ -76,10 +79,12 @@ import org.killbill.billing.lifecycle.api.Lifecycle;
import org.killbill.billing.lifecycle.glue.BusModule;
import org.killbill.billing.mock.MockAccountBuilder;
import org.killbill.billing.osgi.config.OSGIConfig;
+import org.killbill.billing.overdue.OverdueService;
import org.killbill.billing.overdue.api.OverdueApi;
import org.killbill.billing.overdue.api.OverdueConfig;
import org.killbill.billing.overdue.caching.OverdueConfigCache;
import org.killbill.billing.overdue.listener.OverdueListener;
+import org.killbill.billing.overdue.wrapper.OverdueWrapper;
import org.killbill.billing.overdue.wrapper.OverdueWrapperFactory;
import org.killbill.billing.payment.api.Payment;
import org.killbill.billing.payment.api.PaymentApi;
@@ -112,6 +117,7 @@ import org.killbill.billing.util.tag.Tag;
import org.killbill.bus.api.PersistentBus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.testng.Assert;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
@@ -126,6 +132,8 @@ import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Stage;
+import static com.jayway.awaitility.Awaitility.await;
+import static java.util.concurrent.TimeUnit.SECONDS;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertTrue;
@@ -349,6 +357,28 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB {
assertTrue(ctd.toDateTime(testTimeZone).toLocalDate().compareTo(new LocalDate(chargeThroughDate.getYear(), chargeThroughDate.getMonthOfYear(), chargeThroughDate.getDayOfMonth())) == 0);
}
+ protected void checkODState(final String expected, final UUID accountId) {
+ try {
+ // This will test the overdue notification queue: when we move the clock, the overdue system
+ // should get notified to refresh its state.
+ // Calling explicitly refresh here (overdueApi.refreshOverdueStateFor(account)) would not fully
+ // test overdue.
+ // Since we're relying on the notification queue, we may need to wait a bit (hence await()).
+ await().atMost(10, SECONDS).until(new Callable<Boolean>() {
+ @Override
+ public Boolean call() throws Exception {
+ final BlockingState blockingStateForService = blockingApi.getBlockingStateForService(accountId, BlockingStateType.ACCOUNT, OverdueService.OVERDUE_SERVICE_NAME, internalCallContext);
+ final String stateName = blockingStateForService != null ? blockingStateForService.getStateName() : OverdueWrapper.CLEAR_STATE_NAME;
+ return expected.equals(stateName);
+ }
+ });
+ } catch (final Exception e) {
+ final BlockingState blockingStateForService = blockingApi.getBlockingStateForService(accountId, BlockingStateType.ACCOUNT, OverdueService.OVERDUE_SERVICE_NAME, internalCallContext);
+ final String stateName = blockingStateForService != null ? blockingStateForService.getStateName() : OverdueWrapper.CLEAR_STATE_NAME;
+ Assert.assertEquals(stateName, expected, "Got exception: " + e.toString());
+ }
+ }
+
protected DefaultSubscriptionBase subscriptionDataFromSubscription(final SubscriptionBase sub) {
return (DefaultSubscriptionBase) sub;
}
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 f9d1984..9c12c6a 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
@@ -17,6 +17,8 @@
package org.killbill.billing.beatrix.integration;
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
@@ -44,6 +46,8 @@ import org.killbill.billing.invoice.api.InvoiceItem;
import org.killbill.billing.invoice.api.InvoiceItemType;
import org.killbill.billing.invoice.api.InvoicePaymentType;
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.PaymentApiException;
import org.killbill.billing.payment.api.PaymentOptions;
@@ -51,6 +55,7 @@ import org.killbill.billing.payment.api.PaymentTransaction;
import org.killbill.billing.payment.api.PluginProperty;
import org.killbill.billing.payment.api.TransactionStatus;
import org.killbill.billing.payment.invoice.InvoicePaymentControlPluginApi;
+import org.killbill.xmlloader.XMLLoader;
import org.mockito.Mockito;
import org.skife.jdbi.v2.Handle;
import org.skife.jdbi.v2.tweak.HandleCallback;
@@ -595,19 +600,44 @@ public class TestInvoicePayment extends TestIntegrationBase {
@Test(groups = "slow")
public void testWithPendingPaymentThenSuccess() throws Exception {
+ // Verify integration with Overdue in that particular test
+ final String configXml = "<overdueConfig>" +
+ " <accountOverdueStates>" +
+ " <initialReevaluationInterval>" +
+ " <unit>DAYS</unit><number>1</number>" +
+ " </initialReevaluationInterval>" +
+ " <state name=\"OD1\">" +
+ " <condition>" +
+ " <timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>" +
+ " <unit>DAYS</unit><number>1</number>" +
+ " </timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>" +
+ " </condition>" +
+ " <externalMessage>Reached OD1</externalMessage>" +
+ " <blockChanges>true</blockChanges>" +
+ " <disableEntitlementAndChangesBlocked>false</disableEntitlementAndChangesBlocked>" +
+ " </state>" +
+ " </accountOverdueStates>" +
+ "</overdueConfig>";
+ final InputStream is = new ByteArrayInputStream(configXml.getBytes());
+ final DefaultOverdueConfig config = XMLLoader.getObjectFromStreamNoValidation(is, DefaultOverdueConfig.class);
+ overdueConfigCache.loadDefaultOverdueConfig(config);
+
clock.setDay(new LocalDate(2012, 4, 1));
final AccountData accountData = getAccountData(1);
final Account account = createAccountWithNonOsgiPaymentMethod(accountData);
accountChecker.checkAccount(account.getId(), accountData, callContext);
+ checkODState(OverdueWrapper.CLEAR_STATE_NAME, account.getId());
+
paymentPlugin.makeNextPaymentPending();
- createBaseEntitlementAndCheckForCompletion(account.getId(), "bundleKey", "Shotgun", ProductCategory.BASE, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
+ final DefaultEntitlement baseEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "bundleKey", "Shotgun", ProductCategory.BASE, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
- busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT);
- clock.addDays(30);
- assertListenerStatus();
+ // INVOICE_PAYMENT_ERROR is sent for PENDING payments
+ addDaysAndCheckForCompletion(30, NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT_ERROR);
+
+ invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 6, 1), callContext);
final List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
assertEquals(invoices.size(), 2);
@@ -639,15 +669,21 @@ public class TestInvoicePayment extends TestIntegrationBase {
assertEquals(payments.get(0).getPaymentAttempts().get(0).getPluginName(), InvoicePaymentControlPluginApi.PLUGIN_NAME);
assertEquals(payments.get(0).getPaymentAttempts().get(0).getStateName(), "SUCCESS");
+ // Verify account transitions to OD1 while payment is PENDING
+ addDaysAndCheckForCompletion(2, NextEvent.BLOCK);
+ checkODState("OD1", account.getId());
+
// Transition the payment to success
final List<String> paymentControlPluginNames = ImmutableList.<String>of(InvoicePaymentControlPluginApi.PLUGIN_NAME);
final PaymentOptions paymentOptions = Mockito.mock(PaymentOptions.class);
Mockito.when(paymentOptions.getPaymentControlPluginNames()).thenReturn(paymentControlPluginNames);
- busHandler.pushExpectedEvents(NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+ busHandler.pushExpectedEvents(NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT, NextEvent.BLOCK);
paymentApi.notifyPendingTransactionOfStateChangedWithPaymentControl(account, payments.get(0).getTransactions().get(0).getId(), true, paymentOptions, callContext);
assertListenerStatus();
+ checkODState(OverdueWrapper.CLEAR_STATE_NAME, account.getId());
+
final Invoice invoice2 = invoiceUserApi.getInvoice(invoice1.getId(), callContext);
assertTrue(invoice2.getBalance().compareTo(BigDecimal.ZERO) == 0);
assertTrue(invoice2.getPaidAmount().compareTo(new BigDecimal("249.95")) == 0);
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 18404d2..81185d4 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
@@ -183,28 +183,16 @@ public final class InvoicePaymentControlPluginApi implements PaymentControlPlugi
final boolean success = paymentTransactionModelDao.getTransactionStatus() == TransactionStatus.SUCCESS;
log.debug("Notifying invoice of {} paymentId='{}', amount='{}', currency='{}', invoiceId='{}'", success ? "successful" : "pending", paymentControlContext.getPaymentId(), invoicePaymentAmount, paymentControlContext.getCurrency(), invoiceId);
- if (success) {
- invoiceApi.recordPaymentAttemptCompletion(invoiceId,
- invoicePaymentAmount,
- paymentControlContext.getCurrency(),
- paymentControlContext.getProcessedCurrency(),
- paymentControlContext.getPaymentId(),
- paymentControlContext.getTransactionExternalKey(),
- paymentControlContext.getCreatedDate(),
- success,
- internalContext);
- } else {
- // For PENDING payments, we re-call recordPaymentAttemptInit to simply update the current
- // entry in invoice_payments (update the payment id, processed amount, etc.)
- invoiceApi.recordPaymentAttemptInit(invoiceId,
- invoicePaymentAmount,
- paymentControlContext.getCurrency(),
- paymentControlContext.getProcessedCurrency(),
- paymentControlContext.getPaymentId(),
- paymentControlContext.getTransactionExternalKey(),
- paymentControlContext.getCreatedDate(),
- internalContext);
- }
+ // For PENDING payments, the attempt will be kept as unsuccessful and an InvoicePaymentErrorInternalEvent sent on the bus (e.g. for Overdue)
+ invoiceApi.recordPaymentAttemptCompletion(invoiceId,
+ invoicePaymentAmount,
+ paymentControlContext.getCurrency(),
+ paymentControlContext.getProcessedCurrency(),
+ paymentControlContext.getPaymentId(),
+ paymentControlContext.getTransactionExternalKey(),
+ paymentControlContext.getCreatedDate(),
+ success,
+ internalContext);
}
break;