killbill-aplcache
Changes
.gitignore 141(+113 -28)
.idea/compiler.xml 21(+4 -17)
.idea/encodings.xml 1(+0 -1)
.travis.yml 70(+32 -38)
account/pom.xml 10(+5 -5)
beatrix/pom.xml 10(+5 -5)
beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationInvoice.java 144(+144 -0)
beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationParentInvoice.java 2(+1 -1)
currency/pom.xml 10(+5 -5)
entitlement/pom.xml 10(+5 -5)
entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultEntitlement.java 25(+22 -3)
entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultEntitlementApi.java 34(+32 -2)
entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultSubscriptionApi.java 52(+44 -8)
invoice/pom.xml 10(+5 -5)
invoice/src/test/java/org/killbill/billing/invoice/notification/TestNextBillingDateNotifier.java 2(+1 -1)
junction/pom.xml 10(+5 -5)
junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultInternalBillingApi.java 5(+0 -5)
overdue/pom.xml 10(+5 -5)
overdue/src/test/java/org/killbill/billing/overdue/applicator/TestOverdueStateApplicator.java 2(+1 -1)
overdue/src/test/java/org/killbill/billing/overdue/notification/TestOverdueCheckNotifier.java 2(+1 -1)
payment/pom.xml 10(+5 -5)
payment/src/main/java/org/killbill/billing/payment/core/janitor/IncompletePaymentTransactionTask.java 32(+23 -9)
payment/src/main/java/org/killbill/billing/payment/core/sm/control/OperationControlCallback.java 2(+1 -1)
payment/src/test/java/org/killbill/billing/payment/core/janitor/TestIncompletePaymentTransactionTask.java 21(+9 -12)
payment/src/test/java/org/killbill/billing/payment/core/janitor/TestIncompletePaymentTransactionTaskWithDB.java 4(+2 -2)
profiles/killbill/pom.xml 10(+5 -5)
subscription/pom.xml 10(+5 -5)
subscription/src/main/java/org/killbill/billing/subscription/api/svcs/DefaultSubscriptionInternalApi.java 28(+23 -5)
subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBase.java 57(+40 -17)
subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBaseApiService.java 24(+13 -11)
subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java 113(+85 -28)
subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionEventSqlDao.java 4(+2 -2)
subscription/src/main/resources/org/killbill/billing/subscription/engine/dao/SubscriptionEventSqlDao.sql.stg 2(+1 -1)
subscription/src/test/java/org/killbill/billing/subscription/api/user/TestSubscriptionHelper.java 53(+47 -6)
subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiCancel.java 92(+92 -0)
subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiChangePlan.java 117(+109 -8)
util/pom.xml 10(+5 -5)
Details
.gitignore 141(+113 -28)
diff --git a/.gitignore b/.gitignore
index fba5383..dea7666 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,34 +1,119 @@
-.idea/workspace.xml
-.idea/libraries
-.idea/kotlinc.xml
-*.ipr
+#
+# https://github.com/github/gitignore/blob/master/Global/JetBrains.gitignore
+#
+
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff:
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/dictionaries
+
+# Sensitive or high-churn files:
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.xml
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+
+# Gradle:
+.idea/**/gradle.xml
+.idea/**/libraries
+
+# CMake
+cmake-build-debug/
+
+# Mongo Explorer plugin:
+.idea/**/mongoSettings.xml
+
+## File-based project format:
*.iws
-*.DS_Store
-*.bak
+
+## Plugin-specific files:
+
+# IntelliJ
+/out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+### Intellij+iml Patch ###
+# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023
+
*.iml
-target
-staging
-overlays
-examples
-pom.xml.bak
+modules.xml
+.idea/misc.xml
+*.ipr
+
+### Misc. ###
+.idea/kotlinc.xml
+.idea/sbt.xml
+.idea/vcs.xml
+.idea/dbnavigator.xml
+
+#
+# https://github.com/github/gitignore/blob/master/Java.gitignore
+#
+
+# Compiled class file
+*.class
+
+# Log file
+*.log
+
+# BlueJ files
+*.ctxt
+
+# Mobile Tools for Java (J2ME)
+.mtj.tmp/
+
+# Package Files #
+*.jar
+*.war
+*.ear
+*.zip
+*.tar.gz
+*.rar
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*
+
+#
+# https://github.com/github/gitignore/blob/master/Maven.gitignore
+#
+
+target/
+pom.xml.tag
pom.xml.releaseBackup
+pom.xml.versionsBackup
+pom.xml.next
release.properties
+dependency-reduced-pom.xml
+buildNumber.properties
+.mvn/timing.properties
+
+# Avoid ignoring Maven wrapper jar file (.jar files are usually ignored)
+!/.mvn/wrapper/maven-wrapper.jar
+
+#
+# Misc.
+#
+
+*.db
logs/
.logs
-.diskspool
-*.classpath
-*.settings
-*.project
-*/bin/
-catalog/src/test/resources/CatalogSchema.xsd
-*/test-output/
-*/src/test/resources/*.jar
-server/load
-dependency-reduced-pom.xml
-dependency-reduced-pom.xml.bak
-killbill.h2.db
-killbill.lock.db
-killbill.trace.db
-server/test.db
-.idea/dictionaries/
-.idea/dbnavigator.xml
.idea/compiler.xml 21(+4 -17)
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
index d6c37f8..f66e730 100644
--- a/.idea/compiler.xml
+++ b/.idea/compiler.xml
@@ -16,7 +16,6 @@
<sourceOutputDir name="target/generated-sources/annotations" />
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
<outputRelativeToContentRoot value="true" />
- <module name="currency" />
<module name="killbill-account" />
<module name="killbill-beatrix" />
<module name="killbill-catalog" />
@@ -37,7 +36,6 @@
</profile>
</annotationProcessing>
<bytecodeTargetLevel>
- <module name="currency" target="1.6" />
<module name="killbill" target="1.6" />
<module name="killbill-account" target="1.6" />
<module name="killbill-beatrix" target="1.6" />
@@ -48,29 +46,18 @@
<module name="killbill-invoice" target="1.6" />
<module name="killbill-jaxrs" target="1.6" />
<module name="killbill-junction" target="1.6" />
- <module name="killbill-osgi" target="1.6" />
- <module name="killbill-osgi-all-bundles" target="1.6" />
- <module name="killbill-osgi-bundles" target="1.6" />
- <module name="killbill-osgi-bundles-defaultbundles" target="1.6" />
- <module name="killbill-osgi-bundles-jruby" target="1.6" />
- <module name="killbill-osgi-bundles-lib-killbill" target="1.6" />
- <module name="killbill-osgi-bundles-lib-slf4j-osgi" target="1.6" />
- <module name="killbill-osgi-bundles-logger" target="1.6" />
- <module name="killbill-osgi-bundles-test-beatrix" target="1.6" />
- <module name="killbill-osgi-bundles-test-payment" target="1.6" />
- <module name="killbill-osgi-bundles-webconsolebranding" target="1.6" />
- <module name="killbill-osgi-lib-bundles" target="1.6" />
- <module name="killbill-osgi-test-bundles" target="1.6" />
<module name="killbill-overdue" target="1.6" />
<module name="killbill-payment" target="1.6" />
<module name="killbill-profiles" target="1.6" />
<module name="killbill-profiles-killbill" target="1.6" />
<module name="killbill-profiles-killpay" target="1.6" />
- <module name="killbill-server" target="1.6" />
<module name="killbill-subscription" target="1.6" />
<module name="killbill-tenant" target="1.6" />
<module name="killbill-usage" target="1.6" />
<module name="killbill-util" target="1.6" />
</bytecodeTargetLevel>
</component>
-</project>
\ No newline at end of file
+ <component name="JavacSettings">
+ <option name="ADDITIONAL_OPTIONS_STRING" value="-Xlint:unchecked " />
+ </component>
+</project>
.idea/encodings.xml 1(+0 -1)
diff --git a/.idea/encodings.xml b/.idea/encodings.xml
index d0e8896..7108d94 100644
--- a/.idea/encodings.xml
+++ b/.idea/encodings.xml
@@ -23,4 +23,3 @@
<file url="PROJECT" charset="UTF-8" />
</component>
</project>
-
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
index dfffbd8..a106408 100644
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -36,10 +36,11 @@
<inspection_tool class="groupsTestNG" enabled="true" level="WARNING" enabled_by_default="true">
<option name="groups">
<value>
- <list size="3">
+ <list size="4">
<item index="0" class="java.lang.String" itemvalue="slow" />
<item index="1" class="java.lang.String" itemvalue="fast" />
<item index="2" class="java.lang.String" itemvalue="mysql" />
+ <item index="3" class="java.lang.String" itemvalue="load" />
</list>
</value>
</option>
.travis.yml 70(+32 -38)
diff --git a/.travis.yml b/.travis.yml
index 531da15..bdf121e 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,14 +1,22 @@
language: java
sudo: false
-# Flaky - see https://github.com/travis-ci/travis-ci/issues/3566
-#cache:
-# directories:
-# - $HOME/.m2
+cache:
+ directories:
+ - $HOME/.m2
-script: if [[ -v COMMAND ]]; then $COMMAND; else travis_retry mvn -q -Djava.security.egd=file:/dev/./urandom -Dorg.slf4j.simpleLogger.defaultLogLevel=WARN -Dorg.slf4j.simpleLogger.log.org.killbill.billing.util.cache=ERROR -Dorg.slf4j.simpleLogger.log.org.killbill.billing.lifecycle=ERROR -Dlogback.configurationFile=$PWD/profiles/killbill/src/test/resources/logback.travis.xml clean install $PHASE -pl '!beatrix,!profiles,!profiles/killbill,!profiles/killpay' 2>&1 | egrep -v 'Download|Install|[ \t]*at [ \ta-zA-Z0-9\.\:\(\)]+'; [ ${PIPESTATUS[0]} == 0 ]; fi
+dist: trusty
+
+before_install:
+ - echo "<settings><profiles><profile><repositories><repository><id>central</id><name>bintray</name><url>http://jcenter.bintray.com</url></repository></repositories><id>bintray</id></profile></profiles><activeProfiles><activeProfile>bintray</activeProfile></activeProfiles></settings>" > /var/tmp/settings.xml
+ - mvn -N io.takari:maven:wrapper -Dmaven=3.3.9
+
+before_script:
+ - jdk_switcher use $JDK
+
+script: if [[ -v COMMAND ]]; then $COMMAND; else travis_retry ./mvnw -q -Djava.security.egd=file:/dev/./urandom -Dorg.slf4j.simpleLogger.defaultLogLevel=WARN -Dorg.slf4j.simpleLogger.log.org.killbill.billing.util.cache=ERROR -Dorg.slf4j.simpleLogger.log.org.killbill.billing.lifecycle=ERROR -Dlogback.configurationFile=$PWD/profiles/killbill/src/test/resources/logback.travis.xml clean install $PHASE -pl '!beatrix,!profiles,!profiles/killbill,!profiles/killpay' 2>&1 | egrep -v 'Download|Install|[ \t]*at [ \ta-zA-Z0-9\.\:\(\)]+'; [ ${PIPESTATUS[0]} == 0 ]; fi
# Remove --quiet to avoid timeouts
-install: mvn -U install -DskipTests=true | egrep -v 'Download|Install'
+install: mvn -U install -DskipTests=true --settings /var/tmp/settings.xml | egrep -v 'Download|Install'
notifications:
email:
@@ -19,39 +27,25 @@ env:
- MAVEN_OPTS="-Xmx512m -XX:MaxPermSize=192m"
matrix:
- allow_failures:
- - jdk: oraclejdk8
include:
- - env: PHASE="-Pmysql"
- jdk: openjdk7
- - env: PHASE="-Pmysql"
- jdk: oraclejdk7
- - env: PHASE="-Pmysql,jdk17"
- jdk: openjdk7
- - env: PHASE="-Pmysql,jdk17"
- jdk: oraclejdk7
- - env: PHASE="-Pmysql,jdk18"
- jdk: oraclejdk8
- - env: PHASE="-Ppostgresql"
- jdk: openjdk7
- - env: PHASE="-Ppostgresql"
- jdk: oraclejdk7
- - env: PHASE="-Ppostgresql,jdk17"
- jdk: openjdk7
- - env: PHASE="-Ppostgresql,jdk17"
- jdk: oraclejdk7
- - env: PHASE="-Ppostgresql,jdk18"
- jdk: oraclejdk8
- - env: PHASE="-Ptravis"
- jdk: openjdk7
- - env: PHASE="-Ptravis"
- jdk: oraclejdk7
- - env: PHASE="-Ptravis,jdk17"
- jdk: openjdk7
- - env: PHASE="-Ptravis,jdk17"
- jdk: oraclejdk7
- - env: PHASE="-Ptravis,jdk18"
- jdk: oraclejdk8
+ - env: PHASE="-Ptravis,jdk16" JDK=oraclejdk8
+ - env: PHASE="-Ptravis,jdk16" JDK=openjdk8
+ - env: PHASE="-Ptravis,jdk17" JDK=oraclejdk8
+ - env: PHASE="-Ptravis,jdk17" JDK=openjdk8
+ - env: PHASE="-Ptravis,jdk18" JDK=oraclejdk8
+ - env: PHASE="-Ptravis,jdk18" JDK=openjdk8
+ - env: PHASE="-Pmysql,jdk16" JDK=oraclejdk8
+ - env: PHASE="-Pmysql,jdk16" JDK=openjdk8
+ - env: PHASE="-Pmysql,jdk17" JDK=oraclejdk8
+ - env: PHASE="-Pmysql,jdk17" JDK=openjdk8
+ - env: PHASE="-Pmysql,jdk18" JDK=oraclejdk8
+ - env: PHASE="-Pmysql,jdk18" JDK=openjdk8
+ - env: PHASE="-Ppostgresql,jdk16" JDK=oraclejdk8
+ - env: PHASE="-Ppostgresql,jdk16" JDK=openjdk8
+ - env: PHASE="-Ppostgresql,jdk17" JDK=oraclejdk8
+ - env: PHASE="-Ppostgresql,jdk17" JDK=openjdk8
+ - env: PHASE="-Ppostgresql,jdk18" JDK=oraclejdk8
+ - env: PHASE="-Ppostgresql,jdk18" JDK=openjdk8
fast_finish: true
after_success:
account/pom.xml 10(+5 -5)
diff --git a/account/pom.xml b/account/pom.xml
index ad638ed..8f1eaa0 100644
--- a/account/pom.xml
+++ b/account/pom.xml
@@ -49,11 +49,6 @@
<scope>provided</scope>
</dependency>
<dependency>
- <groupId>com.jayway.awaitility</groupId>
- <artifactId>awaitility</artifactId>
- <scope>test</scope>
- </dependency>
- <dependency>
<groupId>io.airlift</groupId>
<artifactId>command</artifactId>
<scope>test</scope>
@@ -88,6 +83,11 @@
<scope>runtime</scope>
</dependency>
<dependency>
+ <groupId>org.awaitility</groupId>
+ <artifactId>awaitility</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
<groupId>org.jdbi</groupId>
<artifactId>jdbi</artifactId>
</dependency>
diff --git a/account/src/test/java/org/killbill/billing/account/api/user/TestDefaultAccountUserApi.java b/account/src/test/java/org/killbill/billing/account/api/user/TestDefaultAccountUserApi.java
index b5dbac0..041c18b 100644
--- a/account/src/test/java/org/killbill/billing/account/api/user/TestDefaultAccountUserApi.java
+++ b/account/src/test/java/org/killbill/billing/account/api/user/TestDefaultAccountUserApi.java
@@ -46,7 +46,7 @@ import org.testng.annotations.Test;
import com.google.common.collect.ImmutableList;
import com.google.common.eventbus.Subscribe;
-import static com.jayway.awaitility.Awaitility.await;
+import static org.awaitility.Awaitility.await;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.killbill.billing.account.AccountTestUtils.createAccountData;
import static org.killbill.billing.account.AccountTestUtils.createTestAccount;
beatrix/pom.xml 10(+5 -5)
diff --git a/beatrix/pom.xml b/beatrix/pom.xml
index 675e238..b534e04 100644
--- a/beatrix/pom.xml
+++ b/beatrix/pom.xml
@@ -56,11 +56,6 @@
<scope>provided</scope>
</dependency>
<dependency>
- <groupId>com.jayway.awaitility</groupId>
- <artifactId>awaitility</artifactId>
- <scope>test</scope>
- </dependency>
- <dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP-java6</artifactId>
<scope>test</scope>
@@ -110,6 +105,11 @@
<scope>test</scope>
</dependency>
<dependency>
+ <groupId>org.awaitility</groupId>
+ <artifactId>awaitility</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<scope>test</scope>
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegration.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegration.java
index fa7cc6c..89507b5 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegration.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegration.java
@@ -160,6 +160,7 @@ public class TestIntegration extends TestIntegrationBase {
invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, callContext, expectedInvoices);
+ clock.addDeltaFromReality(1000); // Make sure CHANGE does not collide with CREATE
changeEntitlementAndCheckForCompletion(baseEntitlement, "Assault-Rifle", BillingPeriod.MONTHLY, null, NextEvent.CHANGE, NextEvent.INVOICE);
invoiceChecker.checkInvoice(account.getId(), invoiceItemCount++, callContext, expectedInvoices);
invoiceChecker.checkChargedThroughDate(subscription.getId(), clock.getUTCToday(), callContext);
@@ -263,6 +264,7 @@ public class TestIntegration extends TestIntegrationBase {
//
// CHANGE PLAN IMMEDIATELY AND EXPECT BOTH EVENTS: NextEvent.CHANGE NextEvent.INVOICE
//
+ clock.addDeltaFromReality(1000); // Make sure CHANGE does not exactly align with CREATE
changeEntitlementAndCheckForCompletion(baseEntitlement, "Assault-Rifle", BillingPeriod.MONTHLY, null, NextEvent.CHANGE, NextEvent.INVOICE);
invoiceChecker.checkInvoice(account.getId(), invoiceItemCount++, callContext, new ExpectedInvoiceItemCheck(initialCreationDate.toLocalDate(), null, InvoiceItemType.FIXED, new BigDecimal("0")));
invoiceChecker.checkChargedThroughDate(subscription.getId(), clock.getUTCToday(), callContext);
@@ -355,6 +357,7 @@ public class TestIntegration extends TestIntegrationBase {
//
// CHANGE PLAN IMMEDIATELY AND EXPECT BOTH EVENTS: NextEvent.CHANGE NextEvent.INVOICE
//
+ clock.addDeltaFromReality(1000); // Ensure CHANGE does not collide with CREATE
baseEntitlement = changeEntitlementAndCheckForCompletion(baseEntitlement, "Assault-Rifle", BillingPeriod.MONTHLY, null, NextEvent.CHANGE, NextEvent.INVOICE);
invoiceChecker.checkInvoice(account.getId(), invoiceItemCount++, callContext, new ExpectedInvoiceItemCheck(initialCreationDate.toLocalDate(), null, InvoiceItemType.FIXED, new BigDecimal("0")));
invoiceChecker.checkChargedThroughDate(subscription.getId(), clock.getUTCToday(), callContext);
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 f32d4bf..06e6817 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
@@ -18,18 +18,15 @@
package org.killbill.billing.beatrix.integration;
-import java.math.BigDecimal;
-import java.util.ArrayList;
-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;
-import javax.inject.Named;
-
+import com.google.common.base.Function;
+import com.google.common.base.Joiner;
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Stage;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.LocalDate;
@@ -129,17 +126,18 @@ import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
-import com.google.common.base.Function;
-import com.google.common.base.Joiner;
-import com.google.common.base.Predicate;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
-import com.google.inject.Guice;
-import com.google.inject.Injector;
-import com.google.inject.Stage;
+import javax.annotation.Nullable;
+import javax.inject.Inject;
+import javax.inject.Named;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.Callable;
-import static com.jayway.awaitility.Awaitility.await;
+import static org.awaitility.Awaitility.await;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
@@ -427,19 +425,22 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB {
return new TestPaymentMethodPlugin();
}
- protected AccountData getAccountData(final int billingDay) {
- return new MockAccountBuilder().name(UUID.randomUUID().toString().substring(1, 8))
- .firstNameLength(6)
- .email(UUID.randomUUID().toString().substring(1, 8))
- .phone(UUID.randomUUID().toString().substring(1, 8))
- .migrated(false)
- .isNotifiedForInvoices(false)
- .externalKey(UUID.randomUUID().toString().substring(1, 8))
- .billingCycleDayLocal(billingDay)
- .currency(Currency.USD)
- .paymentMethodId(UUID.randomUUID())
- .timeZone(DateTimeZone.UTC)
- .build();
+ protected AccountData getAccountData(@Nullable final Integer billingDay) {
+ final MockAccountBuilder builder = new MockAccountBuilder()
+ .name(UUID.randomUUID().toString().substring(1, 8))
+ .firstNameLength(6)
+ .email(UUID.randomUUID().toString().substring(1, 8))
+ .phone(UUID.randomUUID().toString().substring(1, 8))
+ .migrated(false)
+ .isNotifiedForInvoices(false)
+ .externalKey(UUID.randomUUID().toString().substring(1, 8))
+ .currency(Currency.USD)
+ .paymentMethodId(UUID.randomUUID())
+ .timeZone(DateTimeZone.UTC);
+ if (billingDay != null) {
+ builder.billingCycleDayLocal(billingDay);
+ }
+ return builder.build();
}
protected AccountData getChildAccountData(final int billingDay, final UUID parentAccountId, final boolean isPaymentDelegatedToParent) {
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 2402ed2..1f481ea 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,30 +25,39 @@ 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.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.EntitlementApiException;
+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;
import org.killbill.billing.payment.api.Payment;
import org.killbill.billing.payment.api.PluginProperty;
import org.killbill.billing.subscription.api.user.DefaultSubscriptionBase;
+import org.testng.Assert;
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.assertNull;
import static org.testng.Assert.assertTrue;
public class TestIntegrationInvoice extends TestIntegrationBase {
@@ -361,5 +370,140 @@ 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);
+
+
+
+
+ }
}
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationParentInvoice.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationParentInvoice.java
index 1cf0586..945dece 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationParentInvoice.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationParentInvoice.java
@@ -179,7 +179,7 @@ public class TestIntegrationParentInvoice extends TestIntegrationBase {
// upgrade plan
busHandler.pushExpectedEvents(NextEvent.CHANGE, NextEvent.INVOICE);
- baseEntitlementChild.changePlanOverrideBillingPolicy(new PlanSpecifier("Shotgun", BillingPeriod.MONTHLY, baseEntitlementChild.getLastActivePriceList().getName()), null, clock.getToday(childAccount.getTimeZone()), BillingActionPolicy.IMMEDIATE, null, callContext);
+ baseEntitlementChild.changePlanWithDate(new PlanSpecifier("Shotgun", BillingPeriod.MONTHLY, baseEntitlementChild.getLastActivePriceList().getName()), null, null,null, callContext);
assertListenerStatus();
// check parent invoice. Expected to have the same invoice item with the amount updated
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestPublicBus.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestPublicBus.java
index 2d5f61e..353ae0d 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestPublicBus.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestPublicBus.java
@@ -62,7 +62,7 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.eventbus.Subscribe;
-import static com.jayway.awaitility.Awaitility.await;
+import static org.awaitility.Awaitility.await;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.testng.Assert.assertNotNull;
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestSubscription.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestSubscription.java
index 3d4ea4a..880c3a5 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestSubscription.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestSubscription.java
@@ -30,6 +30,7 @@ import org.killbill.billing.api.TestApiListener.NextEvent;
import org.killbill.billing.beatrix.util.InvoiceChecker.ExpectedInvoiceItemCheck;
import org.killbill.billing.catalog.api.BillingActionPolicy;
import org.killbill.billing.catalog.api.BillingPeriod;
+import org.killbill.billing.catalog.api.PlanPhase;
import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
import org.killbill.billing.catalog.api.PriceListSet;
import org.killbill.billing.catalog.api.ProductCategory;
@@ -477,6 +478,9 @@ public class TestSubscription extends TestIntegrationBase {
assertEquals(createdEntitlement.getState(), EntitlementState.PENDING);
assertEquals(createdEntitlement.getEffectiveStartDate().compareTo(futureDate), 0);
assertEquals(createdEntitlement.getEffectiveEndDate(), null);
+ assertEquals(createdEntitlement.getLastActiveProduct().getName(), "Shotgun");
+ assertEquals(createdEntitlement.getLastActivePlan().getName(), "shotgun-annual");
+ assertEquals(createdEntitlement.getLastActiveProductCategory(), ProductCategory.BASE);
assertListenerStatus();
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithFakeKPMPlugin.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithFakeKPMPlugin.java
index 4113d01..b239488 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithFakeKPMPlugin.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithFakeKPMPlugin.java
@@ -79,7 +79,7 @@ import com.google.inject.Module;
import com.google.inject.Stage;
import com.google.inject.util.Modules;
-import static com.jayway.awaitility.Awaitility.await;
+import static org.awaitility.Awaitility.await;
import static java.util.concurrent.TimeUnit.SECONDS;
public class TestWithFakeKPMPlugin extends TestIntegrationBase {
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithPriceOverride.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithPriceOverride.java
index 54de017..bc7a72f 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithPriceOverride.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithPriceOverride.java
@@ -121,7 +121,7 @@ public class TestWithPriceOverride extends TestIntegrationBase {
overrides.add(new DefaultPlanPhasePriceOverride("shotgun-monthly-evergreen", account.getCurrency(), null, new BigDecimal("279.95"), null));
busHandler.pushExpectedEvents(NextEvent.CHANGE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
- bpSubscription.changePlanOverrideBillingPolicy(new PlanSpecifier("Shotgun", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME), overrides, new LocalDate(2012, 5, 1), BillingActionPolicy.IMMEDIATE, ImmutableList.<PluginProperty>of(), callContext);
+ bpSubscription.changePlanOverrideBillingPolicy(new PlanSpecifier("Shotgun", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME), overrides, null, BillingActionPolicy.IMMEDIATE, ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
invoiceChecker.checkInvoice(account.getId(), 3, callContext,
@@ -168,7 +168,7 @@ public class TestWithPriceOverride extends TestIntegrationBase {
overrides.add(new DefaultPlanPhasePriceOverride("telescopic-scope-monthly-evergreen", account.getCurrency(), null, new BigDecimal("1200.00"), null));
busHandler.pushExpectedEvents(NextEvent.CHANGE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
- aoEntitlement.changePlanOverrideBillingPolicy(new PlanSpecifier("Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME), overrides, new LocalDate(2012, 5, 5), BillingActionPolicy.IMMEDIATE, ImmutableList.<PluginProperty>of(), callContext);
+ aoEntitlement.changePlanOverrideBillingPolicy(new PlanSpecifier("Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME), overrides, null, BillingActionPolicy.IMMEDIATE, ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
}
}
currency/pom.xml 10(+5 -5)
diff --git a/currency/pom.xml b/currency/pom.xml
index d813cb8..d7cf768 100644
--- a/currency/pom.xml
+++ b/currency/pom.xml
@@ -47,11 +47,6 @@
<scope>provided</scope>
</dependency>
<dependency>
- <groupId>com.jayway.awaitility</groupId>
- <artifactId>awaitility</artifactId>
- <scope>test</scope>
- </dependency>
- <dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<scope>provided</scope>
@@ -61,6 +56,11 @@
<artifactId>joda-time</artifactId>
</dependency>
<dependency>
+ <groupId>org.awaitility</groupId>
+ <artifactId>awaitility</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
<groupId>org.kill-bill.billing</groupId>
<artifactId>killbill-api</artifactId>
</dependency>
entitlement/pom.xml 10(+5 -5)
diff --git a/entitlement/pom.xml b/entitlement/pom.xml
index f91458f..4373590 100644
--- a/entitlement/pom.xml
+++ b/entitlement/pom.xml
@@ -45,11 +45,6 @@
<scope>provided</scope>
</dependency>
<dependency>
- <groupId>com.jayway.awaitility</groupId>
- <artifactId>awaitility</artifactId>
- <scope>test</scope>
- </dependency>
- <dependency>
<groupId>io.airlift</groupId>
<artifactId>command</artifactId>
<scope>test</scope>
@@ -88,6 +83,11 @@
<artifactId>shiro-core</artifactId>
</dependency>
<dependency>
+ <groupId>org.awaitility</groupId>
+ <artifactId>awaitility</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
<groupId>org.jdbi</groupId>
<artifactId>jdbi</artifactId>
</dependency>
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlement.java b/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlement.java
index 10246cb..ba099eb 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlement.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlement.java
@@ -18,15 +18,7 @@
package org.killbill.billing.entitlement.api;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.UUID;
-
-import javax.annotation.Nullable;
-
+import com.google.common.collect.ImmutableList;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.LocalDate;
@@ -75,7 +67,13 @@ import org.killbill.notificationq.api.NotificationQueueService.NoSuchNotificatio
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import com.google.common.collect.ImmutableList;
+import javax.annotation.Nullable;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.UUID;
import static org.killbill.billing.entitlement.logging.EntitlementLoggingHelper.logCancelEntitlement;
import static org.killbill.billing.entitlement.logging.EntitlementLoggingHelper.logChangePlan;
@@ -348,10 +346,10 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
final InternalCallContext contextWithValidAccountRecordId = internalCallContextFactory.createInternalCallContext(getAccountId(), callContext);
- final DateTime effectiveCancelDate = dateHelper.fromLocalDateAndReferenceTimeWithMinimum(entitlementEffectiveDate, getEventsStream().getEntitlementEffectiveStartDateTime(), contextWithValidAccountRecordId);
+ final DateTime billingEffectiveCancelDate = dateHelper.fromLocalDateAndReferenceTimeWithMinimum(billingEffectiveDate, getEventsStream().getSubscriptionBase().getStartDate(), contextWithValidAccountRecordId);
try {
if (overrideBillingEffectiveDate) {
- getSubscriptionBase().cancelWithDate(effectiveCancelDate, callContext);
+ getSubscriptionBase().cancelWithDate(billingEffectiveCancelDate, callContext);
} else {
getSubscriptionBase().cancel(callContext);
}
@@ -359,14 +357,15 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
throw new EntitlementApiException(e);
}
- final BlockingState newBlockingState = new DefaultBlockingState(getId(), BlockingStateType.SUBSCRIPTION, DefaultEntitlementApi.ENT_STATE_CANCELLED, EntitlementService.ENTITLEMENT_SERVICE_NAME, true, true, false, effectiveCancelDate);
+ final DateTime entitlementEffectiveCancelDate = dateHelper.fromLocalDateAndReferenceTimeWithMinimum(entitlementEffectiveDate, getEventsStream().getEntitlementEffectiveStartDateTime(), contextWithValidAccountRecordId);
+ final BlockingState newBlockingState = new DefaultBlockingState(getId(), BlockingStateType.SUBSCRIPTION, DefaultEntitlementApi.ENT_STATE_CANCELLED, EntitlementService.ENTITLEMENT_SERVICE_NAME, true, true, false, entitlementEffectiveCancelDate);
final Collection<NotificationEvent> notificationEvents = new ArrayList<NotificationEvent>();
- final Collection<BlockingState> addOnsBlockingStates = computeAddOnBlockingStates(effectiveCancelDate, notificationEvents, callContext, contextWithValidAccountRecordId);
+ final Collection<BlockingState> addOnsBlockingStates = computeAddOnBlockingStates(entitlementEffectiveCancelDate, notificationEvents, callContext, contextWithValidAccountRecordId);
// Record the new state first, then insert the notifications to avoid race conditions
setBlockingStates(newBlockingState, addOnsBlockingStates, contextWithValidAccountRecordId);
for (final NotificationEvent notificationEvent : notificationEvents) {
- recordFutureNotification(effectiveCancelDate, notificationEvent, contextWithValidAccountRecordId);
+ recordFutureNotification(entitlementEffectiveCancelDate, notificationEvent, contextWithValidAccountRecordId);
}
return entitlementApi.getEntitlementForId(getId(), callContext);
@@ -641,12 +640,15 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
final WithEntitlementPlugin<Entitlement> changePlanWithPlugin = new WithEntitlementPlugin<Entitlement>() {
@Override
public Entitlement doCall(final EntitlementApi entitlementApi, final EntitlementContext updatedPluginContext) throws EntitlementApiException {
- if (!eventsStream.isEntitlementActive()) {
+
+ if (effectiveDate != null && effectiveDate.compareTo(eventsStream.getEntitlementEffectiveStartDate()) < 0) {
throw new EntitlementApiException(ErrorCode.SUB_CHANGE_NON_ACTIVE, getId(), getState());
}
final InternalCallContext context = internalCallContextFactory.createInternalCallContext(getAccountId(), callContext);
- final DateTime effectiveChangeDate = dateHelper.fromLocalDateAndReferenceTime(updatedPluginContext.getBaseEntitlementWithAddOnsSpecifiers().iterator().next().getBillingEffectiveDate(), context);
+
+
+ final DateTime effectiveChangeDate = effectiveDate != null ? dateHelper.fromLocalDateAndReferenceTime(effectiveDate, context) : null;
final DateTime resultingEffectiveDate;
try {
@@ -685,9 +687,9 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
}
@Override
- public Entitlement changePlanOverrideBillingPolicy(final PlanSpecifier spec, final List<PlanPhasePriceOverride> overrides, final LocalDate entitlementEffectiveDate, final BillingActionPolicy actionPolicy, final Iterable<PluginProperty> properties, final CallContext callContext) throws EntitlementApiException {
+ public Entitlement changePlanOverrideBillingPolicy(final PlanSpecifier spec, final List<PlanPhasePriceOverride> overrides, final LocalDate unused, final BillingActionPolicy actionPolicy, final Iterable<PluginProperty> properties, final CallContext callContext) throws EntitlementApiException {
- logChangePlan(log, this, spec, overrides, entitlementEffectiveDate, actionPolicy);
+ logChangePlan(log, this, spec, overrides, null, actionPolicy);
checkForPermissions(Permission.ENTITLEMENT_CAN_CHANGE_PLAN, callContext);
@@ -698,7 +700,7 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
getBundleId(),
getExternalKey(),
null,
- entitlementEffectiveDate,
+ null,
null,
false);
final List<BaseEntitlementWithAddOnsSpecifier> baseEntitlementWithAddOnsSpecifierList = new ArrayList<BaseEntitlementWithAddOnsSpecifier>();
@@ -714,23 +716,20 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
final WithEntitlementPlugin<Entitlement> changePlanWithPlugin = new WithEntitlementPlugin<Entitlement>() {
@Override
public Entitlement doCall(final EntitlementApi entitlementApi, final EntitlementContext updatedPluginContext) throws EntitlementApiException {
- if (!eventsStream.isEntitlementActive()) {
- throw new EntitlementApiException(ErrorCode.SUB_CHANGE_NON_ACTIVE, getId(), getState());
- }
+
final InternalCallContext context = internalCallContextFactory.createInternalCallContext(getAccountId(), callContext);
- final DateTime effectiveChangeDate;
+ final DateTime resultingEffectiveDate;
try {
- effectiveChangeDate = subscriptionInternalApi.getDryRunChangePlanEffectiveDate(getSubscriptionBase(), spec, null, actionPolicy, overrides, context);
+ resultingEffectiveDate = subscriptionInternalApi.getDryRunChangePlanEffectiveDate(getSubscriptionBase(), spec, null, actionPolicy, overrides, context);
} catch (final SubscriptionBaseApiException e) {
throw new EntitlementApiException(e, e.getCode(), e.getMessage());
} catch (final CatalogApiException e) {
throw new EntitlementApiException(e, e.getCode(), e.getMessage());
}
-
try {
- checker.checkBlockedChange(getSubscriptionBase(), effectiveChangeDate, context);
+ checker.checkBlockedChange(getSubscriptionBase(), resultingEffectiveDate, context);
} catch (final BlockingApiException e) {
throw new EntitlementApiException(e, e.getCode(), e.getMessage());
}
@@ -742,12 +741,12 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
}
final Collection<NotificationEvent> notificationEvents = new ArrayList<NotificationEvent>();
- final Iterable<BlockingState> addOnsBlockingStates = computeAddOnBlockingStates(effectiveChangeDate, notificationEvents, callContext, context);
+ final Iterable<BlockingState> addOnsBlockingStates = computeAddOnBlockingStates(resultingEffectiveDate, notificationEvents, callContext, context);
// Record the new state first, then insert the notifications to avoid race conditions
setBlockingStates(addOnsBlockingStates, context);
for (final NotificationEvent notificationEvent : notificationEvents) {
- recordFutureNotification(effectiveChangeDate, notificationEvent, context);
+ recordFutureNotification(resultingEffectiveDate, notificationEvent, context);
}
return entitlementApi.getEntitlementForId(getId(), callContext);
diff --git a/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultEntitlement.java b/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultEntitlement.java
index 692995f..6771c42 100644
--- a/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultEntitlement.java
+++ b/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultEntitlement.java
@@ -26,6 +26,7 @@ import org.killbill.billing.account.api.AccountApiException;
import org.killbill.billing.api.TestApiListener.NextEvent;
import org.killbill.billing.catalog.api.BillingActionPolicy;
import org.killbill.billing.catalog.api.BillingPeriod;
+import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
import org.killbill.billing.catalog.api.PlanSpecifier;
import org.killbill.billing.catalog.api.PriceListSet;
@@ -262,7 +263,7 @@ public class TestDefaultEntitlement extends EntitlementTestSuiteWithEmbeddedDB {
assertListenerStatus();
// Immediate change during trial
- testListener.pushExpectedEvent(NextEvent.CHANGE);
+ testListener.pushExpectedEvent(NextEvent.CREATE);
entitlement.changePlan(new PlanSpecifier("Assault-Rifle", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME), null, ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
@@ -282,7 +283,7 @@ public class TestDefaultEntitlement extends EntitlementTestSuiteWithEmbeddedDB {
}
@Test(groups = "slow")
- public void testEntitlementStartedInFuture() throws AccountApiException, EntitlementApiException {
+ public void testEntitlementChangePlanOnPendingEntitlement() throws AccountApiException, EntitlementApiException {
final LocalDate initialDate = new LocalDate(2013, 8, 7);
clock.setDay(initialDate);
@@ -298,12 +299,30 @@ public class TestDefaultEntitlement extends EntitlementTestSuiteWithEmbeddedDB {
assertListenerStatus();
assertEquals(entitlement.getState(), EntitlementState.PENDING);
- testListener.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK);
+ final PlanPhaseSpecifier spec2 = new PlanPhaseSpecifier("pistol-monthly", null);
+ try {
+ entitlement.changePlan(spec2, ImmutableList.<PlanPhasePriceOverride>of(), ImmutableList.<PluginProperty>of(), callContext);
+ fail("Changing plan immediately prior the subscription is active is not allowed");
+ } catch (EntitlementApiException e) {
+ assertEquals(e.getCode(), ErrorCode.SUB_CHANGE_NON_ACTIVE.getCode());
+ }
+
+ try {
+ entitlement.changePlanWithDate(spec2, ImmutableList.<PlanPhasePriceOverride>of(), startDate.minusDays(1), ImmutableList.<PluginProperty>of(), callContext);
+ fail("Changing plan immediately prior the subscription is active is not allowed");
+ } catch (EntitlementApiException e) {
+ assertEquals(e.getCode(), ErrorCode.SUB_CHANGE_NON_ACTIVE.getCode());
+ }
+
+ entitlement.changePlanWithDate(spec2, ImmutableList.<PlanPhasePriceOverride>of(), startDate, ImmutableList.<PluginProperty>of(), callContext);
+
+ testListener.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK);
clock.addDays(10);
assertListenerStatus();
final Entitlement entitlement1 = entitlementApi.getEntitlementForId(entitlement.getId(), callContext);
assertEquals(entitlement1.getState(), EntitlementState.ACTIVE);
+ assertEquals(entitlement1.getLastActiveProduct().getName(), "Pistol");
}
}
diff --git a/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultEntitlementApi.java b/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultEntitlementApi.java
index 5eb96de..718b61e 100644
--- a/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultEntitlementApi.java
+++ b/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultEntitlementApi.java
@@ -30,6 +30,7 @@ import org.killbill.billing.account.api.AccountApiException;
import org.killbill.billing.api.TestApiListener.NextEvent;
import org.killbill.billing.catalog.api.BillingActionPolicy;
import org.killbill.billing.catalog.api.BillingPeriod;
+import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
import org.killbill.billing.catalog.api.PriceListSet;
import org.killbill.billing.catalog.api.ProductCategory;
@@ -586,7 +587,7 @@ public class TestDefaultEntitlementApi extends EntitlementTestSuiteWithEmbeddedD
}
@Test(groups = "slow")
- public void testCreateBaseWithDifferentInTheFuture() throws AccountApiException, EntitlementApiException, SubscriptionApiException {
+ public void testCreateBaseWithDifferentStartDate() throws AccountApiException, EntitlementApiException, SubscriptionApiException {
final LocalDate initialDate = new LocalDate(2013, 8, 7);
clock.setDay(initialDate);
@@ -597,19 +598,48 @@ public class TestDefaultEntitlementApi extends EntitlementTestSuiteWithEmbeddedD
final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Shotgun", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
- final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, entitlementDate, billingDate, false, ImmutableList.<PluginProperty>of(), callContext);
+ Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, entitlementDate, billingDate, false, ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
assertEquals(entitlement.getState(), EntitlementState.PENDING);
assertEquals(entitlement.getEffectiveStartDate(), entitlementDate);
+
+
+ // 2013-08-10 : entitlementDate
testListener.pushExpectedEvents(NextEvent.BLOCK);
clock.addDays(3);
assertListenerStatus();
+
+ // Once we pass entitlement startDate, state should be ACTIVE (although we did not pass billing startDate)
+ entitlement = entitlementApi.getEntitlementForId(entitlement.getId(), callContext);
+ assertEquals(entitlement.getState(), EntitlementState.ACTIVE);
+
+ // 2013-08-12 : billingDate
testListener.pushExpectedEvents(NextEvent.CREATE);
clock.addDays(2);
assertListenerStatus();
+
+
+ // effectiveDate = entitlementDate prior billingDate
+ final PlanPhaseSpecifier spec2 = new PlanPhaseSpecifier("Pistol", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
+ try {
+ entitlement.changePlanWithDate(spec2, ImmutableList.<PlanPhasePriceOverride>of(), entitlementDate, ImmutableList.<PluginProperty>of(), callContext);
+ Assert.fail("Change plan prior billingStartDate should fail");
+ } catch (EntitlementApiException e) {
+ Assert.assertEquals(e.getCode(), ErrorCode.SUB_INVALID_REQUESTED_DATE.getCode());
+ }
+
+ // effectiveDate is null (same as first case above), but **did** reach the billing startDate (and entitlement startDate) so will succeed
+ clock.addDeltaFromReality(1000); // Add one sec to make sure CHANGE event does not coincide with CREATE (realistic scenario), and therefore we do expect a CHANGE event
+ testListener.pushExpectedEvents(NextEvent.CHANGE);
+ final Entitlement result = entitlement.changePlanWithDate(spec2, ImmutableList.<PlanPhasePriceOverride>of(), null, ImmutableList.<PluginProperty>of(), callContext);
+ assertListenerStatus();
+
+ assertEquals(result.getState(), EntitlementState.ACTIVE);
+ assertEquals(result.getLastActiveProduct().getName(), "Pistol");
+
}
diff --git a/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultSubscriptionApi.java b/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultSubscriptionApi.java
index f23a365..18ac1dd 100644
--- a/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultSubscriptionApi.java
+++ b/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultSubscriptionApi.java
@@ -514,17 +514,53 @@ public class TestDefaultSubscriptionApi extends EntitlementTestSuiteWithEmbedded
final Entitlement latestEntitlement = entitlementApi.getEntitlementForId(entitlement.getId(), callContext);
assertEquals(latestEntitlement.getLastActivePlan().getProduct().getName(), "Shotgun");
}
+ }
- try {
- entitlement.changePlanOverrideBillingPolicy(new PlanSpecifier("Assault-Rifle", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME), null, clock.getUTCToday(), BillingActionPolicy.IMMEDIATE, ImmutableList.<PluginProperty>of(), callContext);
- fail();
- } catch (final EntitlementApiException e) {
- assertEquals(e.getCode(), ErrorCode.BLOCK_BLOCKED_ACTION.getCode());
- final Entitlement latestEntitlement = entitlementApi.getEntitlementForId(entitlement.getId(), callContext);
- assertEquals(latestEntitlement.getLastActivePlan().getProduct().getName(), "Shotgun");
- }
+ @Test(groups = "slow")
+ public void testCancellationEntitlementDifferentThanBilling() throws AccountApiException, EntitlementApiException, SubscriptionApiException {
+ final LocalDate initialDate = new LocalDate(2013, 8, 7);
+ clock.setDay(initialDate);
+
+ final Account account = createAccount(getAccountData(7));
+
+ // Create entitlement
+ testListener.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK);
+ final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Shotgun", BillingPeriod.ANNUAL, PriceListSet.DEFAULT_PRICELIST_NAME, null);
+ final Entitlement baseEntitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, null, null, false, ImmutableList.<PluginProperty>of(), callContext);
+ assertListenerStatus();
+
+ // 2013-08-10 : Stay in TRIAL to ensure IMMEDIATE billing policy is used
+ clock.addDays(3);
+ assertListenerStatus();
+
+
+ final LocalDate cancelDate = new LocalDate(2013, 8, 14);
+ testListener.pushExpectedEvents(NextEvent.CANCEL);
+ baseEntitlement.cancelEntitlementWithDate(cancelDate, false, ImmutableList.<PluginProperty>of(), callContext);
+ assertListenerStatus();
+
+ final Subscription result1 = subscriptionApi.getSubscriptionForEntitlementId(baseEntitlement.getId(), callContext);
+ assertEquals(result1.getBillingEndDate().compareTo(new LocalDate(2013, 8, 10)), 0);
+ assertEquals(result1.getEffectiveEndDate().compareTo(new LocalDate(2013, 8, 14)), 0);
+ assertEquals(result1.getLastActiveProduct().getName(), "Shotgun");
+ assertEquals(result1.getState(), EntitlementState.ACTIVE);
+
+ // 2013-08-14: entitlement cancelDate
+ testListener.pushExpectedEvents(NextEvent.BLOCK);
+ clock.addDays(4);
+ assertListenerStatus();
+
+ final Subscription result2 = subscriptionApi.getSubscriptionForEntitlementId(baseEntitlement.getId(), callContext);
+ assertEquals(result2.getBillingEndDate().compareTo(new LocalDate(2013, 8, 10)), 0);
+ assertEquals(result2.getEffectiveEndDate().compareTo(new LocalDate(2013, 8, 14)), 0);
+ assertEquals(result2.getLastActiveProduct().getName(), "Shotgun");
+ assertEquals(result2.getState(), EntitlementState.CANCELLED);
}
+
+
+
+
@Test(groups = "slow")
public void testSubscriptionCreationWithExternalKeyOverLimit() throws AccountApiException, SubscriptionApiException, EntitlementApiException {
final String externalKey = "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis,.";
invoice/pom.xml 10(+5 -5)
diff --git a/invoice/pom.xml b/invoice/pom.xml
index 511ba1c..defab3b 100644
--- a/invoice/pom.xml
+++ b/invoice/pom.xml
@@ -50,11 +50,6 @@
<scope>provided</scope>
</dependency>
<dependency>
- <groupId>com.jayway.awaitility</groupId>
- <artifactId>awaitility</artifactId>
- <scope>test</scope>
- </dependency>
- <dependency>
<groupId>io.airlift</groupId>
<artifactId>command</artifactId>
<scope>test</scope>
@@ -89,6 +84,11 @@
<scope>runtime</scope>
</dependency>
<dependency>
+ <groupId>org.awaitility</groupId>
+ <artifactId>awaitility</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
<groupId>org.jdbi</groupId>
<artifactId>jdbi</artifactId>
</dependency>
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/notification/TestNextBillingDateNotifier.java b/invoice/src/test/java/org/killbill/billing/invoice/notification/TestNextBillingDateNotifier.java
index 51ddb3a..763bd45 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/notification/TestNextBillingDateNotifier.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/notification/TestNextBillingDateNotifier.java
@@ -31,7 +31,7 @@ import org.killbill.notificationq.api.NotificationQueue;
import org.testng.Assert;
import org.testng.annotations.Test;
-import static com.jayway.awaitility.Awaitility.await;
+import static org.awaitility.Awaitility.await;
import static java.util.concurrent.TimeUnit.MINUTES;
public class TestNextBillingDateNotifier extends InvoiceTestSuiteWithEmbeddedDB {
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/SubscriptionResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/SubscriptionResource.java
index 8cefa75..7eb19a1 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/SubscriptionResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/SubscriptionResource.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * 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
@@ -71,6 +71,7 @@ import org.killbill.billing.events.BlockingTransitionInternalEvent;
import org.killbill.billing.events.EffectiveSubscriptionInternalEvent;
import org.killbill.billing.events.InvoiceCreationInternalEvent;
import org.killbill.billing.events.InvoicePaymentErrorInternalEvent;
+import org.killbill.billing.events.InvoicePaymentInfoInternalEvent;
import org.killbill.billing.events.NullInvoiceInternalEvent;
import org.killbill.billing.events.PaymentErrorInternalEvent;
import org.killbill.billing.events.PaymentInfoInternalEvent;
@@ -566,7 +567,7 @@ public class SubscriptionResource extends JaxRsResourceBase {
newEntitlement = current.changePlanWithDate(planSpec, overrides, inputLocalDate, pluginProperties, ctx);
} else {
final BillingActionPolicy policy = BillingActionPolicy.valueOf(policyString.toUpperCase());
- newEntitlement = current.changePlanOverrideBillingPolicy(planSpec, overrides, inputLocalDate, policy, pluginProperties, ctx);
+ newEntitlement = current.changePlanOverrideBillingPolicy(planSpec, overrides, null, policy, pluginProperties, ctx);
}
isImmediateOp = newEntitlement.getLastActiveProduct().getName().equals(entitlement.getProductName()) &&
newEntitlement.getLastActivePlan().getRecurringBillingPeriod() == BillingPeriod.valueOf(entitlement.getBillingPeriod()) &&
@@ -785,6 +786,12 @@ public class SubscriptionResource extends JaxRsResourceBase {
}
@Override
+ public void onInvoicePaymentInfo(final InvoicePaymentInfoInternalEvent event) {
+ log.info("Got event InvoicePaymentInfo token='{}'", event.getUserToken());
+ notifyForCompletion();
+ }
+
+ @Override
public void onInvoicePaymentError(final InvoicePaymentErrorInternalEvent event) {
log.info("Got event InvoicePaymentError token='{}'", event.getUserToken());
notifyForCompletion();
junction/pom.xml 10(+5 -5)
diff --git a/junction/pom.xml b/junction/pom.xml
index cecd3b7..a11f142 100644
--- a/junction/pom.xml
+++ b/junction/pom.xml
@@ -45,11 +45,6 @@
<scope>provided</scope>
</dependency>
<dependency>
- <groupId>com.jayway.awaitility</groupId>
- <artifactId>awaitility</artifactId>
- <scope>test</scope>
- </dependency>
- <dependency>
<groupId>io.airlift</groupId>
<artifactId>command</artifactId>
<scope>test</scope>
@@ -74,6 +69,11 @@
<scope>test</scope>
</dependency>
<dependency>
+ <groupId>org.awaitility</groupId>
+ <artifactId>awaitility</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
<groupId>org.kill-bill.billing</groupId>
<artifactId>killbill-account</artifactId>
<type>test-jar</type>
diff --git a/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultInternalBillingApi.java b/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultInternalBillingApi.java
index 83d67c7..bf4f041 100644
--- a/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultInternalBillingApi.java
+++ b/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultInternalBillingApi.java
@@ -188,11 +188,6 @@ public class DefaultInternalBillingApi implements BillingInternalApi {
for (final SubscriptionBase subscription : subscriptions) {
- // The subscription did not even start, so there is nothing to do yet, we can skip and avoid some NPE down the line when calculating the BCD
- if (subscription.getState() == EntitlementState.PENDING) {
- log.info("Skipping subscription id='{}', state = EntitlementState.PENDING", subscription.getId());
- continue;
- }
final List<EffectiveSubscriptionInternalEvent> billingTransitions = subscriptionApi.getBillingTransitions(subscription, context);
if (billingTransitions.isEmpty() ||
overdue/pom.xml 10(+5 -5)
diff --git a/overdue/pom.xml b/overdue/pom.xml
index 92db3c5..27874c3 100644
--- a/overdue/pom.xml
+++ b/overdue/pom.xml
@@ -45,11 +45,6 @@
<scope>provided</scope>
</dependency>
<dependency>
- <groupId>com.jayway.awaitility</groupId>
- <artifactId>awaitility</artifactId>
- <scope>test</scope>
- </dependency>
- <dependency>
<groupId>com.samskivert</groupId>
<artifactId>jmustache</artifactId>
</dependency>
@@ -83,6 +78,11 @@
<scope>test</scope>
</dependency>
<dependency>
+ <groupId>org.awaitility</groupId>
+ <artifactId>awaitility</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
<groupId>org.jdbi</groupId>
<artifactId>jdbi</artifactId>
</dependency>
diff --git a/overdue/src/test/java/org/killbill/billing/overdue/applicator/TestOverdueStateApplicator.java b/overdue/src/test/java/org/killbill/billing/overdue/applicator/TestOverdueStateApplicator.java
index 42e70ce..37d2db8 100644
--- a/overdue/src/test/java/org/killbill/billing/overdue/applicator/TestOverdueStateApplicator.java
+++ b/overdue/src/test/java/org/killbill/billing/overdue/applicator/TestOverdueStateApplicator.java
@@ -36,7 +36,7 @@ import org.killbill.xmlloader.XMLLoader;
import org.killbill.billing.events.OverdueChangeInternalEvent;
import org.killbill.billing.junction.DefaultBlockingState;
-import static com.jayway.awaitility.Awaitility.await;
+import static org.awaitility.Awaitility.await;
import static java.util.concurrent.TimeUnit.SECONDS;
public class TestOverdueStateApplicator extends OverdueTestSuiteWithEmbeddedDB {
diff --git a/overdue/src/test/java/org/killbill/billing/overdue/notification/TestOverdueCheckNotifier.java b/overdue/src/test/java/org/killbill/billing/overdue/notification/TestOverdueCheckNotifier.java
index 9fee915..162d756 100644
--- a/overdue/src/test/java/org/killbill/billing/overdue/notification/TestOverdueCheckNotifier.java
+++ b/overdue/src/test/java/org/killbill/billing/overdue/notification/TestOverdueCheckNotifier.java
@@ -34,7 +34,7 @@ import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
-import static com.jayway.awaitility.Awaitility.await;
+import static org.awaitility.Awaitility.await;
import static java.util.concurrent.TimeUnit.SECONDS;
public class TestOverdueCheckNotifier extends OverdueTestSuiteWithEmbeddedDB {
payment/pom.xml 10(+5 -5)
diff --git a/payment/pom.xml b/payment/pom.xml
index 739ce93..7874638 100644
--- a/payment/pom.xml
+++ b/payment/pom.xml
@@ -60,11 +60,6 @@
<scope>provided</scope>
</dependency>
<dependency>
- <groupId>com.jayway.awaitility</groupId>
- <artifactId>awaitility</artifactId>
- <scope>test</scope>
- </dependency>
- <dependency>
<groupId>com.ning</groupId>
<artifactId>compress-lzf</artifactId>
</dependency>
@@ -101,6 +96,11 @@
<artifactId>shiro-core</artifactId>
</dependency>
<dependency>
+ <groupId>org.awaitility</groupId>
+ <artifactId>awaitility</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
<groupId>org.jdbi</groupId>
<artifactId>jdbi</artifactId>
</dependency>
diff --git a/payment/src/main/java/org/killbill/billing/payment/config/MultiTenantPaymentConfig.java b/payment/src/main/java/org/killbill/billing/payment/config/MultiTenantPaymentConfig.java
index 6e56c52..4998de8 100644
--- a/payment/src/main/java/org/killbill/billing/payment/config/MultiTenantPaymentConfig.java
+++ b/payment/src/main/java/org/killbill/billing/payment/config/MultiTenantPaymentConfig.java
@@ -84,17 +84,31 @@ public class MultiTenantPaymentConfig extends MultiTenantConfigBase implements P
}
@Override
- public List<TimeSpan> getIncompleteTransactionsRetries() {
- return staticConfig.getIncompleteTransactionsRetries();
+ public List<TimeSpan> getUnknownTransactionsRetries() {
+ return staticConfig.getUnknownTransactionsRetries();
}
@Override
- public List<TimeSpan> getIncompleteTransactionsRetries(@Param("dummy") final InternalTenantContext tenantContext) {
- final String result = getStringTenantConfig("getIncompleteTransactionsRetries", tenantContext);
+ public List<TimeSpan> getUnknownTransactionsRetries(@Param("dummy") final InternalTenantContext tenantContext) {
+ final String result = getStringTenantConfig("getUnknownTransactionsRetries", tenantContext);
if (result != null) {
- return convertToListTimeSpan(result, "getIncompleteTransactionsRetries");
+ return convertToListTimeSpan(result, "getUnknownTransactionsRetries");
}
- return getIncompleteTransactionsRetries();
+ return getUnknownTransactionsRetries();
+ }
+
+ @Override
+ public List<TimeSpan> getPendingTransactionsRetries() {
+ return staticConfig.getPendingTransactionsRetries();
+ }
+
+ @Override
+ public List<TimeSpan> getPendingTransactionsRetries(@Param("dummy") final InternalTenantContext tenantContext) {
+ final String result = getStringTenantConfig("getPendingTransactionsRetries", tenantContext);
+ if (result != null) {
+ return convertToListTimeSpan(result, "getPendingTransactionsRetries");
+ }
+ return getPendingTransactionsRetries();
}
@Override
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/janitor/IncompletePaymentTransactionTask.java b/payment/src/main/java/org/killbill/billing/payment/core/janitor/IncompletePaymentTransactionTask.java
index b49b5f7..88fd895 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/janitor/IncompletePaymentTransactionTask.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/janitor/IncompletePaymentTransactionTask.java
@@ -104,11 +104,16 @@ public class IncompletePaymentTransactionTask extends CompletionTaskBase<Payment
tryToProcessNotification(notificationKey, userToken, accountRecordId, tenantRecordId);
} catch (final LockFailedException e) {
log.warn("Error locking accountRecordId='{}', will attempt to retry later", accountRecordId, e);
- insertNewNotificationForUnresolvedTransactionIfNeeded(notificationKey.getUuidKey(), notificationKey.getAttemptNumber(), userToken, accountRecordId, tenantRecordId);
+
+ final InternalTenantContext internalTenantContext = internalCallContextFactory.createInternalTenantContext(tenantRecordId, accountRecordId);
+ final PaymentTransactionModelDao paymentTransaction = paymentDao.getPaymentTransaction(notificationKey.getUuidKey(), internalTenantContext);
+ if (TRANSACTION_STATUSES_TO_CONSIDER.contains(paymentTransaction.getTransactionStatus())) {
+ insertNewNotificationForUnresolvedTransactionIfNeeded(notificationKey.getUuidKey(), paymentTransaction.getTransactionStatus(), notificationKey.getAttemptNumber(), userToken, accountRecordId, tenantRecordId);
+ }
}
}
- public void tryToProcessNotification(final JanitorNotificationKey notificationKey, final UUID userToken, final Long accountRecordId, final long tenantRecordId) throws LockFailedException {
+ private void tryToProcessNotification(final JanitorNotificationKey notificationKey, final UUID userToken, final Long accountRecordId, final long tenantRecordId) throws LockFailedException {
final InternalTenantContext internalTenantContext = internalCallContextFactory.createInternalTenantContext(tenantRecordId, accountRecordId);
tryToDoJanitorOperationWithAccountLock(new JanitorIterationCallback() {
@Override
@@ -158,7 +163,7 @@ public class IncompletePaymentTransactionTask extends CompletionTaskBase<Payment
if (!TRANSACTION_STATUSES_TO_CONSIDER.contains(event.getStatus())) {
return;
}
- insertNewNotificationForUnresolvedTransactionIfNeeded(event.getPaymentTransactionId(), 0, event.getUserToken(), event.getSearchKey1(), event.getSearchKey2());
+ insertNewNotificationForUnresolvedTransactionIfNeeded(event.getPaymentTransactionId(), event.getStatus(), 0, event.getUserToken(), event.getSearchKey1(), event.getSearchKey2());
}
public boolean updatePaymentAndTransactionIfNeededWithAccountLock(final PaymentModelDao payment, final PaymentTransactionModelDao paymentTransaction, final PaymentTransactionInfoPlugin paymentTransactionInfoPlugin, final InternalTenantContext internalTenantContext) {
@@ -211,7 +216,7 @@ public class IncompletePaymentTransactionTask extends CompletionTaskBase<Payment
payment.getId(), paymentTransaction.getId(), paymentTransaction.getTransactionStatus(), transactionStatus);
}
// We can't get anything interesting from the plugin...
- insertNewNotificationForUnresolvedTransactionIfNeeded(paymentTransaction.getId(), attemptNumber, userToken, internalTenantContext.getAccountRecordId(), internalTenantContext.getTenantRecordId());
+ insertNewNotificationForUnresolvedTransactionIfNeeded(paymentTransaction.getId(), transactionStatus, attemptNumber, userToken, internalTenantContext.getAccountRecordId(), internalTenantContext.getTenantRecordId());
return false;
}
@@ -219,7 +224,7 @@ public class IncompletePaymentTransactionTask extends CompletionTaskBase<Payment
if (transactionStatus == paymentTransaction.getTransactionStatus()) {
log.debug("Janitor IncompletePaymentTransactionTask repairing payment {}, transaction {}, transitioning transactionStatus from {} -> {}",
payment.getId(), paymentTransaction.getId(), paymentTransaction.getTransactionStatus(), transactionStatus);
- insertNewNotificationForUnresolvedTransactionIfNeeded(paymentTransaction.getId(), attemptNumber, userToken, internalTenantContext.getAccountRecordId(), internalTenantContext.getTenantRecordId());
+ insertNewNotificationForUnresolvedTransactionIfNeeded(paymentTransaction.getId(), transactionStatus, attemptNumber, userToken, internalTenantContext.getAccountRecordId(), internalTenantContext.getTenantRecordId());
return false;
}
@@ -266,9 +271,18 @@ public class IncompletePaymentTransactionTask extends CompletionTaskBase<Payment
}
@VisibleForTesting
- DateTime getNextNotificationTime(final Integer attemptNumber, final InternalTenantContext tenantContext) {
+ DateTime getNextNotificationTime(final TransactionStatus transactionStatus, final Integer attemptNumber, final InternalTenantContext tenantContext) {
+
+ final List<TimeSpan> retries;
+ if (TransactionStatus.UNKNOWN.equals(transactionStatus)) {
+ retries = paymentConfig.getUnknownTransactionsRetries(tenantContext);
+ } else if (TransactionStatus.PENDING.equals(transactionStatus)) {
+ retries = paymentConfig.getPendingTransactionsRetries(tenantContext);
+ } else {
+ retries = ImmutableList.of();
+ log.warn("Unexpected transactionStatus='{}' from janitor, ignore...", transactionStatus);
+ }
- final List<TimeSpan> retries = paymentConfig.getIncompleteTransactionsRetries(tenantContext);
if (attemptNumber > retries.size()) {
return null;
}
@@ -276,7 +290,7 @@ public class IncompletePaymentTransactionTask extends CompletionTaskBase<Payment
return clock.getUTCNow().plusMillis((int) nextDelay.getMillis());
}
- private void insertNewNotificationForUnresolvedTransactionIfNeeded(final UUID paymentTransactionId, @Nullable final Integer attemptNumber, @Nullable final UUID userToken, final Long accountRecordId, final Long tenantRecordId) {
+ private void insertNewNotificationForUnresolvedTransactionIfNeeded(final UUID paymentTransactionId, final TransactionStatus transactionStatus, @Nullable final Integer attemptNumber, @Nullable final UUID userToken, final Long accountRecordId, final Long tenantRecordId) {
// When we come from a GET path, we don't want to insert a new notification
if (attemptNumber == null) {
return;
@@ -287,7 +301,7 @@ public class IncompletePaymentTransactionTask extends CompletionTaskBase<Payment
// Increment value before we insert
final Integer newAttemptNumber = attemptNumber.intValue() + 1;
final NotificationEvent key = new JanitorNotificationKey(paymentTransactionId, IncompletePaymentTransactionTask.class.toString(), newAttemptNumber);
- final DateTime notificationTime = getNextNotificationTime(newAttemptNumber, tenantContext);
+ final DateTime notificationTime = getNextNotificationTime(transactionStatus, newAttemptNumber, tenantContext);
// Will be null in the GET path or when we run out opf attempts..
if (notificationTime != null) {
try {
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/OperationControlCallback.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/OperationControlCallback.java
index ce634c0..a562e32 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/OperationControlCallback.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/OperationControlCallback.java
@@ -159,7 +159,7 @@ public abstract class OperationControlCallback extends OperationCallbackBase<Pay
if (e.getCause() instanceof OperationException) {
return (OperationException) e.getCause();
}
- logger.warn("Operation failed for accountId='{}' accountExternalKey='{}' error='{}'", paymentStateContext.getAccount().getExternalKey(), e.getMessage());
+ logger.warn("Operation failed for accountId='{}' accountExternalKey='{}' error='{}'", paymentStateContext.getAccount().getId(), paymentStateContext.getAccount().getExternalKey(), e.getMessage());
return new OperationException(e, getOperationResultOnException(paymentStateContext));
}
diff --git a/payment/src/test/java/org/killbill/billing/payment/core/janitor/TestIncompletePaymentTransactionTask.java b/payment/src/test/java/org/killbill/billing/payment/core/janitor/TestIncompletePaymentTransactionTask.java
index a3d260c..c8ffe3b 100644
--- a/payment/src/test/java/org/killbill/billing/payment/core/janitor/TestIncompletePaymentTransactionTask.java
+++ b/payment/src/test/java/org/killbill/billing/payment/core/janitor/TestIncompletePaymentTransactionTask.java
@@ -19,6 +19,7 @@ package org.killbill.billing.payment.core.janitor;
import org.joda.time.DateTime;
import org.killbill.billing.payment.PaymentTestSuiteNoDB;
+import org.killbill.billing.payment.api.TransactionStatus;
import org.testng.annotations.Test;
import com.google.inject.Inject;
@@ -36,31 +37,27 @@ public class TestIncompletePaymentTransactionTask extends PaymentTestSuiteNoDB {
public void testGetNextNotificationTime() {
final DateTime initTime = clock.getUTCNow();
- // Based on config "15s,1m,3m,1h,1d,1d,1d,1d,1d"
- for (int i = 1; i < 10; i++) {
- final DateTime nextTime = incompletePaymentTransactionTask.getNextNotificationTime(i, internalCallContext);
+ // Based on config "5m,1h,1d,1d,1d,1d,1d"
+ for (int i = 1; i < 8; i++) {
+ final DateTime nextTime = incompletePaymentTransactionTask.getNextNotificationTime(TransactionStatus.UNKNOWN, i, internalCallContext);
assertNotNull(nextTime);
assertTrue(nextTime.compareTo(initTime) > 0);
if (i == 0) {
- assertTrue(nextTime.compareTo(initTime.plusSeconds(3).plusSeconds(1)) < 0);
+ assertTrue(nextTime.compareTo(initTime.plusMinutes(5).plusSeconds(1)) < 0);
} else if (i == 1) {
- assertTrue(nextTime.compareTo(initTime.plusMinutes(1).plusSeconds(1)) < 0);
+ assertTrue(nextTime.compareTo(initTime.plusHours(1).plusSeconds(1)) < 0);
} else if (i == 2) {
- assertTrue(nextTime.compareTo(initTime.plusMinutes(3).plusSeconds(1)) < 0);
+ assertTrue(nextTime.compareTo(initTime.plusDays(1).plusSeconds(1)) < 0);
} else if (i == 3) {
- assertTrue(nextTime.compareTo(initTime.plusHours(1).plusSeconds(1)) < 0);
+ assertTrue(nextTime.compareTo(initTime.plusDays(1).plusSeconds(1)) < 0);
} else if (i == 4) {
assertTrue(nextTime.compareTo(initTime.plusDays(1).plusSeconds(1)) < 0);
} else if (i == 5) {
assertTrue(nextTime.compareTo(initTime.plusDays(1).plusSeconds(1)) < 0);
} else if (i == 6) {
assertTrue(nextTime.compareTo(initTime.plusDays(1).plusSeconds(1)) < 0);
- } else if (i == 7) {
- assertTrue(nextTime.compareTo(initTime.plusDays(1).plusSeconds(1)) < 0);
- } else if (i == 8) {
- assertTrue(nextTime.compareTo(initTime.plusDays(1).plusSeconds(1)) < 0);
}
}
- assertNull(incompletePaymentTransactionTask.getNextNotificationTime(10, internalCallContext));
+ assertNull(incompletePaymentTransactionTask.getNextNotificationTime(TransactionStatus.UNKNOWN, 8, internalCallContext));
}
}
diff --git a/payment/src/test/java/org/killbill/billing/payment/core/janitor/TestIncompletePaymentTransactionTaskWithDB.java b/payment/src/test/java/org/killbill/billing/payment/core/janitor/TestIncompletePaymentTransactionTaskWithDB.java
index 191ed5c..8dc40d7 100644
--- a/payment/src/test/java/org/killbill/billing/payment/core/janitor/TestIncompletePaymentTransactionTaskWithDB.java
+++ b/payment/src/test/java/org/killbill/billing/payment/core/janitor/TestIncompletePaymentTransactionTaskWithDB.java
@@ -95,8 +95,8 @@ public class TestIncompletePaymentTransactionTaskWithDB extends PaymentTestSuite
Assert.assertEquals(event.getUuidKey(), transactionId);
Assert.assertEquals((int) event.getAttemptNumber(), 2);
- // Based on config "15s,1m,3m,1h,1d,1d,1d,1d,1d"
- Assert.assertTrue(notificationEventWithMetadata.getEffectiveDate().compareTo(clock.getUTCNow().plusMinutes(1).plusSeconds(1)) < 0);
+ // Based on config "1h, 1d"
+ Assert.assertTrue(notificationEventWithMetadata.getEffectiveDate().compareTo(clock.getUTCNow().plusDays(1).plusSeconds(5)) < 0);
} catch (final LockFailedException e) {
Assert.fail();
} finally {
diff --git a/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPluginOperation.java b/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPluginOperation.java
index 294a5f8..e55fa52 100644
--- a/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPluginOperation.java
+++ b/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPluginOperation.java
@@ -54,7 +54,7 @@ import org.testng.annotations.Test;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
-import com.jayway.awaitility.Awaitility;
+import org.awaitility.Awaitility;
public class TestPluginOperation extends PaymentTestSuiteNoDB {
diff --git a/payment/src/test/java/org/killbill/billing/payment/core/TestPaymentProcessor.java b/payment/src/test/java/org/killbill/billing/payment/core/TestPaymentProcessor.java
index 3e026fa..54d227b 100644
--- a/payment/src/test/java/org/killbill/billing/payment/core/TestPaymentProcessor.java
+++ b/payment/src/test/java/org/killbill/billing/payment/core/TestPaymentProcessor.java
@@ -47,7 +47,7 @@ import org.testng.annotations.Test;
import com.google.common.collect.ImmutableList;
import com.google.common.eventbus.Subscribe;
-import com.jayway.awaitility.Awaitility;
+import org.awaitility.Awaitility;
import static java.math.BigDecimal.ZERO;
diff --git a/payment/src/test/java/org/killbill/billing/payment/TestJanitor.java b/payment/src/test/java/org/killbill/billing/payment/TestJanitor.java
index f6cd52a..73d4bbd 100644
--- a/payment/src/test/java/org/killbill/billing/payment/TestJanitor.java
+++ b/payment/src/test/java/org/killbill/billing/payment/TestJanitor.java
@@ -63,7 +63,6 @@ import org.skife.config.TimeSpan;
import org.skife.jdbi.v2.Handle;
import org.skife.jdbi.v2.tweak.HandleCallback;
import org.testng.Assert;
-import org.testng.annotations.AfterClass;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
@@ -74,7 +73,7 @@ import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.inject.Inject;
-import static com.jayway.awaitility.Awaitility.await;
+import static org.awaitility.Awaitility.await;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.fail;
@@ -403,9 +402,9 @@ public class TestJanitor extends PaymentTestSuiteWithEmbeddedDB {
"loup", "chat", internalCallContext);
testListener.assertListenerStatus();
- // Move clock for notification to be processed
+ // Move clock for notification to be processed ((default config is set for one hour)
testListener.pushExpectedEvent(NextEvent.PAYMENT);
- clock.addDeltaFromReality(5 * 60 * 1000);
+ clock.addDeltaFromReality(1000 * (3600 + 1));
assertNotificationsCompleted(internalCallContext, 5);
testListener.assertListenerStatus();
@@ -442,8 +441,8 @@ public class TestJanitor extends PaymentTestSuiteWithEmbeddedDB {
"loup", "chat", internalCallContext);
testListener.assertListenerStatus();
- // 15s,1m,3m,1h,1d,1d,1d,1d,1d
- for (final TimeSpan cur : paymentConfig.getIncompleteTransactionsRetries(internalCallContext)) {
+ // 1h, 1d
+ for (final TimeSpan cur : paymentConfig.getPendingTransactionsRetries(internalCallContext)) {
// Verify there is a notification to retry updating the value
assertEquals(getPendingNotificationCnt(internalCallContext), 1);
diff --git a/payment/src/test/java/org/killbill/billing/payment/TestRetryService.java b/payment/src/test/java/org/killbill/billing/payment/TestRetryService.java
index 676c940..fcf1dc6 100644
--- a/payment/src/test/java/org/killbill/billing/payment/TestRetryService.java
+++ b/payment/src/test/java/org/killbill/billing/payment/TestRetryService.java
@@ -25,8 +25,8 @@ import java.util.Comparator;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.Callable;
-import java.util.concurrent.TimeoutException;
+import org.awaitility.Duration;
import org.joda.time.LocalDate;
import org.killbill.billing.account.api.Account;
import org.killbill.billing.catalog.api.Currency;
@@ -45,15 +45,12 @@ import org.testng.annotations.Test;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
-import com.jayway.awaitility.Awaitility;
-import com.jayway.awaitility.Duration;
-import static com.jayway.awaitility.Awaitility.await;
-import static com.jayway.awaitility.Awaitility.setDefaultPollInterval;
import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.awaitility.Awaitility.await;
+import static org.awaitility.Awaitility.setDefaultPollInterval;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;
-import static org.testng.Assert.fail;
public class TestRetryService extends PaymentTestSuiteNoDB {
@@ -67,7 +64,6 @@ public class TestRetryService extends PaymentTestSuiteNoDB {
super.beforeMethod();
setDefaultPollInterval(Duration.ONE_HUNDRED_MILLISECONDS);
- Awaitility.setDefaultPollDelay(Duration.SAME_AS_POLL_INTERVAL);
mockPaymentProviderPlugin = (MockPaymentProviderPlugin) registry.getServiceForName(MockPaymentProviderPlugin.PLUGIN_NAME);
mockPaymentProviderPlugin.clear();
@@ -172,25 +168,21 @@ public class TestRetryService extends PaymentTestSuiteNoDB {
moveClockForFailureType(FailureType.PAYMENT_FAILURE, 0);
- try {
- await().atMost(TIMEOUT, SECONDS).until(new Callable<Boolean>() {
- @Override
- public Boolean call() throws Exception {
- final List<PaymentAttemptModelDao> attempts = paymentDao.getPaymentAttempts(paymentExternalKey, internalCallContext);
- final List<PaymentAttemptModelDao> filteredAttempts = ImmutableList.copyOf(Iterables.filter(attempts, new Predicate<PaymentAttemptModelDao>() {
- @Override
- public boolean apply(final PaymentAttemptModelDao input) {
- return input.getStateName().equals("SUCCESS") ||
- input.getStateName().equals("RETRIED") ||
- input.getStateName().equals("ABORTED");
- }
- }));
- return filteredAttempts.size() == 2;
- }
- });
- } catch (final TimeoutException e) {
- fail("Timeout ");
- }
+ await().atMost(TIMEOUT, SECONDS).until(new Callable<Boolean>() {
+ @Override
+ public Boolean call() throws Exception {
+ final List<PaymentAttemptModelDao> attempts = paymentDao.getPaymentAttempts(paymentExternalKey, internalCallContext);
+ final List<PaymentAttemptModelDao> filteredAttempts = ImmutableList.copyOf(Iterables.filter(attempts, new Predicate<PaymentAttemptModelDao>() {
+ @Override
+ public boolean apply(final PaymentAttemptModelDao input) {
+ return input.getStateName().equals("SUCCESS") ||
+ input.getStateName().equals("RETRIED") ||
+ input.getStateName().equals("ABORTED");
+ }
+ }));
+ return filteredAttempts.size() == 2;
+ }
+ });
attempts = paymentDao.getPaymentAttempts(payment.getExternalKey(), internalCallContext);
final int expectedAttempts = 2;
@@ -258,25 +250,21 @@ public class TestRetryService extends PaymentTestSuiteNoDB {
moveClockForFailureType(FailureType.PAYMENT_FAILURE, curFailure);
final int curFailureCondition = curFailure;
- try {
- await().atMost(TIMEOUT, SECONDS).until(new Callable<Boolean>() {
- @Override
- public Boolean call() throws Exception {
- final List<PaymentAttemptModelDao> attempts = paymentDao.getPaymentAttempts(paymentExternalKey, internalCallContext);
- final List<PaymentAttemptModelDao> filteredAttempts = ImmutableList.copyOf(Iterables.filter(attempts, new Predicate<PaymentAttemptModelDao>() {
- @Override
- public boolean apply(final PaymentAttemptModelDao input) {
- return input.getStateName().equals("SUCCESS") ||
- input.getStateName().equals("RETRIED") ||
- input.getStateName().equals("ABORTED");
- }
- }));
- return filteredAttempts.size() == curFailureCondition + 2;
- }
- });
- } catch (final TimeoutException e) {
- fail("Timeout curFailure = " + curFailureCondition);
- }
+ await().atMost(TIMEOUT, SECONDS).until(new Callable<Boolean>() {
+ @Override
+ public Boolean call() throws Exception {
+ final List<PaymentAttemptModelDao> attempts = paymentDao.getPaymentAttempts(paymentExternalKey, internalCallContext);
+ final List<PaymentAttemptModelDao> filteredAttempts = ImmutableList.copyOf(Iterables.filter(attempts, new Predicate<PaymentAttemptModelDao>() {
+ @Override
+ public boolean apply(final PaymentAttemptModelDao input) {
+ return input.getStateName().equals("SUCCESS") ||
+ input.getStateName().equals("RETRIED") ||
+ input.getStateName().equals("ABORTED");
+ }
+ }));
+ return filteredAttempts.size() == curFailureCondition + 2;
+ }
+ });
}
attempts = paymentDao.getPaymentAttempts(payment.getExternalKey(), internalCallContext);
final int expectedAttempts = maxTries + 1;
@@ -343,25 +331,21 @@ public class TestRetryService extends PaymentTestSuiteNoDB {
moveClockForFailureType(FailureType.PAYMENT_FAILURE, curFailure);
final int curFailureCondition = curFailure;
- try {
- await().atMost(TIMEOUT, SECONDS).until(new Callable<Boolean>() {
- @Override
- public Boolean call() throws Exception {
- final List<PaymentAttemptModelDao> attempts = paymentDao.getPaymentAttempts(paymentExternalKey, internalCallContext);
- final List<PaymentAttemptModelDao> filteredAttempts = ImmutableList.copyOf(Iterables.filter(attempts, new Predicate<PaymentAttemptModelDao>() {
- @Override
- public boolean apply(final PaymentAttemptModelDao input) {
- return input.getStateName().equals("SUCCESS") ||
- input.getStateName().equals("RETRIED") ||
- input.getStateName().equals("ABORTED");
- }
- }));
- return filteredAttempts.size() == curFailureCondition + 2;
- }
- });
- } catch (final TimeoutException e) {
- fail("Timeout curFailure = " + curFailureCondition);
- }
+ await().atMost(TIMEOUT, SECONDS).until(new Callable<Boolean>() {
+ @Override
+ public Boolean call() throws Exception {
+ final List<PaymentAttemptModelDao> attempts = paymentDao.getPaymentAttempts(paymentExternalKey, internalCallContext);
+ final List<PaymentAttemptModelDao> filteredAttempts = ImmutableList.copyOf(Iterables.filter(attempts, new Predicate<PaymentAttemptModelDao>() {
+ @Override
+ public boolean apply(final PaymentAttemptModelDao input) {
+ return input.getStateName().equals("SUCCESS") ||
+ input.getStateName().equals("RETRIED") ||
+ input.getStateName().equals("ABORTED");
+ }
+ }));
+ return filteredAttempts.size() == curFailureCondition + 2;
+ }
+ });
}
attempts = paymentDao.getPaymentAttempts(payment.getExternalKey(), internalCallContext);
final int expectedAttempts = maxTries + 1;
profiles/killbill/pom.xml 10(+5 -5)
diff --git a/profiles/killbill/pom.xml b/profiles/killbill/pom.xml
index 440d3bc..834a4e1 100644
--- a/profiles/killbill/pom.xml
+++ b/profiles/killbill/pom.xml
@@ -73,11 +73,6 @@
<scope>compile</scope>
</dependency>
<dependency>
- <groupId>com.jayway.awaitility</groupId>
- <artifactId>awaitility</artifactId>
- <scope>test</scope>
- </dependency>
- <dependency>
<groupId>com.ning</groupId>
<artifactId>async-http-client</artifactId>
</dependency>
@@ -179,6 +174,11 @@
<artifactId>shiro-web</artifactId>
</dependency>
<dependency>
+ <groupId>org.awaitility</groupId>
+ <artifactId>awaitility</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
<groupId>org.codehaus.janino</groupId>
<artifactId>janino</artifactId>
<scope>runtime</scope>
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestBuildResponse.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestBuildResponse.java
index c7c622d..db8a13f 100644
--- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestBuildResponse.java
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestBuildResponse.java
@@ -29,8 +29,7 @@ import org.killbill.billing.server.log.ServerTestSuiteNoDB;
import org.killbill.billing.util.config.definition.JaxrsConfig;
import org.testng.annotations.Test;
-import com.sun.jersey.api.client.ClientResponse.Status;
-
+import static javax.ws.rs.core.Response.Status.CREATED;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertEquals;
@@ -50,7 +49,7 @@ public class TestBuildResponse extends ServerTestSuiteNoDB {
JaxrsUriBuilder uriBuilder = new JaxrsUriBuilder(jaxrsConfig);
Response response = uriBuilder.buildResponse(uriInfo, AccountResource.class, "getAccount", objectId, mockRequest(uriInfo));
- assertEquals(response.getStatus(), Status.CREATED.getStatusCode());
+ assertEquals(response.getStatus(), CREATED.getStatusCode());
assertEquals(response.getMetadata().get("Location").get(0), "/1.0/kb/accounts/" + objectId.toString());
}
@@ -67,7 +66,7 @@ public class TestBuildResponse extends ServerTestSuiteNoDB {
JaxrsUriBuilder uriBuilder = new JaxrsUriBuilder(jaxrsConfig);
Response response = uriBuilder.buildResponse(uriInfo, AccountResource.class, "getAccount", objectId, mockRequest(uriInfo));
- assertEquals(response.getStatus(), Status.CREATED.getStatusCode());
+ assertEquals(response.getStatus(), CREATED.getStatusCode());
assertEquals(response.getMetadata().get("Location").get(0), "/killbill/1.0/kb/accounts/" + objectId.toString());
}
@@ -85,7 +84,7 @@ public class TestBuildResponse extends ServerTestSuiteNoDB {
JaxrsUriBuilder uriBuilder = new JaxrsUriBuilder(jaxrsConfig);
Response response = uriBuilder.buildResponse(uriInfo, AccountResource.class, "getAccount", objectId, mockRequest(uriInfo));
- assertEquals(response.getStatus(), Status.CREATED.getStatusCode());
+ assertEquals(response.getStatus(), CREATED.getStatusCode());
assertEquals(response.getMetadata().get("Location").get(0).toString(), uri.toString() + "/1.0/kb/accounts/" + objectId.toString());
}
@@ -103,7 +102,7 @@ public class TestBuildResponse extends ServerTestSuiteNoDB {
JaxrsUriBuilder uriBuilder = new JaxrsUriBuilder(jaxrsConfig);
Response response = uriBuilder.buildResponse(uriInfo, AccountResource.class, "getAccount", objectId, mockRequest(uriInfo));
- assertEquals(response.getStatus(), Status.CREATED.getStatusCode());
+ assertEquals(response.getStatus(), CREATED.getStatusCode());
assertEquals(response.getMetadata().get("Location").get(0).toString(), uri.toString() + "/1.0/kb/accounts/" + objectId.toString());
}
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPerTenantConfig.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPerTenantConfig.java
index ea4ab18..a8d572d 100644
--- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPerTenantConfig.java
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPerTenantConfig.java
@@ -36,8 +36,8 @@ import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import com.google.inject.Inject;
-import com.jayway.awaitility.Awaitility;
-import com.jayway.awaitility.Duration;
+import org.awaitility.Awaitility;
+import org.awaitility.Duration;
public class TestPerTenantConfig extends TestJaxrsBase {
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestTenantKV.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestTenantKV.java
index 5232201..fbff4b5 100644
--- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestTenantKV.java
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestTenantKV.java
@@ -43,8 +43,8 @@ import org.testng.annotations.Test;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.io.Resources;
-import com.jayway.awaitility.Awaitility;
-import com.jayway.awaitility.Duration;
+import org.awaitility.Awaitility;
+import org.awaitility.Duration;
public class TestTenantKV extends TestJaxrsBase {
subscription/pom.xml 10(+5 -5)
diff --git a/subscription/pom.xml b/subscription/pom.xml
index 740b692..e915aae 100644
--- a/subscription/pom.xml
+++ b/subscription/pom.xml
@@ -45,11 +45,6 @@
<scope>provided</scope>
</dependency>
<dependency>
- <groupId>com.jayway.awaitility</groupId>
- <artifactId>awaitility</artifactId>
- <scope>test</scope>
- </dependency>
- <dependency>
<groupId>io.airlift</groupId>
<artifactId>command</artifactId>
<scope>test</scope>
@@ -84,6 +79,11 @@
<scope>runtime</scope>
</dependency>
<dependency>
+ <groupId>org.awaitility</groupId>
+ <artifactId>awaitility</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
<groupId>org.jdbi</groupId>
<artifactId>jdbi</artifactId>
</dependency>
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/alignment/PlanAligner.java b/subscription/src/main/java/org/killbill/billing/subscription/alignment/PlanAligner.java
index 3b52dc5..6e66d0c 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/alignment/PlanAligner.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/alignment/PlanAligner.java
@@ -115,6 +115,7 @@ public class PlanAligner extends BaseAligner {
final DateTime effectiveDate,
final PhaseType newPlanInitialPhaseType,
final InternalTenantContext context) throws CatalogApiException, SubscriptionBaseApiException {
+
return getTimedPhaseOnChange(subscription, plan, effectiveDate, newPlanInitialPhaseType, WhichPhase.CURRENT, context);
}
@@ -221,10 +222,16 @@ public class PlanAligner extends BaseAligner {
final PhaseType newPlanInitialPhaseType,
final WhichPhase which,
final InternalTenantContext context) throws CatalogApiException, SubscriptionBaseApiException {
+ final SubscriptionBaseTransition pendingOrLastPlanTransition;
+ if (subscription.getState() == EntitlementState.PENDING) {
+ pendingOrLastPlanTransition = subscription.getPendingTransition();
+ } else {
+ pendingOrLastPlanTransition = subscription.getLastTransitionForCurrentPlan();
+ }
return getTimedPhaseOnChange(subscription.getAlignStartDate(),
subscription.getBundleStartDate(),
- subscription.getCurrentPhase(),
- subscription.getCurrentPlan(),
+ pendingOrLastPlanTransition.getNextPhase(),
+ pendingOrLastPlanTransition.getNextPlan(),
nextPlan,
effectiveDate,
// This method is only called while doing the change, hence we want to pass the change effective date
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/svcs/DefaultSubscriptionInternalApi.java b/subscription/src/main/java/org/killbill/billing/subscription/api/svcs/DefaultSubscriptionInternalApi.java
index d3bdedf..fb97c35 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/api/svcs/DefaultSubscriptionInternalApi.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/api/svcs/DefaultSubscriptionInternalApi.java
@@ -615,9 +615,10 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
// verify the number of subscriptions (of the same kind) allowed per bundle
final Catalog catalog = catalogService.getFullCatalog(true, true, context);
final DateTime now = clock.getUTCNow();
- final DateTime effectiveDate = (requestedDateWithMs != null) ? DefaultClock.truncateMs(requestedDateWithMs) : now;
+ final DateTime effectiveDate = (requestedDateWithMs != null) ? DefaultClock.truncateMs(requestedDateWithMs) : null;
+ final DateTime effectiveCatalogDate = effectiveDate != null? effectiveDate : now;
final PlanPhasePriceOverridesWithCallContext overridesWithContext = new DefaultPlanPhasePriceOverridesWithCallContext(overrides, callContext);
- final Plan plan = catalog.createOrFindPlan(spec, overridesWithContext, effectiveDate);
+ final Plan plan = catalog.createOrFindPlan(spec, overridesWithContext, effectiveCatalogDate);
if (ProductCategory.ADD_ON.toString().equalsIgnoreCase(plan.getProduct().getCategory().toString())) {
if (plan.getPlansAllowedInBundle() != -1
&& plan.getPlansAllowedInBundle() > 0
@@ -627,7 +628,7 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
throw new SubscriptionBaseApiException(ErrorCode.SUB_CHANGE_AO_MAX_PLAN_ALLOWED_BY_BUNDLE, plan.getName());
}
}
- return apiService.dryRunChangePlan((DefaultSubscriptionBase) subscription, spec, requestedDateWithMs, requestedPolicy, tenantContext);
+ return apiService.dryRunChangePlan((DefaultSubscriptionBase) subscription, spec, effectiveDate, requestedPolicy, tenantContext);
}
@Override
@@ -729,7 +730,8 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
case CHANGE:
final DefaultSubscriptionBase subscriptionForChange = (DefaultSubscriptionBase) dao.getSubscriptionFromId(dryRunArguments.getSubscriptionId(), context);
- DateTime changeEffectiveDate = dryRunArguments.getEffectiveDate() != null ? context.toUTCDateTime(dryRunArguments.getEffectiveDate()) : null;
+
+ DateTime changeEffectiveDate = getDryRunEffectiveDate(dryRunArguments.getEffectiveDate(), subscriptionForChange, context);
if (changeEffectiveDate == null) {
BillingActionPolicy policy = dryRunArguments.getBillingActionPolicy();
if (policy == null) {
@@ -744,7 +746,8 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
case STOP_BILLING:
final DefaultSubscriptionBase subscriptionForCancellation = (DefaultSubscriptionBase) dao.getSubscriptionFromId(dryRunArguments.getSubscriptionId(), context);
- DateTime cancelEffectiveDate = dryRunArguments.getEffectiveDate() != null ? context.toUTCDateTime(dryRunArguments.getEffectiveDate()) : null;
+
+ DateTime cancelEffectiveDate = getDryRunEffectiveDate(dryRunArguments.getEffectiveDate(), subscriptionForCancellation, context);
if (dryRunArguments.getEffectiveDate() == null) {
BillingActionPolicy policy = dryRunArguments.getBillingActionPolicy();
if (policy == null) {
@@ -771,6 +774,21 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
}
}
+ private DateTime getDryRunEffectiveDate(@Nullable final LocalDate inputDate, final DefaultSubscriptionBase subscription, final InternalTenantContext context) {
+ if (inputDate == null) {
+ return null;
+ }
+
+ // We first use context account reference time to get a candidate)
+ final DateTime tmp = context.toUTCDateTime(inputDate);
+ // If we realize that the candidate is on the same LocalDate boundary as the subscription startDate but a bit prior we correct it to avoid weird things down the line
+ if (inputDate.compareTo(context.toLocalDate(subscription.getStartDate())) == 0 && tmp.compareTo(subscription.getStartDate()) < 0) {
+ return subscription.getStartDate();
+ } else {
+ return tmp;
+ }
+ }
+
@Override
public Iterable<DateTime> getFutureNotificationsForAccount(final InternalCallContext internalCallContext) {
try {
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBase.java b/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBase.java
index 1dd9003..e4fd803 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBase.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBase.java
@@ -18,20 +18,12 @@
package org.killbill.billing.subscription.api.user;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.UUID;
-
-import javax.annotation.Nullable;
-
+import com.google.common.base.Preconditions;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.LocalDate;
-import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.catalog.api.BillingActionPolicy;
import org.killbill.billing.catalog.api.BillingAlignment;
@@ -68,9 +60,14 @@ import org.killbill.clock.Clock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import com.google.common.base.Preconditions;
-import com.google.common.base.Predicate;
-import com.google.common.collect.Iterables;
+import javax.annotation.Nullable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.UUID;
public class DefaultSubscriptionBase extends EntityBase implements SubscriptionBase {
@@ -166,9 +163,7 @@ public class DefaultSubscriptionBase extends EntityBase implements SubscriptionB
}
final SubscriptionBaseTransition pendingTransition = getPendingTransition();
- if (pendingTransition != null &&
- (pendingTransition.getTransitionType().equals(SubscriptionBaseTransitionType.CREATE) ||
- pendingTransition.getTransitionType().equals(SubscriptionBaseTransitionType.TRANSFER))) {
+ if (pendingTransition != null) {
return EntitlementState.PENDING;
}
throw new IllegalStateException("Should return a valid EntitlementState");
@@ -198,6 +193,15 @@ public class DefaultSubscriptionBase extends EntityBase implements SubscriptionB
: getPreviousTransition().getNextPhase();
}
+ public PlanPhase getCurrentOrPendingPhase() {
+ if (getState() == EntitlementState.PENDING) {
+ return getPendingTransition().getNextPhase();
+ } else {
+ return getCurrentPhase();
+ }
+ }
+
+
@Override
public Plan getCurrentPlan() {
return (getPreviousTransition() == null) ? null
@@ -293,6 +297,7 @@ public class DefaultSubscriptionBase extends EntityBase implements SubscriptionB
final SubscriptionBaseTransitionDataIterator it = new SubscriptionBaseTransitionDataIterator(
clock, transitions, Order.ASC_FROM_PAST,
Visibility.ALL, TimeLimit.FUTURE_ONLY);
+
return it.hasNext() ? it.next() : null;
}
@@ -301,6 +306,9 @@ public class DefaultSubscriptionBase extends EntityBase implements SubscriptionB
if (getState() == EntitlementState.CANCELLED) {
final SubscriptionBaseTransition data = getPreviousTransition();
return data.getPreviousPlan().getProduct();
+ } else if (getState() == EntitlementState.PENDING) {
+ final SubscriptionBaseTransition data = getPendingTransition();
+ return data.getNextPlan().getProduct();
} else {
final Plan currentPlan = getCurrentPlan();
// currentPlan can be null when playing with the clock (subscription created in the future)
@@ -313,6 +321,9 @@ public class DefaultSubscriptionBase extends EntityBase implements SubscriptionB
if (getState() == EntitlementState.CANCELLED) {
final SubscriptionBaseTransition data = getPreviousTransition();
return data.getPreviousPriceList();
+ } else if (getState() == EntitlementState.PENDING) {
+ final SubscriptionBaseTransition data = getPendingTransition();
+ return data.getNextPriceList();
} else {
return getCurrentPriceList();
}
@@ -323,6 +334,9 @@ public class DefaultSubscriptionBase extends EntityBase implements SubscriptionB
if (getState() == EntitlementState.CANCELLED) {
final SubscriptionBaseTransition data = getPreviousTransition();
return data.getPreviousPlan().getProduct().getCategory();
+ } else if (getState() == EntitlementState.PENDING) {
+ final SubscriptionBaseTransition data = getPendingTransition();
+ return data.getNextPlan().getProduct().getCategory();
} else {
final Plan currentPlan = getCurrentPlan();
// currentPlan can be null when playing with the clock (subscription created in the future)
@@ -335,6 +349,9 @@ public class DefaultSubscriptionBase extends EntityBase implements SubscriptionB
if (getState() == EntitlementState.CANCELLED) {
final SubscriptionBaseTransition data = getPreviousTransition();
return data.getPreviousPlan();
+ } else if (getState() == EntitlementState.PENDING) {
+ final SubscriptionBaseTransition data = getPendingTransition();
+ return data.getNextPlan();
} else {
return getCurrentPlan();
}
@@ -345,6 +362,9 @@ public class DefaultSubscriptionBase extends EntityBase implements SubscriptionB
if (getState() == EntitlementState.CANCELLED) {
final SubscriptionBaseTransition data = getPreviousTransition();
return data.getPreviousPhase();
+ } else if (getState() == EntitlementState.PENDING) {
+ final SubscriptionBaseTransition data = getPendingTransition();
+ return data.getNextPhase();
} else {
return getCurrentPhase();
}
@@ -355,6 +375,9 @@ public class DefaultSubscriptionBase extends EntityBase implements SubscriptionB
if (getState() == EntitlementState.CANCELLED) {
final SubscriptionBaseTransition data = getPreviousTransition();
return data.getPreviousPlan().getRecurringBillingPeriod();
+ } else if (getState() == EntitlementState.PENDING) {
+ final SubscriptionBaseTransition data = getPendingTransition();
+ return data.getNextPlan().getRecurringBillingPeriod();
} else {
final Plan currentPlan = getCurrentPlan();
// currentPlan can be null when playing with the clock (subscription created in the future)
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBaseApiService.java b/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBaseApiService.java
index f6dac4c..e784b0b 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBaseApiService.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBaseApiService.java
@@ -76,10 +76,8 @@ import org.killbill.clock.Clock;
import org.killbill.clock.DefaultClock;
import com.google.common.base.Preconditions;
-import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Iterables;
import com.google.inject.Inject;
public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiService {
@@ -181,7 +179,7 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
}
final DateTime now = clock.getUTCNow();
- final Plan currentPlan = subscription.getCurrentPlan();
+ final Plan currentPlan = subscription.getCurrentOrPendingPlan();
final PlanPhaseSpecifier planPhase = new PlanPhaseSpecifier(currentPlan.getName(), null);
try {
@@ -341,7 +339,7 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
public DateTime changePlan(final DefaultSubscriptionBase subscription, final PlanSpecifier spec, final List<PlanPhasePriceOverride> overrides, final CallContext context) throws SubscriptionBaseApiException {
final DateTime now = clock.getUTCNow();
- validateEntitlementState(subscription);
+ validateSubscriptionState(subscription, null);
final PlanChangeResult planChangeResult = getPlanChangeResult(subscription, spec, now, context);
final DateTime effectiveDate = dryRunChangePlan(subscription, spec, null, planChangeResult.getPolicy(), context);
@@ -361,7 +359,7 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
final DateTime requestedDateWithMs, final CallContext context) throws SubscriptionBaseApiException {
final DateTime effectiveDate = dryRunChangePlan(subscription, spec, requestedDateWithMs, null, context);
validateEffectiveDate(subscription, effectiveDate);
- validateEntitlementState(subscription);
+ validateSubscriptionState(subscription, requestedDateWithMs);
try {
doChangePlan(subscription, spec, overrides, effectiveDate, context);
@@ -374,9 +372,10 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
@Override
public DateTime changePlanWithPolicy(final DefaultSubscriptionBase subscription, final PlanSpecifier spec, final List<PlanPhasePriceOverride> overrides, final BillingActionPolicy policy, final CallContext context) throws SubscriptionBaseApiException {
- validateEntitlementState(subscription);
final DateTime effectiveDate = dryRunChangePlan(subscription, spec, null, policy, context);
+
+ validateSubscriptionState(subscription, effectiveDate);
try {
doChangePlan(subscription, spec, overrides, effectiveDate, context);
} catch (final CatalogApiException e) {
@@ -391,10 +390,10 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
final PlanChangeResult planChangeResult;
try {
final InternalTenantContext internalCallContext = createTenantContextFromBundleId(subscription.getBundleId(), context);
- final Plan currentPlan = subscription.getCurrentPlan();
+ final Plan currentPlan = subscription.getCurrentOrPendingPlan();
final PlanPhaseSpecifier fromPlanPhase = new PlanPhaseSpecifier(currentPlan.getName(),
- subscription.getCurrentPhase().getPhaseType());
+ subscription.getCurrentOrPendingPhase().getPhaseType());
planChangeResult = catalogService.getFullCatalog(true, true, internalCallContext).planChange(fromPlanPhase, toPlanPhase, effectiveDate);
} catch (final CatalogApiException e) {
throw new SubscriptionBaseApiException(e);
@@ -495,8 +494,11 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
final Collection<SubscriptionBaseEvent> addOnCancelEvents,
final PhaseType initialPhaseType,
final InternalTenantContext internalTenantContext) throws CatalogApiException, SubscriptionBaseApiException {
+
final TimedPhase currentTimedPhase = planAligner.getCurrentTimedPhaseOnChange(subscription, newPlan, effectiveDate, initialPhaseType, internalTenantContext);
+ validateSubscriptionState(subscription, effectiveDate);
+
final SubscriptionBaseEvent changeEvent = new ApiEventChange(new ApiEventBuilder()
.setSubscriptionId(subscription.getId())
.setEventPlan(newPlan.getName())
@@ -519,7 +521,7 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
}
if (subscription.getCategory() == ProductCategory.BASE && addCancellationAddOnForEventsIfRequired) {
- final Product currentBaseProduct = changeEvent.getEffectiveDate().compareTo(clock.getUTCNow()) <= 0 ? newPlan.getProduct() : subscription.getCurrentPlan().getProduct();
+ final Product currentBaseProduct = changeEvent.getEffectiveDate().compareTo(clock.getUTCNow()) <= 0 ? newPlan.getProduct() : subscription.getCurrentOrPendingPlan().getProduct();
addOnSubscriptionsToBeCancelled.addAll(addCancellationAddOnForEventsIfRequired(addOnCancelEvents, currentBaseProduct, subscription.getBundleId(), effectiveDate, internalTenantContext));
}
return changeEvents;
@@ -614,9 +616,9 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
}
}
- private void validateEntitlementState(final DefaultSubscriptionBase subscription) throws SubscriptionBaseApiException {
+ private void validateSubscriptionState(final DefaultSubscriptionBase subscription, @Nullable final DateTime effectiveDate) throws SubscriptionBaseApiException {
final EntitlementState currentState = subscription.getState();
- if (currentState != EntitlementState.ACTIVE) {
+ if (effectiveDate != null && effectiveDate.compareTo(subscription.getStartDate()) < 0) {
throw new SubscriptionBaseApiException(ErrorCode.SUB_CHANGE_NON_ACTIVE, subscription.getId(), currentState);
}
if (subscription.isSubscriptionFutureCancelled()) {
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java
index 332d497..5fe34c7 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java
@@ -33,6 +33,7 @@ import java.util.UUID;
import javax.annotation.Nullable;
import javax.inject.Inject;
+import com.google.common.base.Preconditions;
import org.joda.time.DateTime;
import org.killbill.billing.ErrorCode;
import org.killbill.billing.callcontext.InternalCallContext;
@@ -75,6 +76,7 @@ import org.killbill.billing.subscription.events.user.ApiEvent;
import org.killbill.billing.subscription.events.user.ApiEventBuilder;
import org.killbill.billing.subscription.events.user.ApiEventCancel;
import org.killbill.billing.subscription.events.user.ApiEventChange;
+import org.killbill.billing.subscription.events.user.ApiEventCreate;
import org.killbill.billing.subscription.events.user.ApiEventType;
import org.killbill.billing.subscription.exceptions.SubscriptionBaseError;
import org.killbill.billing.util.cache.CacheControllerDispatcher;
@@ -454,7 +456,7 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<List<SubscriptionBaseEvent>>() {
@Override
public List<SubscriptionBaseEvent> inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
- final List<SubscriptionEventModelDao> models = entitySqlDaoWrapperFactory.become(SubscriptionEventSqlDao.class).getEventsForSubscription(subscriptionId.toString(), context);
+ final List<SubscriptionEventModelDao> models = entitySqlDaoWrapperFactory.become(SubscriptionEventSqlDao.class).getActiveEventsForSubscription(subscriptionId.toString(), context);
return filterSubscriptionBaseEvents(models);
}
});
@@ -641,17 +643,62 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
});
}
+
@Override
- public void changePlan(final DefaultSubscriptionBase subscription, final List<SubscriptionBaseEvent> changeEvents, final List<DefaultSubscriptionBase> subscriptionsToBeCancelled, final List<SubscriptionBaseEvent> cancelEvents, final InternalCallContext context) {
+ public void changePlan(final DefaultSubscriptionBase subscription, final List<SubscriptionBaseEvent> originalInputChangeEvents, final List<DefaultSubscriptionBase> subscriptionsToBeCancelled, final List<SubscriptionBaseEvent> cancelEvents, final InternalCallContext context) {
+
+ // First event is expected to be the subscription CHANGE event
+ final SubscriptionBaseEvent inputChangeEvent = originalInputChangeEvents.get(0);
+ Preconditions.checkState(inputChangeEvent.getType() == EventType.API_USER &&
+ ((ApiEvent) inputChangeEvent).getApiEventType() == ApiEventType.CHANGE);
+ Preconditions.checkState(inputChangeEvent.getSubscriptionId().equals(subscription.getId()));
+
transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<Void>() {
@Override
public Void inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
+
final SubscriptionEventSqlDao transactional = entitySqlDaoWrapperFactory.become(SubscriptionEventSqlDao.class);
- final UUID subscriptionId = subscription.getId();
+ final List<SubscriptionEventModelDao> activeSubscriptionEvents = entitySqlDaoWrapperFactory.become(SubscriptionEventSqlDao.class).getActiveEventsForSubscription(subscription.getId().toString(), context);
+
+ // First event is CREATE/TRANSFER event
+ final SubscriptionEventModelDao firstSubscriptionEvent = activeSubscriptionEvents.get(0);
+ final Iterable<SubscriptionEventModelDao> activePresentOrFutureSubscriptionEvents = Iterables.filter(activeSubscriptionEvents, new Predicate<SubscriptionEventModelDao>() {
+ @Override
+ public boolean apply(SubscriptionEventModelDao input) {
+ return input.getEffectiveDate().compareTo(inputChangeEvent.getEffectiveDate()) >= 0;
+ }
+ });
+
+ // We do a little magic here in case the CHANGE coincides exactly with the CREATE event to invalidate original CREATE event and
+ // change the input CHANGE event into a CREATE event.
+ final boolean isChangePlanOnStartDate = firstSubscriptionEvent.getEffectiveDate().compareTo(inputChangeEvent.getEffectiveDate()) == 0;
+
+ final List<SubscriptionBaseEvent> inputChangeEvents;
+ if (isChangePlanOnStartDate) {
+
+ // Rebuild input event list with first the CREATE event and all original input events except for inputChangeEvent
+ inputChangeEvents = new ArrayList<SubscriptionBaseEvent>();
+ final SubscriptionBaseEvent newCreateEvent = new ApiEventBuilder((ApiEventChange) inputChangeEvent)
+ .setApiEventType(firstSubscriptionEvent.getUserType())
+ .build();
+
+ originalInputChangeEvents.remove(0);
+ inputChangeEvents.add(newCreateEvent);
+ inputChangeEvents.addAll(originalInputChangeEvents);
+
+ // Deactivate original CREATE event
+ unactivateEventFromTransaction(firstSubscriptionEvent, entitySqlDaoWrapperFactory, context);
+
+ } else {
+ inputChangeEvents = originalInputChangeEvents;
+ }
+
+
+ cancelFutureEventsFromTransaction(activePresentOrFutureSubscriptionEvents, entitySqlDaoWrapperFactory, false, context);
+
- cancelFutureEventsFromTransaction(subscriptionId, changeEvents.get(0).getEffectiveDate(), entitySqlDaoWrapperFactory, false, context);
- for (final SubscriptionBaseEvent cur : changeEvents) {
+ for (final SubscriptionBaseEvent cur : inputChangeEvents) {
createAndRefresh(transactional, new SubscriptionEventModelDao(cur), context);
final boolean isBusEvent = cur.getEffectiveDate().compareTo(clock.getUTCNow()) <= 0 && (cur.getType() == EventType.API_USER);
@@ -659,7 +706,7 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
}
// Notify the Bus of the latest requested change
- final SubscriptionBaseEvent finalEvent = changeEvents.get(changeEvents.size() - 1);
+ final SubscriptionBaseEvent finalEvent = inputChangeEvents.get(inputChangeEvents.size() - 1);
notifyBusOfRequestedChange(entitySqlDaoWrapperFactory, subscription, finalEvent, SubscriptionBaseTransitionType.CHANGE, context);
// Cancel associated add-ons
@@ -715,8 +762,12 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
private void cancelFutureEventsFromTransaction(final UUID subscriptionId, final DateTime effectiveDate, final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory, final boolean includingBCDChange, final InternalCallContext context) {
final List<SubscriptionEventModelDao> eventModels = entitySqlDaoWrapperFactory.become(SubscriptionEventSqlDao.class).getFutureOrPresentActiveEventForSubscription(subscriptionId.toString(), effectiveDate.toDate(), context);
- for (final SubscriptionEventModelDao cur : eventModels) {
+ cancelFutureEventsFromTransaction(eventModels, entitySqlDaoWrapperFactory, includingBCDChange, context);
+ }
+
+ private void cancelFutureEventsFromTransaction(final Iterable<SubscriptionEventModelDao> eventModels, final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory, final boolean includingBCDChange, final InternalCallContext context) {
+ for (final SubscriptionEventModelDao cur : eventModels) {
// Skip CREATE event (because of date equality in the query and we don't want to invalidate CREATE event that match a CANCEL event)
if (cur.getEventType() == EventType.API_USER && (cur.getUserType()== ApiEventType.CREATE || cur.getUserType()== ApiEventType.TRANSFER)) {
continue;
@@ -879,40 +930,46 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
return;
}
for (final SubscriptionBaseEvent curDryRun : dryRunEvents) {
+
+ boolean swapChangeEventWithCreate = false;
+
if (curDryRun.getSubscriptionId() != null && curDryRun.getSubscriptionId().equals(subscriptionId)) {
- //boolean inserted = false;
+ final boolean isApiChange = curDryRun.getType() == EventType.API_USER && ((ApiEvent) curDryRun).getApiEventType() == ApiEventType.CHANGE;
final Iterator<SubscriptionBaseEvent> it = events.iterator();
while (it.hasNext()) {
final SubscriptionBaseEvent event = it.next();
if (event.getEffectiveDate().isAfter(curDryRun.getEffectiveDate())) {
it.remove();
+ } else if (event.getEffectiveDate().compareTo(curDryRun.getEffectiveDate()) == 0 &&
+ isApiChange &&
+ (event.getType() == EventType.API_USER && (((ApiEvent) event).getApiEventType() == ApiEventType.CREATE) || ((ApiEvent) event).getApiEventType() == ApiEventType.TRANSFER)) {
+ it.remove();
+ swapChangeEventWithCreate = true;
}
}
// Set total ordering value of the fake dryRun event to make sure billing events are correctly ordered
- final SubscriptionBaseEvent curAdjustedDryRun;
+ // and also transform CHANGE event into CREATE in case of perfect effectiveDate match
+ final EventBaseBuilder eventBuilder;
+ switch (curDryRun.getType()) {
+ case PHASE:
+ eventBuilder = new PhaseEventBuilder((PhaseEvent) curDryRun);
+ break;
+ case BCD_UPDATE:
+ eventBuilder = new BCDEventBuilder((BCDEvent) curDryRun);
+ break;
+ case API_USER:
+ default:
+ eventBuilder = new ApiEventBuilder((ApiEvent) curDryRun);
+ if (swapChangeEventWithCreate) {
+ ((ApiEventBuilder) eventBuilder).setApiEventType(ApiEventType.CREATE);
+ }
+ break;
+ }
if (!events.isEmpty()) {
-
- final EventBaseBuilder eventBuilder;
- switch (curDryRun.getType()) {
- case PHASE:
- eventBuilder = new PhaseEventBuilder((PhaseEvent) curDryRun);
- break;
- case BCD_UPDATE:
- eventBuilder = new BCDEventBuilder((BCDEvent) curDryRun);
- break;
- case API_USER:
- default:
- eventBuilder = new ApiEventBuilder((ApiEvent) curDryRun);
- break;
- }
eventBuilder.setTotalOrdering(events.get(events.size() - 1).getTotalOrdering() + 1);
-
- curAdjustedDryRun = eventBuilder.build();
- } else {
- curAdjustedDryRun = curDryRun;
}
- events.add(curAdjustedDryRun);
+ events.add(eventBuilder.build());
}
}
}
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionEventSqlDao.java b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionEventSqlDao.java
index ab12d94..c47d482 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionEventSqlDao.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionEventSqlDao.java
@@ -51,8 +51,8 @@ public interface SubscriptionEventSqlDao extends EntitySqlDao<SubscriptionEventM
@BindBean final InternalTenantContext context);
@SqlQuery
- public List<SubscriptionEventModelDao> getEventsForSubscription(@Bind("subscriptionId") String subscriptionId,
- @BindBean final InternalTenantContext context);
+ public List<SubscriptionEventModelDao> getActiveEventsForSubscription(@Bind("subscriptionId") String subscriptionId,
+ @BindBean final InternalTenantContext context);
@SqlQuery
public List<SubscriptionEventModelDao> getFutureActiveEventsForAccount(@Bind("now") Date now, @BindBean final InternalTenantContext context);
diff --git a/subscription/src/main/resources/org/killbill/billing/subscription/engine/dao/SubscriptionEventSqlDao.sql.stg b/subscription/src/main/resources/org/killbill/billing/subscription/engine/dao/SubscriptionEventSqlDao.sql.stg
index a924d70..cfb3ec4 100644
--- a/subscription/src/main/resources/org/killbill/billing/subscription/engine/dao/SubscriptionEventSqlDao.sql.stg
+++ b/subscription/src/main/resources/org/killbill/billing/subscription/engine/dao/SubscriptionEventSqlDao.sql.stg
@@ -84,7 +84,7 @@ and effective_date >= :now
;
>>
-getEventsForSubscription() ::= <<
+getActiveEventsForSubscription() ::= <<
select <allTableFields()>
, record_id as total_ordering
from <tableName()>
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestSubscriptionHelper.java b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestSubscriptionHelper.java
index 52ba58e..1886d07 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestSubscriptionHelper.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestSubscriptionHelper.java
@@ -32,12 +32,17 @@ import org.killbill.billing.account.api.AccountUserApi;
import org.killbill.billing.api.TestApiListener;
import org.killbill.billing.api.TestApiListener.NextEvent;
import org.killbill.billing.callcontext.InternalCallContext;
+import org.killbill.billing.catalog.api.BillingActionPolicy;
import org.killbill.billing.catalog.api.BillingPeriod;
import org.killbill.billing.catalog.api.Duration;
import org.killbill.billing.catalog.api.PhaseType;
+import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
import org.killbill.billing.catalog.api.ProductCategory;
import org.killbill.billing.catalog.api.TimeUnit;
+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.subscription.api.SubscriptionBaseInternalApi;
import org.killbill.billing.subscription.engine.dao.SubscriptionDao;
import org.killbill.billing.subscription.events.SubscriptionBaseEvent;
@@ -58,25 +63,58 @@ public class TestSubscriptionHelper {
private final Logger log = LoggerFactory.getLogger(TestSubscriptionHelper.class);
- private final AccountUserApi accountApi;
private final SubscriptionBaseInternalApi subscriptionApi;
private final Clock clock;
private final InternalCallContext internalCallContext;
- private final CallContext callContext;
private final TestApiListener testListener;
private final SubscriptionDao dao;
@Inject
- public TestSubscriptionHelper(final AccountUserApi accountApi, final SubscriptionBaseInternalApi subscriptionApi, final Clock clock, final InternalCallContext internallCallContext, final CallContext callContext, final TestApiListener testListener, final SubscriptionDao dao) {
- this.accountApi = accountApi;
+ public TestSubscriptionHelper(final SubscriptionBaseInternalApi subscriptionApi, final Clock clock, final InternalCallContext internallCallContext, final CallContext callContext, final TestApiListener testListener, final SubscriptionDao dao) {
this.subscriptionApi = subscriptionApi;
this.clock = clock;
this.internalCallContext = internallCallContext;
- this.callContext = callContext;
this.testListener = testListener;
this.dao = dao;
}
+ public DryRunArguments createDryRunArguments(final UUID subscriptionId, final UUID bundleId, final PlanPhaseSpecifier spec, final LocalDate requestedDate, final SubscriptionEventType type, final BillingActionPolicy billingActionPolicy) {
+ return new DryRunArguments() {
+ @Override
+ public DryRunType getDryRunType() {
+ return DryRunType.SUBSCRIPTION_ACTION;
+ }
+ @Override
+ public PlanPhaseSpecifier getPlanPhaseSpecifier() {
+ return spec;
+ }
+ @Override
+ public SubscriptionEventType getAction() {
+ return type;
+ }
+ @Override
+ public UUID getSubscriptionId() {
+ return subscriptionId;
+ }
+ @Override
+ public LocalDate getEffectiveDate() {
+ return requestedDate;
+ }
+ @Override
+ public UUID getBundleId() {
+ return bundleId;
+ }
+ @Override
+ public BillingActionPolicy getBillingActionPolicy() {
+ return billingActionPolicy;
+ }
+ @Override
+ public List<PlanPhasePriceOverride> getPlanPhasePriceOverrides() {
+ return null;
+ }
+ };
+ }
+
public DefaultSubscriptionBase createSubscription(final SubscriptionBaseBundle bundle, final String productName, final BillingPeriod term, final String planSet, final DateTime requestedDate)
throws SubscriptionBaseApiException {
return createSubscriptionWithBundle(bundle.getId(), productName, term, planSet, requestedDate);
@@ -89,7 +127,10 @@ public class TestSubscriptionHelper {
public DefaultSubscriptionBase createSubscriptionWithBundle(final UUID bundleId, final String productName, final BillingPeriod term, final String planSet, final DateTime requestedDate)
throws SubscriptionBaseApiException {
- testListener.pushExpectedEvent(NextEvent.CREATE);
+
+ if (requestedDate == null || requestedDate.compareTo(clock.getUTCNow()) <= 0) {
+ testListener.pushExpectedEvent(NextEvent.CREATE);
+ }
final DefaultSubscriptionBase subscription = (DefaultSubscriptionBase) subscriptionApi.createSubscription(bundleId,
new PlanPhaseSpecifier(productName, term, planSet, null), null,
requestedDate == null ? clock.getUTCNow() : requestedDate, false, internalCallContext);
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiCancel.java b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiCancel.java
index be86546..72dd488 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiCancel.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiCancel.java
@@ -21,6 +21,7 @@ import java.util.UUID;
import org.joda.time.DateTime;
import org.joda.time.Interval;
import org.joda.time.LocalDate;
+import org.killbill.billing.ErrorCode;
import org.killbill.billing.api.TestApiListener.NextEvent;
import org.killbill.billing.catalog.api.BillingActionPolicy;
import org.killbill.billing.catalog.api.BillingPeriod;
@@ -30,6 +31,7 @@ import org.killbill.billing.catalog.api.Plan;
import org.killbill.billing.catalog.api.PlanPhase;
import org.killbill.billing.catalog.api.PriceListSet;
import org.killbill.billing.catalog.api.ProductCategory;
+import org.killbill.billing.entitlement.api.Entitlement;
import org.killbill.billing.entitlement.api.Entitlement.EntitlementState;
import org.killbill.billing.entity.EntityPersistenceException;
import org.killbill.billing.subscription.SubscriptionTestSuiteWithEmbeddedDB;
@@ -46,6 +48,7 @@ import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertNull;
import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
public class TestUserApiCancel extends SubscriptionTestSuiteWithEmbeddedDB {
@@ -426,9 +429,98 @@ public class TestUserApiCancel extends SubscriptionTestSuiteWithEmbeddedDB {
clock.addMonths(1);
assertListenerStatus();
+ }
+
+
+ @Test(groups = "slow")
+ public void testCancelPlanOnPendingSubscription1() throws SubscriptionBaseApiException {
+
+ final String baseProduct = "Shotgun";
+ final BillingPeriod baseTerm = BillingPeriod.MONTHLY;
+ final String basePriceList = PriceListSet.DEFAULT_PRICELIST_NAME;
+ final DateTime startDate = clock.getUTCNow().plusDays(5);
+
+ final DefaultSubscriptionBase subscription = testUtil.createSubscription(bundle, baseProduct, baseTerm, basePriceList, startDate);
+ assertEquals(subscription.getState(), Entitlement.EntitlementState.PENDING);
+ assertEquals(subscription.getStartDate().compareTo(startDate), 0);
+
+ // The code will be smart to infer the cancelation date as being the future startDate
+ subscription.cancel(callContext);
+
+ testListener.pushExpectedEvents(NextEvent.CREATE, NextEvent.CANCEL);
+ clock.addDays(5);
+ assertListenerStatus();
+ final DefaultSubscriptionBase subscription2 = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(subscription.getId(), internalCallContext);
+ assertEquals(subscription2.getStartDate().compareTo(startDate), 0);
+ assertEquals(subscription2.getState(), Entitlement.EntitlementState.CANCELLED);
+ assertNull(subscription2.getCurrentPlan());
}
+ @Test(groups = "slow")
+ public void testCancelPlanOnPendingSubscription2() throws SubscriptionBaseApiException {
+
+ final String baseProduct = "Shotgun";
+ final BillingPeriod baseTerm = BillingPeriod.MONTHLY;
+ final String basePriceList = PriceListSet.DEFAULT_PRICELIST_NAME;
+
+ final DateTime startDate = clock.getUTCNow().plusDays(5);
+
+ final DefaultSubscriptionBase subscription = testUtil.createSubscription(bundle, baseProduct, baseTerm, basePriceList, startDate);
+ assertEquals(subscription.getState(), Entitlement.EntitlementState.PENDING);
+ assertEquals(subscription.getStartDate().compareTo(startDate), 0);
+
+ try {
+ subscription.cancelWithDate(null, callContext);
+ fail("Cancel plan should have failed : subscription PENDING");
+ } catch (SubscriptionBaseApiException e) {
+ assertEquals(e.getCode(), ErrorCode.SUB_INVALID_REQUESTED_DATE.getCode());
+ }
+
+ try {
+ subscription.cancelWithDate(startDate.minusDays(1), callContext);
+ fail("Cancel plan should have failed : subscription PENDING");
+ } catch (SubscriptionBaseApiException e) {
+ assertEquals(e.getCode(), ErrorCode.SUB_INVALID_REQUESTED_DATE.getCode());
+ }
+
+ subscription.cancelWithDate(startDate, callContext);
+
+ testListener.pushExpectedEvents(NextEvent.CREATE, NextEvent.CANCEL);
+ clock.addDays(5);
+ assertListenerStatus();
+
+ final DefaultSubscriptionBase subscription2 = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(subscription.getId(), internalCallContext);
+ assertEquals(subscription2.getStartDate().compareTo(startDate), 0);
+ assertEquals(subscription2.getState(), Entitlement.EntitlementState.CANCELLED);
+ assertNull(subscription2.getCurrentPlan());
+ }
+
+
+ @Test(groups = "slow")
+ public void testCancelPlanOnPendingSubscription3() throws SubscriptionBaseApiException {
+
+ final String baseProduct = "Shotgun";
+ final BillingPeriod baseTerm = BillingPeriod.MONTHLY;
+ final String basePriceList = PriceListSet.DEFAULT_PRICELIST_NAME;
+
+ final DateTime startDate = clock.getUTCNow().plusDays(5);
+
+ final DefaultSubscriptionBase subscription = testUtil.createSubscription(bundle, baseProduct, baseTerm, basePriceList, startDate);
+ assertEquals(subscription.getState(), Entitlement.EntitlementState.PENDING);
+ assertEquals(subscription.getStartDate().compareTo(startDate), 0);
+
+ subscription.cancelWithPolicy(BillingActionPolicy.IMMEDIATE, null, 1, callContext);
+
+ testListener.pushExpectedEvents(NextEvent.CREATE, NextEvent.CANCEL);
+ clock.addDays(5);
+ assertListenerStatus();
+
+ final DefaultSubscriptionBase subscription2 = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(subscription.getId(), internalCallContext);
+ assertEquals(subscription2.getStartDate().compareTo(startDate), 0);
+ assertEquals(subscription2.getState(), Entitlement.EntitlementState.CANCELLED);
+ assertNull(subscription2.getCurrentPlan());
+ }
}
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiChangePlan.java b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiChangePlan.java
index 58fe498..613bb69 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiChangePlan.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiChangePlan.java
@@ -16,36 +16,55 @@
package org.killbill.billing.subscription.api.user;
-import java.util.ArrayList;
-import java.util.List;
-
import org.joda.time.DateTime;
import org.joda.time.Interval;
+import org.joda.time.LocalDate;
import org.killbill.billing.ErrorCode;
-import org.killbill.billing.catalog.api.PlanAlignmentCreate;
-import org.killbill.billing.catalog.api.PlanSpecifier;
-import org.testng.Assert;
-import org.testng.annotations.Test;
-
import org.killbill.billing.api.TestApiListener.NextEvent;
+import org.killbill.billing.catalog.api.BillingActionPolicy;
import org.killbill.billing.catalog.api.BillingPeriod;
import org.killbill.billing.catalog.api.Duration;
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.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.Entitlement;
+import org.killbill.billing.entitlement.api.SubscriptionEventType;
+import org.killbill.billing.invoice.api.DryRunArguments;
import org.killbill.billing.subscription.SubscriptionTestSuiteWithEmbeddedDB;
+import org.killbill.billing.subscription.api.SubscriptionBase;
+import org.killbill.billing.subscription.api.SubscriptionBaseTransitionType;
import org.killbill.billing.subscription.api.SubscriptionBillingApiException;
+import org.killbill.billing.subscription.engine.dao.SubscriptionEventSqlDao;
+import org.killbill.billing.subscription.engine.dao.model.SubscriptionEventModelDao;
import org.killbill.billing.subscription.events.SubscriptionBaseEvent;
import org.killbill.billing.subscription.events.user.ApiEvent;
+import org.killbill.billing.subscription.events.user.ApiEventType;
+import org.skife.jdbi.v2.Handle;
+import org.skife.jdbi.v2.IDBI;
+import org.skife.jdbi.v2.tweak.HandleCallback;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import javax.inject.Inject;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
public class TestUserApiChangePlan extends SubscriptionTestSuiteWithEmbeddedDB {
+ @Inject
+ private IDBI dbi;
+
private void checkChangePlan(final DefaultSubscriptionBase subscription, final String expProduct, final ProductCategory expCategory,
final BillingPeriod expBillingPeriod, final PhaseType expPhase) {
@@ -458,4 +477,86 @@ public class TestUserApiChangePlan extends SubscriptionTestSuiteWithEmbeddedDB {
}
}
+
+ @Test(groups = "slow")
+ public void testChangePlanOnPendingSubscription() throws SubscriptionBaseApiException {
+
+ final String baseProduct = "Shotgun";
+ final BillingPeriod baseTerm = BillingPeriod.MONTHLY;
+ final String basePriceList = PriceListSet.DEFAULT_PRICELIST_NAME;
+
+ final DateTime startDate = clock.getUTCNow().plusDays(5);
+
+ final DefaultSubscriptionBase subscription = testUtil.createSubscription(bundle, baseProduct, baseTerm, basePriceList, startDate);
+ assertEquals(subscription.getState(), Entitlement.EntitlementState.PENDING);
+ assertEquals(subscription.getStartDate().compareTo(startDate), 0);
+
+ final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Pistol", baseTerm, basePriceList, null);
+
+
+ // First try with default api (no date -> IMM) => Call should fail because subscription is PENDING
+ final DryRunArguments dryRunArguments1 = testUtil.createDryRunArguments(subscription.getId(), subscription.getBundleId(), spec, null, SubscriptionEventType.CHANGE, null);
+ final List<SubscriptionBase> result1 = subscriptionInternalApi.getSubscriptionsForBundle(bundle.getId(), dryRunArguments1, internalCallContext);
+
+ // Check we are seeing the right PENDING transition (pistol-monthly), not the START but the CHANGE on the same date
+ assertEquals(((DefaultSubscriptionBase) result1.get(0)).getCurrentOrPendingPlan().getName(), "pistol-monthly");
+ assertEquals(((DefaultSubscriptionBase) result1.get(0)).getPendingTransition().getTransitionType(), SubscriptionBaseTransitionType.CREATE);
+
+ // Second try with date prior to startDate => Call should fail because subscription is PENDING
+ try {
+ final DryRunArguments dryRunArguments2 = testUtil.createDryRunArguments(subscription.getId(), subscription.getBundleId(), spec, new LocalDate(startDate.minusDays(1)), SubscriptionEventType.CHANGE, null);
+ subscriptionInternalApi.getSubscriptionsForBundle(bundle.getId(), dryRunArguments2, internalCallContext);
+ fail("Change plan should have failed : subscription PENDING");
+ } catch (final SubscriptionBaseApiException e) {
+ assertEquals(e.getCode(), ErrorCode.SUB_CHANGE_NON_ACTIVE.getCode());
+ }
+ try {
+ subscription.changePlanWithDate(spec, null, startDate.minusDays(1), callContext);
+ fail("Change plan should have failed : subscription PENDING");
+ } catch (final SubscriptionBaseApiException e) {
+ assertEquals(e.getCode(), ErrorCode.SUB_INVALID_REQUESTED_DATE.getCode());
+ }
+
+ // Third try with date equals to startDate Call should succeed, but no event because action in future
+ final DryRunArguments dryRunArguments3 = testUtil.createDryRunArguments(subscription.getId(), subscription.getBundleId(), spec, internalCallContext.toLocalDate(startDate), SubscriptionEventType.CHANGE, null);
+ final List<SubscriptionBase> result2 = subscriptionInternalApi.getSubscriptionsForBundle(bundle.getId(), dryRunArguments3, internalCallContext);
+ // Check we are seeing the right PENDING transition (pistol-monthly), not the START but the CHANGE on the same date
+ assertEquals(((DefaultSubscriptionBase) result2.get(0)).getCurrentOrPendingPlan().getName(), "pistol-monthly");
+
+
+ // To spice up the test, we insert manually an additional CHANGE event on the exact same dateas the CREATE to verify that code will also invalidate such event when doing the changePlanXX
+
+ final SubscriptionEventModelDao event = new SubscriptionEventModelDao(subscription.getEvents().get(0));
+ event.setId(UUID.randomUUID());
+ event.setUserType(ApiEventType.CHANGE);
+ event.setPlanName("blowdart-monthly");
+ event.setPhaseName("blowdart-monthly-trial");
+ dbi.withHandle(new HandleCallback<Void>() {
+ @Override
+ public Void withHandle(Handle handle) throws Exception {
+ final SubscriptionEventSqlDao sqlDao = handle.attach(SubscriptionEventSqlDao.class);
+ sqlDao.create(event, internalCallContext);
+ return null;
+ }
+ });
+
+ final DefaultSubscriptionBase refreshed1 = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(subscription.getId(), internalCallContext);
+ assertEquals(refreshed1.getEvents().size(), subscription.getEvents().size() + 1);
+
+ subscription.changePlanWithDate(spec, null, startDate, callContext);
+ assertListenerStatus();
+
+ // Move clock to startDate
+ testListener.pushExpectedEvents(NextEvent.CREATE);
+ clock.addDays(5);
+ assertListenerStatus();
+
+ final DefaultSubscriptionBase subscription2 = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(subscription.getId(), internalCallContext);
+ assertEquals(subscription2.getStartDate().compareTo(startDate), 0);
+ assertEquals(subscription2.getState(), Entitlement.EntitlementState.ACTIVE);
+ assertEquals(subscription2.getCurrentPlan().getProduct().getName(), "Pistol");
+ // Same original # active events
+ assertEquals(subscription2.getEvents().size(), subscription.getEvents().size());
+ }
+
}
util/pom.xml 10(+5 -5)
diff --git a/util/pom.xml b/util/pom.xml
index f4887a9..6f3e019 100644
--- a/util/pom.xml
+++ b/util/pom.xml
@@ -74,11 +74,6 @@
<artifactId>guice-multibindings</artifactId>
</dependency>
<dependency>
- <groupId>com.jayway.awaitility</groupId>
- <artifactId>awaitility</artifactId>
- <scope>test</scope>
- </dependency>
- <dependency>
<groupId>com.samskivert</groupId>
<artifactId>jmustache</artifactId>
</dependency>
@@ -150,6 +145,11 @@
<artifactId>shiro-guice</artifactId>
</dependency>
<dependency>
+ <groupId>org.awaitility</groupId>
+ <artifactId>awaitility</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
<groupId>org.ehcache</groupId>
<artifactId>ehcache</artifactId>
</dependency>
diff --git a/util/src/main/java/org/killbill/billing/util/broadcast/dao/BroadcastModelDao.java b/util/src/main/java/org/killbill/billing/util/broadcast/dao/BroadcastModelDao.java
index 14938ea..f480b12 100644
--- a/util/src/main/java/org/killbill/billing/util/broadcast/dao/BroadcastModelDao.java
+++ b/util/src/main/java/org/killbill/billing/util/broadcast/dao/BroadcastModelDao.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * 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
@@ -63,4 +63,17 @@ public class BroadcastModelDao {
public String getCreatedBy() {
return createdBy;
}
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("BroadcastModelDao{");
+ sb.append("recordId=").append(recordId);
+ sb.append(", serviceName='").append(serviceName).append('\'');
+ sb.append(", type='").append(type).append('\'');
+ sb.append(", event='").append(event).append('\'');
+ sb.append(", createdDate=").append(createdDate);
+ sb.append(", createdBy='").append(createdBy).append('\'');
+ sb.append('}');
+ return sb.toString();
+ }
}
diff --git a/util/src/main/java/org/killbill/billing/util/config/definition/PaymentConfig.java b/util/src/main/java/org/killbill/billing/util/config/definition/PaymentConfig.java
index 97a018f..639429f 100644
--- a/util/src/main/java/org/killbill/billing/util/config/definition/PaymentConfig.java
+++ b/util/src/main/java/org/killbill/billing/util/config/definition/PaymentConfig.java
@@ -58,15 +58,26 @@ public interface PaymentConfig extends KillbillConfig {
@Description("Specify the multiplier to apply between in retry before retrying a payment that failed due to a plugin failure (gateway is down, transient error, ...)")
int getPluginFailureRetryMultiplier(@Param("dummy") final InternalTenantContext tenantContext);
- @Config("org.killbill.payment.janitor.transactions.retries")
- @Default("15s,1m,3m,1h,1d,1d,1d,1d,1d")
+ @Config("org.killbill.payment.janitor.unknown.retries")
+ @Default("5m,1h,1d,1d,1d,1d,1d")
@Description("Delay before which unresolved transactions should be retried")
- List<TimeSpan> getIncompleteTransactionsRetries();
+ List<TimeSpan> getUnknownTransactionsRetries();
- @Config("org.killbill.payment.janitor.transactions.retries")
- @Default("15s,1m,3m,1h,1d,1d,1d,1d,1d")
+ @Config("org.killbill.payment.janitor.unknown.retries")
+ @Default("5m,1h,1d,1d,1d,1d,1d")
@Description("Delay before which unresolved transactions should be retried")
- List<TimeSpan> getIncompleteTransactionsRetries(@Param("dummy") final InternalTenantContext tenantContext);
+ List<TimeSpan> getUnknownTransactionsRetries(@Param("dummy") final InternalTenantContext tenantContext);
+
+ @Config("org.killbill.payment.janitor.pending.retries")
+ @Default("1h, 1d")
+ @Description("Delay before which unresolved transactions should be retried")
+ List<TimeSpan> getPendingTransactionsRetries();
+
+ @Config("org.killbill.payment.janitor.pending.retries")
+ @Default("1h, 1d")
+ @Description("Delay before which unresolved transactions should be retried")
+ List<TimeSpan> getPendingTransactionsRetries(@Param("dummy") final InternalTenantContext tenantContext);
+
@Config("org.killbill.payment.failure.retry.max.attempts")
@Default("8")
diff --git a/util/src/main/java/org/killbill/billing/util/userrequest/CompletionUserRequestBase.java b/util/src/main/java/org/killbill/billing/util/userrequest/CompletionUserRequestBase.java
index f48a2e8..81d6575 100644
--- a/util/src/main/java/org/killbill/billing/util/userrequest/CompletionUserRequestBase.java
+++ b/util/src/main/java/org/killbill/billing/util/userrequest/CompletionUserRequestBase.java
@@ -1,7 +1,7 @@
/*
- * Copyright 2010-2011 Ning, Inc.
- * Copyright 2014 Groupon, Inc
- * Copyright 2014 The Billing Project, LLC
+ * Copyright 2010-2013 Ning, Inc.
+ * 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
@@ -30,6 +30,7 @@ import org.killbill.billing.events.BusInternalEvent;
import org.killbill.billing.events.EffectiveSubscriptionInternalEvent;
import org.killbill.billing.events.InvoiceCreationInternalEvent;
import org.killbill.billing.events.InvoicePaymentErrorInternalEvent;
+import org.killbill.billing.events.InvoicePaymentInfoInternalEvent;
import org.killbill.billing.events.NullInvoiceInternalEvent;
import org.killbill.billing.events.PaymentErrorInternalEvent;
import org.killbill.billing.events.PaymentInfoInternalEvent;
@@ -132,6 +133,9 @@ public class CompletionUserRequestBase implements CompletionUserRequest {
case PAYMENT_PLUGIN_ERROR:
onPaymentPluginError((PaymentPluginErrorInternalEvent) curEvent);
break;
+ case INVOICE_PAYMENT_INFO:
+ onInvoicePaymentInfo((InvoicePaymentInfoInternalEvent) curEvent);
+ break;
case INVOICE_PAYMENT_ERROR:
onInvoicePaymentError((InvoicePaymentErrorInternalEvent) curEvent);
break;
@@ -178,6 +182,10 @@ public class CompletionUserRequestBase implements CompletionUserRequest {
}
@Override
+ public void onInvoicePaymentInfo(final InvoicePaymentInfoInternalEvent curEvent) {
+ }
+
+ @Override
public void onInvoicePaymentError(final InvoicePaymentErrorInternalEvent curEvent) {
}
}
diff --git a/util/src/main/java/org/killbill/billing/util/userrequest/CompletionUserRequestWaiter.java b/util/src/main/java/org/killbill/billing/util/userrequest/CompletionUserRequestWaiter.java
index 6876550..827d2ef 100644
--- a/util/src/main/java/org/killbill/billing/util/userrequest/CompletionUserRequestWaiter.java
+++ b/util/src/main/java/org/killbill/billing/util/userrequest/CompletionUserRequestWaiter.java
@@ -1,7 +1,9 @@
/*
- * Copyright 2010-2011 Ning, Inc.
+ * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
*
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
@@ -26,6 +28,7 @@ import org.killbill.billing.events.BusInternalEvent;
import org.killbill.billing.events.EffectiveSubscriptionInternalEvent;
import org.killbill.billing.events.InvoiceCreationInternalEvent;
import org.killbill.billing.events.InvoicePaymentErrorInternalEvent;
+import org.killbill.billing.events.InvoicePaymentInfoInternalEvent;
import org.killbill.billing.events.NullInvoiceInternalEvent;
import org.killbill.billing.events.PaymentErrorInternalEvent;
import org.killbill.billing.events.PaymentInfoInternalEvent;
@@ -53,5 +56,7 @@ public interface CompletionUserRequestWaiter {
public void onPaymentPluginError(final PaymentPluginErrorInternalEvent curEvent);
+ public void onInvoicePaymentInfo(final InvoicePaymentInfoInternalEvent curEvent);
+
public void onInvoicePaymentError(final InvoicePaymentErrorInternalEvent curEvent);
}
diff --git a/util/src/main/java/org/killbill/billing/util/validation/dao/DatabaseSchemaSqlDao.java b/util/src/main/java/org/killbill/billing/util/validation/dao/DatabaseSchemaSqlDao.java
index e9007fb..78e6d0f 100644
--- a/util/src/main/java/org/killbill/billing/util/validation/dao/DatabaseSchemaSqlDao.java
+++ b/util/src/main/java/org/killbill/billing/util/validation/dao/DatabaseSchemaSqlDao.java
@@ -45,8 +45,8 @@ public interface DatabaseSchemaSqlDao {
public DefaultColumnInfo map(final int index, final ResultSet r, final StatementContext ctx) throws SQLException {
final String tableName = r.getString("table_name");
final String columnName = r.getString("column_name");
- final Integer scale = r.getInt("numeric_scale");
- final Integer precision = r.getInt("numeric_precision");
+ final Long scale = r.getLong("numeric_scale");
+ final Long precision = r.getLong("numeric_precision");
// Special handling for PostgreSQL - the implementation of AbstractJdbc2ResultSet#getBoolean doesn't support YES/NO
final String isNullableString = r.getString("is_nullable");
@@ -59,7 +59,7 @@ public interface DatabaseSchemaSqlDao {
isNullable = r.getBoolean("is_nullable");
}
- final Integer maximumLength = r.getInt("character_maximum_length");
+ final Long maximumLength = r.getLong("character_maximum_length");
final String dataType = r.getString("data_type");
return new DefaultColumnInfo(tableName, columnName, scale, precision, isNullable, maximumLength, dataType);
diff --git a/util/src/main/java/org/killbill/billing/util/validation/DefaultColumnInfo.java b/util/src/main/java/org/killbill/billing/util/validation/DefaultColumnInfo.java
index 243e89a..44e0b5d 100644
--- a/util/src/main/java/org/killbill/billing/util/validation/DefaultColumnInfo.java
+++ b/util/src/main/java/org/killbill/billing/util/validation/DefaultColumnInfo.java
@@ -1,7 +1,9 @@
/*
- * Copyright 2010-2011 Ning, Inc.
+ * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
*
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
@@ -22,14 +24,14 @@ public class DefaultColumnInfo implements ColumnInfo {
private final String tableName;
private final String columnName;
- private final int scale;
- private final int precision;
+ private final Long scale;
+ private final Long precision;
private final boolean isNullable;
- private final int maximumLength;
+ private final Long maximumLength;
private final String dataType;
- public DefaultColumnInfo(final String tableName, final String columnName, final int scale, final int precision,
- final boolean nullable, final int maximumLength, final String dataType) {
+ public DefaultColumnInfo(final String tableName, final String columnName, final Long scale, final Long precision,
+ final boolean nullable, final Long maximumLength, final String dataType) {
this.tableName = tableName;
this.columnName = columnName;
this.scale = scale;
@@ -49,11 +51,11 @@ public class DefaultColumnInfo implements ColumnInfo {
return columnName;
}
- public int getScale() {
+ public Long getScale() {
return scale;
}
- public int getPrecision() {
+ public Long getPrecision() {
return precision;
}
@@ -61,7 +63,7 @@ public class DefaultColumnInfo implements ColumnInfo {
return isNullable;
}
- public int getMaximumLength() {
+ public Long getMaximumLength() {
return maximumLength;
}
diff --git a/util/src/test/java/org/killbill/billing/api/TestApiListener.java b/util/src/test/java/org/killbill/billing/api/TestApiListener.java
index 95ac19d..886dd99 100644
--- a/util/src/test/java/org/killbill/billing/api/TestApiListener.java
+++ b/util/src/test/java/org/killbill/billing/api/TestApiListener.java
@@ -53,7 +53,7 @@ import org.testng.Assert;
import com.google.common.base.Joiner;
import com.google.common.eventbus.Subscribe;
-import static com.jayway.awaitility.Awaitility.await;
+import static org.awaitility.Awaitility.await;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.fail;
diff --git a/util/src/test/java/org/killbill/billing/util/broadcast/dao/TestBroadcastDao.java b/util/src/test/java/org/killbill/billing/util/broadcast/dao/TestBroadcastDao.java
index 777cea8..f3ff6e6 100644
--- a/util/src/test/java/org/killbill/billing/util/broadcast/dao/TestBroadcastDao.java
+++ b/util/src/test/java/org/killbill/billing/util/broadcast/dao/TestBroadcastDao.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * 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
@@ -29,8 +29,7 @@ public class TestBroadcastDao extends UtilTestSuiteWithEmbeddedDB {
@Test(groups = "slow")
public void testBasic() throws Exception {
-
- DateTime now = clock.getUTCNow();
+ final DateTime now = clock.getUTCNow();
final BroadcastModelDao b1 = new BroadcastModelDao("svc1", "type1", "{attribute: kewl}", now, "tester");
broadcastDao.create(b1);
@@ -43,8 +42,7 @@ public class TestBroadcastDao extends UtilTestSuiteWithEmbeddedDB {
final List<BroadcastModelDao> all = broadcastDao.getLatestEntriesFrom(0L);
assertEquals(all.size(), 1);
- final List<BroadcastModelDao> none = broadcastDao.getLatestEntriesFrom(1L);
- assertEquals(none.size(), 0);
-
+ final List<BroadcastModelDao> none = broadcastDao.getLatestEntriesFrom(res.getRecordId());
+ assertEquals(none.size(), 0, "Invalid entries: " + none.toString());
}
}
diff --git a/util/src/test/java/org/killbill/billing/util/export/dao/TestCSVExportOutputStream.java b/util/src/test/java/org/killbill/billing/util/export/dao/TestCSVExportOutputStream.java
index 90a96a4..d76554d 100644
--- a/util/src/test/java/org/killbill/billing/util/export/dao/TestCSVExportOutputStream.java
+++ b/util/src/test/java/org/killbill/billing/util/export/dao/TestCSVExportOutputStream.java
@@ -39,9 +39,9 @@ public class TestCSVExportOutputStream extends UtilTestSuiteNoDB {
final String tableName = UUID.randomUUID().toString();
out.newTable(tableName,
ImmutableList.<ColumnInfo>of(
- new DefaultColumnInfo(tableName, "first_name", 0, 0, true, 0, "varchar"),
- new DefaultColumnInfo(tableName, "last_name", 0, 0, true, 0, "varchar"),
- new DefaultColumnInfo(tableName, "age", 0, 0, true, 0, "tinyint"))
+ new DefaultColumnInfo(tableName, "first_name", 0L, 0L, true, 0L, "varchar"),
+ new DefaultColumnInfo(tableName, "last_name", 0L, 0L, true, 0L, "varchar"),
+ new DefaultColumnInfo(tableName, "age", 0L, 0L, true, 0L, "tinyint"))
);
// Write some data
diff --git a/util/src/test/java/org/killbill/billing/util/security/api/TestDefaultSecurityApi.java b/util/src/test/java/org/killbill/billing/util/security/api/TestDefaultSecurityApi.java
index b83c1fa..77fd294 100644
--- a/util/src/test/java/org/killbill/billing/util/security/api/TestDefaultSecurityApi.java
+++ b/util/src/test/java/org/killbill/billing/util/security/api/TestDefaultSecurityApi.java
@@ -1,7 +1,9 @@
/*
- * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2010-2014 Ning, Inc.
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
*
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
@@ -36,8 +38,9 @@ public class TestDefaultSecurityApi extends UtilTestSuiteNoDB {
// We don't want the Guice injected one (it has Shiro disabled)
final SecurityApi securityApi = new DefaultSecurityApi(null);
+ logout();
final Set<Permission> anonsPermissions = securityApi.getCurrentUserPermissions(callContext);
- Assert.assertEquals(anonsPermissions.size(), 0);
+ Assert.assertEquals(anonsPermissions.size(), 0, "Invalid permissions: " + anonsPermissions);
login("pierre");
final Set<Permission> pierresPermissions = securityApi.getCurrentUserPermissions(callContext);
diff --git a/util/src/test/java/org/killbill/billing/util/validation/TestValidationManager.java b/util/src/test/java/org/killbill/billing/util/validation/TestValidationManager.java
index 064a22b..68f8773 100644
--- a/util/src/test/java/org/killbill/billing/util/validation/TestValidationManager.java
+++ b/util/src/test/java/org/killbill/billing/util/validation/TestValidationManager.java
@@ -56,8 +56,8 @@ public class TestValidationManager extends UtilTestSuiteWithEmbeddedDB {
final DefaultColumnInfo numericColumnInfo = vm.getColumnInfo(TABLE_NAME, "column3");
assertNotNull(numericColumnInfo);
- assertEquals(numericColumnInfo.getScale(), 4);
- assertEquals(numericColumnInfo.getPrecision(), 10);
+ assertEquals(numericColumnInfo.getScale(), (Long) 4L);
+ assertEquals(numericColumnInfo.getPrecision(), (Long) 10L);
}
@Test(groups = "slow")