killbill-aplcache
Changes
.idea/libraries/Maven__com_ning_billing_commons_killbill_queue_test_jar_tests_0_1_6_SNAPSHOT.xml 15(+0 -15)
.idea/libraries/Maven__com_ning_billing_killbill_beatrix_test_jar_tests_0_1_56_SNAPSHOT.xml 15(+0 -15)
.travis.yml 2(+1 -1)
account/pom.xml 2(+1 -1)
account/src/main/resources/org/killbill/billing/account/migration/V20170123221645__add_lucky_search_indexes.sql 3(+3 -0)
account/src/test/java/org/killbill/billing/account/api/user/TestDefaultAccountUserApi.java 192(+192 -0)
api/pom.xml 2(+1 -1)
beatrix/pom.xml 2(+1 -1)
beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueIntegration.java 51(+51 -0)
beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueWithSubscriptionEOTCancellation.java 125(+125 -0)
beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithCatalogUpdate.java 37(+36 -1)
bin/import-account 53(+34 -19)
catalog/pom.xml 2(+1 -1)
catalog/src/main/java/org/killbill/billing/catalog/StandaloneCatalogWithPriceOverride.java 19(+18 -1)
catalog/src/test/resources/catalogTest.xml 133(+129 -4)
currency/pom.xml 2(+1 -1)
entitlement/pom.xml 2(+1 -1)
entitlement/src/main/java/org/killbill/billing/entitlement/api/BlockingStateOrdering.java 22(+21 -1)
entitlement/src/main/java/org/killbill/billing/entitlement/api/svcs/DefaultEntitlementInternalApi.java 32(+15 -17)
entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultSubscriptionBundleTimeline.java 168(+119 -49)
invoice/pom.xml 2(+1 -1)
jaxrs/pom.xml 2(+1 -1)
junction/pom.xml 2(+1 -1)
NEWS 6(+6 -0)
overdue/pom.xml 2(+1 -1)
payment/pom.xml 2(+1 -1)
payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentStateMachineHelper.java 33(+31 -2)
payment/src/main/resources/org/killbill/billing/payment/migration/V20170123023324__add_payments_tenant_record_id_state_name_index.sql 1(+1 -0)
pom.xml 2(+1 -1)
profiles/killbill/pom.xml 2(+1 -1)
profiles/killbill/src/main/java/org/killbill/billing/server/filters/ResponseCorsFilter.java 20(+14 -6)
profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPaymentPluginProperties.java 4(+2 -2)
profiles/killpay/pom.xml 2(+1 -1)
profiles/pom.xml 2(+1 -1)
subscription/pom.xml 2(+1 -1)
subscription/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseApiService.java 2(+1 -1)
subscription/src/main/java/org/killbill/billing/subscription/api/svcs/DefaultSubscriptionInternalApi.java 40(+37 -3)
subscription/src/main/java/org/killbill/billing/subscription/api/transfer/DefaultSubscriptionBaseTransferApi.java 1(+1 -0)
subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultEffectiveSubscriptionEvent.java 3(+2 -1)
subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultRequestedSubscriptionEvent.java 5(+3 -2)
subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBase.java 9(+8 -1)
subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBaseApiService.java 57(+43 -14)
subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionEvent.java 9(+9 -0)
subscription/src/main/java/org/killbill/billing/subscription/api/user/SubscriptionBaseTransitionData.java 13(+13 -0)
subscription/src/main/java/org/killbill/billing/subscription/api/user/SubscriptionBuilder.java 11(+11 -0)
subscription/src/main/java/org/killbill/billing/subscription/engine/core/DefaultSubscriptionBaseService.java 2(+1 -1)
subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java 39(+32 -7)
subscription/src/main/java/org/killbill/billing/subscription/engine/dao/model/SubscriptionModelDao.java 4(+2 -2)
subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionDao.java 2(+2 -0)
subscription/src/test/java/org/killbill/billing/subscription/alignment/TestPlanAligner.java 269(+246 -23)
subscription/src/test/java/org/killbill/billing/subscription/api/svcs/TestEffectiveDateForNewBCD.java 234(+234 -0)
subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiCancel.java 43(+43 -0)
subscription/src/test/java/org/killbill/billing/subscription/engine/dao/MockSubscriptionDaoMemory.java 5(+5 -0)
tenant/pom.xml 2(+1 -1)
usage/pom.xml 2(+1 -1)
util/pom.xml 2(+1 -1)
Details
.travis.yml 2(+1 -1)
diff --git a/.travis.yml b/.travis.yml
index e8847be..531da15 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -6,7 +6,7 @@ sudo: false
# 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 2>&1 | egrep -v 'Download|Install|[ \t]*at [ \ta-zA-Z0-9\.\:\(\)]+'; [ ${PIPESTATUS[0]} == 0 ]; fi
+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
# Remove --quiet to avoid timeouts
install: mvn -U install -DskipTests=true | egrep -v 'Download|Install'
account/pom.xml 2(+1 -1)
diff --git a/account/pom.xml b/account/pom.xml
index 08a0cc5..549b678 100644
--- a/account/pom.xml
+++ b/account/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.18.2-SNAPSHOT</version>
+ <version>0.18.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-account</artifactId>
diff --git a/account/src/main/java/org/killbill/billing/account/api/DefaultAccount.java b/account/src/main/java/org/killbill/billing/account/api/DefaultAccount.java
index 0fcf194..8c452f1 100644
--- a/account/src/main/java/org/killbill/billing/account/api/DefaultAccount.java
+++ b/account/src/main/java/org/killbill/billing/account/api/DefaultAccount.java
@@ -312,31 +312,15 @@ public class DefaultAccount extends EntityBase implements Account {
public Account mergeWithDelegate(final Account currentAccount) {
final DefaultMutableAccountData accountData = new DefaultMutableAccountData(this);
- if (externalKey != null && currentAccount.getExternalKey() != null && !currentAccount.getExternalKey().equals(externalKey)) {
- throw new IllegalArgumentException(String.format("Killbill doesn't support updating the account external key yet: new=%s, current=%s",
- externalKey, currentAccount.getExternalKey()));
- } else {
- // Default to current value
- accountData.setExternalKey(currentAccount.getExternalKey());
- }
+ validateAccountUpdateInput(currentAccount, false);
- if (currency != null && currentAccount.getCurrency() != null && !currentAccount.getCurrency().equals(currency)) {
- throw new IllegalArgumentException(String.format("Killbill doesn't support updating the account currency yet: new=%s, current=%s",
- currency, currentAccount.getCurrency()));
- } else {
- // Default to current value
- accountData.setCurrency(currentAccount.getCurrency());
- }
+ accountData.setExternalKey(currentAccount.getExternalKey());
+ accountData.setCurrency(currentAccount.getCurrency());
- if (currentAccount.getBillCycleDayLocal() != DEFAULT_BILLING_CYCLE_DAY_LOCAL && // There is already a BCD set
- billCycleDayLocal != null && // and the proposed date is not null
- billCycleDayLocal != DEFAULT_BILLING_CYCLE_DAY_LOCAL && // and the proposed date is not 0
- !currentAccount.getBillCycleDayLocal().equals(billCycleDayLocal)) { // and it does not match we we have
- throw new IllegalArgumentException(String.format("Killbill doesn't support updating the account BCD yet: new=%s, current=%s", billCycleDayLocal, currentAccount.getBillCycleDayLocal()));
- } else if (currentAccount.getBillCycleDayLocal() == DEFAULT_BILLING_CYCLE_DAY_LOCAL && // There is *not* already a BCD set
- billCycleDayLocal != null && // and the value proposed is not null
- billCycleDayLocal != DEFAULT_BILLING_CYCLE_DAY_LOCAL) { // and the proposed date is not 0
+ if (currentAccount.getBillCycleDayLocal() == DEFAULT_BILLING_CYCLE_DAY_LOCAL && // There is *not* already a BCD set
+ billCycleDayLocal != null && // and the value proposed is not null
+ billCycleDayLocal != DEFAULT_BILLING_CYCLE_DAY_LOCAL) { // and the proposed date is not 0
accountData.setBillCycleDayLocal(billCycleDayLocal);
} else {
accountData.setBillCycleDayLocal(currentAccount.getBillCycleDayLocal());
@@ -525,4 +509,46 @@ public class DefaultAccount extends EntityBase implements Account {
result = 31 * result + (isNotifiedForInvoices != null ? isNotifiedForInvoices.hashCode() : 0);
return result;
}
+
+ public void validateAccountUpdateInput(final Account currentAccount, boolean ignoreNullInput) {
+
+ //
+ // We don't allow update on the following fields:
+ //
+ // All these conditions are written in the exact same way:
+ //
+ // There is already a defined value BUT those don't match (either input is null or different) => Not Allowed
+ // * ignoreNullInput=true (case where we allow to reset values)
+ // * ignoreNullInput=true (case where we DON'T allow to reset values and so is such value is null we ignore the check)
+ //
+ //
+ if ((ignoreNullInput || externalKey != null) &&
+ currentAccount.getExternalKey() != null &&
+ !currentAccount.getExternalKey().equals(externalKey)) {
+ throw new IllegalArgumentException(String.format("Killbill doesn't support updating the account external key yet: new=%s, current=%s",
+ externalKey, currentAccount.getExternalKey()));
+ }
+
+ if ((ignoreNullInput || currency != null) &&
+ currentAccount.getCurrency() != null &&
+ !currentAccount.getCurrency().equals(currency)) {
+ throw new IllegalArgumentException(String.format("Killbill doesn't support updating the account currency yet: new=%s, current=%s",
+ currency, currentAccount.getCurrency()));
+ }
+
+ if ((ignoreNullInput || (billCycleDayLocal != null && billCycleDayLocal != DEFAULT_BILLING_CYCLE_DAY_LOCAL)) &&
+ currentAccount.getBillCycleDayLocal() != DEFAULT_BILLING_CYCLE_DAY_LOCAL && // There is already a BCD set
+ !currentAccount.getBillCycleDayLocal().equals(billCycleDayLocal)) { // and it does not match we we have
+ throw new IllegalArgumentException(String.format("Killbill doesn't support updating the account BCD yet: new=%s, current=%s", billCycleDayLocal, currentAccount.getBillCycleDayLocal()));
+ }
+
+ if ((ignoreNullInput || timeZone != null) &&
+ currentAccount.getTimeZone() != null &&
+ !currentAccount.getTimeZone().equals(timeZone)) {
+ throw new IllegalArgumentException(String.format("Killbill doesn't support updating the account timeZone yet: new=%s, current=%s",
+ timeZone, currentAccount.getTimeZone()));
+ }
+
+ }
+
}
diff --git a/account/src/main/java/org/killbill/billing/account/api/user/DefaultAccountUserApi.java b/account/src/main/java/org/killbill/billing/account/api/user/DefaultAccountUserApi.java
index 8c75df8..a164bef 100644
--- a/account/src/main/java/org/killbill/billing/account/api/user/DefaultAccountUserApi.java
+++ b/account/src/main/java/org/killbill/billing/account/api/user/DefaultAccountUserApi.java
@@ -147,7 +147,20 @@ public class DefaultAccountUserApi extends DefaultAccountApiBase implements Acco
@Override
public void updateAccount(final Account account, final CallContext context) throws AccountApiException {
- updateAccount(account.getId(), account, context);
+
+ // Convert to DefaultAccount to make sure we can safely call validateAccountUpdateInput
+ final DefaultAccount input = new DefaultAccount(account.getId(), account);
+
+ final Account currentAccount = getAccountById(input.getId(), context);
+ if (currentAccount == null) {
+ throw new AccountApiException(ErrorCode.ACCOUNT_DOES_NOT_EXIST_FOR_ID, input.getId());
+ }
+
+ input.validateAccountUpdateInput(currentAccount, true);
+
+ final AccountModelDao updatedAccountModelDao = new AccountModelDao(currentAccount.getId(), input);
+
+ accountDao.update(updatedAccountModelDao, internalCallContextFactory.createInternalCallContext(updatedAccountModelDao.getId(), context));
}
@Override
diff --git a/account/src/main/java/org/killbill/billing/account/dao/AccountSqlDao.java b/account/src/main/java/org/killbill/billing/account/dao/AccountSqlDao.java
index d474747..2eccd3c 100644
--- a/account/src/main/java/org/killbill/billing/account/dao/AccountSqlDao.java
+++ b/account/src/main/java/org/killbill/billing/account/dao/AccountSqlDao.java
@@ -60,4 +60,9 @@ public interface AccountSqlDao extends EntitySqlDao<AccountModelDao, Account> {
@SqlQuery
List<AccountModelDao> getAccountsByParentId(@Bind("parentAccountId") UUID parentAccountId,
@BindBean final InternalTenantContext context);
+
+ @SqlQuery
+ public AccountModelDao luckySearch(@Bind("searchKey") final String searchKey,
+ @BindBean final InternalTenantContext context);
+
}
diff --git a/account/src/main/java/org/killbill/billing/account/dao/DefaultAccountDao.java b/account/src/main/java/org/killbill/billing/account/dao/DefaultAccountDao.java
index f1186a2..b959cb7 100644
--- a/account/src/main/java/org/killbill/billing/account/dao/DefaultAccountDao.java
+++ b/account/src/main/java/org/killbill/billing/account/dao/DefaultAccountDao.java
@@ -38,6 +38,7 @@ import org.killbill.billing.util.audit.ChangeType;
import org.killbill.billing.util.cache.CacheControllerDispatcher;
import org.killbill.billing.util.callcontext.InternalCallContextFactory;
import org.killbill.billing.util.dao.NonEntityDao;
+import org.killbill.billing.util.entity.DefaultPagination;
import org.killbill.billing.util.entity.Pagination;
import org.killbill.billing.util.entity.dao.DefaultPaginationSqlDaoHelper.PaginationIteratorBuilder;
import org.killbill.billing.util.entity.dao.EntityDaoBase;
@@ -51,6 +52,7 @@ import org.skife.jdbi.v2.IDBI;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.google.common.collect.ImmutableList;
import com.google.inject.Inject;
public class DefaultAccountDao extends EntityDaoBase<AccountModelDao, Account, AccountApiException> implements AccountDao {
@@ -110,6 +112,25 @@ public class DefaultAccountDao extends EntityDaoBase<AccountModelDao, Account, A
@Override
public Pagination<AccountModelDao> searchAccounts(final String searchKey, final Long offset, final Long limit, final InternalTenantContext context) {
+ final boolean userIsFeelingLucky = limit == 1 && offset == -1;
+ if (userIsFeelingLucky) {
+ // The use-case we can optimize is when the user is looking for an exact match (e.g. he knows the full email). In that case, we can speed up the queries
+ // by doing exact searches only.
+ final AccountModelDao accountModelDao = transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<AccountModelDao>() {
+ @Override
+ public AccountModelDao inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
+ return entitySqlDaoWrapperFactory.become(AccountSqlDao.class).luckySearch(searchKey, context);
+ }
+ });
+ return new DefaultPagination<AccountModelDao>(0L,
+ 1L,
+ accountModelDao == null ? 0L : 1L,
+ null, // We don't compute stats for speed in that case
+ accountModelDao == null ? ImmutableList.<AccountModelDao>of().iterator() : ImmutableList.<AccountModelDao>of(accountModelDao).iterator());
+ }
+
+ // Otherwise, we pretty much need to do a full table scan (leading % in the like clause).
+ // Note: forcing MySQL to search indexes (like luckySearch above) doesn't always seem to help on large tables, especially with large offsets
return paginationHelper.getPagination(AccountSqlDao.class,
new PaginationIteratorBuilder<AccountModelDao, Account, AccountSqlDao>() {
@Override
diff --git a/account/src/main/resources/org/killbill/billing/account/dao/AccountSqlDao.sql.stg b/account/src/main/resources/org/killbill/billing/account/dao/AccountSqlDao.sql.stg
index d48a16b..f9ddfa5 100644
--- a/account/src/main/resources/org/killbill/billing/account/dao/AccountSqlDao.sql.stg
+++ b/account/src/main/resources/org/killbill/billing/account/dao/AccountSqlDao.sql.stg
@@ -106,6 +106,64 @@ searchQuery(prefix) ::= <<
or <prefix>company_name like :likeSearchKey
>>
+/** Unfortunately, we need to force MySQL to use the individual indexes */
+luckySearch() ::= <<
+select
+<allTableFields("t.")>
+from <tableName()> t
+join (
+ select distinct <recordIdField()>
+ from (
+ (
+ select <recordIdField()>
+ from <tableName()>
+ where <idField()> = :searchKey
+ <andCheckSoftDeletionWithComma()>
+ <AND_CHECK_TENANT()>
+ limit 1
+ )
+ union all
+ (
+ select <recordIdField()>
+ from <tableName()>
+ where name = :searchKey
+ <andCheckSoftDeletionWithComma()>
+ <AND_CHECK_TENANT()>
+ limit 1
+ )
+ union all
+ (
+ select <recordIdField()>
+ from <tableName()>
+ where email = :searchKey
+ <andCheckSoftDeletionWithComma()>
+ <AND_CHECK_TENANT()>
+ limit 1
+ )
+ union all
+ (
+ select <recordIdField()>
+ from <tableName()>
+ where external_key = :searchKey
+ <andCheckSoftDeletionWithComma()>
+ <AND_CHECK_TENANT()>
+ limit 1
+ )
+ union all
+ (
+ select <recordIdField()>
+ from <tableName()>
+ where company_name = :searchKey
+ <andCheckSoftDeletionWithComma()>
+ <AND_CHECK_TENANT()>
+ limit 1
+ )
+ ) search_with_idx
+) optimization on <recordIdField("optimization.")> = <recordIdField("t.")>
+limit 1
+;
+>>
+
getIdFromKey() ::= <<
SELECT id
FROM accounts
@@ -119,4 +177,4 @@ getAccountsByParentId() ::= <<
<AND_CHECK_TENANT()>
<defaultOrderBy()>
;
->>
\ No newline at end of file
+>>
diff --git a/account/src/main/resources/org/killbill/billing/account/ddl.sql b/account/src/main/resources/org/killbill/billing/account/ddl.sql
index a24f272..195eb5b 100644
--- a/account/src/main/resources/org/killbill/billing/account/ddl.sql
+++ b/account/src/main/resources/org/killbill/billing/account/ddl.sql
@@ -37,6 +37,9 @@ CREATE UNIQUE INDEX accounts_id ON accounts(id);
CREATE UNIQUE INDEX accounts_external_key ON accounts(external_key, tenant_record_id);
CREATE INDEX accounts_parents ON accounts(parent_account_id);
CREATE INDEX accounts_tenant_record_id ON accounts(tenant_record_id);
+CREATE INDEX accounts_email_tenant_record_id ON accounts(email, tenant_record_id);
+CREATE INDEX accounts_company_name_tenant_record_id ON accounts(company_name, tenant_record_id);
+CREATE INDEX accounts_name_tenant_record_id ON accounts(name, tenant_record_id);
DROP TABLE IF EXISTS account_history;
CREATE TABLE account_history (
diff --git a/account/src/main/resources/org/killbill/billing/account/migration/V20170123221645__add_lucky_search_indexes.sql b/account/src/main/resources/org/killbill/billing/account/migration/V20170123221645__add_lucky_search_indexes.sql
new file mode 100644
index 0000000..54cd629
--- /dev/null
+++ b/account/src/main/resources/org/killbill/billing/account/migration/V20170123221645__add_lucky_search_indexes.sql
@@ -0,0 +1,3 @@
+alter table accounts add index accounts_email_tenant_record_id(email, tenant_record_id);
+alter table accounts add index accounts_company_name_tenant_record_id(company_name, tenant_record_id);
+alter table accounts add index accounts_name_tenant_record_id(name, tenant_record_id);
diff --git a/account/src/test/java/org/killbill/billing/account/AccountTestUtils.java b/account/src/test/java/org/killbill/billing/account/AccountTestUtils.java
index 7934e12..8b53264 100644
--- a/account/src/test/java/org/killbill/billing/account/AccountTestUtils.java
+++ b/account/src/test/java/org/killbill/billing/account/AccountTestUtils.java
@@ -20,12 +20,12 @@ import java.util.Locale;
import java.util.UUID;
import org.joda.time.DateTimeZone;
-import org.testng.Assert;
-
import org.killbill.billing.account.api.AccountData;
import org.killbill.billing.account.api.DefaultMutableAccountData;
+import org.killbill.billing.account.api.MutableAccountData;
import org.killbill.billing.account.dao.AccountModelDao;
import org.killbill.billing.catalog.api.Currency;
+import org.testng.Assert;
public abstract class AccountTestUtils {
@@ -81,11 +81,11 @@ public abstract class AccountTestUtils {
return new AccountModelDao(UUID.randomUUID(), accountData);
}
- public static AccountData createAccountData() {
+ public static MutableAccountData createAccountData() {
return createAccountData(30, 31, UUID.randomUUID().toString().substring(0, 4));
}
- private static AccountData createAccountData(final int billCycleDayUTC, final int billCycleDayLocal, final String phone) {
+ private static MutableAccountData createAccountData(final int billCycleDayUTC, final int billCycleDayLocal, final String phone) {
final String externalKey = UUID.randomUUID().toString();
final String email = UUID.randomUUID().toString().substring(0, 4) + '@' + UUID.randomUUID().toString().substring(0, 4);
final String name = UUID.randomUUID().toString();
diff --git a/account/src/test/java/org/killbill/billing/account/api/TestDefaultAccount.java b/account/src/test/java/org/killbill/billing/account/api/TestDefaultAccount.java
index f88b9e0..f5f3077 100644
--- a/account/src/test/java/org/killbill/billing/account/api/TestDefaultAccount.java
+++ b/account/src/test/java/org/killbill/billing/account/api/TestDefaultAccount.java
@@ -110,17 +110,20 @@ public class TestDefaultAccount extends AccountTestSuiteNoDB {
// Null BCD -> 0 BCD
Assert.assertEquals(accountWithNullBCD.getBillCycleDayLocal(), (Integer) 0);
- final AccountData accountDataWithZeroBCD = getAccountData(0, currency, externalKey);
+ final DefaultMutableAccountData accountDataWithZeroBCD = new DefaultMutableAccountData(accountDataWithNullBCD);
+ accountDataWithZeroBCD.setBillCycleDayLocal(0);
final Account accountWithZeroBCD = new DefaultAccount(accountId, accountDataWithZeroBCD);
// Null BCD and 0 BCD -> 0 BCD
Assert.assertEquals(accountWithNullBCD.mergeWithDelegate(accountWithZeroBCD).getBillCycleDayLocal(), (Integer) 0);
- final AccountData accountDataWithRealBCD = getAccountData(12, currency, externalKey);
+ final DefaultMutableAccountData accountDataWithRealBCD = new DefaultMutableAccountData(accountDataWithNullBCD);
+ accountDataWithRealBCD.setBillCycleDayLocal(12);
final Account accountWithRealBCD = new DefaultAccount(accountId, accountDataWithRealBCD);
// Null BCD and real BCD -> real BCD
Assert.assertEquals(accountWithNullBCD.mergeWithDelegate(accountWithRealBCD).getBillCycleDayLocal(), (Integer) 12);
- final AccountData accountDataWithAnotherRealBCD = getAccountData(20, currency, externalKey);
+ final DefaultMutableAccountData accountDataWithAnotherRealBCD = new DefaultMutableAccountData(accountDataWithNullBCD);
+ accountDataWithAnotherRealBCD.setBillCycleDayLocal(20);
final Account accountWithAnotherBCD = new DefaultAccount(accountId, accountDataWithAnotherRealBCD);
// Same BCD
Assert.assertEquals(accountWithAnotherBCD.mergeWithDelegate(accountWithAnotherBCD).getBillCycleDayLocal(), (Integer) 20);
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 338c58c..3fa19fc 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
@@ -20,9 +20,12 @@ package org.killbill.billing.account.api.user;
import java.util.LinkedList;
import java.util.List;
+import java.util.TimeZone;
import java.util.UUID;
import java.util.concurrent.Callable;
+import java.util.spi.TimeZoneNameProvider;
+import org.joda.time.DateTimeZone;
import org.killbill.billing.ErrorCode;
import org.killbill.billing.account.AccountTestSuiteWithEmbeddedDB;
import org.killbill.billing.account.api.Account;
@@ -34,18 +37,77 @@ import org.killbill.billing.account.api.MutableAccountData;
import org.killbill.billing.account.dao.AccountModelDao;
import org.killbill.billing.catalog.api.Currency;
import org.killbill.billing.events.AccountCreationInternalEvent;
+import org.killbill.billing.util.entity.Pagination;
import org.testng.Assert;
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 java.util.concurrent.TimeUnit.SECONDS;
+import static org.killbill.billing.account.AccountTestUtils.createAccountData;
import static org.killbill.billing.account.AccountTestUtils.createTestAccount;
+import static org.killbill.billing.account.api.DefaultMutableAccountData.DEFAULT_BILLING_CYCLE_DAY_LOCAL;
import static org.testng.Assert.assertEquals;
public class TestDefaultAccountUserApi extends AccountTestSuiteWithEmbeddedDB {
+ @Test(groups = "slow", description = "Test Account search")
+ public void testSearch() throws Exception {
+ final MutableAccountData mutableAccountData1 = createAccountData();
+ mutableAccountData1.setEmail("john@acme.com");
+ mutableAccountData1.setCompanyName("Acme, Inc.");
+ final AccountModelDao account1ModelDao = new AccountModelDao(UUID.randomUUID(), mutableAccountData1);
+ final AccountData accountData1 = new DefaultAccount(account1ModelDao);
+ accountUserApi.createAccount(accountData1, callContext);
+
+ final MutableAccountData mutableAccountData2 = createAccountData();
+ mutableAccountData2.setEmail("bob@gmail.com");
+ mutableAccountData2.setCompanyName("Acme, Inc.");
+ final AccountModelDao account2ModelDao = new AccountModelDao(UUID.randomUUID(), mutableAccountData2);
+ final AccountData accountData2 = new DefaultAccount(account2ModelDao);
+ accountUserApi.createAccount(accountData2, callContext);
+
+ final Pagination<Account> search1 = accountUserApi.searchAccounts("Inc.", 0L, 5L, callContext);
+ Assert.assertEquals(search1.getCurrentOffset(), (Long) 0L);
+ Assert.assertNull(search1.getNextOffset());
+ Assert.assertEquals(search1.getMaxNbRecords(), (Long) 2L);
+ Assert.assertEquals(search1.getTotalNbRecords(), (Long) 2L);
+ Assert.assertEquals(ImmutableList.<Account>copyOf(search1.iterator()).size(), 2);
+
+ final Pagination<Account> search2 = accountUserApi.searchAccounts("Inc.", 0L, 1L, callContext);
+ Assert.assertEquals(search2.getCurrentOffset(), (Long) 0L);
+ Assert.assertEquals(search2.getNextOffset(), (Long) 1L);
+ Assert.assertEquals(search2.getMaxNbRecords(), (Long) 2L);
+ Assert.assertEquals(search2.getTotalNbRecords(), (Long) 2L);
+ Assert.assertEquals(ImmutableList.<Account>copyOf(search2.iterator()).size(), 1);
+
+ final Pagination<Account> search3 = accountUserApi.searchAccounts("acme.com", 0L, 5L, callContext);
+ Assert.assertEquals(search3.getCurrentOffset(), (Long) 0L);
+ Assert.assertNull(search3.getNextOffset());
+ Assert.assertEquals(search3.getMaxNbRecords(), (Long) 2L);
+ Assert.assertEquals(search3.getTotalNbRecords(), (Long) 1L);
+ Assert.assertEquals(ImmutableList.<Account>copyOf(search3.iterator()).size(), 1);
+
+ // Exact search will fail
+ final Pagination<Account> search4 = accountUserApi.searchAccounts("acme.com", -1L, 1L, callContext);
+ Assert.assertEquals(search4.getCurrentOffset(), (Long) 0L);
+ Assert.assertNull(search4.getNextOffset());
+ // Not computed
+ Assert.assertNull(search4.getMaxNbRecords());
+ Assert.assertEquals(search4.getTotalNbRecords(), (Long) 0L);
+ Assert.assertEquals(ImmutableList.<Account>copyOf(search4.iterator()).size(), 0);
+
+ final Pagination<Account> search5 = accountUserApi.searchAccounts("john@acme.com", -1L, 1L, callContext);
+ Assert.assertEquals(search5.getCurrentOffset(), (Long) 0L);
+ Assert.assertNull(search5.getNextOffset());
+ // Not computed
+ Assert.assertNull(search5.getMaxNbRecords());
+ Assert.assertEquals(search5.getTotalNbRecords(), (Long) 1L);
+ Assert.assertEquals(ImmutableList.<Account>copyOf(search5.iterator()).size(), 1);
+ }
+
@Test(groups = "slow", description = "Test Account creation generates an event")
public void testBusEvents() throws Exception {
final AccountEventHandler eventHandler = new AccountEventHandler();
@@ -119,6 +181,136 @@ public class TestDefaultAccountUserApi extends AccountTestSuiteWithEmbeddedDB {
accountUserApi.updateAccount(new DefaultAccount(account.getId(), otherAccount), callContext);
}
+
+ @Test(groups = "slow", description = "Test Account update to reset notes")
+ public void testAccountResetAccountNotes() throws Exception {
+ final Account account = createAccount(new DefaultAccount(createTestAccount()));
+
+ // Update the address and leave other fields null
+ final MutableAccountData mutableAccountData = new DefaultMutableAccountData(account);
+ mutableAccountData.setNotes(null);
+
+ DefaultAccount newAccount = new DefaultAccount(account.getId(), mutableAccountData);
+ accountUserApi.updateAccount(newAccount, callContext);
+
+ final Account retrievedAccount = accountUserApi.getAccountById(account.getId(), callContext);
+
+ Assert.assertEquals(retrievedAccount.getName(), account.getName());
+ Assert.assertEquals(retrievedAccount.getFirstNameLength(), account.getFirstNameLength());
+ Assert.assertEquals(retrievedAccount.getEmail(), account.getEmail());
+ Assert.assertEquals(retrievedAccount.getBillCycleDayLocal(), account.getBillCycleDayLocal());
+ Assert.assertEquals(retrievedAccount.getCurrency(), account.getCurrency());
+ Assert.assertEquals(retrievedAccount.getPaymentMethodId(), account.getPaymentMethodId());
+ Assert.assertEquals(retrievedAccount.getTimeZone(), account.getTimeZone());
+ Assert.assertEquals(retrievedAccount.getLocale(), account.getLocale());
+ Assert.assertEquals(retrievedAccount.getAddress1(), account.getAddress1());
+ Assert.assertEquals(retrievedAccount.getAddress2(), account.getAddress2());
+ Assert.assertEquals(retrievedAccount.getCompanyName(), account.getCompanyName());
+ Assert.assertEquals(retrievedAccount.getCity(), account.getCity());
+ Assert.assertEquals(retrievedAccount.getStateOrProvince(), account.getStateOrProvince());
+ Assert.assertEquals(retrievedAccount.getPostalCode(), account.getPostalCode());
+ Assert.assertEquals(retrievedAccount.getCountry(), account.getCountry());
+ Assert.assertEquals(retrievedAccount.getPhone(), account.getPhone());
+ Assert.assertEquals(retrievedAccount.isMigrated(), account.isMigrated());
+ Assert.assertEquals(retrievedAccount.isNotifiedForInvoices(), account.isNotifiedForInvoices());
+ Assert.assertEquals(retrievedAccount.getParentAccountId(), account.getParentAccountId());
+ Assert.assertEquals(retrievedAccount.isPaymentDelegatedToParent(), account.isPaymentDelegatedToParent());
+ // Finally check account notes did get reset
+ Assert.assertNull(retrievedAccount.getNotes());
+ }
+
+
+ @Test(groups = "slow", description = "Test failure on resetting externalKey", expectedExceptions = IllegalArgumentException.class)
+ public void testAccountResetExternalKey() throws Exception {
+ final Account account = createAccount(new DefaultAccount(createTestAccount()));
+
+ // Update the address and leave other fields null
+ final MutableAccountData mutableAccountData = new DefaultMutableAccountData(account);
+ mutableAccountData.setExternalKey(null);
+
+ DefaultAccount newAccount = new DefaultAccount(account.getId(), mutableAccountData);
+ accountUserApi.updateAccount(newAccount, callContext);
+ }
+
+
+ @Test(groups = "slow", description = "Test failure on changing externalKey", expectedExceptions = IllegalArgumentException.class)
+ public void testAccountChangeExternalKey() throws Exception {
+ final Account account = createAccount(new DefaultAccount(createTestAccount()));
+
+ // Update the address and leave other fields null
+ final MutableAccountData mutableAccountData = new DefaultMutableAccountData(account);
+ mutableAccountData.setExternalKey("somethingVeryDifferent");
+
+ DefaultAccount newAccount = new DefaultAccount(account.getId(), mutableAccountData);
+ accountUserApi.updateAccount(newAccount, callContext);
+ }
+
+
+ @Test(groups = "slow", description = "Test failure on resetting currency", expectedExceptions = IllegalArgumentException.class)
+ public void testAccountResetCurrency() throws Exception {
+ final Account account = createAccount(new DefaultAccount(createTestAccount()));
+
+ // Update the address and leave other fields null
+ final MutableAccountData mutableAccountData = new DefaultMutableAccountData(account);
+ mutableAccountData.setCurrency(null);
+
+ DefaultAccount newAccount = new DefaultAccount(account.getId(), mutableAccountData);
+ accountUserApi.updateAccount(newAccount, callContext);
+ }
+
+
+ @Test(groups = "slow", description = "Test failure on changing currency", expectedExceptions = IllegalArgumentException.class)
+ public void testAccountChangeCurrency() throws Exception {
+ final Account account = createAccount(new DefaultAccount(createTestAccount()));
+
+ // Update the address and leave other fields null
+ final MutableAccountData mutableAccountData = new DefaultMutableAccountData(account);
+ mutableAccountData.setCurrency(Currency.AFN);
+
+ DefaultAccount newAccount = new DefaultAccount(account.getId(), mutableAccountData);
+ accountUserApi.updateAccount(newAccount, callContext);
+ }
+
+ @Test(groups = "slow", description = "Test failure on resetting BCD", expectedExceptions = IllegalArgumentException.class)
+ public void testAccountResetBCD() throws Exception {
+ final Account account = createAccount(new DefaultAccount(createTestAccount()));
+
+ // Update the address and leave other fields null
+ final MutableAccountData mutableAccountData = new DefaultMutableAccountData(account);
+ mutableAccountData.setBillCycleDayLocal(DEFAULT_BILLING_CYCLE_DAY_LOCAL);
+
+ DefaultAccount newAccount = new DefaultAccount(account.getId(), mutableAccountData);
+ accountUserApi.updateAccount(newAccount, callContext);
+ }
+
+ @Test(groups = "slow", description = "Test failure on resetting timeZone", expectedExceptions = IllegalArgumentException.class)
+ public void testAccountResetTimeZone() throws Exception {
+ final Account account = createAccount(new DefaultAccount(createTestAccount()));
+
+ // Update the address and leave other fields null
+ final MutableAccountData mutableAccountData = new DefaultMutableAccountData(account);
+ mutableAccountData.setTimeZone(null);
+
+ DefaultAccount newAccount = new DefaultAccount(account.getId(), mutableAccountData);
+ accountUserApi.updateAccount(newAccount, callContext);
+ }
+
+
+ @Test(groups = "slow", description = "Test failure on changing timeZone", expectedExceptions = IllegalArgumentException.class)
+ public void testAccountChangingTimeZone() throws Exception {
+ final Account account = createAccount(new DefaultAccount(createTestAccount()));
+
+ // Update the address and leave other fields null
+ final MutableAccountData mutableAccountData = new DefaultMutableAccountData(account);
+ mutableAccountData.setTimeZone(DateTimeZone.UTC);
+
+ DefaultAccount newAccount = new DefaultAccount(account.getId(), mutableAccountData);
+ accountUserApi.updateAccount(newAccount, callContext);
+ }
+
+
+
+
private static final class AccountEventHandler {
private final List<AccountCreationInternalEvent> accountCreationInternalEvents = new LinkedList<AccountCreationInternalEvent>();
api/pom.xml 2(+1 -1)
diff --git a/api/pom.xml b/api/pom.xml
index f024002..60fcf7c 100644
--- a/api/pom.xml
+++ b/api/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.18.2-SNAPSHOT</version>
+ <version>0.18.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-internal-api</artifactId>
diff --git a/api/src/main/java/org/killbill/billing/events/SubscriptionInternalEvent.java b/api/src/main/java/org/killbill/billing/events/SubscriptionInternalEvent.java
index 1073d87..7937ed8 100644
--- a/api/src/main/java/org/killbill/billing/events/SubscriptionInternalEvent.java
+++ b/api/src/main/java/org/killbill/billing/events/SubscriptionInternalEvent.java
@@ -30,6 +30,8 @@ public interface SubscriptionInternalEvent extends BusInternalEvent {
UUID getBundleId();
+ String getBundleExternalKey();
+
UUID getSubscriptionId();
DateTime getSubscriptionStartDate();
beatrix/pom.xml 2(+1 -1)
diff --git a/beatrix/pom.xml b/beatrix/pom.xml
index af68cf9..2bbe74a 100644
--- a/beatrix/pom.xml
+++ b/beatrix/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.18.2-SNAPSHOT</version>
+ <version>0.18.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-beatrix</artifactId>
diff --git a/beatrix/src/main/java/org/killbill/billing/beatrix/extbus/BeatrixListener.java b/beatrix/src/main/java/org/killbill/billing/beatrix/extbus/BeatrixListener.java
index 8d4eff7..913eb94 100644
--- a/beatrix/src/main/java/org/killbill/billing/beatrix/extbus/BeatrixListener.java
+++ b/beatrix/src/main/java/org/killbill/billing/beatrix/extbus/BeatrixListener.java
@@ -162,7 +162,7 @@ public class BeatrixListener {
}
SubscriptionMetadata.ActionType actionType = (event instanceof EffectiveSubscriptionInternalEvent) ? ActionType.EFFECTIVE : ActionType.REQUESTED;
- final SubscriptionMetadata subscriptionMetadataObj = new SubscriptionMetadata(actionType);
+ final SubscriptionMetadata subscriptionMetadataObj = new SubscriptionMetadata(actionType, realEventST.getBundleExternalKey());
metaData = objectMapper.writeValueAsString(subscriptionMetadataObj);
break;
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueIntegration.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueIntegration.java
index 7b6b8da..7dd3961 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueIntegration.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueIntegration.java
@@ -45,6 +45,7 @@ import org.killbill.billing.invoice.api.Invoice;
import org.killbill.billing.invoice.api.InvoiceItem;
import org.killbill.billing.invoice.api.InvoiceItemType;
import org.killbill.billing.invoice.api.InvoicePayment;
+import org.killbill.billing.invoice.generator.InvoiceWithMetadata;
import org.killbill.billing.invoice.model.ExternalChargeInvoiceItem;
import org.killbill.billing.overdue.config.DefaultOverdueConfig;
import org.killbill.billing.overdue.wrapper.OverdueWrapper;
@@ -1097,6 +1098,56 @@ public class TestOverdueIntegration extends TestOverdueBase {
checkODState(OverdueWrapper.CLEAR_STATE_NAME);
}
+
+
+ @Test(groups = "slow", description = "Test clearing balance with credit also clears overdue state")
+ public void testOverdueClearWithCredit() throws Exception {
+ // 2012-05-01T00:03:42.000Z
+ clock.setTime(new DateTime(2012, 5, 1, 0, 3, 42, 0));
+
+ setupAccount();
+
+ // Set next invoice to fail and create subscription
+ paymentPlugin.makeAllInvoicesFailWithError(true);
+ final DefaultEntitlement baseEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "externalKey", productName, ProductCategory.BASE, term, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
+ bundle = subscriptionApi.getSubscriptionBundle(baseEntitlement.getBundleId(), callContext);
+
+ invoiceChecker.checkInvoice(account.getId(), 1, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), null, InvoiceItemType.FIXED, new BigDecimal("0")));
+ invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 5, 1), callContext);
+
+ // 2012-05-31 => DAY 30 have to get out of trial {I0, P0}
+ addDaysAndCheckForCompletion(30, NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
+
+ invoiceChecker.checkInvoice(account.getId(), 2, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 31), new LocalDate(2012, 6, 30), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
+ invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 6, 30), callContext);
+
+ // 2012-06-08 => Retry P0
+ addDaysAndCheckForCompletion(8, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
+ checkODState(OverdueWrapper.CLEAR_STATE_NAME);
+
+ // 2012-06-16 => Retry P0
+ addDaysAndCheckForCompletion(8, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
+ checkODState(OverdueWrapper.CLEAR_STATE_NAME);
+
+ // 2012-06-24 => Retry P0
+ addDaysAndCheckForCompletion(8, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
+ checkODState(OverdueWrapper.CLEAR_STATE_NAME);
+
+ // 2012-06-30 => P1
+ addDaysAndCheckForCompletion(6, NextEvent.BLOCK, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
+ checkODState("OD1");
+ checkChangePlanWithOverdueState(baseEntitlement, true, true);
+ invoiceChecker.checkInvoice(account.getId(), 3, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 6, 30), new LocalDate(2012, 7, 31), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
+ invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 7, 31), callContext);
+
+ final BigDecimal accountBalance = invoiceUserApi.getAccountBalance(account.getId(), callContext);
+
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.BLOCK);
+ invoiceUserApi.insertCredit(account.getId(), accountBalance, new LocalDate(2012, 06, 30), account.getCurrency(), true, "credit invoice", callContext);
+ assertListenerStatus();
+
+ }
+
private void allowPaymentsAndResetOverdueToClearByPayingAllUnpaidInvoices(final boolean extraPayment) {
// Reset plugin so payments should now succeed
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueWithSubscriptionEOTCancellation.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueWithSubscriptionEOTCancellation.java
new file mode 100644
index 0000000..a91f3b0
--- /dev/null
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueWithSubscriptionEOTCancellation.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.beatrix.integration.overdue;
+
+import java.math.BigDecimal;
+
+import org.joda.time.DateTime;
+import org.joda.time.LocalDate;
+import org.killbill.billing.api.TestApiListener.NextEvent;
+import org.killbill.billing.beatrix.util.InvoiceChecker.ExpectedInvoiceItemCheck;
+import org.killbill.billing.catalog.api.BillingPeriod;
+import org.killbill.billing.catalog.api.ProductCategory;
+import org.killbill.billing.entitlement.api.DefaultEntitlement;
+import org.killbill.billing.entitlement.api.Entitlement.EntitlementState;
+import org.killbill.billing.entitlement.api.Subscription;
+import org.killbill.billing.invoice.api.InvoiceItemType;
+import org.killbill.billing.overdue.wrapper.OverdueWrapper;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+@Test(groups = "slow")
+public class TestOverdueWithSubscriptionEOTCancellation extends TestOverdueBase {
+
+ @Override
+ public String getOverdueConfig() {
+ final String configXml = "<overdueConfig>" +
+ " <accountOverdueStates>" +
+ " <initialReevaluationInterval>" +
+ " <unit>DAYS</unit><number>5</number>" +
+ " </initialReevaluationInterval>" +
+ " <state name=\"OD1\">" +
+ " <condition>" +
+ " <timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>" +
+ " <unit>DAYS</unit><number>5</number>" +
+ " </timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>" +
+ " </condition>" +
+ " <externalMessage>Reached OD1</externalMessage>" +
+ " <blockChanges>true</blockChanges>" +
+ " <disableEntitlementAndChangesBlocked>false</disableEntitlementAndChangesBlocked>" +
+ " <subscriptionCancellationPolicy>END_OF_TERM</subscriptionCancellationPolicy>" +
+ " <autoReevaluationInterval>" +
+ " <unit>DAYS</unit><number>5</number>" +
+ " </autoReevaluationInterval>" +
+ " </state>" +
+ " </accountOverdueStates>" +
+ "</overdueConfig>";
+ return configXml;
+ }
+
+ @Test(groups = "slow")
+ public void testCheckSubscriptionEOTCancellation() throws Exception {
+ clock.setTime(new DateTime(2012, 5, 1, 0, 3, 42, 0));
+
+ setupAccount();
+
+ // Set next invoice to fail and create subscription
+ paymentPlugin.makeAllInvoicesFailWithError(true);
+ final DefaultEntitlement baseEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "externalKey", productName, ProductCategory.BASE, term, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
+ bundle = subscriptionApi.getSubscriptionBundle(baseEntitlement.getBundleId(), callContext);
+
+ invoiceChecker.checkInvoice(account.getId(), 1, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), null, InvoiceItemType.FIXED, new BigDecimal("0")));
+ invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 5, 1), callContext);
+
+ final DefaultEntitlement addOn1 = addAOEntitlementAndCheckForCompletion(baseEntitlement.getBundleId(), "Holster", ProductCategory.ADD_ON, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
+
+ // DAY 30 have to get out of trial before first payment
+ addDaysAndCheckForCompletion(30, NextEvent.PHASE, NextEvent.PHASE, NextEvent.NULL_INVOICE, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
+
+ invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 6, 30), callContext);
+ invoiceChecker.checkChargedThroughDate(addOn1.getId(), new LocalDate(2012, 6, 30), callContext);
+
+ // Should still be in clear state
+ checkODState(OverdueWrapper.CLEAR_STATE_NAME);
+
+ // We expect one event for OD1 transition and for for each entitlement cancellation (entitlement cancellation is immediate)
+ addDaysAndCheckForCompletion(6, NextEvent.BLOCK, NextEvent.BLOCK, NextEvent.BLOCK);
+
+ // Should be in OD1
+ checkODState("OD1");
+
+ final Subscription cancelledBaseSubscription = subscriptionApi.getSubscriptionForEntitlementId(baseEntitlement.getId(), callContext);
+ assertTrue(cancelledBaseSubscription.getState() == EntitlementState.CANCELLED);
+ assertEquals(cancelledBaseSubscription.getEffectiveEndDate(), new LocalDate(2012, 6, 05));
+ assertEquals(cancelledBaseSubscription.getBillingEndDate(), new LocalDate(2012, 6, 30));
+
+ final Subscription cancelledAddon1 = subscriptionApi.getSubscriptionForEntitlementId(addOn1.getId(), callContext);
+ assertTrue(cancelledAddon1.getState() == EntitlementState.CANCELLED);
+ assertEquals(cancelledAddon1.getEffectiveEndDate(), new LocalDate(2012, 6, 05));
+ assertEquals(cancelledAddon1.getBillingEndDate(), new LocalDate(2012, 6, 30));
+
+ // Payment Retry on the 2012-6-8
+ addDaysAndCheckForCompletion(2, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
+ assertListenerStatus();
+
+ // Payment Retry on the 2012-6-16
+ addDaysAndCheckForCompletion(8, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
+ assertListenerStatus();
+
+ // Payment Retry on the 2012-6-24
+ addDaysAndCheckForCompletion(8, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
+ assertListenerStatus();
+
+ // 2012-6-30
+ addDaysAndCheckForCompletion(6, NextEvent.NULL_INVOICE, NextEvent.NULL_INVOICE, NextEvent.CANCEL, NextEvent.CANCEL);
+ assertListenerStatus();
+ }
+
+}
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithCatalogUpdate.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithCatalogUpdate.java
index f07eb82..bce98f5 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithCatalogUpdate.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithCatalogUpdate.java
@@ -359,9 +359,44 @@ public class TestIntegrationWithCatalogUpdate extends TestIntegrationBase {
refreshedBaseEntitlement2 = subscriptionApi.getSubscriptionForEntitlementId(baseEntitlement2.getId(), testCallContext);
assertEquals(refreshedBaseEntitlement2.getChargedThroughDate(), new LocalDate(2017, 1, 1));
+ }
+
+
+ @Test(groups = "slow")
+ public void testWithWeeklyTrials() throws Exception {
+
+ // Create a per-tenant catalog with one plan
+ final SimplePlanDescriptor simplePlanDescriptor = new DefaultSimplePlanDescriptor("hello-monthly", "Hello", ProductCategory.BASE, account.getCurrency(), BigDecimal.ONE, BillingPeriod.MONTHLY, 1, TimeUnit.WEEKS, ImmutableList.<String>of());
+ catalogUserApi.addSimplePlan(simplePlanDescriptor, init, testCallContext);
+ StaticCatalog catalog = catalogUserApi.getCurrentCatalog("dummy", testCallContext);
+ assertEquals(catalog.getCurrentPlans().size(), 1);
+
+ final PlanPhaseSpecifier planPhaseSpec = new PlanPhaseSpecifier("hello-monthly", null);
+
+ busHandler.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
+ final Entitlement baseEntitlement = entitlementApi.createBaseEntitlement(account.getId(), planPhaseSpec, UUID.randomUUID().toString(), ImmutableList.<PlanPhasePriceOverride>of(), null, null, false, ImmutableList.<PluginProperty>of(), testCallContext);
+ assertListenerStatus();
+
+ Subscription refreshedBaseEntitlement = subscriptionApi.getSubscriptionForEntitlementId(baseEntitlement.getId(), testCallContext);
+ assertEquals(refreshedBaseEntitlement.getChargedThroughDate(), new LocalDate(2016, 6, 1));
+
+ busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.INVOICE_PAYMENT, NextEvent.PAYMENT);
+ clock.addWeeks(1);
+ assertListenerStatus();
+ refreshedBaseEntitlement = subscriptionApi.getSubscriptionForEntitlementId(baseEntitlement.getId(), testCallContext);
+ assertEquals(refreshedBaseEntitlement.getChargedThroughDate(), new LocalDate(2016, 7, 8));
+
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.INVOICE_PAYMENT, NextEvent.PAYMENT);
+ clock.addMonths(1);
+ assertListenerStatus();
+
+
+ refreshedBaseEntitlement = subscriptionApi.getSubscriptionForEntitlementId(baseEntitlement.getId(), testCallContext);
+ assertEquals(refreshedBaseEntitlement.getChargedThroughDate(), new LocalDate(2016, 8, 8));
}
+
private Entitlement createEntitlement(final String planName, final boolean expectPayment) throws EntitlementApiException {
final PlanPhaseSpecifier spec = new PlanPhaseSpecifier(planName, null);
return createEntitlement(spec, null, expectPayment);
@@ -392,7 +427,7 @@ public class TestIntegrationWithCatalogUpdate extends TestIntegrationBase {
private void setupAccount() throws Exception {
- final AccountData accountData = getAccountData(1);
+ final AccountData accountData = getAccountData(0);
account = accountUserApi.createAccount(accountData, testCallContext);
assertNotNull(account);
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestPaymentRefund.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestPaymentRefund.java
index b501af7..86912e4 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestPaymentRefund.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestPaymentRefund.java
@@ -116,8 +116,7 @@ public class TestPaymentRefund extends TestIntegrationBase {
}
}
- @Test(groups = "slow", description = "https://github.com/killbill/killbill/issues/255",
- expectedExceptions = PaymentApiException.class, expectedExceptionsMessageRegExp = "Payment method .* does not exist")
+ @Test(groups = "slow", description = "https://github.com/killbill/killbill/issues/255")
public void testRefundWithDeletedPaymentMethod() throws Exception {
// delete payment method
@@ -126,8 +125,10 @@ public class TestPaymentRefund extends TestIntegrationBase {
assertListenerStatus();
// try to create a refund for a payment with its payment method deleted
+ busHandler.pushExpectedEvent(NextEvent.PAYMENT);
paymentApi.createRefund(account, payment.getId(), payment.getPurchasedAmount(), payment.getCurrency(),
UUID.randomUUID().toString(), PLUGIN_PROPERTIES, callContext);
+ assertListenerStatus();
}
private void setupRefundTest() throws Exception {
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 ae8b7a3..2d5f61e 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
@@ -18,6 +18,7 @@
package org.killbill.billing.beatrix.integration;
+import java.io.IOException;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicInteger;
@@ -26,12 +27,15 @@ import org.joda.time.DateTime;
import org.killbill.billing.DBTestingHelper;
import org.killbill.billing.account.api.Account;
import org.killbill.billing.api.TestApiListener.NextEvent;
+import org.killbill.billing.beatrix.extbus.DefaultBusExternalEvent;
import org.killbill.billing.callcontext.DefaultCallContext;
import org.killbill.billing.catalog.api.BillingPeriod;
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.notification.plugin.api.ExtBusEvent;
+import org.killbill.billing.notification.plugin.api.ExtBusEventType;
+import org.killbill.billing.notification.plugin.api.SubscriptionMetadata;
import org.killbill.billing.overdue.api.OverdueConfig;
import org.killbill.billing.platform.api.KillbillConfigSource;
import org.killbill.billing.tenant.api.DefaultTenant;
@@ -41,15 +45,19 @@ import org.killbill.billing.tenant.api.TenantKV.TenantKey;
import org.killbill.billing.util.callcontext.CallContext;
import org.killbill.billing.util.callcontext.CallOrigin;
import org.killbill.billing.util.callcontext.UserType;
+import org.killbill.billing.util.jackson.ObjectMapper;
import org.killbill.billing.util.nodes.NodeCommand;
import org.killbill.billing.util.nodes.NodeCommandMetadata;
import org.killbill.billing.util.nodes.NodeCommandProperty;
import org.killbill.billing.util.nodes.PluginNodeCommandMetadata;
import org.killbill.billing.util.nodes.SystemNodeCommandType;
+import org.testng.Assert;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
+import com.fasterxml.jackson.core.JsonParseException;
+import com.fasterxml.jackson.databind.JsonMappingException;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.eventbus.Subscribe;
@@ -64,6 +72,8 @@ public class TestPublicBus extends TestIntegrationBase {
private AtomicInteger externalBusCount;
+ private final ObjectMapper mapper = new ObjectMapper();
+
@Override
protected KillbillConfigSource getConfigSource() {
ImmutableMap additionalProperties = new ImmutableMap.Builder()
@@ -78,6 +88,26 @@ public class TestPublicBus extends TestIntegrationBase {
@Subscribe
public void handleExternalEvents(final ExtBusEvent event) {
log.info("GOT EXT EVENT " + event);
+
+ if (event.getEventType() == ExtBusEventType.SUBSCRIPTION_CREATION ||
+ event.getEventType() == ExtBusEventType.SUBSCRIPTION_CANCEL ||
+ event.getEventType() == ExtBusEventType.SUBSCRIPTION_PHASE ||
+ event.getEventType() == ExtBusEventType.SUBSCRIPTION_CHANGE ||
+ event.getEventType() == ExtBusEventType.SUBSCRIPTION_UNCANCEL ||
+ event.getEventType() == ExtBusEventType.SUBSCRIPTION_BCD_CHANGE) {
+ try {
+ final SubscriptionMetadata obj = (SubscriptionMetadata) mapper.readValue(event.getMetaData(), SubscriptionMetadata.class);
+ Assert.assertNotNull(obj.getBundleExternalKey());
+ Assert.assertNotNull(obj.getActionType());
+ } catch (JsonParseException e) {
+ Assert.fail("Could not deserialize metada section", e);
+ } catch (JsonMappingException e) {
+ Assert.fail("Could not deserialize metada section", e);
+ } catch (IOException e) {
+ Assert.fail("Could not deserialize metada section", e);
+ }
+ }
+
externalBusCount.incrementAndGet();
}
bin/import-account 53(+34 -19)
diff --git a/bin/import-account b/bin/import-account
index 1d3460c..74343dd 100755
--- a/bin/import-account
+++ b/bin/import-account
@@ -25,6 +25,14 @@
KILLBILL_HOST=${KILLBILL_HOST-127.0.0.1}
KILLBILL_URL=http://${KILLBILL_HOST}:8080
+# USER/PWD
+KILLBILL_USER=${KILLBILL_USER-admin}
+KILLBILL_PWD=${KILLBILL_PWD-password}
+
+# TENANT KEY
+KILLBILL_API_KEY=${KILLBILL_API_KEY-bob}
+KILLBILL_API_SECRET=${KILLBILL_API_SECRET-lazar}
+
# Destination database
DATABASE=${DATABASE-killbill}
USERNAME=${USERNAME-root}
@@ -39,18 +47,18 @@ function fill_empty_columns() {
local filename=$1
local tmp=${filename}.tmp
- grep ',,' $filename > /dev/null
+ grep '||' $filename > /dev/null
while [[ $? = 0 ]]; do
- cat $filename | sed s/,,/,\\\\N,/ > $tmp
+ cat $filename | sed s/\|\|/\|\\\\N\|/ > $tmp
mv $tmp $filename
- grep ',,' $filename > /dev/null
+ grep '||' $filename > /dev/null
done
- grep ',$' $filename > /dev/null
+ grep '|$' $filename > /dev/null
while [[ $? = 0 ]]; do
- cat $filename | sed s/,$/,\\\\N/ > $tmp
+ cat $filename | sed s/\|$/\|\\\\N/ > $tmp
mv $tmp $filename
- grep ',$' $filename > /dev/null
+ grep '|$' $filename > /dev/null
done
}
@@ -60,16 +68,16 @@ function replace_boolean() {
local filename=$1
local tmp=${filename}.tmp
- cat $filename | sed s/,true/,1/g > $tmp
+ cat $filename | sed s/\|true/\|1/g > $tmp
mv $tmp $filename
- cat $filename | sed s/true,/1,/g > $tmp
+ cat $filename | sed s/true\|/1\|/g > $tmp
mv $tmp $filename
- cat $filename | sed s/,false/,0/g > $tmp
+ cat $filename | sed s/\|false/\|0/g > $tmp
mv $tmp $filename
- cat $filename | sed s/false,/0,/g > $tmp
+ cat $filename | sed s/false\|/0\|/g > $tmp
mv $tmp $filename
}
@@ -78,19 +86,22 @@ function fix_dates() {
local filename=$1
local tmp=${filename}.tmp
- cat $filename | sed s/+0000\",/\",/g > $tmp
+ cat $filename | sed s/+0000\"\|/\"\|/g > $tmp
mv $tmp $filename
}
function export_data() {
local account_id=$1
- curl $KILLBILL_URL/1.0/kb/export/$1 -H"X-Killbill-CreatedBy: $WHO" > $TMP_DIR/kbdump
+ curl $KILLBILL_URL/1.0/kb/export/$1 -u "$KILLBILL_USER:$KILLBILL_PWD" -H "X-Killbill-ApiKey: $KILLBILL_API_KEY" -H "X-Killbill-ApiSecret: $KILLBILL_API_SECRET" -H"X-Killbill-CreatedBy: $WHO" > $TMP_DIR/kbdump
+ echo "Data exported under $TMP_DIR/kbdump"
}
function import_data() {
local filename=$1
- local columns_names=$2
- mysql --local-infile --execute="LOAD DATA LOCAL INFILE '$TMP_DIR/$filename' INTO TABLE $filename FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '\"' IGNORE 1 LINES ($columns_names); SHOW WARNINGS" -u$USERNAME -p$PASSWORD $DATABASE
+ # Separator for column names needs to be ',' and not the '|'
+ local columns_names=`echo "$2" | sed s/\|/,/g`
+
+ mysql --local-infile --execute="LOAD DATA LOCAL INFILE '$TMP_DIR/$filename' INTO TABLE $filename FIELDS TERMINATED BY '|' OPTIONALLY ENCLOSED BY '\"' IGNORE 1 LINES ($columns_names); SHOW WARNINGS" -u$USERNAME -p$PASSWORD $DATABASE
}
function sanitize_and_import() {
@@ -131,7 +142,7 @@ eval set -- "${ARGS}"
function usage() {
echo -n "./import-account"
- echo -n " -a|--action <export|import>"
+ echo -n " -a|--action <export|import|all>"
echo -n " --help this message"
echo
exit 1
@@ -145,24 +156,28 @@ while true; do
esac
done
+
if [ -z $ACTION ]; then
echo "Need to specify an action"
usage
fi
-if [ $ACTION == "export" ]; then
+if [ $ACTION == "export" ] || [ $ACTION == "all" ]; then
if [ -z $1 ]; then
echo "Need to specify an account id"
usage
fi
export_data $1
- sanitize_and_import $TMP_DIR/kbdump
fi
-if [ $ACTION == "import" ]; then
+if [ $ACTION == "import" ] || [ $ACTION == "all" ]; then
if [ -z $1 ]; then
echo "Need to specify a file"
usage
fi
- sanitize_and_import $1
+ if [ $ACTION == "import" ]; then
+ sanitize_and_import $1
+ else
+ sanitize_and_import $TMP_DIR/kbdump
+ fi
fi
catalog/pom.xml 2(+1 -1)
diff --git a/catalog/pom.xml b/catalog/pom.xml
index c5f4978..f8ff03e 100644
--- a/catalog/pom.xml
+++ b/catalog/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.18.2-SNAPSHOT</version>
+ <version>0.18.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-catalog</artifactId>
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/api/user/DefaultCatalogUserApi.java b/catalog/src/main/java/org/killbill/billing/catalog/api/user/DefaultCatalogUserApi.java
index 48e22ad..048f41c 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/api/user/DefaultCatalogUserApi.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/api/user/DefaultCatalogUserApi.java
@@ -94,25 +94,38 @@ public class DefaultCatalogUserApi implements CatalogUserApi {
final InternalTenantContext internalTenantContext = createInternalTenantContext(callContext);
try {
- final StaticCatalog currentCatalog = catalogService.getCurrentCatalog(false, true, internalTenantContext);
-
+ final VersionedCatalog versionedCatalog = (VersionedCatalog) catalogService.getFullCatalog(false, true, internalTenantContext);
// Validation purpose: Will throw if bad XML or catalog validation fails
final InputStream stream = new ByteArrayInputStream(catalogXML.getBytes());
final StaticCatalog newCatalogVersion = XMLLoader.getObjectFromStream(new URI("dummy"), stream, StandaloneCatalog.class);
- // currentCatalog.getCatalogName() could be null if tenant was created with a default catalog
- if (currentCatalog != null && currentCatalog.getCatalogName() != null) {
- if (!newCatalogVersion.getCatalogName().equals(currentCatalog.getCatalogName())) {
+ if (versionedCatalog != null) {
+
+ // currentCatalog.getCatalogName() could be null if tenant was created with a default catalog
+ if (versionedCatalog.getCatalogName() != null && !newCatalogVersion.getCatalogName().equals(versionedCatalog.getCatalogName())) {
final ValidationErrors errors = new ValidationErrors();
- errors.add(String.format("Catalog name '%s' should match previous catalog name '%s'", newCatalogVersion.getCatalogName(), currentCatalog.getCatalogName()),
- new URI("dummy"), StandaloneCatalog.class, "");
+ errors.add(String.format("Catalog name '%s' should match previous catalog name '%s'", newCatalogVersion.getCatalogName(), versionedCatalog.getCatalogName()),
+ new URI("dummy"), StandaloneCatalog.class, "");
// Bummer ValidationException CTOR is private to package...
//final ValidationException validationException = new ValidationException(errors);
//throw new CatalogApiException(errors, ErrorCode.CAT_INVALID_FOR_TENANT, internalTenantContext.getTenantRecordId());
logger.info("Failed to load new catalog version: " + errors.toString());
throw new CatalogApiException(ErrorCode.CAT_INVALID_FOR_TENANT, internalTenantContext.getTenantRecordId());
}
+
+ for (StandaloneCatalog c : versionedCatalog.getVersions()) {
+ if (c.getEffectiveDate().compareTo(newCatalogVersion.getEffectiveDate()) == 0) {
+ final ValidationErrors errors = new ValidationErrors();
+ errors.add(String.format("Catalog version for effectiveDate '%s' already exists", newCatalogVersion.getEffectiveDate()),
+ new URI("dummy"), StandaloneCatalog.class, "");
+ // Bummer ValidationException CTOR is private to package...
+ //final ValidationException validationException = new ValidationException(errors);
+ //throw new CatalogApiException(errors, ErrorCode.CAT_INVALID_FOR_TENANT, internalTenantContext.getTenantRecordId());
+ logger.info("Failed to load new catalog version: " + errors.toString());
+ throw new CatalogApiException(ErrorCode.CAT_INVALID_FOR_TENANT, internalTenantContext.getTenantRecordId());
+ }
+ }
}
catalogCache.clearCatalog(internalTenantContext);
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/CatalogSafetyInitializer.java b/catalog/src/main/java/org/killbill/billing/catalog/CatalogSafetyInitializer.java
index 68d90d8..60f56e3 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/CatalogSafetyInitializer.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/CatalogSafetyInitializer.java
@@ -33,6 +33,7 @@ public class CatalogSafetyInitializer {
public static final Integer DEFAULT_NON_REQUIRED_INTEGER_FIELD_VALUE = -1;
+ public static final Double DEFAULT_NON_REQUIRED_DOUBLE_FIELD_VALUE = new Double(-1);
private static final Map<Class, LinkedList<Field>> perCatalogClassNonRequiredFields = new HashMap<Class, LinkedList<Field>>();
@@ -60,6 +61,8 @@ public class CatalogSafetyInitializer {
}
} else if (Integer.class.equals(f.getType())) {
initializeFieldWithValue(obj, f, DEFAULT_NON_REQUIRED_INTEGER_FIELD_VALUE);
+ } else if (Double.class.equals(f.getType())) {
+ initializeFieldWithValue(obj, f, DEFAULT_NON_REQUIRED_DOUBLE_FIELD_VALUE);
}
}
}
@@ -97,6 +100,8 @@ public class CatalogSafetyInitializer {
}
} else if (Integer.class.equals(f.getType())) {
result.add(f);
+ } else if (Double.class.equals(f.getType())) {
+ result.add(f);
}
}
}
@@ -104,19 +109,23 @@ public class CatalogSafetyInitializer {
}
private static void initializeFieldWithValue(final Object obj, final Field f, final Object value) throws IllegalAccessException, ClassNotFoundException {
- f.setAccessible(true);
- if (f.get(obj) == null) {
- f.set(obj, value);
+ synchronized (perCatalogClassNonRequiredFields) {
+ f.setAccessible(true);
+ if (f.get(obj) == null) {
+ f.set(obj, value);
+ }
+ f.setAccessible(false);
}
- f.setAccessible(false);
}
private static void initializeArrayIfNull(final Object obj, final Field f) throws IllegalAccessException, ClassNotFoundException {
- f.setAccessible(true);
- if (f.get(obj) == null) {
- f.set(obj, getZeroLengthArrayInitializer(f));
+ synchronized (perCatalogClassNonRequiredFields) {
+ f.setAccessible(true);
+ if (f.get(obj) == null) {
+ f.set(obj, getZeroLengthArrayInitializer(f));
+ }
+ f.setAccessible(false);
}
- f.setAccessible(false);
}
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultBlock.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultBlock.java
index 6f05fd6..e4c347b 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultBlock.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultBlock.java
@@ -18,6 +18,7 @@
package org.killbill.billing.catalog;
import java.math.BigDecimal;
+import java.net.URI;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
@@ -81,7 +82,7 @@ public class DefaultBlock extends ValidatingConfig<StandaloneCatalog> implements
@Override
public Double getMinTopUpCredit() throws CatalogApiException {
- if (minTopUpCredit != null && type != BlockType.TOP_UP) {
+ if (minTopUpCredit != CatalogSafetyInitializer.DEFAULT_NON_REQUIRED_DOUBLE_FIELD_VALUE && type != BlockType.TOP_UP) {
throw new CatalogApiException(ErrorCode.CAT_NOT_TOP_UP_BLOCK, phase.getName());
}
return minTopUpCredit;
@@ -94,7 +95,7 @@ public class DefaultBlock extends ValidatingConfig<StandaloneCatalog> implements
throw new IllegalStateException("type should have been automatically been initialized with VANILLA ");
}
- if (type == BlockType.TOP_UP && minTopUpCredit == null) {
+ if (type == BlockType.TOP_UP && minTopUpCredit == CatalogSafetyInitializer.DEFAULT_NON_REQUIRED_DOUBLE_FIELD_VALUE) {
errors.add(new ValidationError(String.format("TOP_UP block needs to define minTopUpCredit for phase %s",
phase.getName()), catalog.getCatalogURI(), DefaultUsage.class, ""));
}
@@ -110,6 +111,12 @@ public class DefaultBlock extends ValidatingConfig<StandaloneCatalog> implements
this.prices = prices != null ? new DefaultInternationalPrice(prices, overriddenPrice, currency) : null;
}
+ @Override
+ public void initialize(final StandaloneCatalog catalog, final URI sourceURI) {
+ super.initialize(catalog, sourceURI);
+ CatalogSafetyInitializer.initializeNonRequiredNullFieldsWithDefaultValue(this);
+ }
+
public DefaultBlock setType(final BlockType type) {
this.type = type;
return this;
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultDuration.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultDuration.java
index b7e5fd4..f993a03 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultDuration.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultDuration.java
@@ -71,6 +71,8 @@ public class DefaultDuration extends ValidatingConfig<StandaloneCatalog> impleme
switch (unit) {
case DAYS:
return dateTime.plusDays(number);
+ case WEEKS:
+ return dateTime.plusWeeks(number);
case MONTHS:
return dateTime.plusMonths(number);
case YEARS:
@@ -90,6 +92,8 @@ public class DefaultDuration extends ValidatingConfig<StandaloneCatalog> impleme
switch (unit) {
case DAYS:
return localDate.plusDays(number);
+ case WEEKS:
+ return localDate.plusWeeks(number);
case MONTHS:
return localDate.plusMonths(number);
case YEARS:
@@ -145,14 +149,15 @@ public class DefaultDuration extends ValidatingConfig<StandaloneCatalog> impleme
switch (unit) {
case DAYS:
return new Period().withDays(number);
+ case WEEKS:
+ return new Period().withWeeks(number);
case MONTHS:
return new Period().withMonths(number);
case YEARS:
return new Period().withYears(number);
case UNLIMITED:
- return new Period().withYears(100);
default:
- return new Period();
+ throw new IllegalStateException("Unexpected duration unit " + unit);
}
}
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultLimit.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultLimit.java
index 30176b5..b594c03 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultLimit.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultLimit.java
@@ -69,12 +69,13 @@ public class DefaultLimit extends ValidatingConfig<StandaloneCatalog> implements
@Override
public ValidationErrors validate(StandaloneCatalog root, ValidationErrors errors) {
- if (max == null && min == null) {
+ if (max == CatalogSafetyInitializer.DEFAULT_NON_REQUIRED_DOUBLE_FIELD_VALUE && min == CatalogSafetyInitializer.DEFAULT_NON_REQUIRED_DOUBLE_FIELD_VALUE) {
errors.add(new ValidationError("max and min cannot both be ommitted", root.getCatalogURI(), Limit.class, ""));
- } else if (max != null && min != null && max.doubleValue() < min.doubleValue()) {
+ } else if (max != CatalogSafetyInitializer.DEFAULT_NON_REQUIRED_DOUBLE_FIELD_VALUE &&
+ min != CatalogSafetyInitializer.DEFAULT_NON_REQUIRED_DOUBLE_FIELD_VALUE &&
+ max.doubleValue() < min.doubleValue()) {
errors.add(new ValidationError("max must be greater than min", root.getCatalogURI(), Limit.class, ""));
}
-
return errors;
}
@@ -87,12 +88,12 @@ public class DefaultLimit extends ValidatingConfig<StandaloneCatalog> implements
@Override
public boolean compliesWith(double value) {
- if (max != null) {
+ if (max != CatalogSafetyInitializer.DEFAULT_NON_REQUIRED_DOUBLE_FIELD_VALUE) {
if (value > max.doubleValue()) {
return false;
}
}
- if (min != null) {
+ if (min != CatalogSafetyInitializer.DEFAULT_NON_REQUIRED_DOUBLE_FIELD_VALUE) {
if (value < min.doubleValue()) {
return false;
}
@@ -126,10 +127,10 @@ public class DefaultLimit extends ValidatingConfig<StandaloneCatalog> implements
final DefaultLimit that = (DefaultLimit) o;
- if (max != null ? !max.equals(that.max) : that.max != null) {
+ if (!max.equals(that.max)) {
return false;
}
- if (min != null ? !min.equals(that.min) : that.min != null) {
+ if (!min.equals(that.min)) {
return false;
}
if (unit != null ? !unit.equals(that.unit) : that.unit != null) {
@@ -142,8 +143,8 @@ public class DefaultLimit extends ValidatingConfig<StandaloneCatalog> implements
@Override
public int hashCode() {
int result = unit != null ? unit.hashCode() : 0;
- result = 31 * result + (max != null ? max.hashCode() : 0);
- result = 31 * result + (min != null ? min.hashCode() : 0);
+ result = 31 * result + max.hashCode();
+ result = 31 * result + min.hashCode();
return result;
}
}
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultPlan.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultPlan.java
index 5f2e9ea..0d1757f 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultPlan.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultPlan.java
@@ -45,6 +45,7 @@ import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
import org.killbill.billing.catalog.api.PriceList;
import org.killbill.billing.catalog.api.Product;
import org.killbill.billing.catalog.api.Recurring;
+import org.killbill.billing.catalog.api.TimeUnit;
import org.killbill.xmlloader.ValidatingConfig;
import org.killbill.xmlloader.ValidationError;
import org.killbill.xmlloader.ValidationErrors;
@@ -272,7 +273,8 @@ public class DefaultPlan extends ValidatingConfig<StandaloneCatalog> implements
}
}
final Recurring recurring = phase.getRecurring();
- if (recurring == null || recurring.getRecurringPrice() == null || recurring.getRecurringPrice().isZero()) {
+ if (phase.getDuration().getUnit() != TimeUnit.UNLIMITED &&
+ (recurring == null || recurring.getRecurringPrice() == null || recurring.getRecurringPrice().isZero())) {
try {
result = phase.getDuration().addToDateTime(result);
} catch (final CatalogApiException ignored) {
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultTier.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultTier.java
index b14a148..e6c3bf3 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultTier.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultTier.java
@@ -168,6 +168,12 @@ public class DefaultTier extends ValidatingConfig<StandaloneCatalog> implements
public void initialize(final StandaloneCatalog catalog, final URI sourceURI) {
super.initialize(catalog, sourceURI);
CatalogSafetyInitializer.initializeNonRequiredNullFieldsWithDefaultValue(this);
+ for (DefaultLimit cur : limits) {
+ cur.initialize(catalog, sourceURI);
+ }
+ for (DefaultBlock cur : blocks) {
+ cur.initialize(catalog, sourceURI);
+ }
}
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/StandaloneCatalogWithPriceOverride.java b/catalog/src/main/java/org/killbill/billing/catalog/StandaloneCatalogWithPriceOverride.java
index 7e6deb9..d7a2d9b 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/StandaloneCatalogWithPriceOverride.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/StandaloneCatalogWithPriceOverride.java
@@ -24,6 +24,7 @@ import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.catalog.api.CatalogApiException;
import org.killbill.billing.catalog.api.Plan;
import org.killbill.billing.catalog.api.PlanPhase;
+import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
import org.killbill.billing.catalog.api.PlanPhasePriceOverridesWithCallContext;
import org.killbill.billing.catalog.api.PlanSpecifier;
import org.killbill.billing.catalog.api.Product;
@@ -74,7 +75,8 @@ public class StandaloneCatalogWithPriceOverride extends StandaloneCatalog implem
final Plan defaultPlan = super.createOrFindCurrentPlan(spec, null);
if (overrides == null ||
overrides.getOverrides() == null ||
- overrides.getOverrides().isEmpty()) {
+ overrides.getOverrides().isEmpty() ||
+ isOverrideUsedForPlanAlignmentTargetPhaseType(overrides)) {
return defaultPlan;
}
@@ -82,6 +84,21 @@ public class StandaloneCatalogWithPriceOverride extends StandaloneCatalog implem
return priceOverride.getOrCreateOverriddenPlan(this, defaultPlan, CatalogDateHelper.toUTCDateTime(getEffectiveDate()), overrides.getOverrides(), internalCallContext);
}
+ // This is a hack used to specify a target PhaseType when making a changePlan operation. Undocumented feature
+ private boolean isOverrideUsedForPlanAlignmentTargetPhaseType(final PlanPhasePriceOverridesWithCallContext overrides) {
+ if (overrides.getOverrides().size() != 1) {
+ return false;
+ }
+
+ final PlanPhasePriceOverride override = overrides.getOverrides().get(0);
+ return override.getCurrency() == null &&
+ override.getFixedPrice() == null &&
+ override.getRecurringPrice() == null &&
+ override.getPhaseName() == null &&
+ override.getPlanPhaseSpecifier() != null &&
+ override.getPlanPhaseSpecifier().getPhaseType() != null;
+ }
+
@Override
public DefaultPlan findCurrentPlan(final String planName) throws CatalogApiException {
final Matcher m = DefaultPriceOverride.CUSTOM_PLAN_NAME_PATTERN.matcher(planName);
catalog/src/test/resources/catalogTest.xml 133(+129 -4)
diff --git a/catalog/src/test/resources/catalogTest.xml b/catalog/src/test/resources/catalogTest.xml
index 8f28d1c..78e4449 100644
--- a/catalog/src/test/resources/catalogTest.xml
+++ b/catalog/src/test/resources/catalogTest.xml
@@ -211,6 +211,32 @@
</recurring>
</finalPhase>
</plan>
+
+ <plan name="pistol-monthly-notrial">
+ <product>Pistol</product>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <recurring>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price>
+ <currency>USD</currency>
+ <value>19.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>19.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>19.95</value>
+ </price>
+ </recurringPrice>
+ </recurring>
+ </finalPhase>
+ </plan>
<plan name="blowdart-monthly">
<product>Blowdart</product>
<initialPhases>
@@ -382,6 +408,46 @@
</recurring>
</finalPhase>
</plan>
+
+ <plan name="pistol-monthly-fixedterm">
+ <product>Pistol</product>
+ <initialPhases>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <fixed>
+ <fixedPrice>
+ </fixedPrice>
+ </fixed>
+ </phase>
+ </initialPhases>
+ <finalPhase type="FIXEDTERM">
+ <duration>
+ <unit>MONTHS</unit>
+ <number>12</number>
+ </duration>
+ <recurring>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price>
+ <currency>GBP</currency>
+ <value>29.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>29.95</value>
+ </price>
+ <price>
+ <currency>USD</currency>
+ <value>29.95</value>
+ </price>
+ </recurringPrice>
+ </recurring>
+ </finalPhase>
+ </plan>
+
<plan name="shotgun-monthly">
<product>Shotgun</product>
<initialPhases>
@@ -665,6 +731,56 @@
</recurring>
</finalPhase>
</plan>
+ <plan name="pistol-annual-gunclub-discount-notrial">
+ <product>Pistol</product>
+ <initialPhases>
+ <phase type="DISCOUNT">
+ <duration>
+ <unit>MONTHS</unit>
+ <number>6</number>
+ </duration>
+ <recurring>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price>
+ <currency>USD</currency>
+ <value>9.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>9.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>9.95</value>
+ </price>
+ </recurringPrice>
+ </recurring>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <recurring>
+ <billingPeriod>ANNUAL</billingPeriod>
+ <recurringPrice>
+ <price>
+ <currency>USD</currency>
+ <value>199.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>199.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>199.95</value>
+ </price>
+ </recurringPrice>
+ </recurring>
+ </finalPhase>
+ </plan>
<plan name="shotgun-annual-gunclub-discount">
<product>Shotgun</product>
<initialPhases>
@@ -1188,17 +1304,19 @@
<plan>holster-monthly-regular</plan>
<plan>refurbish-maintenance</plan>
<plan>bullets-usage-in-arrear</plan>
+ <plan>holster-monthly-special</plan>
</plans>
</defaultPriceList>
<childPriceList name="gunclubDiscount">
<plans>
- <plan>pistol-monthly</plan>
- <plan>shotgun-monthly</plan>
- <plan>assault-rifle-monthly</plan>
<plan>pistol-annual-gunclub-discount</plan>
<plan>shotgun-annual-gunclub-discount</plan>
<plan>assault-rifle-annual-gunclub-discount</plan>
- <plan>holster-monthly-special</plan>
+ </plans>
+ </childPriceList>
+ <childPriceList name="gunclubDiscountNoTrial">
+ <plans>
+ <plan>pistol-annual-gunclub-discount-notrial</plan>
</plans>
</childPriceList>
<childPriceList name="rescue">
@@ -1206,9 +1324,16 @@
<plan>assault-rifle-annual-rescue</plan>
</plans>
</childPriceList>
+ <childPriceList name="fixedTerm">
+ <plans>
+ <plan>pistol-monthly-fixedterm</plan>
+ </plans>
+ </childPriceList>
<childPriceList name="notrial">
<plans>
<plan>blowdart-monthly-notrial</plan>
+ <plan>pistol-monthly-notrial</plan>
+
</plans>
</childPriceList>
</priceLists>
currency/pom.xml 2(+1 -1)
diff --git a/currency/pom.xml b/currency/pom.xml
index d7c5a18..f9003f4 100644
--- a/currency/pom.xml
+++ b/currency/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.18.2-SNAPSHOT</version>
+ <version>0.18.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-currency</artifactId>
entitlement/pom.xml 2(+1 -1)
diff --git a/entitlement/pom.xml b/entitlement/pom.xml
index 1eef933..c691cc9 100644
--- a/entitlement/pom.xml
+++ b/entitlement/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.18.2-SNAPSHOT</version>
+ <version>0.18.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-entitlement</artifactId>
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/api/BlockingStateOrdering.java b/entitlement/src/main/java/org/killbill/billing/entitlement/api/BlockingStateOrdering.java
index 178af0a..0c1c5b7 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/api/BlockingStateOrdering.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/api/BlockingStateOrdering.java
@@ -100,7 +100,27 @@ public class BlockingStateOrdering extends EntitlementOrderingBase {
while (it.hasNext()) {
final DefaultSubscriptionEvent cur = (DefaultSubscriptionEvent) it.next();
final int compEffectiveDate = currentBlockingState.getEffectiveDate().compareTo(cur.getEffectiveDateTime());
- final boolean shouldContinue = (compEffectiveDate >= 0);
+
+ final boolean shouldContinue;
+ switch (compEffectiveDate) {
+ case -1:
+ shouldContinue = false;
+ break;
+ case 0:
+ // In case of exact same date, we want to make sure that a START_ENTITLEMENT event gets correctly populated when the STOP_BILLING is also on the same date
+ if (currentBlockingState.getStateName().equals(DefaultEntitlementApi.ENT_STATE_START) && cur.getSubscriptionEventType() != SubscriptionEventType.STOP_BILLING) {
+ shouldContinue = false;
+ } else {
+ shouldContinue = true;
+ }
+ break;
+ case 1:
+ shouldContinue = true;
+ break;
+ default:
+ // Make compiler happy
+ throw new IllegalStateException("Cannot reach statement");
+ }
if (!shouldContinue) {
break;
}
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 9680a76..10246cb 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
@@ -493,9 +493,6 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
throw new EntitlementApiException(ErrorCode.SUB_CANCEL_BAD_STATE, getId(), EntitlementState.CANCELLED);
}
- // Make sure to compute the entitlement effective date first to avoid timing issues for IMM cancellations
- // (we don't want an entitlement cancel date one second or so after the subscription cancel date or add-ons cancellations
- // computations won't work).
final InternalCallContext contextWithValidAccountRecordId = internalCallContextFactory.createInternalCallContext(getAccountId(), callContext);
try {
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlementApi.java b/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlementApi.java
index 88f580a..8df759d 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlementApi.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlementApi.java
@@ -222,6 +222,13 @@ public class DefaultEntitlementApi extends DefaultEntitlementApiBase implements
final InternalCallContext contextWithValidAccountRecordId = internalCallContextFactory.createInternalCallContext(accountId, callContext);
try {
+ // First verify bundleKey
+ for (final BaseEntitlementWithAddOnsSpecifier baseEntitlementWithAddOnsSpecifier : baseEntitlementWithAddOnsSpecifiers) {
+ if (entitlementUtils.getFirstActiveSubscriptionIdForKeyOrNull(baseEntitlementWithAddOnsSpecifier.getExternalKey(), contextWithValidAccountRecordId) != null) {
+ throw new EntitlementApiException(new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_ACTIVE_BUNDLE_KEY_EXISTS, baseEntitlementWithAddOnsSpecifier.getExternalKey()));
+ }
+ }
+
final List<SubscriptionBaseWithAddOns> subscriptionsWithAddOns = subscriptionBaseInternalApi.createBaseSubscriptionsWithAddOns(accountId, baseEntitlementWithAddOnsSpecifiers, contextWithValidAccountRecordId);
final Map<BlockingState, UUID> blockingStateMap = new HashMap<BlockingState, UUID>();
int i = 0;
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/api/svcs/DefaultEntitlementInternalApi.java b/entitlement/src/main/java/org/killbill/billing/entitlement/api/svcs/DefaultEntitlementInternalApi.java
index 982b932..5a59a28 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/api/svcs/DefaultEntitlementInternalApi.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/api/svcs/DefaultEntitlementInternalApi.java
@@ -146,15 +146,14 @@ public class DefaultEntitlementInternalApi extends DefaultEntitlementApiBase imp
callContext);
pluginContexts.add(pluginContext);
- final DefaultEntitlement defaultEntitlement = getDefaultEntitlement(entitlement, internalCallContext);
- final WithEntitlementPlugin<Entitlement> cancelEntitlementWithPlugin = new WithDateOverrideBillingPolicyEntitlementCanceler(defaultEntitlement,
+ final WithEntitlementPlugin<Entitlement> cancelEntitlementWithPlugin = new WithDateOverrideBillingPolicyEntitlementCanceler((DefaultEntitlement) entitlement,
blockingStates,
notificationEvents,
callContext,
internalCallContext);
callbacks.add(cancelEntitlementWithPlugin);
- subscriptions.add(defaultEntitlement.getSubscriptionBase());
+ subscriptions.add(((DefaultEntitlement) entitlement).getSubscriptionBase());
}
final Callable<Void> preCallbacksCallback = new BulkSubscriptionBaseCancellation(subscriptions,
@@ -188,16 +187,6 @@ public class DefaultEntitlementInternalApi extends DefaultEntitlementApiBase imp
}
}
- // For forward-compatibility
- private DefaultEntitlement getDefaultEntitlement(final Entitlement entitlement, final InternalTenantContext context) throws EntitlementApiException {
- if (entitlement instanceof DefaultEntitlement) {
- return (DefaultEntitlement) entitlement;
- } else {
- // Safe cast
- return (DefaultEntitlement) getEntitlementForId(entitlement.getId(), context);
- }
- }
-
private class BulkSubscriptionBaseCancellation implements Callable<Void> {
private final Iterable<SubscriptionBase> subscriptions;
@@ -254,10 +243,19 @@ public class DefaultEntitlementInternalApi extends DefaultEntitlementApiBase imp
@Override
public Entitlement doCall(final EntitlementApi entitlementApi, final EntitlementContext updatedPluginContext) throws EntitlementApiException {
DateTime effectiveDate = dateHelper.fromLocalDateAndReferenceTime(updatedPluginContext.getBaseEntitlementWithAddOnsSpecifiers().iterator().next().getEntitlementEffectiveDate(), internalCallContext);
- // Avoid timing issues for IMM cancellations (we don't want an entitlement cancel date one second or so after the subscription cancel date or
- // add-ons cancellations computations won't work).
- if (effectiveDate.compareTo(entitlement.getSubscriptionBase().getEndDate()) > 0) {
- effectiveDate = entitlement.getSubscriptionBase().getEndDate();
+
+ //
+ // If the entitlementDate provided is ahead we default to the effective subscriptionBase cancellationDate to avoid weird timing issues.
+ //
+ // (Note that entitlement.getSubscriptionBase() returns the right state (although we did not refresh context) because the DefaultSubscriptionBaseApiService#doCancelPlan
+ // rebuild transitions on that same DefaultSubscriptionBase object)
+ //
+ final DateTime subscriptionBaseCancellationDate = entitlement.getSubscriptionBase().getEndDate() != null ?
+ entitlement.getSubscriptionBase().getEndDate() :
+ entitlement.getSubscriptionBase().getFutureEndDate();
+
+ if (effectiveDate.compareTo(subscriptionBaseCancellationDate) > 0) {
+ effectiveDate = subscriptionBaseCancellationDate;
}
final BlockingState newBlockingState = new DefaultBlockingState(entitlement.getId(), BlockingStateType.SUBSCRIPTION, DefaultEntitlementApi.ENT_STATE_CANCELLED, EntitlementService.ENTITLEMENT_SERVICE_NAME, true, true, false, effectiveDate);
diff --git a/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultSubscriptionBundleTimeline.java b/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultSubscriptionBundleTimeline.java
index dc0198b..fc7fb25 100644
--- a/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultSubscriptionBundleTimeline.java
+++ b/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultSubscriptionBundleTimeline.java
@@ -53,11 +53,13 @@ import static org.testng.Assert.assertNull;
public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteNoDB {
private UUID bundleId;
+ private String bundleExternalKey;
@BeforeClass(groups = "fast")
protected void beforeClass() throws Exception {
super.beforeClass();
bundleId = UUID.randomUUID();
+ bundleExternalKey = bundleId.toString();
}
public class TestSubscriptionBundleTimeline extends DefaultSubscriptionBundleTimeline {
@@ -279,7 +281,6 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
final DateTimeZone accountTimeZone = DateTimeZone.UTC;
final UUID accountId = UUID.randomUUID();
- final UUID bundleId = UUID.randomUUID();
final String externalKey = "foo";
final List<BlockingState> blockingStates = new ArrayList<BlockingState>();
@@ -288,9 +289,8 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
final List<SubscriptionBaseTransition> allTransitions = new ArrayList<SubscriptionBaseTransition>();
- final DateTime requestedDate = new DateTime();
DateTime effectiveDate = new DateTime(2013, 1, 1, 15, 43, 25, 0, DateTimeZone.UTC);
- final SubscriptionBaseTransition tr1 = createTransition(entitlementId, EventType.API_USER, ApiEventType.CREATE, requestedDate, effectiveDate, clock.getUTCNow(), null, "trial");
+ final SubscriptionBaseTransition tr1 = createTransition(entitlementId, EventType.API_USER, ApiEventType.CREATE, effectiveDate, clock.getUTCNow(), null, "trial");
allTransitions.add(tr1);
if (!regressionFlagForOlderVersionThan_0_17_X) {
@@ -303,12 +303,12 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
effectiveDate = effectiveDate.plusDays(30);
clock.addDays(30);
- final SubscriptionBaseTransition tr2 = createTransition(entitlementId, EventType.PHASE, null, requestedDate, effectiveDate, clock.getUTCNow(), "trial", "phase");
+ final SubscriptionBaseTransition tr2 = createTransition(entitlementId, EventType.PHASE, null, effectiveDate, clock.getUTCNow(), "trial", "phase");
allTransitions.add(tr2);
effectiveDate = effectiveDate.plusDays(15);
clock.addDays(15);
- final SubscriptionBaseTransition tr3 = createTransition(entitlementId, EventType.API_USER, ApiEventType.CANCEL, requestedDate, effectiveDate, clock.getUTCNow(), "phase", null);
+ final SubscriptionBaseTransition tr3 = createTransition(entitlementId, EventType.API_USER, ApiEventType.CANCEL, effectiveDate, clock.getUTCNow(), "phase", null);
allTransitions.add(tr3);
final List<Entitlement> entitlements = new ArrayList<Entitlement>();
@@ -349,6 +349,82 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
}
+ @Test(groups="fast", enabled = true)
+ public void testOneSimpleEntitlementCancelImmediately() throws CatalogApiException {
+ testOneSimpleEntitlementCancelImmediatelyImpl(false);
+ }
+
+ @Test(groups="fast", enabled = true)
+ public void testOneSimpleEntitlementCancelImmediatelyWithRegression() throws CatalogApiException {
+ testOneSimpleEntitlementCancelImmediatelyImpl(true);
+ }
+
+
+ private void testOneSimpleEntitlementCancelImmediatelyImpl(boolean regressionFlagForOlderVersionThan_0_17_X) throws CatalogApiException {
+
+ clock.setDay(new LocalDate(2013, 1, 1));
+
+ final UUID accountId = UUID.randomUUID();
+ final String externalKey = "foo";
+
+ final List<BlockingState> blockingStates = new ArrayList<BlockingState>();
+
+ final UUID entitlementId = UUID.randomUUID();
+
+ final List<SubscriptionBaseTransition> allTransitions = new ArrayList<SubscriptionBaseTransition>();
+
+ DateTime effectiveDate = new DateTime(2013, 1, 1, 15, 43, 25, 0, DateTimeZone.UTC);
+ final SubscriptionBaseTransition tr1 = createTransition(entitlementId, EventType.API_USER, ApiEventType.CREATE, effectiveDate, clock.getUTCNow(), null, "trial");
+ allTransitions.add(tr1);
+
+ if (!regressionFlagForOlderVersionThan_0_17_X) {
+ final BlockingState bsCreate = new DefaultBlockingState(UUID.randomUUID(), entitlementId, BlockingStateType.SUBSCRIPTION,
+ DefaultEntitlementApi.ENT_STATE_START, DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME,
+ false, false, false, effectiveDate, clock.getUTCNow(), clock.getUTCNow(), 0L);
+ blockingStates.add(bsCreate);
+ }
+
+ final SubscriptionBaseTransition tr2 = createTransition(entitlementId, EventType.API_USER, ApiEventType.CANCEL, effectiveDate, clock.getUTCNow(), "trial", null);
+ allTransitions.add(tr2);
+
+ final BlockingState bsCancel = new DefaultBlockingState(UUID.randomUUID(), entitlementId, BlockingStateType.SUBSCRIPTION,
+ DefaultEntitlementApi.ENT_STATE_CANCELLED, DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME,
+ false, false, false, effectiveDate, clock.getUTCNow(), clock.getUTCNow(), 0L);
+ blockingStates.add(bsCancel);
+
+
+
+ final List<Entitlement> entitlements = new ArrayList<Entitlement>();
+ final Entitlement entitlement = createEntitlement(entitlementId, allTransitions, blockingStates);
+ entitlements.add(entitlement);
+
+ final SubscriptionBundleTimeline timeline = new DefaultSubscriptionBundleTimeline(accountId, bundleId, externalKey, entitlements, internalCallContext);
+
+ assertEquals(timeline.getAccountId(), accountId);
+ assertEquals(timeline.getBundleId(), bundleId);
+ assertEquals(timeline.getExternalKey(), externalKey);
+
+ final List<SubscriptionEvent> events = timeline.getSubscriptionEvents();
+ assertEquals(events.size(), 4);
+
+ assertEquals(events.get(0).getSubscriptionEventType(), SubscriptionEventType.START_ENTITLEMENT);
+ assertEquals(events.get(1).getSubscriptionEventType(), SubscriptionEventType.START_BILLING);
+ assertEquals(events.get(2).getSubscriptionEventType(), SubscriptionEventType.STOP_ENTITLEMENT);
+ assertEquals(events.get(3).getSubscriptionEventType(), SubscriptionEventType.STOP_BILLING);
+
+ assertNull(events.get(0).getPrevPhase());
+ assertEquals(events.get(0).getNextPhase().getName(), "trial");
+
+ assertNull(events.get(1).getPrevPhase());
+ assertEquals(events.get(1).getNextPhase().getName(), "trial");
+
+ assertEquals(events.get(2).getPrevPhase().getName(), "trial");
+ assertNull(events.get(2).getNextPhase());
+
+ assertEquals(events.get(3).getPrevPhase().getName(), "trial");
+ assertNull(events.get(3).getNextPhase());
+ }
+
@Test(groups = "fast")
public void testCancelBundleBeforeSubscription() throws CatalogApiException {
@@ -372,9 +448,8 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
final List<SubscriptionBaseTransition> allTransitions = new ArrayList<SubscriptionBaseTransition>();
final List<BlockingState> blockingStates = new ArrayList<BlockingState>();
- final DateTime requestedDate = new DateTime();
DateTime effectiveDate = new DateTime(2013, 1, 1, 15, 43, 25, 0, DateTimeZone.UTC);
- final SubscriptionBaseTransition tr1 = createTransition(entitlementId, EventType.API_USER, ApiEventType.CREATE, requestedDate, effectiveDate, clock.getUTCNow(), null, "trial");
+ final SubscriptionBaseTransition tr1 = createTransition(entitlementId, EventType.API_USER, ApiEventType.CREATE, effectiveDate, clock.getUTCNow(), null, "trial");
allTransitions.add(tr1);
if (!regressionFlagForOlderVersionThan_0_17_X) {
@@ -395,7 +470,7 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
effectiveDate = effectiveDate.plusDays(15);
clock.addDays(15);
- final SubscriptionBaseTransition tr2 = createTransition(entitlementId, EventType.API_USER, ApiEventType.CANCEL, requestedDate, effectiveDate, clock.getUTCNow(), "trial", null);
+ final SubscriptionBaseTransition tr2 = createTransition(entitlementId, EventType.API_USER, ApiEventType.CANCEL, effectiveDate, clock.getUTCNow(), "trial", null);
allTransitions.add(tr2);
final List<Entitlement> entitlements = new ArrayList<Entitlement>();
@@ -457,9 +532,8 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
final List<SubscriptionBaseTransition> allTransitions = new ArrayList<SubscriptionBaseTransition>();
final List<BlockingState> blockingStates = new ArrayList<BlockingState>();
- final DateTime requestedDate = new DateTime();
DateTime effectiveDate = new DateTime(2013, 1, 1, 15, 43, 25, 0, DateTimeZone.UTC);
- final SubscriptionBaseTransition tr1 = createTransition(entitlementId, EventType.API_USER, ApiEventType.CREATE, requestedDate, effectiveDate, clock.getUTCNow(), null, "trial");
+ final SubscriptionBaseTransition tr1 = createTransition(entitlementId, EventType.API_USER, ApiEventType.CREATE, effectiveDate, clock.getUTCNow(), null, "trial");
allTransitions.add(tr1);
if (!regressionFlagForOlderVersionThan_0_17_X) {
@@ -473,7 +547,7 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
effectiveDate = effectiveDate.plusDays(30);
clock.addDays(30);
- final SubscriptionBaseTransition tr2 = createTransition(entitlementId, EventType.PHASE, null, requestedDate, effectiveDate, clock.getUTCNow(), "trial", "phase");
+ final SubscriptionBaseTransition tr2 = createTransition(entitlementId, EventType.PHASE, null, effectiveDate, clock.getUTCNow(), "trial", "phase");
allTransitions.add(tr2);
effectiveDate = effectiveDate.plusDays(12);
@@ -584,6 +658,7 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
testOneEntitlementWithOverduePauseThenCancelImpl(false);
}
+ @Test(groups = "fast")
public void testOneEntitlementWithOverduePauseThenCancelWithRegression() throws CatalogApiException {
testOneEntitlementWithOverduePauseThenCancelImpl(true);
}
@@ -601,9 +676,8 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
final List<SubscriptionBaseTransition> allTransitions = new ArrayList<SubscriptionBaseTransition>();
final List<BlockingState> blockingStates = new ArrayList<BlockingState>();
- final DateTime requestedDate = new DateTime();
DateTime effectiveDate = new DateTime(2013, 1, 1, 15, 43, 25, 0, DateTimeZone.UTC);
- final SubscriptionBaseTransition tr1 = createTransition(entitlementId, EventType.API_USER, ApiEventType.CREATE, requestedDate, effectiveDate, clock.getUTCNow(), null, "trial");
+ final SubscriptionBaseTransition tr1 = createTransition(entitlementId, EventType.API_USER, ApiEventType.CREATE, effectiveDate, clock.getUTCNow(), null, "trial");
allTransitions.add(tr1);
if (!regressionFlagForOlderVersionThan_0_17_X) {
@@ -616,7 +690,7 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
effectiveDate = effectiveDate.plusDays(30);
clock.addDays(30);
- final SubscriptionBaseTransition tr2 = createTransition(entitlementId, EventType.PHASE, null, requestedDate, effectiveDate, clock.getUTCNow(), "trial", "phase");
+ final SubscriptionBaseTransition tr2 = createTransition(entitlementId, EventType.PHASE, null, effectiveDate, clock.getUTCNow(), "trial", "phase");
allTransitions.add(tr2);
final String overdueService = "overdue-service";
@@ -660,7 +734,7 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
blockingStates.add(bs5);
// Note: cancellation event and ODE4 at the same effective date (see https://github.com/killbill/killbill/issues/148)
- final SubscriptionBaseTransition tr3 = createTransition(entitlementId, EventType.API_USER, ApiEventType.CANCEL, effectiveDate, effectiveDate, clock.getUTCNow(), "phase", null);
+ final SubscriptionBaseTransition tr3 = createTransition(entitlementId, EventType.API_USER, ApiEventType.CANCEL, effectiveDate, clock.getUTCNow(), "phase", null);
allTransitions.add(tr3);
final List<Entitlement> entitlements = new ArrayList<Entitlement>();
@@ -773,9 +847,8 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
clock.addDays(1);
- final DateTime requestedDate = new DateTime();
DateTime effectiveDate = new DateTime(2013, 1, 1, 15, 43, 25, 0, DateTimeZone.UTC);
- final SubscriptionBaseTransition tr1 = createTransition(entitlementId, EventType.API_USER, ApiEventType.CREATE, requestedDate, effectiveDate, clock.getUTCNow(), null, "trial");
+ final SubscriptionBaseTransition tr1 = createTransition(entitlementId, EventType.API_USER, ApiEventType.CREATE, effectiveDate, clock.getUTCNow(), null, "trial");
allTransitions.add(tr1);
if (!regressionFlagForOlderVersionThan_0_17_X) {
@@ -788,7 +861,7 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
effectiveDate = effectiveDate.plusDays(30);
clock.addDays(30);
- final SubscriptionBaseTransition tr2 = createTransition(entitlementId, EventType.PHASE, null, requestedDate, effectiveDate, clock.getUTCNow(), "trial", "phase");
+ final SubscriptionBaseTransition tr2 = createTransition(entitlementId, EventType.PHASE, null, effectiveDate, clock.getUTCNow(), "trial", "phase");
allTransitions.add(tr2);
final String service = "boo";
@@ -800,7 +873,7 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
effectiveDate = effectiveDate.plusDays(15);
clock.addDays(15);
- final SubscriptionBaseTransition tr3 = createTransition(entitlementId, EventType.API_USER, ApiEventType.CANCEL, requestedDate, effectiveDate, clock.getUTCNow(), "phase", null);
+ final SubscriptionBaseTransition tr3 = createTransition(entitlementId, EventType.API_USER, ApiEventType.CANCEL, effectiveDate, clock.getUTCNow(), "phase", null);
allTransitions.add(tr3);
final List<Entitlement> entitlements = new ArrayList<Entitlement>();
@@ -868,9 +941,8 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
final List<SubscriptionBaseTransition> allTransitions = new ArrayList<SubscriptionBaseTransition>();
final List<BlockingState> blockingStates = new ArrayList<BlockingState>();
- final DateTime requestedDate = new DateTime();
DateTime effectiveDate = new DateTime(2013, 1, 1, 15, 43, 25, 0, DateTimeZone.UTC);
- final SubscriptionBaseTransition tr1 = createTransition(entitlementId, EventType.API_USER, ApiEventType.CREATE, requestedDate, effectiveDate, clock.getUTCNow(), null, "trial");
+ final SubscriptionBaseTransition tr1 = createTransition(entitlementId, EventType.API_USER, ApiEventType.CREATE, effectiveDate, clock.getUTCNow(), null, "trial");
allTransitions.add(tr1);
if (!regressionFlagForOlderVersionThan_0_17_X) {
@@ -883,7 +955,7 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
effectiveDate = effectiveDate.plusDays(30);
clock.addDays(30);
- final SubscriptionBaseTransition tr2 = createTransition(entitlementId, EventType.PHASE, null, requestedDate, effectiveDate, clock.getUTCNow(), "trial", "phase");
+ final SubscriptionBaseTransition tr2 = createTransition(entitlementId, EventType.PHASE, null, effectiveDate, clock.getUTCNow(), "trial", "phase");
allTransitions.add(tr2);
effectiveDate = effectiveDate.plusDays(5);
@@ -895,7 +967,7 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
effectiveDate = effectiveDate.plusDays(15);
clock.addDays(15);
- final SubscriptionBaseTransition tr3 = createTransition(entitlementId, EventType.API_USER, ApiEventType.CANCEL, requestedDate, effectiveDate, clock.getUTCNow(), "phase", null);
+ final SubscriptionBaseTransition tr3 = createTransition(entitlementId, EventType.API_USER, ApiEventType.CANCEL, effectiveDate, clock.getUTCNow(), "phase", null);
allTransitions.add(tr3);
final BlockingState bs2 = new DefaultBlockingState(UUID.randomUUID(), entitlementId, BlockingStateType.SUBSCRIPTION,
DefaultEntitlementApi.ENT_STATE_CANCELLED, DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME,
@@ -964,7 +1036,6 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
final DateTimeZone accountTimeZone = DateTimeZone.UTC;
final UUID accountId = UUID.randomUUID();
- final UUID bundleId = UUID.randomUUID();
final String externalKey = "foo";
final UUID entitlementId1 = UUID.fromString("cf5a597a-cf15-45d3-8f02-95371be7f927");
@@ -972,11 +1043,11 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
final List<SubscriptionBaseTransition> allTransitions1 = new ArrayList<SubscriptionBaseTransition>();
final List<SubscriptionBaseTransition> allTransitions2 = new ArrayList<SubscriptionBaseTransition>();
- final List<BlockingState> blockingStates = new ArrayList<BlockingState>();
+ final List<BlockingState> blockingStatesEnt1 = new ArrayList<BlockingState>();
+ final List<BlockingState> blockingStatesEnt2 = new ArrayList<BlockingState>();
- final DateTime requestedDate = new DateTime();
DateTime effectiveDate = new DateTime(2013, 1, 1, 15, 43, 25, 0, DateTimeZone.UTC);
- final SubscriptionBaseTransition ent1Tr1 = createTransition(entitlementId1, EventType.API_USER, ApiEventType.CREATE, requestedDate, effectiveDate, clock.getUTCNow(), null, "trial1");
+ final SubscriptionBaseTransition ent1Tr1 = createTransition(entitlementId1, EventType.API_USER, ApiEventType.CREATE, effectiveDate, clock.getUTCNow(), null, "trial1");
allTransitions1.add(ent1Tr1);
if (!regressionFlagForOlderVersionThan_0_17_X) {
@@ -984,12 +1055,12 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
DefaultEntitlementApi.ENT_STATE_START, DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME,
false, false, false, effectiveDate, clock.getUTCNow(), clock.getUTCNow(), 0L);
- blockingStates.add(bsCreate);
+ blockingStatesEnt1.add(bsCreate);
}
effectiveDate = effectiveDate.plusDays(15);
clock.addDays(15);
- final SubscriptionBaseTransition ent2Tr1 = createTransition(entitlementId2, EventType.API_USER, ApiEventType.TRANSFER, requestedDate, effectiveDate, clock.getUTCNow(), null, "phase2");
+ final SubscriptionBaseTransition ent2Tr1 = createTransition(entitlementId2, EventType.API_USER, ApiEventType.TRANSFER, effectiveDate, clock.getUTCNow(), null, "phase2");
allTransitions2.add(ent2Tr1);
if (!regressionFlagForOlderVersionThan_0_17_X ) {
@@ -997,12 +1068,12 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
DefaultEntitlementApi.ENT_STATE_START, DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME,
false, false, false, effectiveDate, clock.getUTCNow(), clock.getUTCNow(), 0L);
- blockingStates.add(bsCreate2);
+ blockingStatesEnt2.add(bsCreate2);
}
effectiveDate = effectiveDate.plusDays(15);
clock.addDays(15);
- final SubscriptionBaseTransition ent1Tr2 = createTransition(entitlementId1, EventType.PHASE, null, requestedDate, effectiveDate, clock.getUTCNow(), "trial1", "phase1");
+ final SubscriptionBaseTransition ent1Tr2 = createTransition(entitlementId1, EventType.PHASE, null, effectiveDate, clock.getUTCNow(), "trial1", "phase1");
allTransitions1.add(ent1Tr2);
effectiveDate = effectiveDate.plusDays(5);
@@ -1010,23 +1081,24 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
final BlockingState bs1 = new DefaultBlockingState(UUID.randomUUID(), bundleId, BlockingStateType.SUBSCRIPTION_BUNDLE,
DefaultEntitlementApi.ENT_STATE_BLOCKED, DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME,
true, true, false, effectiveDate, clock.getUTCNow(), clock.getUTCNow(), 0L);
- blockingStates.add(bs1);
+ blockingStatesEnt1.add(bs1);
+ blockingStatesEnt2.add(bs1);
effectiveDate = effectiveDate.plusDays(15);
clock.addDays(15);
- final SubscriptionBaseTransition ent1Tr3 = createTransition(entitlementId1, EventType.API_USER, ApiEventType.CANCEL, requestedDate, effectiveDate, clock.getUTCNow(), "phase1", null);
+ final SubscriptionBaseTransition ent1Tr3 = createTransition(entitlementId1, EventType.API_USER, ApiEventType.CANCEL, effectiveDate, clock.getUTCNow(), "phase1", null);
allTransitions1.add(ent1Tr3);
final BlockingState bs2 = new DefaultBlockingState(UUID.randomUUID(), entitlementId1, BlockingStateType.SUBSCRIPTION,
DefaultEntitlementApi.ENT_STATE_CANCELLED, DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME,
true, true, false, effectiveDate, clock.getUTCNow(), clock.getUTCNow(), 1L);
- blockingStates.add(bs2);
+ blockingStatesEnt1.add(bs2);
final List<Entitlement> entitlements = new ArrayList<Entitlement>();
- final Entitlement entitlement1 = createEntitlement(entitlementId1, allTransitions1, blockingStates);
+ final Entitlement entitlement1 = createEntitlement(entitlementId1, allTransitions1, blockingStatesEnt1);
entitlements.add(entitlement1);
- final Entitlement entitlement2 = createEntitlement(entitlementId2, allTransitions2, blockingStates);
+ final Entitlement entitlement2 = createEntitlement(entitlementId2, allTransitions2, blockingStatesEnt2);
entitlements.add(entitlement2);
final SubscriptionBundleTimeline timeline = new DefaultSubscriptionBundleTimeline(accountId, bundleId, externalKey, entitlements, internalCallContext);
@@ -1074,11 +1146,14 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
assertEquals(events.get(8).getServiceName(), EntitlementOrderingBase.BILLING_SERVICE_NAME);
assertNull(events.get(0).getPrevPhase());
+ assertEquals(events.get(0).getNextPhase().getName(), "trial1");
+
assertNull(events.get(1).getPrevPhase());
assertEquals(events.get(1).getNextPhase().getName(), "trial1");
assertNull(events.get(2).getPrevPhase());
assertEquals(events.get(2).getNextPhase().getName(), "phase2");
+
assertNull(events.get(3).getPrevPhase());
assertEquals(events.get(3).getNextPhase().getName(), "phase2");
@@ -1122,9 +1197,8 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
final List<BlockingState> blockingStates = new ArrayList<BlockingState>();
- final DateTime requestedDate = new DateTime();
DateTime effectiveDate = new DateTime(2013, 1, 1, 23, 11, 8, 0, DateTimeZone.UTC);
- final SubscriptionBaseTransition tr1 = createTransition(entitlementId, EventType.API_USER, ApiEventType.CREATE, requestedDate, effectiveDate, clock.getUTCNow(), null, "trial");
+ final SubscriptionBaseTransition tr1 = createTransition(entitlementId, EventType.API_USER, ApiEventType.CREATE, effectiveDate, clock.getUTCNow(), null, "trial");
allTransitions.add(tr1);
if (!regressionFlagForOlderVersionThan_0_17_X) {
@@ -1137,12 +1211,12 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
effectiveDate = effectiveDate.plusDays(30);
clock.addDays(30);
- final SubscriptionBaseTransition tr2 = createTransition(entitlementId, EventType.PHASE, null, requestedDate, effectiveDate, clock.getUTCNow(), "trial", "phase");
+ final SubscriptionBaseTransition tr2 = createTransition(entitlementId, EventType.PHASE, null, effectiveDate, clock.getUTCNow(), "trial", "phase");
allTransitions.add(tr2);
effectiveDate = effectiveDate.plusDays(40); // 2013-03-12
clock.addDays(40);
- final SubscriptionBaseTransition tr3 = createTransition(entitlementId, EventType.API_USER, ApiEventType.CANCEL, requestedDate, effectiveDate, clock.getUTCNow(), "phase", null);
+ final SubscriptionBaseTransition tr3 = createTransition(entitlementId, EventType.API_USER, ApiEventType.CANCEL, effectiveDate, clock.getUTCNow(), "phase", null);
allTransitions.add(tr3);
// Verify the timeline without the blocking state events
@@ -1233,6 +1307,7 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
testRemoveOverlappingBlockingStatesImpl(false);
}
+ @Test(groups = "fast")
public void testRemoveOverlappingBlockingStatesWithRegression() throws CatalogApiException {
testRemoveOverlappingBlockingStatesImpl(true);
}
@@ -1250,9 +1325,8 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
final List<SubscriptionBaseTransition> allTransitions = new ArrayList<SubscriptionBaseTransition>();
final List<BlockingState> blockingStates = new ArrayList<BlockingState>();
- final DateTime requestedDate = new DateTime();
DateTime effectiveDate = new DateTime(2013, 1, 1, 15, 43, 25, 0, DateTimeZone.UTC);
- final SubscriptionBaseTransition tr1 = createTransition(entitlementId, EventType.API_USER, ApiEventType.CREATE, requestedDate, effectiveDate, clock.getUTCNow(), null, "trial");
+ final SubscriptionBaseTransition tr1 = createTransition(entitlementId, EventType.API_USER, ApiEventType.CREATE, effectiveDate, clock.getUTCNow(), null, "trial");
allTransitions.add(tr1);
if (!regressionFlagForOlderVersionThan_0_17_X) {
@@ -1333,6 +1407,7 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
testVariousBlockingStatesAtTheSameEffectiveDateImpl(false);
}
+ @Test(groups = "fast")
public void testVariousBlockingStatesAtTheSameEffectiveDateWithRegression() throws CatalogApiException {
testVariousBlockingStatesAtTheSameEffectiveDateImpl(true);
}
@@ -1350,9 +1425,8 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
final List<SubscriptionBaseTransition> allTransitions = new ArrayList<SubscriptionBaseTransition>();
final List<BlockingState> blockingStates = new ArrayList<BlockingState>();
- final DateTime requestedDate = new DateTime();
DateTime effectiveDate = new DateTime(2013, 1, 1, 15, 43, 25, 0, DateTimeZone.UTC);
- final SubscriptionBaseTransition tr1 = createTransition(entitlementId, EventType.API_USER, ApiEventType.CREATE, requestedDate, effectiveDate, clock.getUTCNow(), null, "trial");
+ final SubscriptionBaseTransition tr1 = createTransition(entitlementId, EventType.API_USER, ApiEventType.CREATE, effectiveDate, clock.getUTCNow(), null, "trial");
allTransitions.add(tr1);
if (!regressionFlagForOlderVersionThan_0_17_X) {
@@ -1480,10 +1554,6 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
assertEquals(events.get(10).getNextPhase().getName(), "trial");
}
- private Entitlement createEntitlement(final UUID entitlementId, final List<SubscriptionBaseTransition> allTransitions) {
- return createEntitlement(entitlementId, allTransitions, ImmutableList.<BlockingState>of());
- }
-
private Entitlement createEntitlement(final UUID entitlementId, final List<SubscriptionBaseTransition> allTransitions, final Collection<BlockingState> blockingStates) {
final DefaultEntitlement result = Mockito.mock(DefaultEntitlement.class);
Mockito.when(result.getId()).thenReturn(entitlementId);
@@ -1502,7 +1572,6 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
private SubscriptionBaseTransition createTransition(final UUID entitlementId,
final EventType eventType,
final ApiEventType apiEventType,
- final DateTime requestedDate,
final DateTime effectiveDate,
final DateTime createdDate,
final String prevPhaseName,
@@ -1559,6 +1628,7 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
return new SubscriptionBaseTransitionData(UUID.randomUUID(),
entitlementId,
bundleId,
+ bundleExternalKey,
eventType,
apiEventType,
effectiveDate,
invoice/pom.xml 2(+1 -1)
diff --git a/invoice/pom.xml b/invoice/pom.xml
index 7079bc7..e713886 100644
--- a/invoice/pom.xml
+++ b/invoice/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.18.2-SNAPSHOT</version>
+ <version>0.18.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-invoice</artifactId>
jaxrs/pom.xml 2(+1 -1)
diff --git a/jaxrs/pom.xml b/jaxrs/pom.xml
index c878e78..5a0b837 100644
--- a/jaxrs/pom.xml
+++ b/jaxrs/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.18.2-SNAPSHOT</version>
+ <version>0.18.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-jaxrs</artifactId>
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/AccountJson.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/AccountJson.java
index e3d3e80..fe81d21 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/AccountJson.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/AccountJson.java
@@ -24,9 +24,11 @@ import java.util.UUID;
import javax.annotation.Nullable;
+import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.killbill.billing.account.api.Account;
import org.killbill.billing.account.api.AccountData;
+import org.killbill.billing.account.api.MutableAccountData;
import org.killbill.billing.catalog.api.Currency;
import org.killbill.billing.util.audit.AccountAuditLogs;
@@ -150,8 +152,14 @@ public class AccountJson extends JsonBase {
this.accountCBA = accountCBA;
}
- public AccountData toAccountData() {
- return new AccountData() {
+ public Account toAccount(@Nullable final UUID accountId) {
+ return new Account() {
+
+ @Override
+ public UUID getId() {
+ return accountId;
+ }
+
@Override
public DateTimeZone getTimeZone() {
if (Strings.emptyToNull(timeZone) == null) {
@@ -215,6 +223,7 @@ public class AccountJson extends JsonBase {
return firstNameLength;
}
+
@Override
public String getExternalKey() {
return externalKey;
@@ -277,6 +286,39 @@ public class AccountJson extends JsonBase {
public Boolean isPaymentDelegatedToParent() {
return isPaymentDelegatedToParent;
}
+
+ //
+ // Non implemented
+ //
+ @Override
+ public DateTimeZone getFixedOffsetTimeZone() {
+ return null;
+ }
+
+ @Override
+ public DateTime getReferenceTime() {
+ return null;
+ }
+
+ @Override
+ public DateTime getCreatedDate() {
+ return null;
+ }
+
+ @Override
+ public DateTime getUpdatedDate() {
+ return null;
+ }
+
+ @Override
+ public MutableAccountData toMutableAccountData() {
+ return null;
+ }
+
+ @Override
+ public Account mergeWithDelegate(final Account delegate) {
+ return null;
+ }
};
}
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/PhasePriceOverrideJson.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/PhasePriceOverrideJson.java
index 066353b..e6233f1 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/PhasePriceOverrideJson.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/PhasePriceOverrideJson.java
@@ -223,8 +223,8 @@ public class PhasePriceOverrideJson {
final PlanPhaseSpecifier planPhaseSpecifier = spec.getPlanName() != null ?
new PlanPhaseSpecifier(spec.getPlanName(), phaseType) :
new PlanPhaseSpecifier(spec.getProductName(), spec.getBillingPeriod(), spec.getPriceListName(), phaseType);
- return new DefaultPlanPhasePriceOverride(planPhaseSpecifier, currency, input.getFixedPrice(), input.getRecurringPrice(), usagePriceOverrides);
-
+ final Currency resolvedCurrency = input.getFixedPrice() != null || input.getRecurringPrice() != null ? currency : null;
+ return new DefaultPlanPhasePriceOverride(planPhaseSpecifier, resolvedCurrency, input.getFixedPrice(), input.getRecurringPrice(), usagePriceOverrides);
}
}
}));
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/SubscriptionJson.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/SubscriptionJson.java
index 3a254de..5e17d23 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/SubscriptionJson.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/SubscriptionJson.java
@@ -346,7 +346,8 @@ public class SubscriptionJson extends JsonBase {
this.startDate = subscription.getEffectiveStartDate();
// last* fields can be null if the subscription starts in the future - rely on the first available event instead
- final SubscriptionEvent firstEvent = subscription.getSubscriptionEvents().isEmpty() ? null : subscription.getSubscriptionEvents().get(0);
+ final List<SubscriptionEvent> subscriptionEvents = subscription.getSubscriptionEvents();
+ final SubscriptionEvent firstEvent = subscriptionEvents.isEmpty() ? null : subscriptionEvents.get(0);
if (subscription.getLastActiveProduct() == null) {
this.productName = firstEvent == null ? null : firstEvent.getNextProduct().getName();
} else {
@@ -395,9 +396,16 @@ public class SubscriptionJson extends JsonBase {
this.priceOverrides = new ArrayList<PhasePriceOverrideJson>();
String currentPhaseName = null;
+ String currentPlanName = null;
for (final SubscriptionEvent subscriptionEvent : subscription.getSubscriptionEvents()) {
this.events.add(new EventSubscriptionJson(subscriptionEvent, accountAuditLogs));
if (currency != null) {
+
+ final Plan curPlan = subscriptionEvent.getNextPlan();
+ if (curPlan != null && (currentPlanName == null || !curPlan.getName().equals(currentPlanName))) {
+ currentPlanName = curPlan.getName();
+ }
+
final PlanPhase curPlanPhase = subscriptionEvent.getNextPhase();
if (curPlanPhase == null || curPlanPhase.getName().equals(currentPhaseName)) {
continue;
@@ -406,7 +414,7 @@ public class SubscriptionJson extends JsonBase {
final BigDecimal fixedPrice = curPlanPhase.getFixed() != null ? curPlanPhase.getFixed().getPrice().getPrice(currency) : null;
final BigDecimal recurringPrice = curPlanPhase.getRecurring() != null ? curPlanPhase.getRecurring().getRecurringPrice().getPrice(currency) : null;
- final PhasePriceOverrideJson phase = new PhasePriceOverrideJson(subscriptionEvent.getNextPlan().getName(), curPlanPhase.getName(), curPlanPhase.getPhaseType().toString(), fixedPrice, recurringPrice, curPlanPhase.getUsages(),currency);
+ final PhasePriceOverrideJson phase = new PhasePriceOverrideJson(currentPlanName, curPlanPhase.getName(), curPlanPhase.getPhaseType().toString(), fixedPrice, recurringPrice, curPlanPhase.getUsages(), currency);
priceOverrides.add(phase);
}
}
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AccountResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AccountResource.java
index 982e965..b9ab2f0 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AccountResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AccountResource.java
@@ -354,9 +354,9 @@ public class AccountResource extends JaxRsResourceBase {
@javax.ws.rs.core.Context final UriInfo uriInfo) throws AccountApiException {
verifyNonNullOrEmpty(json, "AccountJson body should be specified");
- final AccountData data = json.toAccountData();
+ final AccountData data = json.toAccount(null);
final Account account = accountUserApi.createAccount(data, context.createContext(createdBy, reason, comment, request));
- return uriBuilder.buildResponse(uriInfo, AccountResource.class, "getAccount", account.getId());
+ return uriBuilder.buildResponse(uriInfo, AccountResource.class, "getAccount", account.getId(), request);
}
@TimedResource
@@ -368,15 +368,20 @@ public class AccountResource extends JaxRsResourceBase {
@ApiResponses(value = {@ApiResponse(code = 400, message = "Invalid account data supplied")})
public Response updateAccount(final AccountJson json,
@PathParam("accountId") final String accountId,
+ @QueryParam(QUERY_ACCOUNT_TREAT_NULL_AS_RESET) @DefaultValue("false") final Boolean treatNullValueAsReset,
@HeaderParam(HDR_CREATED_BY) final String createdBy,
@HeaderParam(HDR_REASON) final String reason,
@HeaderParam(HDR_COMMENT) final String comment,
@javax.ws.rs.core.Context final HttpServletRequest request) throws AccountApiException {
verifyNonNullOrEmpty(json, "AccountJson body should be specified");
- final AccountData data = json.toAccountData();
final UUID uuid = UUID.fromString(accountId);
- accountUserApi.updateAccount(uuid, data, context.createContext(createdBy, reason, comment, request));
+ final Account data = json.toAccount(uuid);
+ if (treatNullValueAsReset) {
+ accountUserApi.updateAccount(data, context.createContext(createdBy, reason, comment, request));
+ } else {
+ accountUserApi.updateAccount(uuid, data, context.createContext(createdBy, reason, comment, request));
+ }
return getAccount(accountId, false, false, new AuditMode(AuditLevel.NONE.toString()), request);
}
@@ -783,7 +788,7 @@ public class AccountResource extends JaxRsResourceBase {
createPurchaseForInvoice(account, invoice.getId(), invoice.getBalance(), paymentMethodId, false, null, null, pluginProperties, callContext);
}
}
- return uriBuilder.buildResponse(PaymentMethodResource.class, "getPaymentMethod", paymentMethodId, uriInfo.getBaseUri().toString());
+ return uriBuilder.buildResponse(uriInfo, PaymentMethodResource.class, "getPaymentMethod", paymentMethodId, request);
}
@TimedResource
@@ -930,7 +935,7 @@ public class AccountResource extends JaxRsResourceBase {
final CallContext callContext = context.createContext(createdBy, reason, comment, request);
final Account account = accountUserApi.getAccountByKey(externalKey, callContext);
- return processPayment(json, account, paymentMethodIdStr, paymentControlPluginNames, pluginPropertiesString, uriInfo, callContext);
+ return processPayment(json, account, paymentMethodIdStr, paymentControlPluginNames, pluginPropertiesString, uriInfo, callContext, request);
}
@TimedResource(name = "processPayment")
@@ -961,7 +966,7 @@ public class AccountResource extends JaxRsResourceBase {
final CallContext callContext = context.createContext(createdBy, reason, comment, request);
final Account account = accountUserApi.getAccountById(accountId, callContext);
- return processPayment(json, account, paymentMethodIdStr, paymentControlPluginNames, pluginPropertiesString, uriInfo, callContext);
+ return processPayment(json, account, paymentMethodIdStr, paymentControlPluginNames, pluginPropertiesString, uriInfo, callContext, request);
}
private Response processPayment(final PaymentTransactionJson json,
@@ -970,7 +975,8 @@ public class AccountResource extends JaxRsResourceBase {
final List<String> paymentControlPluginNames,
final List<String> pluginPropertiesString,
final UriInfo uriInfo,
- final CallContext callContext) throws PaymentApiException {
+ final CallContext callContext,
+ final HttpServletRequest request) throws PaymentApiException {
verifyNonNullOrEmpty(json, "PaymentTransactionJson body should be specified");
verifyNonNullOrEmpty(json.getTransactionType(), "PaymentTransactionJson transactionType needs to be set",
json.getAmount(), "PaymentTransactionJson amount needs to be set");
@@ -993,7 +999,7 @@ public class AccountResource extends JaxRsResourceBase {
json != null ? json.getTransactionType() : null);
// If transaction was already completed, return early (See #626)
if (pendingOrSuccessTransaction.getTransactionStatus() == TransactionStatus.SUCCESS) {
- return uriBuilder.buildResponse(uriInfo, PaymentResource.class, "getPayment", pendingOrSuccessTransaction.getPaymentId());
+ return uriBuilder.buildResponse(uriInfo, PaymentResource.class, "getPayment", pendingOrSuccessTransaction.getPaymentId(), request);
}
paymentMethodId = initialPayment.getPaymentMethodId();
@@ -1025,7 +1031,7 @@ public class AccountResource extends JaxRsResourceBase {
default:
return Response.status(Status.PRECONDITION_FAILED).entity("TransactionType " + transactionType + " is not allowed for an account").build();
}
- return createPaymentResponse(uriInfo, result, transactionType, json.getTransactionExternalKey());
+ return createPaymentResponse(uriInfo, result, transactionType, json.getTransactionExternalKey(), request);
}
/*
@@ -1130,7 +1136,7 @@ public class AccountResource extends JaxRsResourceBase {
@javax.ws.rs.core.Context final HttpServletRequest request,
@javax.ws.rs.core.Context final UriInfo uriInfo) throws CustomFieldApiException {
return super.createCustomFields(UUID.fromString(id), customFields, context.createContext(createdBy, reason,
- comment, request), uriInfo);
+ comment, request), uriInfo, request);
}
@TimedResource
@@ -1203,7 +1209,7 @@ public class AccountResource extends JaxRsResourceBase {
@javax.ws.rs.core.Context final UriInfo uriInfo,
@javax.ws.rs.core.Context final HttpServletRequest request) throws TagApiException {
return super.createTags(UUID.fromString(id), tagList, uriInfo,
- context.createContext(createdBy, reason, comment, request));
+ context.createContext(createdBy, reason, comment, request), request);
}
@TimedResource
@@ -1303,7 +1309,7 @@ public class AccountResource extends JaxRsResourceBase {
accountUserApi.addEmail(accountId, json.toAccountEmail(UUIDs.randomUUID()), callContext);
}
- return uriBuilder.buildResponse(uriInfo, AccountResource.class, "getEmails", json.getAccountId());
+ return uriBuilder.buildResponse(uriInfo, AccountResource.class, "getEmails", json.getAccountId(), request);
}
@TimedResource
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AdminResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AdminResource.java
index 4ca6487..b391d41 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AdminResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AdminResource.java
@@ -20,13 +20,19 @@ package org.killbill.billing.jaxrs.resources;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.LinkedList;
+import java.util.List;
import java.util.UUID;
+import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.DefaultValue;
+import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
@@ -39,6 +45,8 @@ import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.StreamingOutput;
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
import org.killbill.billing.ErrorCode;
import org.killbill.billing.ObjectType;
import org.killbill.billing.account.api.AccountUserApi;
@@ -67,13 +75,22 @@ import org.killbill.billing.util.callcontext.TenantContext;
import org.killbill.billing.util.entity.Pagination;
import org.killbill.billing.util.tag.Tag;
import org.killbill.billing.util.tag.dao.SystemTags;
+import org.killbill.bus.api.BusEvent;
+import org.killbill.bus.api.BusEventWithMetadata;
+import org.killbill.bus.api.PersistentBus;
import org.killbill.clock.Clock;
+import org.killbill.notificationq.api.NotificationEvent;
+import org.killbill.notificationq.api.NotificationEventWithMetadata;
+import org.killbill.notificationq.api.NotificationQueue;
+import org.killbill.notificationq.api.NotificationQueueService;
import com.fasterxml.jackson.core.JsonGenerator;
import com.google.common.base.Predicate;
+import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
+import com.google.common.collect.Ordering;
import com.google.inject.Singleton;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
@@ -96,15 +113,88 @@ public class AdminResource extends JaxRsResourceBase {
private final TenantUserApi tenantApi;
private final CacheManager cacheManager;
private final RecordIdApi recordIdApi;
+ private final PersistentBus persistentBus;
+ private final NotificationQueueService notificationQueueService;
@Inject
- public AdminResource(final JaxrsUriBuilder uriBuilder, final TagUserApi tagUserApi, final CustomFieldUserApi customFieldUserApi, final AuditUserApi auditUserApi, final AccountUserApi accountUserApi, final PaymentApi paymentApi, final AdminPaymentApi adminPaymentApi, final InvoiceUserApi invoiceUserApi, final CacheManager cacheManager, final TenantUserApi tenantApi, final RecordIdApi recordIdApi, final Clock clock, final Context context) {
+ public AdminResource(final JaxrsUriBuilder uriBuilder,
+ final TagUserApi tagUserApi,
+ final CustomFieldUserApi customFieldUserApi,
+ final AuditUserApi auditUserApi,
+ final AccountUserApi accountUserApi,
+ final PaymentApi paymentApi,
+ final AdminPaymentApi adminPaymentApi,
+ final InvoiceUserApi invoiceUserApi,
+ final CacheManager cacheManager,
+ final TenantUserApi tenantApi,
+ final RecordIdApi recordIdApi,
+ final PersistentBus persistentBus,
+ final NotificationQueueService notificationQueueService,
+ final Clock clock,
+ final Context context) {
super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, null, clock, context);
this.adminPaymentApi = adminPaymentApi;
this.invoiceUserApi = invoiceUserApi;
this.tenantApi = tenantApi;
this.recordIdApi = recordIdApi;
this.cacheManager = cacheManager;
+ this.persistentBus = persistentBus;
+ this.notificationQueueService = notificationQueueService;
+ }
+
+ @GET
+ @Path("/queues")
+ @Produces(APPLICATION_JSON)
+ @ApiOperation(value = "Get queues entries", response = Response.class)
+ @ApiResponses(value = {})
+ public Response getQueueEntries(@QueryParam("accountId") final String accountIdStr,
+ @QueryParam("queueName") final String queueName,
+ @QueryParam("serviceName") final String serviceName,
+ @QueryParam("withHistory") @DefaultValue("true") final Boolean withHistory,
+ @QueryParam("minDate") final String minDateOrNull,
+ @QueryParam("withInProcessing") @DefaultValue("true") final Boolean withInProcessing,
+ @QueryParam("withBusEvents") @DefaultValue("true") final Boolean withBusEvents,
+ @QueryParam("withNotifications") @DefaultValue("true") final Boolean withNotifications,
+ @javax.ws.rs.core.Context final HttpServletRequest request) {
+ final TenantContext tenantContext = context.createContext(request);
+ final Long tenantRecordId = recordIdApi.getRecordId(tenantContext.getTenantId(), ObjectType.TENANT, tenantContext);
+ final Long accountRecordId = Strings.isNullOrEmpty(accountIdStr) ? null : recordIdApi.getRecordId(UUID.fromString(accountIdStr), ObjectType.ACCOUNT, tenantContext);
+
+ // Limit search results by default
+ final DateTime minDate = Strings.isNullOrEmpty(minDateOrNull) ? clock.getUTCNow().minusMonths(2) : DATE_TIME_FORMATTER.parseDateTime(minDateOrNull).toDateTime(DateTimeZone.UTC);
+
+ final StreamingOutput json = new StreamingOutput() {
+ @Override
+ public void write(final OutputStream output) throws IOException, WebApplicationException {
+ final JsonGenerator generator = mapper.getFactory().createGenerator(output);
+ generator.configure(JsonGenerator.Feature.AUTO_CLOSE_TARGET, false);
+
+ generator.writeStartObject();
+
+ if (withBusEvents) {
+ generator.writeFieldName("busEvents");
+ generator.writeStartArray();
+ for (final BusEventWithMetadata<BusEvent> busEvent : getBusEvents(withInProcessing, withHistory, minDate, accountRecordId, tenantRecordId)) {
+ generator.writeObject(new BusEventWithRichMetadata(busEvent));
+ }
+ generator.writeEndArray();
+ }
+
+ if (withNotifications) {
+ generator.writeFieldName("notifications");
+ generator.writeStartArray();
+ for (final NotificationEventWithMetadata<NotificationEvent> notification : getNotifications(queueName, serviceName, withInProcessing, withHistory, minDate, accountRecordId, tenantRecordId)) {
+ generator.writeObject(notification);
+ }
+ generator.writeEndArray();
+ }
+
+ generator.writeEndObject();
+ generator.close();
+ }
+ };
+
+ return Response.status(Status.OK).entity(json).build();
}
@PUT
@@ -296,4 +386,103 @@ public class AdminResource extends JaxRsResourceBase {
}
}
+ private Iterable<NotificationEventWithMetadata<NotificationEvent>> getNotifications(@Nullable final String queueName,
+ @Nullable final String serviceName,
+ final boolean includeInProcessing,
+ final boolean includeHistory,
+ @Nullable final DateTime minEffectiveDate,
+ @Nullable final Long accountRecordId,
+ final Long tenantRecordId) {
+ final Collection<NotificationEventWithMetadata<NotificationEvent>> notifications = new LinkedList<NotificationEventWithMetadata<NotificationEvent>>();
+ for (final NotificationQueue notificationQueue : notificationQueueService.getNotificationQueues()) {
+ if (queueName != null && !queueName.equals(notificationQueue.getQueueName())) {
+ continue;
+ } else if (serviceName != null && !serviceName.equals(notificationQueue.getServiceName())) {
+ continue;
+ }
+
+ final List<NotificationEventWithMetadata<NotificationEvent>> notificationsForQueue;
+ if (includeInProcessing) {
+ if (accountRecordId != null) {
+ notificationsForQueue = notificationQueue.getFutureOrInProcessingNotificationForSearchKeys(accountRecordId, tenantRecordId);
+ } else {
+ notificationsForQueue = notificationQueue.getFutureOrInProcessingNotificationForSearchKey2(tenantRecordId);
+ }
+ } else {
+ if (accountRecordId != null) {
+ notificationsForQueue = notificationQueue.getFutureNotificationForSearchKeys(accountRecordId, tenantRecordId);
+ } else {
+ notificationsForQueue = notificationQueue.getFutureNotificationForSearchKey2(tenantRecordId);
+ }
+ }
+
+ notifications.addAll(notificationsForQueue);
+
+ if (includeHistory) {
+ if (accountRecordId != null) {
+ notifications.addAll(notificationQueue.getHistoricalNotificationForSearchKeys(accountRecordId, tenantRecordId));
+ } else {
+ notifications.addAll(notificationQueue.getHistoricalNotificationForSearchKey2(minEffectiveDate, tenantRecordId));
+ }
+ }
+ }
+
+ return Ordering.<NotificationEventWithMetadata<NotificationEvent>>from(new Comparator<NotificationEventWithMetadata<NotificationEvent>>() {
+ @Override
+ public int compare(final NotificationEventWithMetadata<NotificationEvent> o1, final NotificationEventWithMetadata<NotificationEvent> o2) {
+ final int effectiveDateComparison = o1.getEffectiveDate().compareTo(o2.getEffectiveDate());
+ return effectiveDateComparison == 0 ? o1.getRecordId().compareTo(o2.getRecordId()) : effectiveDateComparison;
+ }
+ }).sortedCopy(notifications);
+ }
+
+ private Iterable<BusEventWithMetadata<BusEvent>> getBusEvents(final boolean includeInProcessing,
+ final boolean includeHistory,
+ @Nullable final DateTime minCreatedDate,
+ @Nullable final Long accountRecordId,
+ final Long tenantRecordId) {
+ final Collection<BusEventWithMetadata<BusEvent>> busEvents = new LinkedList<BusEventWithMetadata<BusEvent>>();
+ if (includeInProcessing) {
+ if (accountRecordId != null) {
+ busEvents.addAll(persistentBus.getAvailableOrInProcessingBusEventsForSearchKeys(accountRecordId, tenantRecordId));
+ } else {
+ busEvents.addAll(persistentBus.getAvailableOrInProcessingBusEventsForSearchKey2(tenantRecordId));
+ }
+ } else {
+ if (accountRecordId != null) {
+ busEvents.addAll(persistentBus.getAvailableBusEventsForSearchKeys(accountRecordId, tenantRecordId));
+ } else {
+ busEvents.addAll(persistentBus.getAvailableBusEventsForSearchKey2(tenantRecordId));
+ }
+ }
+
+ if (includeHistory) {
+ if (accountRecordId != null) {
+ busEvents.addAll(persistentBus.getHistoricalBusEventsForSearchKeys(accountRecordId, tenantRecordId));
+ } else {
+ busEvents.addAll(persistentBus.getHistoricalBusEventsForSearchKey2(minCreatedDate, tenantRecordId));
+ }
+ }
+
+ return Ordering.<BusEventWithMetadata<BusEvent>>from(new Comparator<BusEventWithMetadata<BusEvent>>() {
+ @Override
+ public int compare(final BusEventWithMetadata<BusEvent> o1, final BusEventWithMetadata<BusEvent> o2) {
+ return o1.getRecordId().compareTo(o2.getRecordId());
+ }
+ }).sortedCopy(busEvents);
+ }
+
+ private class BusEventWithRichMetadata extends BusEventWithMetadata<BusEvent> {
+
+ private final String className;
+
+ public BusEventWithRichMetadata(final BusEventWithMetadata<BusEvent> event) {
+ super(event.getRecordId(), event.getUserToken(), event.getCreatedDate(), event.getSearchKey1(), event.getSearchKey2(), event.getEvent());
+ this.className = event.getEvent().getClass().getName();
+ }
+
+ public String getClassName() {
+ return className;
+ }
+ }
}
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/BundleResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/BundleResource.java
index 04e39d5..c54164a 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/BundleResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/BundleResource.java
@@ -321,7 +321,7 @@ public class BundleResource extends JaxRsResourceBase {
@javax.ws.rs.core.Context final HttpServletRequest request,
@javax.ws.rs.core.Context final UriInfo uriInfo) throws CustomFieldApiException {
return super.createCustomFields(UUID.fromString(id), customFields,
- context.createContext(createdBy, reason, comment, request), uriInfo);
+ context.createContext(createdBy, reason, comment, request), uriInfo, request);
}
@TimedResource
@@ -389,7 +389,7 @@ public class BundleResource extends JaxRsResourceBase {
final LocalDate inputLocalDate = toLocalDate(requestedDate);
final UUID newBundleId = entitlementApi.transferEntitlementsOverrideBillingPolicy(bundle.getAccountId(), UUID.fromString(json.getAccountId()), bundle.getExternalKey(), inputLocalDate, policy, pluginProperties, callContext);
- return uriBuilder.buildResponse(BundleResource.class, "getBundle", newBundleId, uriInfo.getBaseUri().toString());
+ return uriBuilder.buildResponse(uriInfo, BundleResource.class, "getBundle", newBundleId, request);
}
@TimedResource
@@ -407,7 +407,7 @@ public class BundleResource extends JaxRsResourceBase {
@javax.ws.rs.core.Context final UriInfo uriInfo,
@javax.ws.rs.core.Context final HttpServletRequest request) throws TagApiException {
return super.createTags(UUID.fromString(id), tagList, uriInfo,
- context.createContext(createdBy, reason, comment, request));
+ context.createContext(createdBy, reason, comment, request), request);
}
@TimedResource
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/CatalogResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/CatalogResource.java
index 22f7568..3f842fe 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/CatalogResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/CatalogResource.java
@@ -36,9 +36,7 @@ import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.killbill.billing.account.api.AccountUserApi;
import org.killbill.billing.catalog.StandaloneCatalog;
-import org.killbill.billing.catalog.StandaloneCatalogWithPriceOverride;
import org.killbill.billing.catalog.VersionedCatalog;
-import org.killbill.billing.catalog.api.Catalog;
import org.killbill.billing.catalog.api.CatalogApiException;
import org.killbill.billing.catalog.api.CatalogUserApi;
import org.killbill.billing.catalog.api.Listing;
@@ -113,7 +111,7 @@ public class CatalogResource extends JaxRsResourceBase {
@javax.ws.rs.core.Context final UriInfo uriInfo) throws Exception {
final CallContext callContext = context.createContext(createdBy, reason, comment, request);
catalogUserApi.uploadCatalog(catalogXML, callContext);
- return uriBuilder.buildResponse(uriInfo, CatalogResource.class, null, null);
+ return uriBuilder.buildResponse(uriInfo, CatalogResource.class, null, null, request);
}
@TimedResource
@@ -219,7 +217,7 @@ public class CatalogResource extends JaxRsResourceBase {
simplePlan.getTrialTimeUnit(),
simplePlan.getAvailableBaseProducts());
catalogUserApi.addSimplePlan(desc, clock.getUTCNow(), callContext);
- return uriBuilder.buildResponse(uriInfo, CatalogResource.class, null, null);
+ return uriBuilder.buildResponse(uriInfo, CatalogResource.class, null, null, request);
}
}
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/ComboPaymentResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/ComboPaymentResource.java
index 947e124..b2299ad 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/ComboPaymentResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/ComboPaymentResource.java
@@ -76,7 +76,7 @@ public abstract class ComboPaymentResource extends JaxRsResourceBase {
}
}
// Finally create if does not exist
- return accountUserApi.createAccount(accountJson.toAccountData(), callContext);
+ return accountUserApi.createAccount(accountJson.toAccount(null), callContext);
}
protected UUID getOrCreatePaymentMethod(final Account account, final PaymentMethodJson paymentMethodJson, final Iterable<PluginProperty> pluginProperties, final CallContext callContext) throws PaymentApiException {
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/CreditResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/CreditResource.java
index b6d005f..17e2c9f 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/CreditResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/CreditResource.java
@@ -131,7 +131,7 @@ public class CreditResource extends JaxRsResourceBase {
account.getCurrency(), autoCommit, json.getDescription(), callContext);
}
- return uriBuilder.buildResponse(uriInfo, CreditResource.class, "getCredit", credit.getId());
+ return uriBuilder.buildResponse(uriInfo, CreditResource.class, "getCredit", credit.getId(), request);
}
@Override
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoicePaymentResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoicePaymentResource.java
index ff2ed5a..e5249e9 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoicePaymentResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoicePaymentResource.java
@@ -57,7 +57,6 @@ import org.killbill.billing.jaxrs.util.JaxrsUriBuilder;
import org.killbill.billing.payment.api.Payment;
import org.killbill.billing.payment.api.PaymentApi;
import org.killbill.billing.payment.api.PaymentApiException;
-import org.killbill.billing.payment.api.PaymentMethod;
import org.killbill.billing.payment.api.PluginProperty;
import org.killbill.billing.util.UUIDs;
import org.killbill.billing.util.api.AuditUserApi;
@@ -200,7 +199,7 @@ public class InvoicePaymentResource extends JaxRsResourceBase {
pluginProperties, createInvoicePaymentControlPluginApiPaymentOptions(false), callContext);
}
- return uriBuilder.buildResponse(InvoicePaymentResource.class, "getInvoicePayment", result.getId(), uriInfo.getBaseUri().toString());
+ return uriBuilder.buildResponse(uriInfo, InvoicePaymentResource.class, "getInvoicePayment", result.getId(), request);
}
@TimedResource
@@ -229,7 +228,7 @@ public class InvoicePaymentResource extends JaxRsResourceBase {
final Payment result = paymentApi.createChargebackWithPaymentControl(account, payment.getId(), json.getAmount(), account.getCurrency(),
transactionExternalKey, createInvoicePaymentControlPluginApiPaymentOptions(false), callContext);
- return uriBuilder.buildResponse(uriInfo, InvoicePaymentResource.class, "getInvoicePayment", result.getId());
+ return uriBuilder.buildResponse(uriInfo, InvoicePaymentResource.class, "getInvoicePayment", result.getId(), request);
}
@TimedResource
@@ -256,7 +255,7 @@ public class InvoicePaymentResource extends JaxRsResourceBase {
final Account account = accountUserApi.getAccountById(payment.getAccountId(), callContext);
final Payment result = paymentApi.createChargebackReversalWithPaymentControl(account, payment.getId(), json.getTransactionExternalKey(), createInvoicePaymentControlPluginApiPaymentOptions(false), callContext);
- return uriBuilder.buildResponse(uriInfo, InvoicePaymentResource.class, "getInvoicePayment", result.getId());
+ return uriBuilder.buildResponse(uriInfo, InvoicePaymentResource.class, "getInvoicePayment", result.getId(), request);
}
@TimedResource
@@ -286,7 +285,7 @@ public class InvoicePaymentResource extends JaxRsResourceBase {
@javax.ws.rs.core.Context final HttpServletRequest request,
@javax.ws.rs.core.Context final UriInfo uriInfo) throws CustomFieldApiException {
return super.createCustomFields(UUID.fromString(id), customFields,
- context.createContext(createdBy, reason, comment, request), uriInfo);
+ context.createContext(createdBy, reason, comment, request), uriInfo, request);
}
@TimedResource
@@ -340,7 +339,7 @@ public class InvoicePaymentResource extends JaxRsResourceBase {
@javax.ws.rs.core.Context final UriInfo uriInfo,
@javax.ws.rs.core.Context final HttpServletRequest request) throws TagApiException {
return super.createTags(UUID.fromString(id), tagList, uriInfo,
- context.createContext(createdBy, reason, comment, request));
+ context.createContext(createdBy, reason, comment, request), request);
}
@TimedResource
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoiceResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoiceResource.java
index 7a0e59c..b89a910 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoiceResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoiceResource.java
@@ -93,7 +93,6 @@ import org.killbill.billing.tenant.api.TenantApiException;
import org.killbill.billing.tenant.api.TenantKV.TenantKey;
import org.killbill.billing.tenant.api.TenantUserApi;
import org.killbill.billing.util.LocaleUtils;
-import org.killbill.billing.util.UUIDs;
import org.killbill.billing.util.api.AuditUserApi;
import org.killbill.billing.util.api.CustomFieldApiException;
import org.killbill.billing.util.api.CustomFieldUserApi;
@@ -311,7 +310,7 @@ public class InvoiceResource extends JaxRsResourceBase {
try {
final Invoice generatedInvoice = invoiceApi.triggerInvoiceGeneration(UUID.fromString(accountId), inputDate, null,
callContext);
- return uriBuilder.buildResponse(uriInfo, InvoiceResource.class, "getInvoice", generatedInvoice.getId());
+ return uriBuilder.buildResponse(uriInfo, InvoiceResource.class, "getInvoice", generatedInvoice.getId(), request);
} catch (InvoiceApiException e) {
if (e.getCode() == ErrorCode.INVOICE_NOTHING_TO_DO.getCode()) {
return Response.status(Status.NOT_FOUND).build();
@@ -341,7 +340,7 @@ public class InvoiceResource extends JaxRsResourceBase {
final Iterable<InvoiceItem> sanitizedInvoiceItems = validateSanitizeAndTranformInputItems(account.getCurrency(), items);
final LocalDate resolvedTargetDate = toLocalDateDefaultToday(account, targetDate, callContext);
final UUID invoiceId = invoiceApi.createMigrationInvoice(UUID.fromString(accountId), resolvedTargetDate, sanitizedInvoiceItems, callContext);
- return uriBuilder.buildResponse(uriInfo, InvoiceResource.class, "getInvoice", invoiceId);
+ return uriBuilder.buildResponse(uriInfo, InvoiceResource.class, "getInvoice", invoiceId, request);
}
@@ -475,7 +474,7 @@ public class InvoiceResource extends JaxRsResourceBase {
callContext);
}
- return uriBuilder.buildResponse(uriInfo, InvoiceResource.class, "getInvoice", adjustmentItem.getInvoiceId());
+ return uriBuilder.buildResponse(uriInfo, InvoiceResource.class, "getInvoice", adjustmentItem.getInvoiceId(), request);
}
@TimedResource
@@ -663,7 +662,7 @@ public class InvoiceResource extends JaxRsResourceBase {
final Payment result = createPurchaseForInvoice(account, invoiceId, payment.getPurchasedAmount(), paymentMethodId, externalPayment,
(payment.getPaymentExternalKey() != null) ? payment.getPaymentExternalKey() : null, null, pluginProperties, callContext);
return result != null ?
- uriBuilder.buildResponse(uriInfo, InvoicePaymentResource.class, "getInvoicePayment", result.getId()) :
+ uriBuilder.buildResponse(uriInfo, InvoicePaymentResource.class, "getInvoicePayment", result.getId(), request) :
Response.status(Status.NO_CONTENT).build();
}
@@ -878,7 +877,7 @@ public class InvoiceResource extends JaxRsResourceBase {
}
}
tenantApi.addTenantKeyValue(tenantKeyStr, templateResource, callContext);
- return uriBuilder.buildResponse(uriInfo, InvoiceResource.class, getMethodStr, localeStr);
+ return uriBuilder.buildResponse(uriInfo, InvoiceResource.class, getMethodStr, localeStr, request);
}
private Response getTemplateResource(@Nullable final String localeStr,
@@ -919,7 +918,7 @@ public class InvoiceResource extends JaxRsResourceBase {
@javax.ws.rs.core.Context final HttpServletRequest request,
@javax.ws.rs.core.Context final UriInfo uriInfo) throws CustomFieldApiException {
return super.createCustomFields(UUID.fromString(id), customFields,
- context.createContext(createdBy, reason, comment, request), uriInfo);
+ context.createContext(createdBy, reason, comment, request), uriInfo, request);
}
@TimedResource
@@ -971,7 +970,7 @@ public class InvoiceResource extends JaxRsResourceBase {
@javax.ws.rs.core.Context final UriInfo uriInfo,
@javax.ws.rs.core.Context final HttpServletRequest request) throws TagApiException {
return super.createTags(UUID.fromString(id), tagList, uriInfo,
- context.createContext(createdBy, reason, comment, request));
+ context.createContext(createdBy, reason, comment, request), request);
}
@TimedResource
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxrsResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxrsResource.java
index cecf4f7..dd706db 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxrsResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxrsResource.java
@@ -85,9 +85,11 @@ public interface JaxrsResource {
public static final String QUERY_SEARCH_OFFSET = "offset";
public static final String QUERY_SEARCH_LIMIT = "limit";
public static final String QUERY_ENTITLEMENT_EFFECTIVE_FROM_DT = "effectiveFromDate";
+ public static final String QUERY_FORCE_NEW_BCD_WITH_PAST_EFFECTIVE_DATE = "forceNewBcdWithPastEffectiveDate";
public static final String QUERY_ACCOUNT_WITH_BALANCE = "accountWithBalance";
public static final String QUERY_ACCOUNT_WITH_BALANCE_AND_CBA = "accountWithBalanceAndCBA";
+ public static final String QUERY_ACCOUNT_TREAT_NULL_AS_RESET = "treatNullAsReset";
public static final String QUERY_ACCOUNT_ID = "accountId";
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxRsResourceBase.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxRsResourceBase.java
index 591dbfc..c61ada1 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxRsResourceBase.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxRsResourceBase.java
@@ -78,7 +78,6 @@ import org.killbill.billing.payment.api.PaymentTransaction;
import org.killbill.billing.payment.api.PluginProperty;
import org.killbill.billing.payment.api.TransactionStatus;
import org.killbill.billing.payment.api.TransactionType;
-import org.killbill.billing.util.UUIDs;
import org.killbill.billing.util.api.AuditUserApi;
import org.killbill.billing.util.api.CustomFieldApiException;
import org.killbill.billing.util.api.CustomFieldUserApi;
@@ -213,11 +212,12 @@ public abstract class JaxRsResourceBase implements JaxrsResource {
protected Response createTags(final UUID id,
final String tagList,
final UriInfo uriInfo,
- final CallContext context) throws TagApiException {
+ final CallContext context,
+ final HttpServletRequest request) throws TagApiException {
final Collection<UUID> input = getTagDefinitionUUIDs(tagList);
tagUserApi.addTags(id, getObjectType(), input, context);
// TODO This will always return 201, even if some (or all) tags already existed (in which case we don't do anything)
- return uriBuilder.buildResponse(this.getClass(), "getTags", id, uriInfo.getBaseUri().toString());
+ return uriBuilder.buildResponse(uriInfo, this.getClass(), "getTags", id, request);
}
protected Collection<UUID> getTagDefinitionUUIDs(final String tagList) {
@@ -255,7 +255,8 @@ public abstract class JaxRsResourceBase implements JaxrsResource {
protected Response createCustomFields(final UUID id,
final List<CustomFieldJson> customFields,
final CallContext context,
- final UriInfo uriInfo) throws CustomFieldApiException {
+ final UriInfo uriInfo,
+ final HttpServletRequest request) throws CustomFieldApiException {
final LinkedList<CustomField> input = new LinkedList<CustomField>();
for (final CustomFieldJson cur : customFields) {
verifyNonNullOrEmpty(cur.getName(), "CustomFieldJson name needs to be set");
@@ -264,7 +265,7 @@ public abstract class JaxRsResourceBase implements JaxrsResource {
}
customFieldUserApi.addCustomFields(input, context);
- return uriBuilder.buildResponse(uriInfo, this.getClass(), "getCustomFields", id);
+ return uriBuilder.buildResponse(uriInfo, this.getClass(), "getCustomFields", id, request);
}
/**
@@ -554,7 +555,7 @@ public abstract class JaxRsResourceBase implements JaxrsResource {
}
}
- protected Response createPaymentResponse(final UriInfo uriInfo, final Payment payment, final TransactionType transactionType, @Nullable final String transactionExternalKey) {
+ protected Response createPaymentResponse(final UriInfo uriInfo, final Payment payment, final TransactionType transactionType, @Nullable final String transactionExternalKey, final HttpServletRequest request) {
final PaymentTransaction createdTransaction = findCreatedTransaction(payment, transactionType, transactionExternalKey);
Preconditions.checkNotNull(createdTransaction, "No transaction of type '%s' found", transactionType);
@@ -563,7 +564,7 @@ public abstract class JaxRsResourceBase implements JaxrsResource {
switch (createdTransaction.getTransactionStatus()) {
case PENDING:
case SUCCESS:
- return uriBuilder.buildResponse(uriInfo, PaymentResource.class, "getPayment", payment.getId());
+ return uriBuilder.buildResponse(uriInfo, PaymentResource.class, "getPayment", payment.getId(), request);
case PAYMENT_FAILURE:
// 402 - Payment Required
responseBuilder = Response.status(402);
@@ -590,7 +591,7 @@ public abstract class JaxRsResourceBase implements JaxrsResource {
exception = createBillingException("This should never have happened!!!");
}
addExceptionToResponse(responseBuilder, exception);
- return responseBuilder.location(getPaymentLocation(uriInfo, payment)).build();
+ return uriBuilder.buildResponse(responseBuilder, uriInfo, PaymentResource.class, "getPayment", payment.getId(), request);
}
private void addExceptionToResponse(final ResponseBuilder responseBuilder, final BillingExceptionJson exception) {
@@ -608,10 +609,6 @@ public abstract class JaxRsResourceBase implements JaxrsResource {
return exception;
}
- private URI getPaymentLocation(final UriInfo uriInfo, final Payment payment) {
- return uriBuilder.buildLocation(uriInfo, PaymentResource.class, "getPayment", payment.getId(), null);
- }
-
private PaymentTransaction findCreatedTransaction(final Payment payment, final TransactionType transactionType, @Nullable final String transactionExternalKey) {
// Make sure we start looking from the latest transaction created
final List<PaymentTransaction> reversedTransactions = Lists.reverse(payment.getTransactions());
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/OverdueResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/OverdueResource.java
index 534d07e..a3deec0 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/OverdueResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/OverdueResource.java
@@ -107,7 +107,7 @@ public class OverdueResource extends JaxRsResourceBase {
final CallContext callContext = context.createContext(createdBy, reason, comment, request);
overdueApi.uploadOverdueConfig(overdueXML, callContext);
- return uriBuilder.buildResponse(uriInfo, OverdueResource.class, null, null);
+ return uriBuilder.buildResponse(uriInfo, OverdueResource.class, null, null, request);
}
@TimedResource
@@ -140,7 +140,7 @@ public class OverdueResource extends JaxRsResourceBase {
final OverdueConfig overdueConfig = OverdueJson.toOverdueConfigWithValidation(overdueJson);
overdueApi.uploadOverdueConfig(overdueConfig, callContext);
- return uriBuilder.buildResponse(uriInfo, OverdueResource.class, null, null);
+ return uriBuilder.buildResponse(uriInfo, OverdueResource.class, null, null, request);
}
}
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentMethodResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentMethodResource.java
index a5b528b..22e7533 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentMethodResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentMethodResource.java
@@ -303,7 +303,7 @@ public class PaymentMethodResource extends JaxRsResourceBase {
@javax.ws.rs.core.Context final HttpServletRequest request,
@javax.ws.rs.core.Context final UriInfo uriInfo) throws CustomFieldApiException {
return super.createCustomFields(UUID.fromString(paymentMethodId), customFields,
- context.createContext(createdBy, reason, comment, request), uriInfo);
+ context.createContext(createdBy, reason, comment, request), uriInfo, request);
}
@TimedResource
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentResource.java
index 653adb8..8fc205b 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentResource.java
@@ -323,7 +323,7 @@ public class PaymentResource extends ComboPaymentResource {
json != null ? json.getTransactionType() : null);
// If transaction was already completed, return early (See #626)
if (pendingOrSuccessTransaction.getTransactionStatus() == TransactionStatus.SUCCESS) {
- return uriBuilder.buildResponse(uriInfo, PaymentResource.class, "getPayment", pendingOrSuccessTransaction.getPaymentId());
+ return uriBuilder.buildResponse(uriInfo, PaymentResource.class, "getPayment", pendingOrSuccessTransaction.getPaymentId(), request);
}
@@ -357,7 +357,7 @@ public class PaymentResource extends ComboPaymentResource {
default:
return Response.status(Status.PRECONDITION_FAILED).entity("TransactionType " + pendingTransaction.getTransactionType() + " cannot be completed").build();
}
- return createPaymentResponse(uriInfo, result, pendingTransaction.getTransactionType(), pendingTransaction.getExternalKey());
+ return createPaymentResponse(uriInfo, result, pendingTransaction.getTransactionType(), pendingTransaction.getExternalKey(), request);
}
@TimedResource(name = "captureAuthorization")
@@ -433,7 +433,7 @@ public class PaymentResource extends ComboPaymentResource {
final Payment payment = paymentApi.createCaptureWithPaymentControl(account, initialPayment.getId(), json.getAmount(), currency,
json.getTransactionExternalKey(), pluginProperties, paymentOptions, callContext);
- return createPaymentResponse(uriInfo, payment, TransactionType.CAPTURE, json.getTransactionExternalKey());
+ return createPaymentResponse(uriInfo, payment, TransactionType.CAPTURE, json.getTransactionExternalKey(), request);
}
@TimedResource(name = "refundPayment")
@@ -512,7 +512,7 @@ public class PaymentResource extends ComboPaymentResource {
final Payment payment = paymentApi.createRefundWithPaymentControl(account, initialPayment.getId(), json.getAmount(), currency,
json.getTransactionExternalKey(), pluginProperties, paymentOptions, callContext);
- return createPaymentResponse(uriInfo, payment, TransactionType.REFUND, json.getTransactionExternalKey());
+ return createPaymentResponse(uriInfo, payment, TransactionType.REFUND, json.getTransactionExternalKey(), request);
}
@TimedResource(name = "voidPayment")
@@ -584,7 +584,7 @@ public class PaymentResource extends ComboPaymentResource {
final Payment payment = paymentApi.createVoidWithPaymentControl(account, initialPayment.getId(), transactionExternalKey,
pluginProperties, paymentOptions, callContext);
- return createPaymentResponse(uriInfo, payment, TransactionType.VOID, json.getTransactionExternalKey());
+ return createPaymentResponse(uriInfo, payment, TransactionType.VOID, json.getTransactionExternalKey(), request);
}
@TimedResource(name = "chargebackPayment")
@@ -660,7 +660,7 @@ public class PaymentResource extends ComboPaymentResource {
final Payment payment = paymentApi.createChargebackWithPaymentControl(account, initialPayment.getId(), json.getAmount(), currency,
json.getTransactionExternalKey(), paymentOptions, callContext);
- return createPaymentResponse(uriInfo, payment, TransactionType.CHARGEBACK, json.getTransactionExternalKey());
+ return createPaymentResponse(uriInfo, payment, TransactionType.CHARGEBACK, json.getTransactionExternalKey(), request);
}
@TimedResource(name = "chargebackReversalPayment")
@@ -734,7 +734,7 @@ public class PaymentResource extends ComboPaymentResource {
final PaymentOptions paymentOptions = createControlPluginApiPaymentOptions(paymentControlPluginNames);
final Payment payment = paymentApi.createChargebackReversalWithPaymentControl(account, initialPayment.getId(), json.getTransactionExternalKey(), paymentOptions, callContext);
- return createPaymentResponse(uriInfo, payment, TransactionType.CHARGEBACK, json.getTransactionExternalKey());
+ return createPaymentResponse(uriInfo, payment, TransactionType.CHARGEBACK, json.getTransactionExternalKey(), request);
}
@TimedResource
@@ -794,7 +794,7 @@ public class PaymentResource extends ComboPaymentResource {
default:
return Response.status(Status.PRECONDITION_FAILED).entity("TransactionType " + transactionType + " is not allowed for an account").build();
}
- return createPaymentResponse(uriInfo, result, transactionType, paymentTransactionJson.getTransactionExternalKey());
+ return createPaymentResponse(uriInfo, result, transactionType, paymentTransactionJson.getTransactionExternalKey(), request);
}
@TimedResource(name = "cancelScheduledPaymentTransaction")
@@ -863,7 +863,7 @@ public class PaymentResource extends ComboPaymentResource {
@javax.ws.rs.core.Context final HttpServletRequest request,
@javax.ws.rs.core.Context final UriInfo uriInfo) throws CustomFieldApiException {
return super.createCustomFields(UUID.fromString(id), customFields,
- context.createContext(createdBy, reason, comment, request), uriInfo);
+ context.createContext(createdBy, reason, comment, request), uriInfo, request);
}
@TimedResource
@@ -915,7 +915,7 @@ public class PaymentResource extends ComboPaymentResource {
@javax.ws.rs.core.Context final UriInfo uriInfo,
@javax.ws.rs.core.Context final HttpServletRequest request) throws TagApiException {
return super.createTags(UUID.fromString(id), tagList, uriInfo,
- context.createContext(createdBy, reason, comment, request));
+ context.createContext(createdBy, reason, comment, request), request);
}
@TimedResource
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/SecurityResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/SecurityResource.java
index b6f7b27..77d72c2 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/SecurityResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/SecurityResource.java
@@ -143,6 +143,18 @@ public class SecurityResource extends JaxRsResourceBase {
return Response.status(Status.OK).build();
}
+ @TimedResource
+ @GET
+ @Produces(APPLICATION_JSON)
+ @Path("/users/{username:" + ANYTHING_PATTERN + "}/roles")
+ @ApiOperation(value = "Get roles associated to a user")
+ public Response getUserRoles(@PathParam("username") final String username,
+ @javax.ws.rs.core.Context final HttpServletRequest request,
+ @javax.ws.rs.core.Context final UriInfo uriInfo) throws SecurityApiException {
+ final List<String> roles = securityApi.getUserRoles(username, context.createContext(request));
+ final UserRolesJson userRolesJson = new UserRolesJson(username, null, roles);
+ return Response.status(Status.OK).entity(userRolesJson).build();
+ }
@TimedResource
@PUT
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 6cd4f7c..fa205c8 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
@@ -240,7 +240,7 @@ public class SubscriptionResource extends JaxRsResourceBase {
@Override
public Response doResponseOk(final Entitlement createdEntitlement) {
- return uriBuilder.buildResponse(uriInfo, SubscriptionResource.class, "getEntitlement", createdEntitlement.getId());
+ return uriBuilder.buildResponse(uriInfo, SubscriptionResource.class, "getEntitlement", createdEntitlement.getId(), request);
}
};
@@ -326,7 +326,7 @@ public class SubscriptionResource extends JaxRsResourceBase {
}
@Override
public Response doResponseOk(final List<Entitlement> entitlements) {
- return uriBuilder.buildResponse(uriInfo, BundleResource.class, "getBundle", entitlements.get(0).getBundleId());
+ return uriBuilder.buildResponse(uriInfo, BundleResource.class, "getBundle", entitlements.get(0).getBundleId(), request);
}
};
final EntitlementCallCompletion<List<Entitlement>> callCompletionCreation = new EntitlementCallCompletion<List<Entitlement>>();
@@ -416,7 +416,7 @@ public class SubscriptionResource extends JaxRsResourceBase {
}
@Override
public Response doResponseOk(final List<Entitlement> entitlements) {
- return uriBuilder.buildResponse(uriInfo, AccountResource.class, "getAccountBundles", entitlements.get(0).getAccountId(), buildQueryParams(buildBundleIdList(entitlements)));
+ return uriBuilder.buildResponse(uriInfo, AccountResource.class, "getAccountBundles", entitlements.get(0).getAccountId(), buildQueryParams(buildBundleIdList(entitlements)), request);
}
};
final EntitlementCallCompletion<List<Entitlement>> callCompletionCreation = new EntitlementCallCompletion<List<Entitlement>>();
@@ -710,20 +710,42 @@ public class SubscriptionResource extends JaxRsResourceBase {
public Response updateSubscriptionBCD(final SubscriptionJson json,
@PathParam(ID_PARAM_NAME) final String id,
@QueryParam(QUERY_ENTITLEMENT_EFFECTIVE_FROM_DT) final String effectiveFromDateStr,
+ @QueryParam(QUERY_FORCE_NEW_BCD_WITH_PAST_EFFECTIVE_DATE) @DefaultValue("false") final Boolean forceNewBcdWithPastEffectiveDate,
@HeaderParam(HDR_CREATED_BY) final String createdBy,
@HeaderParam(HDR_REASON) final String reason,
@HeaderParam(HDR_COMMENT) final String comment,
@javax.ws.rs.core.Context final UriInfo uriInfo,
- @javax.ws.rs.core.Context final HttpServletRequest request) throws SubscriptionApiException, EntitlementApiException {
+ @javax.ws.rs.core.Context final HttpServletRequest request) throws SubscriptionApiException, EntitlementApiException, AccountApiException {
verifyNonNullOrEmpty(json, "SubscriptionJson body should be specified");
verifyNonNullOrEmpty(json.getBillCycleDayLocal(), "SubscriptionJson new BCD should be specified");
- final LocalDate effectiveFromDate = toLocalDate(effectiveFromDateStr);
+ LocalDate effectiveFromDate = toLocalDate(effectiveFromDateStr);
final CallContext callContext = context.createContext(createdBy, reason, comment, request);
+
final UUID subscriptionId = UUID.fromString(id);
final Entitlement entitlement = entitlementApi.getEntitlementForId(subscriptionId, callContext);
+ if (effectiveFromDateStr != null) {
+ final Account account = accountUserApi.getAccountById(entitlement.getAccountId(), callContext);
+ final LocalDate accountToday = new LocalDate(clock.getUTCNow(), account.getTimeZone());
+ int comp = effectiveFromDate.compareTo(accountToday);
+ switch (comp) {
+ case -1:
+ if (!forceNewBcdWithPastEffectiveDate) {
+ throw new IllegalArgumentException("Changing a subscription BCD in the past may have consequences on previous invoice generated. Use flag forceNewBcdWithPastEffectiveDate to force this behavior");
+ }
+ break;
+ case 0:
+ // Ensure system will use curremt time for the event so it happens immediately
+ effectiveFromDate = null;
+ break;
+ case 1:
+ // Future date, normal case where such effectiveFromDateStr is being passed
+ break;
+ }
+ }
+
entitlement.updateBCD(json.getBillCycleDayLocal(), effectiveFromDate, callContext);
return Response.status(Status.OK).build();
}
@@ -849,7 +871,7 @@ public class SubscriptionResource extends JaxRsResourceBase {
@javax.ws.rs.core.Context final HttpServletRequest request,
@javax.ws.rs.core.Context final UriInfo uriInfo) throws CustomFieldApiException {
return super.createCustomFields(UUID.fromString(id), customFields,
- context.createContext(createdBy, reason, comment, request), uriInfo);
+ context.createContext(createdBy, reason, comment, request), uriInfo, request);
}
@DELETE
@@ -899,7 +921,7 @@ public class SubscriptionResource extends JaxRsResourceBase {
@javax.ws.rs.core.Context final UriInfo uriInfo,
@javax.ws.rs.core.Context final HttpServletRequest request) throws TagApiException {
return super.createTags(UUID.fromString(id), tagList, uriInfo,
- context.createContext(createdBy, reason, comment, request));
+ context.createContext(createdBy, reason, comment, request), request);
}
@DELETE
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TagDefinitionResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TagDefinitionResource.java
index 8e30db8..90e9b8e 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TagDefinitionResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TagDefinitionResource.java
@@ -131,7 +131,7 @@ public class TagDefinitionResource extends JaxRsResourceBase {
json.getDescription(), "TagDefinition description needs to be set");
final TagDefinition createdTagDef = tagUserApi.createTagDefinition(json.getName(), json.getDescription(), context.createContext(createdBy, reason, comment, request));
- return uriBuilder.buildResponse(uriInfo, TagDefinitionResource.class, "getTagDefinition", createdTagDef.getId());
+ return uriBuilder.buildResponse(uriInfo, TagDefinitionResource.class, "getTagDefinition", createdTagDef.getId(), request);
}
@TimedResource
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TenantResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TenantResource.java
index d348596..e3cbaa2 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TenantResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TenantResource.java
@@ -142,7 +142,7 @@ public class TenantResource extends JaxRsResourceBase {
UserType.CUSTOMER, Context.getOrCreateUserToken(), clock);
catalogUserApi.createDefaultEmptyCatalog(clock.getUTCNow(),callContext);
}
- return uriBuilder.buildResponse(uriInfo, TenantResource.class, "getTenant", tenant.getId());
+ return uriBuilder.buildResponse(uriInfo, TenantResource.class, "getTenant", tenant.getId(), request);
}
@TimedResource
@@ -341,7 +341,7 @@ public class TenantResource extends JaxRsResourceBase {
@javax.ws.rs.core.Context final UriInfo uriInfo) throws TenantApiException {
final CallContext callContext = context.createContext(createdBy, reason, comment, request);
tenantApi.addTenantKeyValue(key, value, callContext);
- return uriBuilder.buildResponse(uriInfo, TenantResource.class, "getUserKeyValue", key);
+ return uriBuilder.buildResponse(uriInfo, TenantResource.class, "getUserKeyValue", key, request);
}
@TimedResource
@@ -389,7 +389,7 @@ public class TenantResource extends JaxRsResourceBase {
final String tenantKey = keyPostfix != null ? key.toString() + keyPostfix : key.toString();
tenantApi.addTenantKeyValue(tenantKey, value, callContext);
- return uriBuilder.buildResponse(uriInfo, TenantResource.class, getMethodStr, keyPostfix);
+ return uriBuilder.buildResponse(uriInfo, TenantResource.class, getMethodStr, keyPostfix, request);
}
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TransactionResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TransactionResource.java
index 1fe9878..da45da3 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TransactionResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TransactionResource.java
@@ -40,6 +40,7 @@ import org.killbill.billing.account.api.Account;
import org.killbill.billing.account.api.AccountApiException;
import org.killbill.billing.account.api.AccountUserApi;
import org.killbill.billing.jaxrs.json.CustomFieldJson;
+import org.killbill.billing.jaxrs.json.PaymentJson;
import org.killbill.billing.jaxrs.json.PaymentTransactionJson;
import org.killbill.billing.jaxrs.json.TagJson;
import org.killbill.billing.jaxrs.util.Context;
@@ -47,7 +48,6 @@ import org.killbill.billing.jaxrs.util.JaxrsUriBuilder;
import org.killbill.billing.payment.api.Payment;
import org.killbill.billing.payment.api.PaymentApi;
import org.killbill.billing.payment.api.PaymentApiException;
-import org.killbill.billing.payment.api.PaymentTransaction;
import org.killbill.billing.payment.api.PluginProperty;
import org.killbill.billing.payment.api.TransactionStatus;
import org.killbill.billing.util.api.AuditUserApi;
@@ -56,6 +56,7 @@ import org.killbill.billing.util.api.CustomFieldUserApi;
import org.killbill.billing.util.api.TagApiException;
import org.killbill.billing.util.api.TagDefinitionApiException;
import org.killbill.billing.util.api.TagUserApi;
+import org.killbill.billing.util.audit.AccountAuditLogs;
import org.killbill.billing.util.callcontext.CallContext;
import org.killbill.billing.util.callcontext.TenantContext;
import org.killbill.clock.Clock;
@@ -87,6 +88,27 @@ public class TransactionResource extends JaxRsResourceBase {
super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, null, clock, context);
}
+ @TimedResource(name = "getPaymentByTransactionId")
+ @GET
+ @Path("/{transactionId:" + UUID_PATTERN + "}/")
+ @Produces(APPLICATION_JSON)
+ @ApiOperation(value = "Retrieve a payment by transaction id", response = PaymentJson.class)
+ @ApiResponses(value = {@ApiResponse(code = 404, message = "Payment not found")})
+ public Response getPaymentByTransactionId(@PathParam("transactionId") final String transactionIdStr,
+ @QueryParam(QUERY_WITH_PLUGIN_INFO) @DefaultValue("false") final Boolean withPluginInfo,
+ @QueryParam(QUERY_WITH_ATTEMPTS) @DefaultValue("false") final Boolean withAttempts,
+ @QueryParam(QUERY_PLUGIN_PROPERTY) final List<String> pluginPropertiesString,
+ @QueryParam(QUERY_AUDIT) @DefaultValue("NONE") final AuditMode auditMode,
+ @javax.ws.rs.core.Context final HttpServletRequest request) throws PaymentApiException {
+ final Iterable<PluginProperty> pluginProperties = extractPluginProperties(pluginPropertiesString);
+ final UUID transactionIdId = UUID.fromString(transactionIdStr);
+ final TenantContext tenantContext = context.createContext(request);
+ final Payment payment = paymentApi.getPaymentByTransactionId(transactionIdId, withPluginInfo, withAttempts, pluginProperties, tenantContext);
+ final AccountAuditLogs accountAuditLogs = auditUserApi.getAccountAuditLogs(payment.getAccountId(), auditMode.getLevel(), tenantContext);
+ final PaymentJson result = new PaymentJson(payment, accountAuditLogs);
+ return Response.status(Response.Status.OK).entity(result).build();
+ }
+
@TimedResource
@POST
@Path("/{transactionId:" + UUID_PATTERN + "}/")
@@ -114,7 +136,7 @@ public class TransactionResource extends JaxRsResourceBase {
final boolean success = TransactionStatus.SUCCESS.name().equals(json.getStatus());
final Payment result = paymentApi.notifyPendingTransactionOfStateChanged(account, UUID.fromString(transactionIdStr), success, callContext);
- return uriBuilder.buildResponse(uriInfo, PaymentResource.class, "getPayment", result.getId());
+ return uriBuilder.buildResponse(uriInfo, PaymentResource.class, "getPayment", result.getId(), request);
}
@@ -145,7 +167,7 @@ public class TransactionResource extends JaxRsResourceBase {
@javax.ws.rs.core.Context final HttpServletRequest request,
@javax.ws.rs.core.Context final UriInfo uriInfo) throws CustomFieldApiException {
return super.createCustomFields(UUID.fromString(id), customFields,
- context.createContext(createdBy, reason, comment, request), uriInfo);
+ context.createContext(createdBy, reason, comment, request), uriInfo, request);
}
@TimedResource
@@ -196,7 +218,7 @@ public class TransactionResource extends JaxRsResourceBase {
@javax.ws.rs.core.Context final UriInfo uriInfo,
@javax.ws.rs.core.Context final HttpServletRequest request) throws TagApiException {
return super.createTags(UUID.fromString(id), tagList, uriInfo,
- context.createContext(createdBy, reason, comment, request));
+ context.createContext(createdBy, reason, comment, request), request);
}
@TimedResource
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/util/JaxrsUriBuilder.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/util/JaxrsUriBuilder.java
index 23d28b4..b0f210e 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/util/JaxrsUriBuilder.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/util/JaxrsUriBuilder.java
@@ -22,8 +22,10 @@ import java.util.Map;
import javax.annotation.Nullable;
import javax.inject.Inject;
+import javax.servlet.ServletRequest;
import javax.ws.rs.Path;
import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.ResponseBuilder;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
@@ -31,6 +33,8 @@ import org.killbill.billing.jaxrs.resources.JaxRsResourceBase;
import org.killbill.billing.jaxrs.resources.JaxrsResource;
import org.killbill.billing.util.config.definition.JaxrsConfig;
+import com.google.common.base.MoreObjects;
+
public class JaxrsUriBuilder {
private final JaxrsConfig jaxrsConfig;
@@ -44,24 +48,51 @@ public class JaxrsUriBuilder {
this.jaxrsConfig = jaxrsConfig;
}
- public Response buildResponse(final UriInfo uriInfo, final Class<? extends JaxrsResource> theClass,
- final String getMethodName, final Object objectId) {
- final URI location = buildLocation(uriInfo, theClass, getMethodName, objectId, null);
- return !jaxrsConfig.isJaxrsLocationFullUrl() ?
- Response.status(Response.Status.CREATED).header("Location", location.getPath()).build() :
- Response.created(location).build();
+ public Response buildResponse(final UriInfo uriInfo,
+ final Class<? extends JaxrsResource> theClass,
+ final String getMethodName,
+ final Object objectId,
+ final ServletRequest request) {
+ return buildResponse(uriInfo, theClass, getMethodName, objectId, null, request);
+ }
+
+ public Response buildResponse(final UriInfo uriInfo,
+ final Class<? extends JaxrsResource> theClass,
+ final String getMethodName,
+ final Object objectId,
+ final Map<String, String> params,
+ final ServletRequest request) {
+ return buildResponse(Response.status(Response.Status.CREATED), uriInfo, theClass, getMethodName, objectId, params, request);
+ }
+
+ public Response buildResponse(final ResponseBuilder responseBuilder,
+ final UriInfo uriInfo,
+ final Class<? extends JaxrsResource> theClass,
+ final String getMethodName,
+ final Object objectId,
+ final ServletRequest request) {
+ return buildResponse(responseBuilder, uriInfo, theClass, getMethodName, objectId, null, request);
}
- public Response buildResponse(final UriInfo uriInfo, final Class<? extends JaxrsResource> theClass,
- final String getMethodName, final Object objectId, final Map<String, String> params) {
- final URI location = buildLocation(uriInfo, theClass, getMethodName, objectId, params);
+ public Response buildResponse(final ResponseBuilder responseBuilder,
+ final UriInfo uriInfo,
+ final Class<? extends JaxrsResource> theClass,
+ final String getMethodName,
+ final Object objectId,
+ final Map<String, String> params,
+ final ServletRequest request) {
+ final URI location = buildLocation(uriInfo, theClass, getMethodName, objectId, params, request);
return !jaxrsConfig.isJaxrsLocationFullUrl() ?
- Response.status(Response.Status.CREATED).header("Location", location.getPath()).build() :
- Response.created(location).build();
+ responseBuilder.header("Location", location.getPath()).build() :
+ responseBuilder.location(location).build();
}
- public URI buildLocation(final UriInfo uriInfo, final Class<? extends JaxrsResource> theClass,
- final String getMethodName, final Object objectId, final Map<String, String> params) {
+ private URI buildLocation(final UriInfo uriInfo,
+ final Class<? extends JaxrsResource> theClass,
+ final String getMethodName,
+ final Object objectId,
+ final Map<String, String> params,
+ final ServletRequest request) {
final UriBuilder uriBuilder = getUriBuilder(uriInfo.getBaseUri().getPath(), theClass, getMethodName);
if (null != params && !params.isEmpty()) {
@@ -71,9 +102,17 @@ public class JaxrsUriBuilder {
}
if (jaxrsConfig.isJaxrsLocationFullUrl()) {
- uriBuilder.scheme(uriInfo.getAbsolutePath().getScheme())
- .host(uriInfo.getAbsolutePath().getHost())
- .port(uriInfo.getAbsolutePath().getPort());
+ if (jaxrsConfig.isJaxrsLocationUseForwardHeaders()) {
+ // Use "remote" value to support X-Forwarded headers (assumes RemoteIpValve or similar is configured)
+ // See https://github.com/killbill/killbill/issues/566
+ uriBuilder.scheme(request.getScheme())
+ .host(MoreObjects.firstNonNull(jaxrsConfig.getJaxrsLocationHost(), uriInfo.getAbsolutePath().getHost())) // Should we look for X-Forwarded-By instead?
+ .port(request.getServerPort());
+ } else {
+ uriBuilder.scheme(uriInfo.getAbsolutePath().getScheme())
+ .host(uriInfo.getAbsolutePath().getHost())
+ .port(uriInfo.getAbsolutePath().getPort());
+ }
}
return objectId != null ? uriBuilder.build(objectId) : uriBuilder.build();
}
@@ -92,23 +131,6 @@ public class JaxrsUriBuilder {
return uriBuilder.build();
}
- public Response buildResponse(final Class<? extends JaxrsResource> theClass, final String getMethodName, final Object objectId, final String baseUri) {
-
- // Let's build a n absolute location for cross resources
- // See Jersey ContainerResponse.setHeaders
- final StringBuilder tmp = new StringBuilder(baseUri.substring(0, baseUri.length() - 1));
- tmp.append(getUriBuilder(theClass, getMethodName).build(objectId).toString());
- final URI newUriFromResource = UriBuilder.fromUri(tmp.toString()).build();
- final Response.ResponseBuilder ri = Response.created(newUriFromResource);
- final Object obj = new Object() {
- @SuppressWarnings(value = "all")
- public URI getUri() {
- return newUriFromResource;
- }
- };
- return ri.entity(obj).build();
- }
-
private UriBuilder getUriBuilder(final String path, final Class<? extends JaxrsResource> theClassMaybeEnhanced, @Nullable final String getMethodName) {
final Class theClass = getNonEnhancedClass(theClassMaybeEnhanced);
return getMethodName != null ? fromPath(path.equals("/") ? path.substring(1) : path, theClass, getMethodName) : fromPath(path, theClass);
junction/pom.xml 2(+1 -1)
diff --git a/junction/pom.xml b/junction/pom.xml
index 996d9e8..a8335a3 100644
--- a/junction/pom.xml
+++ b/junction/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.18.2-SNAPSHOT</version>
+ <version>0.18.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-junction</artifactId>
diff --git a/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBillingApi.java b/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBillingApi.java
index eddb672..b6bc999 100644
--- a/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBillingApi.java
+++ b/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBillingApi.java
@@ -78,6 +78,7 @@ public class TestBillingApi extends JunctionTestSuiteNoDB {
private static final UUID eventId = new UUID(0L, 0L);
private static final UUID subId = new UUID(1L, 0L);
private static final UUID bunId = new UUID(2L, 0L);
+ private static final String bunKey = bunId.toString();
private List<EffectiveSubscriptionInternalEvent> effectiveSubscriptionTransitions;
private SubscriptionBase subscription;
@@ -282,7 +283,7 @@ public class TestBillingApi extends JunctionTestSuiteNoDB {
final PriceList nextPriceList = catalog.findPriceList(PriceListSet.DEFAULT_PRICELIST_NAME, now);
final EffectiveSubscriptionInternalEvent t = new MockEffectiveSubscriptionEvent(
- eventId, subId, bunId, then, now, null, null, null, null, null, EntitlementState.ACTIVE,
+ eventId, subId, bunId, bunKey, then, now, null, null, null, null, null, EntitlementState.ACTIVE,
nextPlan.getName(), nextPhase.getName(),
nextPriceList.getName(), null, 1L,
SubscriptionBaseTransitionType.CREATE, 1, null, 1L, 2L, null);
NEWS 6(+6 -0)
diff --git a/NEWS b/NEWS
index 9edb09e..d841538 100644
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,9 @@
+0.18.3
+ See https://github.com/killbill/killbill/releases/tag/killbill-0.18.3
+
+0.18.2
+ See https://github.com/killbill/killbill/releases/tag/killbill-0.18.2
+
0.18.1
See https://github.com/killbill/killbill/releases/tag/killbill-0.18.1
overdue/pom.xml 2(+1 -1)
diff --git a/overdue/pom.xml b/overdue/pom.xml
index 43076b0..4f3e728 100644
--- a/overdue/pom.xml
+++ b/overdue/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.18.2-SNAPSHOT</version>
+ <version>0.18.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-overdue</artifactId>
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultDuration.java b/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultDuration.java
index 49396ea..3ae374c 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultDuration.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultDuration.java
@@ -58,6 +58,8 @@ public class DefaultDuration extends ValidatingConfig<DefaultOverdueConfig> impl
switch (unit) {
case DAYS:
return dateTime.plusDays(number);
+ case WEEKS:
+ return dateTime.plusWeeks(number);
case MONTHS:
return dateTime.plusMonths(number);
case YEARS:
@@ -77,6 +79,8 @@ public class DefaultDuration extends ValidatingConfig<DefaultOverdueConfig> impl
switch (unit) {
case DAYS:
return localDate.plusDays(number);
+ case WEEKS:
+ return localDate.plusWeeks(number);
case MONTHS:
return localDate.plusMonths(number);
case YEARS:
@@ -95,6 +99,8 @@ public class DefaultDuration extends ValidatingConfig<DefaultOverdueConfig> impl
switch (unit) {
case DAYS:
return new Period().withDays(number);
+ case WEEKS:
+ return new Period().withWeeks(number);
case MONTHS:
return new Period().withMonths(number);
case YEARS:
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/listener/OverdueListener.java b/overdue/src/main/java/org/killbill/billing/overdue/listener/OverdueListener.java
index a0abc46..43f00c1 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/listener/OverdueListener.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/listener/OverdueListener.java
@@ -31,6 +31,7 @@ import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.events.ControlTagCreationInternalEvent;
import org.killbill.billing.events.ControlTagDeletionInternalEvent;
import org.killbill.billing.events.InvoiceAdjustmentInternalEvent;
+import org.killbill.billing.events.InvoiceCreationInternalEvent;
import org.killbill.billing.events.InvoicePaymentErrorInternalEvent;
import org.killbill.billing.events.InvoicePaymentInfoInternalEvent;
import org.killbill.billing.overdue.api.OverdueApiException;
@@ -132,6 +133,13 @@ public class OverdueListener {
insertBusEventIntoNotificationQueue(event.getAccountId(), event);
}
+ @AllowConcurrentEvents
+ @Subscribe
+ public void handleInvoiceCreation(final InvoiceCreationInternalEvent event) {
+ log.debug("Received InvoiceCreation event {}", event);
+ insertBusEventIntoNotificationQueue(event.getAccountId(), event);
+ }
+
private void insertBusEventIntoNotificationQueue(final UUID accountId, final BusEvent event) {
final InternalCallContext internalCallContext = createCallContext(event.getUserToken(), event.getSearchKey1(), event.getSearchKey2());
insertBusEventIntoNotificationQueue(accountId, OverdueAsyncBusNotificationAction.REFRESH, internalCallContext);
payment/pom.xml 2(+1 -1)
diff --git a/payment/pom.xml b/payment/pom.xml
index 31b7a5d..3841404 100644
--- a/payment/pom.xml
+++ b/payment/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.18.2-SNAPSHOT</version>
+ <version>0.18.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-payment</artifactId>
diff --git a/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentApi.java b/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentApi.java
index c98a029..7a0c2bf 100644
--- a/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentApi.java
+++ b/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentApi.java
@@ -1069,7 +1069,7 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
@Override
public Payment getPaymentByTransactionId(final UUID transactionId, final boolean withPluginInfo, final boolean withAttempts, final Iterable<PluginProperty> properties, final TenantContext context) throws PaymentApiException {
- final Payment payment = paymentProcessor.getPaymentByTransactionId(transactionId, withPluginInfo, withAttempts, properties, context, internalCallContextFactory.createInternalTenantContext(transactionId, ObjectType.PAYMENT, context));
+ final Payment payment = paymentProcessor.getPaymentByTransactionId(transactionId, withPluginInfo, withAttempts, properties, context, internalCallContextFactory.createInternalTenantContext(transactionId, ObjectType.TRANSACTION, context));
if (payment == null) {
throw new PaymentApiException(ErrorCode.PAYMENT_NO_SUCH_PAYMENT, transactionId);
}
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/PaymentProcessor.java b/payment/src/main/java/org/killbill/billing/payment/core/PaymentProcessor.java
index 3acf35f..f4a0491 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/PaymentProcessor.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/PaymentProcessor.java
@@ -87,9 +87,11 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Function;
+import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;
import com.google.common.collect.Lists;
@@ -241,17 +243,45 @@ public class PaymentProcessor extends ProcessorBase {
public Pagination<Payment> getPayments(final Long offset, final Long limit, final boolean withPluginInfo, final boolean withAttempts,
final Iterable<PluginProperty> properties, final TenantContext tenantContext, final InternalTenantContext internalTenantContext) {
- return getEntityPaginationFromPlugins(true,
- getAvailablePlugins(),
- offset,
- limit,
- new EntityPaginationBuilder<Payment, PaymentApiException>() {
- @Override
- public Pagination<Payment> build(final Long offset, final Long limit, final String pluginName) throws PaymentApiException {
- return getPayments(offset, limit, pluginName, withPluginInfo, withAttempts, properties, tenantContext, internalTenantContext);
- }
- }
- );
+ final Map<UUID, Optional<PaymentPluginApi>> paymentMethodIdToPaymentPluginApi = new HashMap<UUID, Optional<PaymentPluginApi>>();
+
+ try {
+ return getEntityPagination(limit,
+ new SourcePaginationBuilder<PaymentModelDao, PaymentApiException>() {
+ @Override
+ public Pagination<PaymentModelDao> build() {
+ // Find all payments for all accounts
+ return paymentDao.get(offset, limit, internalTenantContext);
+ }
+ },
+ new Function<PaymentModelDao, Payment>() {
+ @Override
+ public Payment apply(final PaymentModelDao paymentModelDao) {
+ final PaymentPluginApi pluginApi;
+ if (!withPluginInfo) {
+ pluginApi = null;
+ } else {
+ if (paymentMethodIdToPaymentPluginApi.get(paymentModelDao.getPaymentMethodId()) == null) {
+ try {
+ final PaymentPluginApi paymentProviderPlugin = getPaymentProviderPlugin(paymentModelDao.getPaymentMethodId(), internalTenantContext);
+ paymentMethodIdToPaymentPluginApi.put(paymentModelDao.getPaymentMethodId(), Optional.<PaymentPluginApi>of(paymentProviderPlugin));
+ } catch (final PaymentApiException e) {
+ log.warn("Unable to retrieve PaymentPluginApi for paymentMethodId='{}'", paymentModelDao.getPaymentMethodId(), e);
+ // We use Optional to avoid printing the log line for each result
+ paymentMethodIdToPaymentPluginApi.put(paymentModelDao.getPaymentMethodId(), Optional.<PaymentPluginApi>absent());
+ }
+ }
+ pluginApi = paymentMethodIdToPaymentPluginApi.get(paymentModelDao.getPaymentMethodId()).orNull();
+ }
+ final List<PaymentTransactionInfoPlugin> pluginInfo = getPaymentTransactionInfoPluginsIfNeeded(pluginApi, paymentModelDao, tenantContext);
+ return toPayment(paymentModelDao.getId(), pluginInfo, withAttempts, internalTenantContext);
+ }
+ }
+ );
+ } catch (final PaymentApiException e) {
+ log.warn("Unable to get payments", e);
+ return new DefaultPagination<Payment>(offset, limit, null, null, ImmutableSet.<Payment>of().iterator());
+ }
}
public Pagination<Payment> getPayments(final Long offset, final Long limit, final String pluginName, final boolean withPluginInfo, final boolean withAttempts, final Iterable<PluginProperty> properties, final TenantContext tenantContext, final InternalTenantContext internalTenantContext) throws PaymentApiException {
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonRunner.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonRunner.java
index c1304a7..2a238a5 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonRunner.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonRunner.java
@@ -187,11 +187,13 @@ public class PaymentAutomatonRunner {
operationCallback = new VoidOperation(daoHelper, locker, paymentPluginDispatcher, paymentConfig, paymentStateContext);
leavingStateCallback = new VoidInitiated(daoHelper, paymentStateContext);
enteringStateCallback = new VoidCompleted(daoHelper, paymentStateContext);
+ includeDeletedPaymentMethod = Boolean.TRUE;
break;
case REFUND:
operationCallback = new RefundOperation(daoHelper, locker, paymentPluginDispatcher, paymentConfig, paymentStateContext);
leavingStateCallback = new RefundInitiated(daoHelper, paymentStateContext);
enteringStateCallback = new RefundCompleted(daoHelper, paymentStateContext);
+ includeDeletedPaymentMethod = Boolean.TRUE;
break;
case CREDIT:
operationCallback = new CreditOperation(daoHelper, locker, paymentPluginDispatcher, paymentConfig, paymentStateContext);
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentStateMachineHelper.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentStateMachineHelper.java
index 5ed95a9..daee9eb 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentStateMachineHelper.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentStateMachineHelper.java
@@ -68,7 +68,7 @@ public class PaymentStateMachineHelper {
private static final String VOID_FAILED = "VOID_FAILED";
private static final String CHARGEBACK_FAILED = "CHARGEBACK_FAILED";
- private static final String AUTH_ERRORED = "AUTH_ERRORED";
+ private static final String AUTHORIZE_ERRORED = "AUTH_ERRORED";
private static final String CAPTURE_ERRORED = "CAPTURE_ERRORED";
private static final String PURCHASE_ERRORED = "PURCHASE_ERRORED";
private static final String REFUND_ERRORED = "REFUND_ERRORED";
@@ -78,6 +78,35 @@ public class PaymentStateMachineHelper {
private final StateMachineConfigCache stateMachineConfigCache;
+ public static final String[] STATE_NAMES = {AUTHORIZE_ERRORED,
+ AUTHORIZE_FAILED,
+ AUTHORIZE_PENDING,
+ AUTHORIZE_SUCCESS,
+ CAPTURE_ERRORED,
+ CAPTURE_FAILED,
+ CAPTURE_PENDING,
+ CAPTURE_SUCCESS,
+ CHARGEBACK_ERRORED,
+ CHARGEBACK_FAILED,
+ CHARGEBACK_PENDING,
+ CHARGEBACK_SUCCESS,
+ CREDIT_ERRORED,
+ CREDIT_FAILED,
+ CREDIT_PENDING,
+ CREDIT_SUCCESS,
+ PURCHASE_ERRORED,
+ PURCHASE_FAILED,
+ PURCHASE_PENDING,
+ PURCHASE_SUCCESS,
+ REFUND_ERRORED,
+ REFUND_FAILED,
+ REFUND_PENDING,
+ REFUND_SUCCESS,
+ VOID_ERRORED,
+ VOID_FAILED,
+ VOID_PENDING,
+ VOID_SUCCESS};
+
@Inject
public PaymentStateMachineHelper(final StateMachineConfigCache stateMachineConfigCache) {
this.stateMachineConfigCache = stateMachineConfigCache;
@@ -132,7 +161,7 @@ public class PaymentStateMachineHelper {
public String getErroredStateForTransaction(final TransactionType transactionType) {
switch (transactionType) {
case AUTHORIZE:
- return AUTH_ERRORED;
+ return AUTHORIZE_ERRORED;
case CAPTURE:
return CAPTURE_ERRORED;
case PURCHASE:
diff --git a/payment/src/main/java/org/killbill/billing/payment/dao/DefaultPaymentDao.java b/payment/src/main/java/org/killbill/billing/payment/dao/DefaultPaymentDao.java
index 0e67704..020ff49 100644
--- a/payment/src/main/java/org/killbill/billing/payment/dao/DefaultPaymentDao.java
+++ b/payment/src/main/java/org/killbill/billing/payment/dao/DefaultPaymentDao.java
@@ -19,16 +19,19 @@
package org.killbill.billing.payment.dao;
import java.math.BigDecimal;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;
+import java.util.regex.Pattern;
import javax.annotation.Nullable;
import javax.inject.Inject;
import org.joda.time.DateTime;
+import org.killbill.billing.ErrorCode;
import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.catalog.api.Currency;
@@ -38,10 +41,12 @@ import org.killbill.billing.payment.api.DefaultPaymentErrorEvent;
import org.killbill.billing.payment.api.DefaultPaymentInfoEvent;
import org.killbill.billing.payment.api.DefaultPaymentPluginErrorEvent;
import org.killbill.billing.payment.api.Payment;
+import org.killbill.billing.payment.api.PaymentApiException;
import org.killbill.billing.payment.api.PaymentMethod;
import org.killbill.billing.payment.api.PaymentTransaction;
import org.killbill.billing.payment.api.TransactionStatus;
import org.killbill.billing.payment.api.TransactionType;
+import org.killbill.billing.payment.core.sm.PaymentStateMachineHelper;
import org.killbill.billing.util.cache.CacheControllerDispatcher;
import org.killbill.billing.util.callcontext.InternalCallContextFactory;
import org.killbill.billing.util.dao.NonEntityDao;
@@ -49,6 +54,7 @@ import org.killbill.billing.util.entity.Entity;
import org.killbill.billing.util.entity.Pagination;
import org.killbill.billing.util.entity.dao.DefaultPaginationSqlDaoHelper;
import org.killbill.billing.util.entity.dao.DefaultPaginationSqlDaoHelper.PaginationIteratorBuilder;
+import org.killbill.billing.util.entity.dao.EntityDaoBase;
import org.killbill.billing.util.entity.dao.EntitySqlDaoTransactionWrapper;
import org.killbill.billing.util.entity.dao.EntitySqlDaoTransactionalJdbiWrapper;
import org.killbill.billing.util.entity.dao.EntitySqlDaoWrapperFactory;
@@ -66,11 +72,10 @@ import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
-public class DefaultPaymentDao implements PaymentDao {
+public class DefaultPaymentDao extends EntityDaoBase<PaymentModelDao, Payment, PaymentApiException> implements PaymentDao {
private static final Logger log = LoggerFactory.getLogger(DefaultPaymentDao.class);
- private final EntitySqlDaoTransactionalJdbiWrapper transactionalSqlDao;
private final DefaultPaginationSqlDaoHelper paginationHelper;
private final PersistentBus eventBus;
private final Clock clock;
@@ -78,7 +83,7 @@ public class DefaultPaymentDao implements PaymentDao {
@Inject
public DefaultPaymentDao(final IDBI dbi, final Clock clock, final CacheControllerDispatcher cacheControllerDispatcher,
final NonEntityDao nonEntityDao, final InternalCallContextFactory internalCallContextFactory, final PersistentBus eventBus) {
- this.transactionalSqlDao = new EntitySqlDaoTransactionalJdbiWrapper(dbi, clock, cacheControllerDispatcher, nonEntityDao, internalCallContextFactory);
+ super(new EntitySqlDaoTransactionalJdbiWrapper(dbi, clock, cacheControllerDispatcher, nonEntityDao, internalCallContextFactory), PaymentSqlDao.class);
this.paginationHelper = new DefaultPaginationSqlDaoHelper(transactionalSqlDao);
this.eventBus = eventBus;
this.clock = clock;
@@ -248,16 +253,20 @@ public class DefaultPaymentDao implements PaymentDao {
@Override
public Pagination<PaymentModelDao> searchPayments(final String searchKey, final Long offset, final Long limit, final InternalTenantContext context) {
+ // Optimization: if the search key looks like a state name (e.g. _ERRORED), assume the user is searching by state only
+ final List<String> paymentStates = expandSearchFilterToStateNames(searchKey);
+
+ final String likeSearchKey = String.format("%%%s%%", searchKey);
return paginationHelper.getPagination(PaymentSqlDao.class,
new PaginationIteratorBuilder<PaymentModelDao, Payment, PaymentSqlDao>() {
@Override
public Long getCount(final PaymentSqlDao paymentSqlDao, final InternalTenantContext context) {
- return paymentSqlDao.getSearchCount(searchKey, String.format("%%%s%%", searchKey), context);
+ return !paymentStates.isEmpty() ? paymentSqlDao.getSearchByStateCount(paymentStates, context) : paymentSqlDao.getSearchCount(searchKey, likeSearchKey, context);
}
@Override
public Iterator<PaymentModelDao> build(final PaymentSqlDao paymentSqlDao, final Long limit, final InternalTenantContext context) {
- return paymentSqlDao.search(searchKey, String.format("%%%s%%", searchKey), offset, limit, context);
+ return !paymentStates.isEmpty() ? paymentSqlDao.searchByState(paymentStates, offset, limit, context) : paymentSqlDao.search(searchKey, likeSearchKey, offset, limit, context);
}
},
offset,
@@ -265,6 +274,20 @@ public class DefaultPaymentDao implements PaymentDao {
context);
}
+ private List<String> expandSearchFilterToStateNames(final String searchKey) {
+ final Pattern pattern = Pattern.compile(".*" + searchKey + ".*");
+
+ // Note that technically, we should look at all of the available state names in the database instead since the state machine is configurable. The common use-case
+ // is to override transitions though, not to introduce new states, and since some of it is already hardcoded in PaymentStateMachineHelper anyways, it's probably good enough for now.
+ final List<String> stateNames = new ArrayList<String>();
+ for (final String stateName : PaymentStateMachineHelper.STATE_NAMES) {
+ if (pattern.matcher(stateName).matches()) {
+ stateNames.add(stateName);
+ }
+ }
+ return stateNames;
+ }
+
@Override
public PaymentModelDao insertPaymentWithFirstTransaction(final PaymentModelDao payment, final PaymentTransactionModelDao paymentTransaction, final InternalCallContext context) {
@@ -650,4 +673,9 @@ public class DefaultPaymentDao implements PaymentDao {
private InternalCallContext contextWithUpdatedDate(final InternalCallContext input) {
return new InternalCallContext(input, clock.getUTCNow());
}
+
+ @Override
+ protected PaymentApiException generateAlreadyExistsException(final PaymentModelDao entity, final InternalCallContext context) {
+ return new PaymentApiException(ErrorCode.PAYMENT_INTERNAL_ERROR, "Payment already exists");
+ }
}
diff --git a/payment/src/main/java/org/killbill/billing/payment/dao/PaymentDao.java b/payment/src/main/java/org/killbill/billing/payment/dao/PaymentDao.java
index dc1f8f7..2c46e15 100644
--- a/payment/src/main/java/org/killbill/billing/payment/dao/PaymentDao.java
+++ b/payment/src/main/java/org/killbill/billing/payment/dao/PaymentDao.java
@@ -24,11 +24,14 @@ import org.joda.time.DateTime;
import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.catalog.api.Currency;
+import org.killbill.billing.payment.api.Payment;
+import org.killbill.billing.payment.api.PaymentApiException;
import org.killbill.billing.payment.api.TransactionStatus;
import org.killbill.billing.payment.api.TransactionType;
import org.killbill.billing.util.entity.Pagination;
+import org.killbill.billing.util.entity.dao.EntityDao;
-public interface PaymentDao {
+public interface PaymentDao extends EntityDao<PaymentModelDao, Payment, PaymentApiException> {
public Pagination<PaymentTransactionModelDao> getByTransactionStatusAcrossTenants(final Iterable<TransactionStatus> transactionStatuses, DateTime createdBeforeDate, DateTime createdAfterDate, final Long offset, final Long limit);
diff --git a/payment/src/main/java/org/killbill/billing/payment/dao/PaymentSqlDao.java b/payment/src/main/java/org/killbill/billing/payment/dao/PaymentSqlDao.java
index 0f5475b..23dcd3f 100644
--- a/payment/src/main/java/org/killbill/billing/payment/dao/PaymentSqlDao.java
+++ b/payment/src/main/java/org/killbill/billing/payment/dao/PaymentSqlDao.java
@@ -33,7 +33,6 @@ import org.skife.jdbi.v2.sqlobject.Bind;
import org.skife.jdbi.v2.sqlobject.BindBean;
import org.skife.jdbi.v2.sqlobject.SqlQuery;
import org.skife.jdbi.v2.sqlobject.SqlUpdate;
-import org.skife.jdbi.v2.sqlobject.customizers.Define;
@EntitySqlDaoStringTemplate
public interface PaymentSqlDao extends EntitySqlDao<PaymentModelDao, Payment> {
@@ -69,6 +68,17 @@ public interface PaymentSqlDao extends EntitySqlDao<PaymentModelDao, Payment> {
@SqlQuery
@SmartFetchSize(shouldStream = true)
+ public Iterator<PaymentModelDao> searchByState(@PaymentStateCollectionBinder final Collection<String> paymentStates,
+ @Bind("offset") final Long offset,
+ @Bind("rowCount") final Long rowCount,
+ @BindBean final InternalTenantContext context);
+
+ @SqlQuery
+ public Long getSearchByStateCount(@PaymentStateCollectionBinder final Collection<String> paymentStates,
+ @BindBean final InternalTenantContext context);
+
+ @SqlQuery
+ @SmartFetchSize(shouldStream = true)
public Iterator<PaymentModelDao> getByPluginName(@Bind("pluginName") final String pluginName,
@Bind("offset") final Long offset,
@Bind("rowCount") final Long rowCount,
diff --git a/payment/src/main/java/org/killbill/billing/payment/dao/PaymentStateCollectionBinder.java b/payment/src/main/java/org/killbill/billing/payment/dao/PaymentStateCollectionBinder.java
new file mode 100644
index 0000000..addde2a
--- /dev/null
+++ b/payment/src/main/java/org/killbill/billing/payment/dao/PaymentStateCollectionBinder.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.payment.dao;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.Collection;
+
+import org.killbill.billing.payment.dao.PaymentStateCollectionBinder.PaymentStateCollectionBinderFactory;
+import org.skife.jdbi.v2.SQLStatement;
+import org.skife.jdbi.v2.sqlobject.Binder;
+import org.skife.jdbi.v2.sqlobject.BinderFactory;
+import org.skife.jdbi.v2.sqlobject.BindingAnnotation;
+
+@BindingAnnotation(PaymentStateCollectionBinderFactory.class)
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.PARAMETER})
+public @interface PaymentStateCollectionBinder {
+
+ public static class PaymentStateCollectionBinderFactory implements BinderFactory {
+
+ @Override
+ public Binder build(final Annotation annotation) {
+ return new Binder<PaymentStateCollectionBinder, Collection<String>>() {
+
+ @Override
+ public void bind(final SQLStatement<?> query, final PaymentStateCollectionBinder bind, final Collection<String> allPaymentState) {
+ query.define("states", allPaymentState);
+
+ int idx = 0;
+ for (final String paymentState : allPaymentState) {
+ query.bind("state_" + idx, paymentState);
+ idx++;
+ }
+ }
+ };
+ }
+ }
+}
diff --git a/payment/src/main/resources/org/killbill/billing/payment/dao/PaymentSqlDao.sql.stg b/payment/src/main/resources/org/killbill/billing/payment/dao/PaymentSqlDao.sql.stg
index 6b110e9..bf30477 100644
--- a/payment/src/main/resources/org/killbill/billing/payment/dao/PaymentSqlDao.sql.stg
+++ b/payment/src/main/resources/org/killbill/billing/payment/dao/PaymentSqlDao.sql.stg
@@ -80,7 +80,33 @@ searchQuery(prefix) ::= <<
or <prefix>account_id = :searchKey
or <prefix>payment_method_id = :searchKey
or <prefix>external_key like :likeSearchKey
- or <prefix>state_name like :likeSearchKey
+>>
+
+searchByState(states) ::= <<
+select
+<allTableFields("t.")>
+from <tableName()> t
+join (
+ select <recordIdField()>
+ from <tableName()>
+ where state_name in (<states: {state | :state_<i0>}; separator="," >)
+ <AND_CHECK_TENANT()>
+ <andCheckSoftDeletionWithComma()>
+ order by <recordIdField()>
+ limit :rowCount offset :offset
+) optimization on <recordIdField("optimization.")> = <recordIdField("t.")>
+order by <recordIdField("t.")>
+;
+>>
+
+getSearchByStateCount(states) ::= <<
+select
+ count(1) as count
+from <tableName()> t
+where t.state_name in (<states: {state | :state_<i0>}; separator="," >)
+<andCheckSoftDeletionWithComma("t.")>
+<AND_CHECK_TENANT("t.")>
+;
>>
getByPluginName() ::= <<
diff --git a/payment/src/main/resources/org/killbill/billing/payment/ddl.sql b/payment/src/main/resources/org/killbill/billing/payment/ddl.sql
index 019e3cf..e994fa9 100644
--- a/payment/src/main/resources/org/killbill/billing/payment/ddl.sql
+++ b/payment/src/main/resources/org/killbill/billing/payment/ddl.sql
@@ -122,6 +122,7 @@ CREATE UNIQUE INDEX payments_id ON payments(id);
CREATE UNIQUE INDEX payments_key ON payments(external_key, tenant_record_id);
CREATE INDEX payments_accnt ON payments(account_id);
CREATE INDEX payments_tenant_account_record_id ON payments(tenant_record_id, account_record_id);
+CREATE INDEX payments_tenant_record_id_state_name ON payments(tenant_record_id, state_name);
DROP TABLE IF EXISTS payment_history;
diff --git a/payment/src/main/resources/org/killbill/billing/payment/migration/V20170123023324__add_payments_tenant_record_id_state_name_index.sql b/payment/src/main/resources/org/killbill/billing/payment/migration/V20170123023324__add_payments_tenant_record_id_state_name_index.sql
new file mode 100644
index 0000000..11ba386
--- /dev/null
+++ b/payment/src/main/resources/org/killbill/billing/payment/migration/V20170123023324__add_payments_tenant_record_id_state_name_index.sql
@@ -0,0 +1 @@
+alter table payments add index payments_tenant_record_id_state_name(tenant_record_id, state_name);
diff --git a/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApi.java b/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApi.java
index af49a16..d31b5ca 100644
--- a/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApi.java
+++ b/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApi.java
@@ -26,8 +26,10 @@ import java.util.List;
import java.util.UUID;
import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
import org.joda.time.LocalDate;
+import org.joda.time.LocalDate.Property;
import org.killbill.billing.ErrorCode;
import org.killbill.billing.account.api.Account;
import org.killbill.billing.catalog.api.Currency;
@@ -183,6 +185,19 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
checkPaymentMethodPagination(paymentMethodId, baseNbRecords + 1, false);
}
+ @Test(groups = "slow", description="Verify we can make a refund on payment whose original payment method was deleted. See 694")
+ public void testRefundAfterDeletedPaymentMethod() throws PaymentApiException {
+
+ final BigDecimal requestedAmount = BigDecimal.TEN;
+ final Payment payment = paymentApi.createPurchase(account, account.getPaymentMethodId(), null, requestedAmount, Currency.EUR, UUID.randomUUID().toString(), UUID.randomUUID().toString(),
+ ImmutableList.<PluginProperty>of(), callContext);
+
+ paymentApi.deletePaymentMethod(account, account.getPaymentMethodId(), false, true, ImmutableList.<PluginProperty>of(), callContext);
+
+ final Payment newPayment = paymentApi.createRefund(account, payment.getId(),requestedAmount, Currency.EUR, UUID.randomUUID().toString(), ImmutableList.<PluginProperty>of(), callContext);
+ Assert.assertEquals(newPayment.getTransactions().size(), 2);
+ }
+
@Test(groups = "slow")
public void testCreateSuccessPurchase() throws PaymentApiException {
diff --git a/payment/src/test/java/org/killbill/billing/payment/dao/MockPaymentDao.java b/payment/src/test/java/org/killbill/billing/payment/dao/MockPaymentDao.java
index 0798535..12ce2be 100644
--- a/payment/src/test/java/org/killbill/billing/payment/dao/MockPaymentDao.java
+++ b/payment/src/test/java/org/killbill/billing/payment/dao/MockPaymentDao.java
@@ -34,16 +34,19 @@ import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.catalog.api.Currency;
import org.killbill.billing.dao.MockNonEntityDao;
+import org.killbill.billing.payment.api.Payment;
+import org.killbill.billing.payment.api.PaymentApiException;
import org.killbill.billing.payment.api.TransactionStatus;
import org.killbill.billing.payment.api.TransactionType;
import org.killbill.billing.util.entity.DefaultPagination;
import org.killbill.billing.util.entity.Pagination;
+import org.killbill.billing.util.entity.dao.MockEntityDaoBase;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
-public class MockPaymentDao implements PaymentDao {
+public class MockPaymentDao extends MockEntityDaoBase<PaymentModelDao, Payment, PaymentApiException> implements PaymentDao {
private final Map<UUID, PaymentModelDao> payments = new HashMap<UUID, PaymentModelDao>();
private final Map<UUID, PaymentTransactionModelDao> transactions = new HashMap<UUID, PaymentTransactionModelDao>();
diff --git a/payment/src/test/java/org/killbill/billing/payment/dao/TestDefaultPaymentDao.java b/payment/src/test/java/org/killbill/billing/payment/dao/TestDefaultPaymentDao.java
index 4d6a938..35d2c42 100644
--- a/payment/src/test/java/org/killbill/billing/payment/dao/TestDefaultPaymentDao.java
+++ b/payment/src/test/java/org/killbill/billing/payment/dao/TestDefaultPaymentDao.java
@@ -27,6 +27,7 @@ import org.killbill.billing.catalog.api.Currency;
import org.killbill.billing.payment.PaymentTestSuiteWithEmbeddedDB;
import org.killbill.billing.payment.api.TransactionStatus;
import org.killbill.billing.payment.api.TransactionType;
+import org.killbill.billing.payment.core.sm.PaymentStateMachineHelper;
import org.testng.Assert;
import org.testng.annotations.Test;
@@ -37,11 +38,11 @@ public class TestDefaultPaymentDao extends PaymentTestSuiteWithEmbeddedDB {
@Test(groups = "slow")
public void testPaymentCRUD() throws Exception {
for (int i = 0; i < 3; i++) {
- testPaymentCRUDForAccount();
+ testPaymentCRUDForAccount(i + 1);
}
}
- public void testPaymentCRUDForAccount() throws Exception {
+ private void testPaymentCRUDForAccount(final int runNb) throws Exception {
final Account account = testHelper.createTestAccount(UUID.randomUUID().toString(), true);
final UUID accountId = account.getId();
@@ -68,8 +69,8 @@ public class TestDefaultPaymentDao extends PaymentTestSuiteWithEmbeddedDB {
specifiedSecondPaymentTransactionModelDao.getAttemptId(),
specifiedSecondPaymentTransactionModelDao.getPaymentId(),
specifiedFirstPaymentTransactionModelDao.getTransactionType(),
- "SOME_ERRORED_STATE",
- "SOME_ERRORED_STATE",
+ PaymentStateMachineHelper.STATE_NAMES[0],
+ PaymentStateMachineHelper.STATE_NAMES[0],
specifiedSecondPaymentTransactionModelDao.getId(),
TransactionStatus.PAYMENT_FAILURE,
processedAmount,
@@ -99,6 +100,7 @@ public class TestDefaultPaymentDao extends PaymentTestSuiteWithEmbeddedDB {
// Verify search APIs
Assert.assertEquals(ImmutableList.<PaymentModelDao>copyOf(paymentDao.searchPayments(accountId.toString(), 0L, 100L, internalCallContext).iterator()).size(), 4);
+ Assert.assertEquals(ImmutableList.<PaymentModelDao>copyOf(paymentDao.searchPayments("_ERRORED", 0L, 100L, internalCallContext).iterator()).size(), runNb);
}
private void verifyPaymentAndTransactions(final InternalCallContext accountCallContext, final PaymentModelDao specifiedFirstPaymentModelDao, final PaymentTransactionModelDao... specifiedFirstPaymentTransactionModelDaos) {
pom.xml 2(+1 -1)
diff --git a/pom.xml b/pom.xml
index 583b16d..ec1e3ea 100644
--- a/pom.xml
+++ b/pom.xml
@@ -24,7 +24,7 @@
<version>0.141-SNAPSHOT</version>
</parent>
<artifactId>killbill</artifactId>
- <version>0.18.2-SNAPSHOT</version>
+ <version>0.18.4-SNAPSHOT</version>
<packaging>pom</packaging>
<name>killbill</name>
<description>Library for managing recurring subscriptions and the associated billing</description>
profiles/killbill/pom.xml 2(+1 -1)
diff --git a/profiles/killbill/pom.xml b/profiles/killbill/pom.xml
index 566614e..d5c9444 100644
--- a/profiles/killbill/pom.xml
+++ b/profiles/killbill/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>killbill-profiles</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.18.2-SNAPSHOT</version>
+ <version>0.18.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-profiles-killbill</artifactId>
diff --git a/profiles/killbill/src/main/java/org/killbill/billing/server/filters/ResponseCorsFilter.java b/profiles/killbill/src/main/java/org/killbill/billing/server/filters/ResponseCorsFilter.java
index f8b0c37..3f8e6b2 100644
--- a/profiles/killbill/src/main/java/org/killbill/billing/server/filters/ResponseCorsFilter.java
+++ b/profiles/killbill/src/main/java/org/killbill/billing/server/filters/ResponseCorsFilter.java
@@ -26,12 +26,15 @@ import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.killbill.billing.jaxrs.resources.JaxrsResource;
import com.google.common.base.Joiner;
+import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableList;
+import com.google.common.net.HttpHeaders;
@Singleton
public class ResponseCorsFilter implements Filter {
@@ -39,8 +42,9 @@ public class ResponseCorsFilter implements Filter {
private final String allowedHeaders;
public ResponseCorsFilter() {
- allowedHeaders = Joiner.on(",").join(ImmutableList.<String>of("Authorization",
- "Content-Type",
+ allowedHeaders = Joiner.on(",").join(ImmutableList.<String>of(HttpHeaders.AUTHORIZATION,
+ HttpHeaders.CONTENT_TYPE,
+ HttpHeaders.LOCATION,
JaxrsResource.HDR_API_KEY,
JaxrsResource.HDR_API_SECRET,
JaxrsResource.HDR_COMMENT,
@@ -60,10 +64,14 @@ public class ResponseCorsFilter implements Filter {
@Override
public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException {
final HttpServletResponse res = (HttpServletResponse) response;
- res.addHeader("Access-Control-Allow-Origin", "*");
- res.addHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT, OPTIONS");
- res.addHeader("Access-Control-Allow-Headers", allowedHeaders);
- res.addHeader("Access-Control-Expose-Headers", allowedHeaders);
+ final HttpServletRequest req = (HttpServletRequest) request;
+
+ final String origin = MoreObjects.firstNonNull(req.getHeader(HttpHeaders.ORIGIN), "*");
+ res.addHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, origin);
+ res.addHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, "GET, POST, DELETE, PUT, OPTIONS");
+ res.addHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, allowedHeaders);
+ res.addHeader(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, allowedHeaders);
+ res.addHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
chain.doFilter(request, response);
}
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/KillbillClient.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/KillbillClient.java
index 8672065..25517ab 100644
--- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/KillbillClient.java
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/KillbillClient.java
@@ -145,7 +145,7 @@ public abstract class KillbillClient extends GuicyKillbillTestSuiteWithEmbeddedD
input.setBillingPeriod(billingPeriod);
input.setPriceList(PriceListSet.DEFAULT_PRICELIST_NAME);
- return killBillClient.createSubscription(input, null, waitCompletion ? DEFAULT_WAIT_COMPLETION_TIMEOUT_SEC : -1, basicRequestOptions());
+ return killBillClient.createSubscription(input, null, waitCompletion ? DEFAULT_WAIT_COMPLETION_TIMEOUT_SEC : -1, requestOptions);
}
protected Account createAccountWithPMBundleAndSubscriptionAndWaitForFirstInvoice() throws Exception {
@@ -257,14 +257,4 @@ public abstract class KillbillClient extends GuicyKillbillTestSuiteWithEmbeddedD
Thread.sleep(sleepValueMSec);
}
- /**
- * Return a RequestOptions instance with the createdBy, reason and comment fields populated
- * @return an instance of RequestOptions
- */
- protected RequestOptions basicRequestOptions() {
- return RequestOptions.builder()
- .withCreatedBy(createdBy)
- .withReason(reason)
- .withComment(comment).build();
- }
}
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestAccount.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestAccount.java
index d714352..83f655b 100644
--- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestAccount.java
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestAccount.java
@@ -78,7 +78,7 @@ public class TestAccount extends TestJaxrsBase {
public void testEmptyAccount() throws Exception {
final Account emptyAccount = new Account();
- final Account account = killBillClient.createAccount(emptyAccount, createdBy, reason, comment);
+ final Account account = killBillClient.createAccount(emptyAccount, requestOptions);
Assert.assertNotNull(account.getExternalKey());
Assert.assertNull(account.getName());
Assert.assertNull(account.getEmail());
@@ -90,12 +90,12 @@ public class TestAccount extends TestJaxrsBase {
final Account inputWithNoExternalKey = getAccount(UUID.randomUUID().toString(), null, UUID.randomUUID().toString());
Assert.assertNull(inputWithNoExternalKey.getExternalKey());
- final Account account = killBillClient.createAccount(inputWithNoExternalKey, createdBy, reason, comment);
+ final Account account = killBillClient.createAccount(inputWithNoExternalKey, requestOptions);
Assert.assertNotNull(account.getExternalKey());
final Account inputWithSameExternalKey = getAccount(UUID.randomUUID().toString(), account.getExternalKey(), UUID.randomUUID().toString());
try {
- killBillClient.createAccount(inputWithSameExternalKey, createdBy, reason, comment);
+ killBillClient.createAccount(inputWithSameExternalKey, requestOptions);
Assert.fail();
} catch (final KillBillClientException e) {
Assert.assertEquals(e.getBillingException().getCode(), (Integer) ErrorCode.ACCOUNT_ALREADY_EXISTS.getCode());
@@ -107,7 +107,7 @@ public class TestAccount extends TestJaxrsBase {
final Account input = createAccount();
// Retrieves by external key
- final Account retrievedAccount = killBillClient.getAccount(input.getExternalKey());
+ final Account retrievedAccount = killBillClient.getAccount(input.getExternalKey(), requestOptions);
Assert.assertTrue(retrievedAccount.equals(input));
// Try search endpoint
@@ -119,18 +119,92 @@ public class TestAccount extends TestJaxrsBase {
"USD", null, false, null, "UTC",
"bl1", "bh2", "", "", "ca", "San Francisco", "usa", "en", "415-255-2991",
"notes", false, false, null, null);
- final Account updatedAccount = killBillClient.updateAccount(newInput, createdBy, reason, comment);
+ final Account updatedAccount = killBillClient.updateAccount(newInput, requestOptions);
Assert.assertTrue(updatedAccount.equals(newInput));
// Try search endpoint
searchAccount(input, null);
}
+ @Test(groups = "slow", description = "Can reset account notes using flag treatNullAsReset")
+ public void testResetAccountNotes() throws Exception {
+
+ final Account input = createAccount();
+ Assert.assertNotNull(input.getExternalKey());
+ Assert.assertNotNull(input.getNotes());
+ Assert.assertEquals(input.getNotes(), "notes");
+ Assert.assertEquals(input.getTimeZone(), "UTC");
+ Assert.assertEquals(input.getAddress1(), "12 rue des ecoles");
+ Assert.assertEquals(input.getAddress2(), "Poitier");
+ Assert.assertEquals(input.getCity(), "Quelque part");
+ Assert.assertEquals(input.getState(), "Poitou");
+ Assert.assertEquals(input.getCountry(), "France");
+ Assert.assertEquals(input.getLocale(), "fr");
+
+
+ // Set notes to something else
+ final Account newInput = new Account(input.getAccountId(),
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ "notes2",
+ null,
+ null,
+ null,
+ null);
+
+
+ // Update notes, all other fields remaining the same (value set to null but treatNullAsReset defaults to false)
+ Account updatedAccount = killBillClient.updateAccount(newInput, requestOptions);
+
+ Assert.assertNotNull(updatedAccount.getExternalKey());
+ Assert.assertNotNull(updatedAccount.getNotes());
+ Assert.assertEquals(updatedAccount.getNotes(), "notes2");
+ Assert.assertEquals(updatedAccount.getTimeZone(), "UTC");
+ Assert.assertEquals(updatedAccount.getAddress1(), "12 rue des ecoles");
+ Assert.assertEquals(updatedAccount.getAddress2(), "Poitier");
+ Assert.assertEquals(updatedAccount.getCity(), "Quelque part");
+ Assert.assertEquals(updatedAccount.getState(), "Poitou");
+ Assert.assertEquals(updatedAccount.getCountry(), "France");
+ Assert.assertEquals(updatedAccount.getLocale(), "fr");
+
+ // Reset notes, all other fields remaining the same
+ updatedAccount.setNotes(null);
+ updatedAccount = killBillClient.updateAccount(updatedAccount, true, requestOptions);
+
+ Assert.assertNotNull(updatedAccount.getExternalKey());
+ Assert.assertNull(updatedAccount.getNotes());
+ Assert.assertEquals(updatedAccount.getTimeZone(), "UTC");
+ Assert.assertEquals(updatedAccount.getAddress1(), "12 rue des ecoles");
+ Assert.assertEquals(updatedAccount.getAddress2(), "Poitier");
+ Assert.assertEquals(updatedAccount.getCity(), "Quelque part");
+ Assert.assertEquals(updatedAccount.getState(), "Poitou");
+ Assert.assertEquals(updatedAccount.getCountry(), "France");
+ Assert.assertEquals(updatedAccount.getLocale(), "fr");
+ }
+
+
@Test(groups = "slow", description = "Can retrieve the account balance")
public void testAccountWithBalance() throws Exception {
final Account accountJson = createAccountNoPMBundleAndSubscriptionAndWaitForFirstInvoice();
- final Account accountWithBalance = killBillClient.getAccount(accountJson.getAccountId(), true, false);
+ final Account accountWithBalance = killBillClient.getAccount(accountJson.getAccountId(), true, false, requestOptions);
final BigDecimal accountBalance = accountWithBalance.getAccountBalance();
Assert.assertTrue(accountBalance.compareTo(BigDecimal.ZERO) > 0);
}
@@ -139,13 +213,13 @@ public class TestAccount extends TestJaxrsBase {
public void testUpdateNonExistentAccount() throws Exception {
final Account input = getAccount();
- Assert.assertNull(killBillClient.updateAccount(input, createdBy, reason, comment));
+ Assert.assertNull(killBillClient.updateAccount(input, requestOptions));
}
@Test(groups = "slow", description = "Cannot retrieve non-existent account")
public void testAccountNonExistent() throws Exception {
- Assert.assertNull(killBillClient.getAccount(UUID.randomUUID()));
- Assert.assertNull(killBillClient.getAccount(UUID.randomUUID().toString()));
+ Assert.assertNull(killBillClient.getAccount(UUID.randomUUID(), requestOptions));
+ Assert.assertNull(killBillClient.getAccount(UUID.randomUUID().toString(), requestOptions));
}
@Test(groups = "slow", description = "Can CRUD payment methods")
@@ -156,7 +230,7 @@ public class TestAccount extends TestJaxrsBase {
final PaymentMethodPluginDetail info = new PaymentMethodPluginDetail();
info.setProperties(getPaymentMethodCCProperties());
PaymentMethod paymentMethodJson = new PaymentMethod(null, UUID.randomUUID().toString(), accountJson.getAccountId(), true, PLUGIN_NAME, info);
- final PaymentMethod paymentMethodCC = killBillClient.createPaymentMethod(paymentMethodJson, createdBy, reason, comment);
+ final PaymentMethod paymentMethodCC = killBillClient.createPaymentMethod(paymentMethodJson, requestOptions);
assertTrue(paymentMethodCC.getIsDefault());
//
@@ -165,40 +239,40 @@ public class TestAccount extends TestJaxrsBase {
final PaymentMethodPluginDetail info2 = new PaymentMethodPluginDetail();
info2.setProperties(getPaymentMethodPaypalProperties());
paymentMethodJson = new PaymentMethod(null, UUID.randomUUID().toString(), accountJson.getAccountId(), false, PLUGIN_NAME, info2);
- final PaymentMethod paymentMethodPP = killBillClient.createPaymentMethod(paymentMethodJson, createdBy, reason, comment);
+ final PaymentMethod paymentMethodPP = killBillClient.createPaymentMethod(paymentMethodJson, requestOptions);
assertFalse(paymentMethodPP.getIsDefault());
//
// FETCH ALL PAYMENT METHODS
//
- List<PaymentMethod> paymentMethods = killBillClient.getPaymentMethodsForAccount(accountJson.getAccountId());
+ List<PaymentMethod> paymentMethods = killBillClient.getPaymentMethodsForAccount(accountJson.getAccountId(), requestOptions);
assertEquals(paymentMethods.size(), 2);
//
// CHANGE DEFAULT
//
- assertTrue(killBillClient.getPaymentMethod(paymentMethodCC.getPaymentMethodId()).getIsDefault());
- assertFalse(killBillClient.getPaymentMethod(paymentMethodPP.getPaymentMethodId()).getIsDefault());
- killBillClient.updateDefaultPaymentMethod(accountJson.getAccountId(), paymentMethodPP.getPaymentMethodId(), createdBy, reason, comment);
- assertTrue(killBillClient.getPaymentMethod(paymentMethodPP.getPaymentMethodId()).getIsDefault());
- assertFalse(killBillClient.getPaymentMethod(paymentMethodCC.getPaymentMethodId()).getIsDefault());
+ assertTrue(killBillClient.getPaymentMethod(paymentMethodCC.getPaymentMethodId(), requestOptions).getIsDefault());
+ assertFalse(killBillClient.getPaymentMethod(paymentMethodPP.getPaymentMethodId(), requestOptions).getIsDefault());
+ killBillClient.updateDefaultPaymentMethod(accountJson.getAccountId(), paymentMethodPP.getPaymentMethodId(), requestOptions);
+ assertTrue(killBillClient.getPaymentMethod(paymentMethodPP.getPaymentMethodId(), requestOptions).getIsDefault());
+ assertFalse(killBillClient.getPaymentMethod(paymentMethodCC.getPaymentMethodId(), requestOptions).getIsDefault());
//
// DELETE NON DEFAULT PM
//
- killBillClient.deletePaymentMethod(paymentMethodCC.getPaymentMethodId(), false, false, createdBy, reason, comment);
+ killBillClient.deletePaymentMethod(paymentMethodCC.getPaymentMethodId(), false, false, requestOptions);
//
// FETCH ALL PAYMENT METHODS
//
- paymentMethods = killBillClient.getPaymentMethodsForAccount(accountJson.getAccountId());
+ paymentMethods = killBillClient.getPaymentMethodsForAccount(accountJson.getAccountId(), requestOptions);
assertEquals(paymentMethods.size(), 1);
//
// DELETE DEFAULT PAYMENT METHOD (without special flag first)
//
try {
- killBillClient.deletePaymentMethod(paymentMethodPP.getPaymentMethodId(), false, false, createdBy, reason, comment);
+ killBillClient.deletePaymentMethod(paymentMethodPP.getPaymentMethodId(), false, false, requestOptions);
fail();
} catch (final KillBillClientException e) {
}
@@ -206,17 +280,17 @@ public class TestAccount extends TestJaxrsBase {
//
// RETRY TO DELETE DEFAULT PAYMENT METHOD (with special flag this time)
//
- killBillClient.deletePaymentMethod(paymentMethodPP.getPaymentMethodId(), true, false, createdBy, reason, comment);
+ killBillClient.deletePaymentMethod(paymentMethodPP.getPaymentMethodId(), true, false, requestOptions);
// CHECK ACCOUNT IS NOW AUTO_PAY_OFF
- final List<Tag> tagsJson = killBillClient.getAccountTags(accountJson.getAccountId());
+ final List<Tag> tagsJson = killBillClient.getAccountTags(accountJson.getAccountId(), requestOptions);
Assert.assertEquals(tagsJson.size(), 1);
final Tag tagJson = tagsJson.get(0);
Assert.assertEquals(tagJson.getTagDefinitionName(), "AUTO_PAY_OFF");
Assert.assertEquals(tagJson.getTagDefinitionId(), new UUID(0, 1));
// FETCH ACCOUNT AGAIN AND CHECK THERE IS NO DEFAULT PAYMENT METHOD SET
- final Account updatedAccount = killBillClient.getAccount(accountJson.getAccountId());
+ final Account updatedAccount = killBillClient.getAccount(accountJson.getAccountId(), requestOptions);
Assert.assertEquals(updatedAccount.getAccountId(), accountJson.getAccountId());
Assert.assertNull(updatedAccount.getPaymentMethodId());
@@ -224,7 +298,7 @@ public class TestAccount extends TestJaxrsBase {
// FINALLY TRY TO REMOVE AUTO_PAY_OFF WITH NO DEFAULT PAYMENT METHOD ON ACCOUNT
//
try {
- killBillClient.deleteAccountTag(accountJson.getAccountId(), new UUID(0, 1), createdBy, reason, comment);
+ killBillClient.deleteAccountTag(accountJson.getAccountId(), new UUID(0, 1), requestOptions);
} catch (final KillBillClientException e) {
}
}
@@ -234,7 +308,7 @@ public class TestAccount extends TestJaxrsBase {
final Account accountJson = createAccountWithPMBundleAndSubscriptionAndWaitForFirstInvoice();
// Verify payments
- final InvoicePayments objFromJson = killBillClient.getInvoicePaymentsForAccount(accountJson.getAccountId());
+ final InvoicePayments objFromJson = killBillClient.getInvoicePaymentsForAccount(accountJson.getAccountId(), requestOptions);
Assert.assertEquals(objFromJson.size(), 1);
}
@@ -245,19 +319,19 @@ public class TestAccount extends TestJaxrsBase {
final UUID autoPayOffId = new UUID(0, 1);
// Add a tag
- killBillClient.createAccountTag(input.getAccountId(), autoPayOffId, createdBy, reason, comment);
+ killBillClient.createAccountTag(input.getAccountId(), autoPayOffId, requestOptions);
// Retrieves all tags
- final List<Tag> tags1 = killBillClient.getAccountTags(input.getAccountId(), AuditLevel.FULL);
+ final List<Tag> tags1 = killBillClient.getAccountTags(input.getAccountId(), AuditLevel.FULL, requestOptions);
Assert.assertEquals(tags1.size(), 1);
Assert.assertEquals(tags1.get(0).getTagDefinitionId(), autoPayOffId);
// Verify adding the same tag a second time doesn't do anything
- killBillClient.createAccountTag(input.getAccountId(), autoPayOffId, createdBy, reason, comment);
+ killBillClient.createAccountTag(input.getAccountId(), autoPayOffId, requestOptions);
// Retrieves all tags again
- killBillClient.createAccountTag(input.getAccountId(), autoPayOffId, createdBy, reason, comment);
- final List<Tag> tags2 = killBillClient.getAccountTags(input.getAccountId(), AuditLevel.FULL);
+ killBillClient.createAccountTag(input.getAccountId(), autoPayOffId, requestOptions);
+ final List<Tag> tags2 = killBillClient.getAccountTags(input.getAccountId(), AuditLevel.FULL, requestOptions);
Assert.assertEquals(tags2, tags1);
// Verify audit logs
@@ -281,46 +355,46 @@ public class TestAccount extends TestJaxrsBase {
customFields.add(new CustomField(null, accountJson.getAccountId(), ObjectType.ACCOUNT, "2", "value2", null));
customFields.add(new CustomField(null, accountJson.getAccountId(), ObjectType.ACCOUNT, "3", "value3", null));
- killBillClient.createAccountCustomFields(accountJson.getAccountId(), customFields, createdBy, reason, comment);
+ killBillClient.createAccountCustomFields(accountJson.getAccountId(), customFields, requestOptions);
- final List<CustomField> accountCustomFields = killBillClient.getAccountCustomFields(accountJson.getAccountId());
+ final List<CustomField> accountCustomFields = killBillClient.getAccountCustomFields(accountJson.getAccountId(), requestOptions);
assertEquals(accountCustomFields.size(), 3);
// Delete all custom fields for account
- killBillClient.deleteAccountCustomFields(accountJson.getAccountId(), createdBy, reason, comment);
+ killBillClient.deleteAccountCustomFields(accountJson.getAccountId(), requestOptions);
- final List<CustomField> remainingCustomFields = killBillClient.getAccountCustomFields(accountJson.getAccountId());
+ final List<CustomField> remainingCustomFields = killBillClient.getAccountCustomFields(accountJson.getAccountId(), requestOptions);
assertEquals(remainingCustomFields.size(), 0);
}
@Test(groups = "slow", description = "refresh payment methods")
public void testRefreshPaymentMethods() throws Exception {
- Account account = createAccountWithDefaultPaymentMethod("someExternalKey");
+ final Account account = createAccountWithDefaultPaymentMethod("someExternalKey");
- final PaymentMethods paymentMethodsBeforeRefreshing = killBillClient.getPaymentMethodsForAccount(account.getAccountId());
+ final PaymentMethods paymentMethodsBeforeRefreshing = killBillClient.getPaymentMethodsForAccount(account.getAccountId(), requestOptions);
assertEquals(paymentMethodsBeforeRefreshing.size(), 1);
assertEquals(paymentMethodsBeforeRefreshing.get(0).getExternalKey(), "someExternalKey");
// WITH NAME OF AN EXISTING PLUGIN
- killBillClient.refreshPaymentMethods(account.getAccountId(), PLUGIN_NAME, ImmutableMap.<String, String>of(), createdBy, reason, comment);
+ killBillClient.refreshPaymentMethods(account.getAccountId(), PLUGIN_NAME, ImmutableMap.<String, String>of(), requestOptions);
- final PaymentMethods paymentMethodsAfterExistingPluginCall = killBillClient.getPaymentMethodsForAccount(account.getAccountId());
+ final PaymentMethods paymentMethodsAfterExistingPluginCall = killBillClient.getPaymentMethodsForAccount(account.getAccountId(), requestOptions);
assertEquals(paymentMethodsAfterExistingPluginCall.size(), 1);
assertEquals(paymentMethodsAfterExistingPluginCall.get(0).getExternalKey(), "someExternalKey");
// WITHOUT PLUGIN NAME
- killBillClient.refreshPaymentMethods(account.getAccountId(), ImmutableMap.<String, String>of(), createdBy, reason, comment);
+ killBillClient.refreshPaymentMethods(account.getAccountId(), ImmutableMap.<String, String>of(), requestOptions);
- final PaymentMethods paymentMethodsAfterNoPluginNameCall = killBillClient.getPaymentMethodsForAccount(account.getAccountId());
+ final PaymentMethods paymentMethodsAfterNoPluginNameCall = killBillClient.getPaymentMethodsForAccount(account.getAccountId(), requestOptions);
assertEquals(paymentMethodsAfterNoPluginNameCall.size(), 1);
assertEquals(paymentMethodsAfterNoPluginNameCall.get(0).getExternalKey(), "someExternalKey");
// WITH WRONG PLUGIN NAME
try {
- killBillClient.refreshPaymentMethods(account.getAccountId(), "GreatestPluginEver", ImmutableMap.<String, String>of(), createdBy, reason, comment);
+ killBillClient.refreshPaymentMethods(account.getAccountId(), "GreatestPluginEver", ImmutableMap.<String, String>of(), requestOptions);
Assert.fail();
- } catch (KillBillClientException e) {
+ } catch (final KillBillClientException e) {
Assert.assertEquals(e.getBillingException().getCode(), (Integer) ErrorCode.PAYMENT_NO_SUCH_PAYMENT_PLUGIN.getCode());
}
}
@@ -331,10 +405,10 @@ public class TestAccount extends TestJaxrsBase {
createAccount();
}
- final Accounts allAccounts = killBillClient.getAccounts();
+ final Accounts allAccounts = killBillClient.getAccounts(requestOptions);
Assert.assertEquals(allAccounts.size(), 5);
- Accounts page = killBillClient.getAccounts(0L, 1L);
+ Accounts page = killBillClient.getAccounts(0L, 1L, requestOptions);
for (int i = 0; i < 5; i++) {
Assert.assertNotNull(page);
Assert.assertEquals(page.size(), 1);
@@ -361,14 +435,14 @@ public class TestAccount extends TestJaxrsBase {
// Search by external key.
// Note: we will always find a match since we don't update it
- final List<Account> accountsByExternalKey = killBillClient.searchAccounts(input.getExternalKey());
+ final List<Account> accountsByExternalKey = killBillClient.searchAccounts(input.getExternalKey(), requestOptions);
Assert.assertEquals(accountsByExternalKey.size(), 1);
Assert.assertEquals(accountsByExternalKey.get(0).getAccountId(), input.getAccountId());
Assert.assertEquals(accountsByExternalKey.get(0).getExternalKey(), input.getExternalKey());
}
private void doSearchAccount(final String key, @Nullable final Account output) throws Exception {
- final List<Account> accountsByKey = killBillClient.searchAccounts(key);
+ final List<Account> accountsByKey = killBillClient.searchAccounts(key, requestOptions);
if (output == null) {
Assert.assertEquals(accountsByKey.size(), 0);
} else {
@@ -385,10 +459,10 @@ public class TestAccount extends TestJaxrsBase {
final Account childInput = getAccount();
childInput.setParentAccountId(parentAccount.getAccountId());
childInput.setIsPaymentDelegatedToParent(true);
- final Account childAccount = killBillClient.createAccount(childInput, createdBy, reason, comment);
+ final Account childAccount = killBillClient.createAccount(childInput, requestOptions);
// Retrieves child account by external key
- final Account retrievedAccount = killBillClient.getAccount(childAccount.getExternalKey());
+ final Account retrievedAccount = killBillClient.getAccount(childAccount.getExternalKey(), requestOptions);
Assert.assertTrue(retrievedAccount.equals(childAccount));
Assert.assertEquals(retrievedAccount.getParentAccountId(), parentAccount.getAccountId());
Assert.assertTrue(retrievedAccount.getIsPaymentDelegatedToParent());
@@ -402,14 +476,14 @@ public class TestAccount extends TestJaxrsBase {
final Account childInput = getAccount();
childInput.setParentAccountId(parentAccount.getAccountId());
childInput.setIsPaymentDelegatedToParent(true);
- Account childAccount = killBillClient.createAccount(childInput, createdBy, reason, comment);
- childAccount = killBillClient.getAccount(childAccount.getAccountId(), true, true, basicRequestOptions());
+ Account childAccount = killBillClient.createAccount(childInput, requestOptions);
+ childAccount = killBillClient.getAccount(childAccount.getAccountId(), true, true, requestOptions);
final Account childInput2 = getAccount();
childInput2.setParentAccountId(parentAccount.getAccountId());
childInput2.setIsPaymentDelegatedToParent(true);
- Account childAccount2 = killBillClient.createAccount(childInput2, createdBy, reason, comment);
- childAccount2 = killBillClient.getAccount(childAccount2.getAccountId(), true, true, basicRequestOptions());
+ Account childAccount2 = killBillClient.createAccount(childInput2, requestOptions);
+ childAccount2 = killBillClient.getAccount(childAccount2.getAccountId(), true, true, requestOptions);
// Retrieves children accounts by parent account id
final Accounts childrenAccounts = killBillClient.getChildrenAccounts(parentAccount.getAccountId(), true, true, requestOptions);
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestAdmin.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestAdmin.java
index 4377c5a..4a28ea3 100644
--- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestAdmin.java
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestAdmin.java
@@ -68,7 +68,7 @@ public class TestAdmin extends TestJaxrsBase {
authTransaction.setPaymentExternalKey(paymentExternalKey);
authTransaction.setTransactionExternalKey(authTransactionExternalKey);
authTransaction.setTransactionType("AUTHORIZE");
- final Payment authPayment = killBillClient.createPayment(account.getAccountId(), account.getPaymentMethodId(), authTransaction, basicRequestOptions());
+ final Payment authPayment = killBillClient.createPayment(account.getAccountId(), account.getPaymentMethodId(), authTransaction, requestOptions);
// First fix transactionStatus and paymentSstate (but not lastSuccessPaymentState
// Note that state is not consistent between TransactionStatus and lastSuccessPaymentState but we don't care.
@@ -176,7 +176,7 @@ public class TestAdmin extends TestJaxrsBase {
captureTransaction.setPaymentExternalKey(payment.getPaymentExternalKey());
captureTransaction.setTransactionExternalKey(capture1TransactionExternalKey);
try {
- killBillClient.captureAuthorization(captureTransaction, basicRequestOptions());
+ killBillClient.captureAuthorization(captureTransaction, requestOptions);
if (expectException) {
Assert.fail("Capture should not succeed, after auth was moved to a PAYMENT_FAILURE");
}
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 1b54361..c7c622d 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
@@ -16,23 +16,24 @@
package org.killbill.billing.jaxrs;
+import java.net.URI;
+import java.util.UUID;
+
+import javax.servlet.ServletRequest;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+
import org.killbill.billing.jaxrs.resources.AccountResource;
import org.killbill.billing.jaxrs.util.JaxrsUriBuilder;
import org.killbill.billing.server.log.ServerTestSuiteNoDB;
import org.killbill.billing.util.config.definition.JaxrsConfig;
import org.testng.annotations.Test;
-import javax.ws.rs.core.Response;
-import javax.ws.rs.core.UriInfo;
-import java.net.URI;
-import java.util.UUID;
-
import com.sun.jersey.api.client.ClientResponse.Status;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertEqualsNoOrder;
public class TestBuildResponse extends ServerTestSuiteNoDB {
@@ -47,7 +48,7 @@ public class TestBuildResponse extends ServerTestSuiteNoDB {
JaxrsConfig jaxrsConfig = mock(JaxrsConfig.class);
when(jaxrsConfig.isJaxrsLocationFullUrl()).thenReturn(false);
JaxrsUriBuilder uriBuilder = new JaxrsUriBuilder(jaxrsConfig);
- Response response = uriBuilder.buildResponse(uriInfo, AccountResource.class, "getAccount", objectId);
+ Response response = uriBuilder.buildResponse(uriInfo, AccountResource.class, "getAccount", objectId, mockRequest(uriInfo));
assertEquals(response.getStatus(), Status.CREATED.getStatusCode());
assertEquals(response.getMetadata().get("Location").get(0), "/1.0/kb/accounts/" + objectId.toString());
@@ -64,7 +65,7 @@ public class TestBuildResponse extends ServerTestSuiteNoDB {
JaxrsConfig jaxrsConfig = mock(JaxrsConfig.class);
when(jaxrsConfig.isJaxrsLocationFullUrl()).thenReturn(false);
JaxrsUriBuilder uriBuilder = new JaxrsUriBuilder(jaxrsConfig);
- Response response = uriBuilder.buildResponse(uriInfo, AccountResource.class, "getAccount", objectId);
+ Response response = uriBuilder.buildResponse(uriInfo, AccountResource.class, "getAccount", objectId, mockRequest(uriInfo));
assertEquals(response.getStatus(), Status.CREATED.getStatusCode());
assertEquals(response.getMetadata().get("Location").get(0), "/killbill/1.0/kb/accounts/" + objectId.toString());
@@ -82,7 +83,7 @@ public class TestBuildResponse extends ServerTestSuiteNoDB {
JaxrsConfig jaxrsConfig = mock(JaxrsConfig.class);
when(jaxrsConfig.isJaxrsLocationFullUrl()).thenReturn(true);
JaxrsUriBuilder uriBuilder = new JaxrsUriBuilder(jaxrsConfig);
- Response response = uriBuilder.buildResponse(uriInfo, AccountResource.class, "getAccount", objectId);
+ Response response = uriBuilder.buildResponse(uriInfo, AccountResource.class, "getAccount", objectId, mockRequest(uriInfo));
assertEquals(response.getStatus(), Status.CREATED.getStatusCode());
assertEquals(response.getMetadata().get("Location").get(0).toString(), uri.toString() + "/1.0/kb/accounts/" + objectId.toString());
@@ -100,9 +101,21 @@ public class TestBuildResponse extends ServerTestSuiteNoDB {
JaxrsConfig jaxrsConfig = mock(JaxrsConfig.class);
when(jaxrsConfig.isJaxrsLocationFullUrl()).thenReturn(true);
JaxrsUriBuilder uriBuilder = new JaxrsUriBuilder(jaxrsConfig);
- Response response = uriBuilder.buildResponse(uriInfo, AccountResource.class, "getAccount", objectId);
+ Response response = uriBuilder.buildResponse(uriInfo, AccountResource.class, "getAccount", objectId, mockRequest(uriInfo));
assertEquals(response.getStatus(), Status.CREATED.getStatusCode());
assertEquals(response.getMetadata().get("Location").get(0).toString(), uri.toString() + "/1.0/kb/accounts/" + objectId.toString());
}
+
+ private ServletRequest mockRequest(final UriInfo uriInfo) throws Exception {
+ final ServletRequest request = mock(ServletRequest.class);
+ final URI absolutePath = uriInfo.getAbsolutePath();
+ if (absolutePath != null) {
+ final String scheme = absolutePath.getScheme();
+ when(request.getScheme()).thenReturn(scheme);
+ final int port = absolutePath.getPort();
+ when(request.getServerPort()).thenReturn(port);
+ }
+ return request;
+ }
}
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestBundle.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestBundle.java
index 4c158af..4323f25 100644
--- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestBundle.java
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestBundle.java
@@ -180,13 +180,13 @@ public class TestBundle extends TestJaxrsBase {
final Subscription subscription2 = killBillClient.getSubscription(entitlement.getSubscriptionId());
assertEquals(subscription2.getState(), EntitlementState.ACTIVE);
- final BlockingStates blockingStates = killBillClient.getBlockingStates(accountJson.getAccountId(), null, ImmutableList.<String>of("service"), AuditLevel.FULL, basicRequestOptions());
+ final BlockingStates blockingStates = killBillClient.getBlockingStates(accountJson.getAccountId(), null, ImmutableList.<String>of("service"), AuditLevel.FULL, requestOptions);
Assert.assertEquals(blockingStates.size(), 2);
- final BlockingStates blockingStates2 = killBillClient.getBlockingStates(accountJson.getAccountId(), ImmutableList.<BlockingStateType>of(BlockingStateType.SUBSCRIPTION_BUNDLE), null, AuditLevel.FULL, basicRequestOptions());
+ final BlockingStates blockingStates2 = killBillClient.getBlockingStates(accountJson.getAccountId(), ImmutableList.<BlockingStateType>of(BlockingStateType.SUBSCRIPTION_BUNDLE), null, AuditLevel.FULL, requestOptions);
Assert.assertEquals(blockingStates2.size(), 2);
- final BlockingStates blockingStates3 = killBillClient.getBlockingStates(accountJson.getAccountId(), null, null, AuditLevel.FULL, basicRequestOptions());
+ final BlockingStates blockingStates3 = killBillClient.getBlockingStates(accountJson.getAccountId(), null, null, AuditLevel.FULL, requestOptions);
Assert.assertEquals(blockingStates3.size(), 3);
}
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestCatalog.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestCatalog.java
index 84f3d50..1485fb4 100644
--- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestCatalog.java
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestCatalog.java
@@ -26,9 +26,8 @@ import java.util.Set;
import java.util.UUID;
import org.joda.time.DateTime;
-import org.killbill.billing.catalog.StandaloneCatalog;
-import org.killbill.billing.catalog.VersionedCatalog;
import org.killbill.billing.catalog.api.BillingPeriod;
+import org.killbill.billing.catalog.api.CatalogApiException;
import org.killbill.billing.catalog.api.Currency;
import org.killbill.billing.catalog.api.ProductCategory;
import org.killbill.billing.catalog.api.TimeUnit;
@@ -41,7 +40,6 @@ import org.killbill.billing.client.model.Product;
import org.killbill.billing.client.model.SimplePlan;
import org.killbill.billing.client.model.Tenant;
import org.killbill.billing.client.model.Usage;
-import org.killbill.xmlloader.XMLLoader;
import org.testng.Assert;
import org.testng.annotations.Test;
@@ -53,8 +51,8 @@ public class TestCatalog extends TestJaxrsBase {
@Test(groups = "slow", description = "Upload and retrieve a per tenant catalog")
public void testMultiTenantCatalog() throws Exception {
final String versionPath1 = Resources.getResource("SpyCarBasic.xml").getPath();
- killBillClient.uploadXMLCatalog(versionPath1, createdBy, reason, comment);
- String catalog = killBillClient.getXMLCatalog();
+ killBillClient.uploadXMLCatalog(versionPath1, requestOptions);
+ String catalog = killBillClient.getXMLCatalog(requestOptions);
Assert.assertNotNull(catalog);
//
// We can't deserialize the VersionedCatalog using our JAXB models because it contains several
@@ -62,17 +60,52 @@ public class TestCatalog extends TestJaxrsBase {
//
}
+ @Test(groups = "slow")
+ public void testUploadAndFetchUsageCatlog() throws Exception {
+ final String versionPath1 = Resources.getResource("UsageExperimental.xml").getPath();
+ killBillClient.uploadXMLCatalog(versionPath1, requestOptions);
+ String catalog = killBillClient.getXMLCatalog(requestOptions);
+ Assert.assertNotNull(catalog);
+ }
+
+
+ @Test(groups = "slow")
+ public void testUploadWithErrors() throws Exception {
+ final String versionPath1 = Resources.getResource("SpyCarBasic.xml").getPath();
+ killBillClient.uploadXMLCatalog(versionPath1, requestOptions);
+
+ // Retry to upload same version
+ try {
+ killBillClient.uploadXMLCatalog(versionPath1, requestOptions);
+ Assert.fail("Uploading same version should fail");
+ } catch (KillBillClientException e) {
+ Assert.assertEquals(e.getMessage(), "Invalid catalog for tenant : 1");
+ }
+
+ // Try to upload another version with an invalid name (different than orignal name)
+ try {
+ final String versionPath2 = Resources.getResource("SpyCarBasicInvalidName.xml").getPath();
+ killBillClient.uploadXMLCatalog(versionPath2, requestOptions);
+ Assert.fail("Uploading same version should fail");
+ } catch (KillBillClientException e) {
+ Assert.assertEquals(e.getMessage(), "Invalid catalog for tenant : 1");
+ }
+
+ String catalog = killBillClient.getXMLCatalog(requestOptions);
+ Assert.assertNotNull(catalog);
+ }
+
@Test(groups = "slow", description = "Can retrieve a json version of the catalog")
public void testCatalog() throws Exception {
final Set<String> allBasePlans = new HashSet<String>();
- final List<Catalog> catalogsJson = killBillClient.getJSONCatalog();
+ final List<Catalog> catalogsJson = killBillClient.getJSONCatalog(requestOptions);
Assert.assertEquals(catalogsJson.get(0).getName(), "Firearms");
Assert.assertEquals(catalogsJson.get(0).getEffectiveDate(), Date.valueOf("2011-01-01"));
Assert.assertEquals(catalogsJson.get(0).getCurrencies().size(), 3);
Assert.assertEquals(catalogsJson.get(0).getProducts().size(), 11);
- Assert.assertEquals(catalogsJson.get(0).getPriceLists().size(), 4);
+ Assert.assertEquals(catalogsJson.get(0).getPriceLists().size(), 6);
for (final Product productJson : catalogsJson.get(0).getProducts()) {
if (!"BASE".equals(productJson.getType())) {
@@ -99,7 +132,7 @@ public class TestCatalog extends TestJaxrsBase {
}
// Retrieve available products (addons) for that base product
- final List<PlanDetail> availableAddons = killBillClient.getAvailableAddons(productJson.getName());
+ final List<PlanDetail> availableAddons = killBillClient.getAvailableAddons(productJson.getName(), requestOptions);
final Set<String> availableAddonsNames = new HashSet<String>();
for (final PlanDetail planDetailJson : availableAddons) {
availableAddonsNames.add(planDetailJson.getProduct());
@@ -108,7 +141,7 @@ public class TestCatalog extends TestJaxrsBase {
}
// Verify base plans endpoint
- final List<PlanDetail> basePlans = killBillClient.getBasePlans();
+ final List<PlanDetail> basePlans = killBillClient.getBasePlans(requestOptions);
final Set<String> foundBasePlans = new HashSet<String>();
for (final PlanDetail planDetailJson : basePlans) {
foundBasePlans.add(planDetailJson.getPlan());
@@ -120,7 +153,7 @@ public class TestCatalog extends TestJaxrsBase {
expectedExceptions = KillBillClientException.class,
expectedExceptionsMessageRegExp = "There is no catalog version that applies for the given date.*")
public void testCatalogInvalidDate() throws Exception {
- final List<Catalog> catalogsJson = killBillClient.getJSONCatalog(DateTime.parse("2008-01-01"));
+ final List<Catalog> catalogsJson = killBillClient.getJSONCatalog(DateTime.parse("2008-01-01"), requestOptions);
Assert.fail();
}
@@ -189,4 +222,6 @@ public class TestCatalog extends TestJaxrsBase {
Assert.assertEquals(catalogsJson.get(0).getPriceLists().get(0).getPlans().size(), 2);
}
+
+
}
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestEntitlement.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestEntitlement.java
index ef680e5..bd9c2cf 100644
--- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestEntitlement.java
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestEntitlement.java
@@ -291,7 +291,7 @@ public class TestEntitlement extends TestJaxrsBase {
final Subscription newInput = new Subscription();
newInput.setSubscriptionId(subscription2.getSubscriptionId());
newInput.setPlanName("pistol-monthly");
- final Subscription subscription3 = killBillClient.updateSubscription(newInput, null, BillingActionPolicy.IMMEDIATE, DEFAULT_WAIT_COMPLETION_TIMEOUT_SEC, basicRequestOptions());
+ final Subscription subscription3 = killBillClient.updateSubscription(newInput, null, BillingActionPolicy.IMMEDIATE, DEFAULT_WAIT_COMPLETION_TIMEOUT_SEC, requestOptions);
Assert.assertEquals(subscription3.getEvents().size(), 4);
Assert.assertEquals(subscription3.getEvents().get(0).getEventType(), SubscriptionEventType.START_ENTITLEMENT.name());
@@ -515,7 +515,7 @@ public class TestEntitlement extends TestJaxrsBase {
final Subscription updatedSubscription = new Subscription();
updatedSubscription.setSubscriptionId(entitlementJson.getSubscriptionId());
updatedSubscription.setBillCycleDayLocal(9);
- killBillClient.updateSubscriptionBCD(updatedSubscription, null, DEFAULT_WAIT_COMPLETION_TIMEOUT_SEC, basicRequestOptions());
+ killBillClient.updateSubscriptionBCD(updatedSubscription, null, DEFAULT_WAIT_COMPLETION_TIMEOUT_SEC, requestOptions);
final Subscription result = killBillClient.getSubscription(entitlementJson.getSubscriptionId());
@@ -545,7 +545,7 @@ public class TestEntitlement extends TestJaxrsBase {
input.setExternalKey("somethingSpecial");
input.setPlanName("shotgun-monthly");
- final Subscription entitlementJson = killBillClient.createSubscription(input, null, DEFAULT_WAIT_COMPLETION_TIMEOUT_SEC, basicRequestOptions());
+ final Subscription entitlementJson = killBillClient.createSubscription(input, null, DEFAULT_WAIT_COMPLETION_TIMEOUT_SEC, requestOptions);
Assert.assertEquals(entitlementJson.getProductName(), "Shotgun");
Assert.assertEquals(entitlementJson.getBillingPeriod(), BillingPeriod.MONTHLY);
Assert.assertEquals(entitlementJson.getPriceList(), DefaultPriceListSet.DEFAULT_PRICELIST_NAME);
@@ -556,7 +556,7 @@ public class TestEntitlement extends TestJaxrsBase {
newInput.setAccountId(entitlementJson.getAccountId());
newInput.setSubscriptionId(entitlementJson.getSubscriptionId());
newInput.setPlanName("pistol-monthly");
- final Subscription newEntitlementJson = killBillClient.updateSubscription(newInput, null, null, DEFAULT_WAIT_COMPLETION_TIMEOUT_SEC, basicRequestOptions());
+ final Subscription newEntitlementJson = killBillClient.updateSubscription(newInput, null, null, DEFAULT_WAIT_COMPLETION_TIMEOUT_SEC, requestOptions);
Assert.assertEquals(newEntitlementJson.getProductName(), "Pistol");
Assert.assertEquals(newEntitlementJson.getBillingPeriod(), BillingPeriod.MONTHLY);
Assert.assertEquals(newEntitlementJson.getPriceList(), DefaultPriceListSet.DEFAULT_PRICELIST_NAME);
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestInvoice.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestInvoice.java
index dfd8d8c..d70c150 100644
--- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestInvoice.java
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestInvoice.java
@@ -751,7 +751,7 @@ public class TestInvoice extends TestJaxrsBase {
final Account accountWithBalance = killBillClient.getAccount(accountJson.getAccountId(), true, true);
- final Invoice migrationInvoice = killBillClient.createMigrationInvoice(accountJson.getAccountId(), null, ImmutableList.<InvoiceItem>of(externalCharge), basicRequestOptions());
+ final Invoice migrationInvoice = killBillClient.createMigrationInvoice(accountJson.getAccountId(), null, ImmutableList.<InvoiceItem>of(externalCharge), requestOptions);
assertEquals(migrationInvoice.getBalance(), BigDecimal.ZERO);
assertEquals(migrationInvoice.getItems().size(), 1);
assertEquals(migrationInvoice.getItems().get(0).getAmount().compareTo(chargeAmount), 0);
@@ -786,7 +786,7 @@ public class TestInvoice extends TestJaxrsBase {
Assert.assertEquals(parentInvoices.size(), 0);
// transfer credit to parent account
- killBillClient.transferChildCreditToParent(childAccount.getAccountId(), basicRequestOptions());
+ killBillClient.transferChildCreditToParent(childAccount.getAccountId(), requestOptions);
childInvoices = killBillClient.getInvoicesForAccount(childAccount.getAccountId(), true, false);
Assert.assertEquals(childInvoices.size(), 2);
@@ -803,7 +803,7 @@ public class TestInvoice extends TestJaxrsBase {
final Account account = createAccount();
// transfer credit to parent account
- killBillClient.transferChildCreditToParent(account.getAccountId(), basicRequestOptions());
+ killBillClient.transferChildCreditToParent(account.getAccountId(), requestOptions);
}
@@ -814,7 +814,7 @@ public class TestInvoice extends TestJaxrsBase {
final Account childAccount = createAccount(parentAccount.getAccountId());
// transfer credit to parent account
- killBillClient.transferChildCreditToParent(childAccount.getAccountId(), basicRequestOptions());
+ killBillClient.transferChildCreditToParent(childAccount.getAccountId(), requestOptions);
}
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestInvoicePayment.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestInvoicePayment.java
index de45ad7..661a1c6 100644
--- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestInvoicePayment.java
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestInvoicePayment.java
@@ -238,22 +238,22 @@ public class TestInvoicePayment extends TestJaxrsBase {
final Account accountJson = createAccountWithPMBundleAndSubscriptionAndWaitForFirstInvoice();
- InvoicePayments invoicePayments = killBillClient.getInvoicePaymentsForAccount(accountJson.getAccountId(), basicRequestOptions());
+ InvoicePayments invoicePayments = killBillClient.getInvoicePaymentsForAccount(accountJson.getAccountId(), requestOptions);
assertEquals(invoicePayments.size(), 1);
final InvoicePayment invoicePayment = invoicePayments.get(0);
// Verify targetInvoiceId is not Null. See #593
assertNotNull(invoicePayment.getTargetInvoiceId());
- final Invoices invoices = killBillClient.getInvoicesForAccount(accountJson.getAccountId(), basicRequestOptions());
+ final Invoices invoices = killBillClient.getInvoicesForAccount(accountJson.getAccountId(), requestOptions);
assertEquals(invoices.size(), 2);
final Invoice invoice = invoices.get(1);
// Verify this is the correct value
assertEquals(invoicePayment.getTargetInvoiceId(), invoice.getInvoiceId());
// Make a payment and verify both invoice payment point to the same targetInvoiceId
- killBillClient.payAllInvoices(accountJson.getAccountId(), false, null, basicRequestOptions());
- invoicePayments = killBillClient.getInvoicePaymentsForAccount(accountJson.getAccountId(), basicRequestOptions());
+ killBillClient.payAllInvoices(accountJson.getAccountId(), false, null, requestOptions);
+ invoicePayments = killBillClient.getInvoicePaymentsForAccount(accountJson.getAccountId(), requestOptions);
assertEquals(invoicePayments.size(), 2);
for (final InvoicePayment cur : invoicePayments) {
assertEquals(cur.getTargetInvoiceId(), invoice.getInvoiceId());
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPayment.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPayment.java
index 88cb7ea..13b0fa4 100644
--- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPayment.java
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPayment.java
@@ -114,7 +114,7 @@ public class TestPayment extends TestJaxrsBase {
authTransaction.setTransactionType(TransactionType.AUTHORIZE.name());
final Payment payment = killBillClient.createPayment(account.getAccountId(), account.getPaymentMethodId(), authTransaction,
- ImmutableMap.<String, String>of(), basicRequestOptions());
+ ImmutableMap.<String, String>of(), requestOptions);
final PaymentTransaction paymentTransaction = payment.getTransactions().get(0);
assertEquals(paymentTransaction.getStatus(), TransactionStatus.PAYMENT_FAILURE.toString());
assertEquals(paymentTransaction.getGatewayErrorCode(), MockPaymentProviderPlugin.GATEWAY_ERROR_CODE);
@@ -122,6 +122,34 @@ public class TestPayment extends TestJaxrsBase {
}
@Test(groups = "slow")
+ public void testWithFailedPaymentAndWithoutFollowLocation() throws Exception {
+ final Account account = createAccountWithDefaultPaymentMethod();
+
+ mockPaymentProviderPlugin.makeNextPaymentFailWithError();
+
+ final PaymentTransaction authTransaction = new PaymentTransaction();
+ authTransaction.setAmount(BigDecimal.ONE);
+ authTransaction.setCurrency(account.getCurrency());
+ authTransaction.setTransactionType(TransactionType.AUTHORIZE.name());
+
+ final RequestOptions requestOptionsWithoutFollowLocation = RequestOptions.builder()
+ .withCreatedBy(createdBy)
+ .withReason(reason)
+ .withComment(comment)
+ .withFollowLocation(false)
+ .build();
+
+ try {
+ killBillClient.createPayment(account.getAccountId(), account.getPaymentMethodId(), authTransaction,
+ ImmutableMap.<String, String>of(), requestOptionsWithoutFollowLocation);
+ fail();
+ } catch (final KillBillClientException e) {
+ assertEquals(e.getResponse().getStatusCode(), 402);
+ assertEquals(e.getBillingException().getMessage(), "Payment decline by gateway. Error message: gatewayError");
+ }
+ }
+
+ @Test(groups = "slow")
public void testWithCanceledPayment() throws Exception {
final Account account = createAccountWithDefaultPaymentMethod();
@@ -131,7 +159,7 @@ public class TestPayment extends TestJaxrsBase {
authTransaction.setAmount(BigDecimal.ONE);
authTransaction.setCurrency(account.getCurrency());
authTransaction.setTransactionType(TransactionType.AUTHORIZE.name());
- final Payment payment = killBillClient.createPayment(account.getAccountId(), account.getPaymentMethodId(), authTransaction, ImmutableMap.<String, String>of(), basicRequestOptions());
+ final Payment payment = killBillClient.createPayment(account.getAccountId(), account.getPaymentMethodId(), authTransaction, ImmutableMap.<String, String>of(), requestOptions);
final PaymentTransaction paymentTransaction = payment.getTransactions().get(0);
assertEquals(paymentTransaction.getStatus(), TransactionStatus.PLUGIN_FAILURE.toString());
}
@@ -147,7 +175,7 @@ public class TestPayment extends TestJaxrsBase {
authTransaction.setCurrency(account.getCurrency());
authTransaction.setTransactionType(TransactionType.AUTHORIZE.name());
try {
- killBillClient.createPayment(account.getAccountId(), account.getPaymentMethodId(), authTransaction, ImmutableMap.<String, String>of(), basicRequestOptions());
+ killBillClient.createPayment(account.getAccountId(), account.getPaymentMethodId(), authTransaction, ImmutableMap.<String, String>of(), requestOptions);
fail();
} catch (KillBillClientException e) {
assertEquals(504, e.getResponse().getStatusCode());
@@ -313,27 +341,27 @@ public class TestPayment extends TestJaxrsBase {
// Complete operation: first, only specify the payment id
final PaymentTransaction completeTransactionByPaymentId = new PaymentTransaction();
completeTransactionByPaymentId.setPaymentId(initialPayment.getPaymentId());
- final Payment completedPaymentByPaymentId = killBillClient.completePayment(completeTransactionByPaymentId, pluginProperties, basicRequestOptions());
+ final Payment completedPaymentByPaymentId = killBillClient.completePayment(completeTransactionByPaymentId, pluginProperties, requestOptions);
verifyPayment(account, paymentMethodId, completedPaymentByPaymentId, paymentExternalKey, authTransactionExternalKey, transactionType.toString(), pending, amount, authAmount, BigDecimal.ZERO, BigDecimal.ZERO, 1, paymentNb);
// Second, only specify the payment external key
final PaymentTransaction completeTransactionByPaymentExternalKey = new PaymentTransaction();
completeTransactionByPaymentExternalKey.setPaymentExternalKey(initialPayment.getPaymentExternalKey());
- final Payment completedPaymentByExternalKey = killBillClient.completePayment(completeTransactionByPaymentExternalKey, pluginProperties, basicRequestOptions());
+ final Payment completedPaymentByExternalKey = killBillClient.completePayment(completeTransactionByPaymentExternalKey, pluginProperties, requestOptions);
verifyPayment(account, paymentMethodId, completedPaymentByExternalKey, paymentExternalKey, authTransactionExternalKey, transactionType.toString(), pending, amount, authAmount, BigDecimal.ZERO, BigDecimal.ZERO, 1, paymentNb);
// Third, specify the payment id and transaction external key
final PaymentTransaction completeTransactionWithTypeAndKey = new PaymentTransaction();
completeTransactionWithTypeAndKey.setPaymentId(initialPayment.getPaymentId());
completeTransactionWithTypeAndKey.setTransactionExternalKey(authPaymentTransaction.getTransactionExternalKey());
- final Payment completedPaymentByTypeAndKey = killBillClient.completePayment(completeTransactionWithTypeAndKey, pluginProperties, basicRequestOptions());
+ final Payment completedPaymentByTypeAndKey = killBillClient.completePayment(completeTransactionWithTypeAndKey, pluginProperties, requestOptions);
verifyPayment(account, paymentMethodId, completedPaymentByTypeAndKey, paymentExternalKey, authTransactionExternalKey, transactionType.toString(), pending, amount, authAmount, BigDecimal.ZERO, BigDecimal.ZERO, 1, paymentNb);
// Finally, specify the payment id and transaction id
final PaymentTransaction completeTransactionWithTypeAndId = new PaymentTransaction();
completeTransactionWithTypeAndId.setPaymentId(initialPayment.getPaymentId());
completeTransactionWithTypeAndId.setTransactionId(authPaymentTransaction.getTransactionId());
- final Payment completedPaymentByTypeAndId = killBillClient.completePayment(completeTransactionWithTypeAndId, pluginProperties, basicRequestOptions());
+ final Payment completedPaymentByTypeAndId = killBillClient.completePayment(completeTransactionWithTypeAndId, pluginProperties, requestOptions);
verifyPayment(account, paymentMethodId, completedPaymentByTypeAndId, paymentExternalKey, authTransactionExternalKey, transactionType.toString(), pending, amount, authAmount, BigDecimal.ZERO, BigDecimal.ZERO, 1, paymentNb);
}
}
@@ -358,7 +386,7 @@ public class TestPayment extends TestJaxrsBase {
// Complete operation: first, only specify the payment id
final PaymentTransaction completeTransactionByPaymentId = new PaymentTransaction();
completeTransactionByPaymentId.setPaymentId(initialPayment.getPaymentId());
- final Payment completedPaymentByPaymentId = killBillClient.completePayment(completeTransactionByPaymentId, pluginProperties, basicRequestOptions());
+ final Payment completedPaymentByPaymentId = killBillClient.completePayment(completeTransactionByPaymentId, pluginProperties, requestOptions);
verifyPayment(account, paymentMethodId, completedPaymentByPaymentId, paymentExternalKey, authTransactionExternalKey, transactionType.toString(), TransactionStatus.SUCCESS.name(), amount, amount, BigDecimal.ZERO, BigDecimal.ZERO, 1, 1);
}
@@ -385,7 +413,7 @@ public class TestPayment extends TestJaxrsBase {
completeTransactionByPaymentIdAndInvalidTransactionId.setPaymentId(initialPayment.getPaymentId());
completeTransactionByPaymentIdAndInvalidTransactionId.setTransactionId(UUID.randomUUID());
try {
- killBillClient.completePayment(completeTransactionByPaymentIdAndInvalidTransactionId, pluginProperties, basicRequestOptions());
+ killBillClient.completePayment(completeTransactionByPaymentIdAndInvalidTransactionId, pluginProperties, requestOptions);
fail("Payment completion should fail when invalid transaction id has been provided" );
} catch (final KillBillClientException expected) {
}
@@ -393,7 +421,7 @@ public class TestPayment extends TestJaxrsBase {
final PaymentTransaction completeTransactionByPaymentIdAndTransactionId = new PaymentTransaction();
completeTransactionByPaymentIdAndTransactionId.setPaymentId(initialPayment.getPaymentId());
completeTransactionByPaymentIdAndTransactionId.setTransactionId(initialPayment.getTransactions().get(0).getTransactionId());
- final Payment completedPaymentByPaymentId = killBillClient.completePayment(completeTransactionByPaymentIdAndTransactionId, pluginProperties, basicRequestOptions());
+ final Payment completedPaymentByPaymentId = killBillClient.completePayment(completeTransactionByPaymentIdAndTransactionId, pluginProperties, requestOptions);
verifyPayment(account, paymentMethodId, completedPaymentByPaymentId, paymentExternalKey, authTransactionExternalKey, transactionType.toString(), TransactionStatus.SUCCESS.name(), amount, amount, BigDecimal.ZERO, BigDecimal.ZERO, 1, 1);
}
@@ -418,7 +446,7 @@ public class TestPayment extends TestJaxrsBase {
completeTransactionByPaymentIdAndInvalidTransactionExternalKey.setPaymentId(initialPayment.getPaymentId());
completeTransactionByPaymentIdAndInvalidTransactionExternalKey.setTransactionExternalKey("bozo");
try {
- killBillClient.completePayment(completeTransactionByPaymentIdAndInvalidTransactionExternalKey, pluginProperties, basicRequestOptions());
+ killBillClient.completePayment(completeTransactionByPaymentIdAndInvalidTransactionExternalKey, pluginProperties, requestOptions);
fail("Payment completion should fail when invalid transaction externalKey has been provided" );
} catch (final KillBillClientException expected) {
}
@@ -426,7 +454,7 @@ public class TestPayment extends TestJaxrsBase {
final PaymentTransaction completeTransactionByPaymentIdAndTransactionExternalKey = new PaymentTransaction();
completeTransactionByPaymentIdAndTransactionExternalKey.setPaymentId(initialPayment.getPaymentId());
completeTransactionByPaymentIdAndTransactionExternalKey.setTransactionExternalKey(authTransactionExternalKey);
- final Payment completedPaymentByPaymentId = killBillClient.completePayment(completeTransactionByPaymentIdAndTransactionExternalKey, pluginProperties, basicRequestOptions());
+ final Payment completedPaymentByPaymentId = killBillClient.completePayment(completeTransactionByPaymentIdAndTransactionExternalKey, pluginProperties, requestOptions);
verifyPayment(account, paymentMethodId, completedPaymentByPaymentId, paymentExternalKey, authTransactionExternalKey, transactionType.toString(), TransactionStatus.SUCCESS.name(), amount, amount, BigDecimal.ZERO, BigDecimal.ZERO, 1, 1);
}
@@ -453,7 +481,7 @@ public class TestPayment extends TestJaxrsBase {
completeTransactionByPaymentIdAndInvalidTransactionType.setPaymentId(initialPayment.getPaymentId());
completeTransactionByPaymentIdAndInvalidTransactionType.setTransactionType(TransactionType.CAPTURE.name());
try {
- killBillClient.completePayment(completeTransactionByPaymentIdAndInvalidTransactionType, pluginProperties, basicRequestOptions());
+ killBillClient.completePayment(completeTransactionByPaymentIdAndInvalidTransactionType, pluginProperties, requestOptions);
fail("Payment completion should fail when invalid transaction type has been provided" );
} catch (final KillBillClientException expected) {
}
@@ -461,7 +489,7 @@ public class TestPayment extends TestJaxrsBase {
final PaymentTransaction completeTransactionByPaymentIdAndTransactionType = new PaymentTransaction();
completeTransactionByPaymentIdAndTransactionType.setPaymentId(initialPayment.getPaymentId());
completeTransactionByPaymentIdAndTransactionType.setTransactionType(transactionType.name());
- final Payment completedPaymentByPaymentId = killBillClient.completePayment(completeTransactionByPaymentIdAndTransactionType, pluginProperties, basicRequestOptions());
+ final Payment completedPaymentByPaymentId = killBillClient.completePayment(completeTransactionByPaymentIdAndTransactionType, pluginProperties, requestOptions);
verifyPayment(account, paymentMethodId, completedPaymentByPaymentId, paymentExternalKey, authTransactionExternalKey, transactionType.toString(), TransactionStatus.SUCCESS.name(), amount, amount, BigDecimal.ZERO, BigDecimal.ZERO, 1, 1);
}
@@ -486,7 +514,7 @@ public class TestPayment extends TestJaxrsBase {
final PaymentTransaction completeTransactionWithTypeAndKey = new PaymentTransaction();
completeTransactionWithTypeAndKey.setPaymentId(initialPayment.getPaymentId());
completeTransactionWithTypeAndKey.setTransactionExternalKey(authTransactionExternalKey);
- final Payment completedPaymentByPaymentId = killBillClient.completePayment(completeTransactionWithTypeAndKey, pluginProperties, basicRequestOptions());
+ final Payment completedPaymentByPaymentId = killBillClient.completePayment(completeTransactionWithTypeAndKey, pluginProperties, requestOptions);
verifyPayment(account, paymentMethodId, completedPaymentByPaymentId, paymentExternalKey, authTransactionExternalKey, transactionType.toString(), TransactionStatus.SUCCESS.name(), amount, amount, BigDecimal.ZERO, BigDecimal.ZERO, 1, 1);
}
@@ -508,7 +536,7 @@ public class TestPayment extends TestJaxrsBase {
// The payment was already completed, it should succeed (no-op)
final PaymentTransaction completeTransactionByPaymentId = new PaymentTransaction();
completeTransactionByPaymentId.setPaymentId(initialPayment.getPaymentId());
- killBillClient.completePayment(completeTransactionByPaymentId, pluginProperties, basicRequestOptions());
+ killBillClient.completePayment(completeTransactionByPaymentId, pluginProperties, requestOptions);
}
@@ -534,21 +562,21 @@ public class TestPayment extends TestJaxrsBase {
refundTransaction.setTransactionExternalKey(refundTransactionExternalKey);
refundTransaction.setAmount(purchaseAmount);
refundTransaction.setCurrency(authPayment.getCurrency());
- final Payment refundPayment = killBillClient.refundPayment(refundTransaction, null, pluginProperties, basicRequestOptions());
+ final Payment refundPayment = killBillClient.refundPayment(refundTransaction, null, pluginProperties, requestOptions);
verifyPaymentWithPendingRefund(account, paymentMethodId, paymentExternalKey, purchaseTransactionExternalKey, purchaseAmount, refundTransactionExternalKey, refundPayment);
final PaymentTransaction completeTransactionWithTypeAndKey = new PaymentTransaction();
completeTransactionWithTypeAndKey.setPaymentId(refundPayment.getPaymentId());
completeTransactionWithTypeAndKey.setTransactionExternalKey(refundTransactionExternalKey);
- final Payment completedPaymentByTypeAndKey = killBillClient.completePayment(completeTransactionWithTypeAndKey, pluginProperties, basicRequestOptions());
+ final Payment completedPaymentByTypeAndKey = killBillClient.completePayment(completeTransactionWithTypeAndKey, pluginProperties, requestOptions);
verifyPaymentWithPendingRefund(account, paymentMethodId, paymentExternalKey, purchaseTransactionExternalKey, purchaseAmount, refundTransactionExternalKey, completedPaymentByTypeAndKey);
// Also, it should work if we specify the payment id and transaction id
final PaymentTransaction completeTransactionWithTypeAndId = new PaymentTransaction();
completeTransactionWithTypeAndId.setPaymentId(refundPayment.getPaymentId());
completeTransactionWithTypeAndId.setTransactionId(refundPayment.getTransactions().get(1).getTransactionId());
- final Payment completedPaymentByTypeAndId = killBillClient.completePayment(completeTransactionWithTypeAndId, pluginProperties, basicRequestOptions());
+ final Payment completedPaymentByTypeAndId = killBillClient.completePayment(completeTransactionWithTypeAndId, pluginProperties, requestOptions);
verifyPaymentWithPendingRefund(account, paymentMethodId, paymentExternalKey, purchaseTransactionExternalKey, purchaseAmount, refundTransactionExternalKey, completedPaymentByTypeAndId);
}
@@ -560,12 +588,12 @@ public class TestPayment extends TestJaxrsBase {
final ComboPaymentTransaction comboPaymentTransaction = createComboPaymentTransaction(accountJson, paymentExternalKey);
- final Payment payment = killBillClient.createPayment(comboPaymentTransaction, ImmutableMap.<String, String>of(), basicRequestOptions());
+ final Payment payment = killBillClient.createPayment(comboPaymentTransaction, ImmutableMap.<String, String>of(), requestOptions);
verifyComboPayment(payment, paymentExternalKey, BigDecimal.TEN, BigDecimal.ZERO, BigDecimal.ZERO, 1, 1);
// Void payment using externalKey
final String voidTransactionExternalKey = UUID.randomUUID().toString();
- final Payment voidPayment = killBillClient.voidPayment(null, paymentExternalKey, voidTransactionExternalKey, null, ImmutableMap.<String, String>of(), basicRequestOptions());
+ final Payment voidPayment = killBillClient.voidPayment(null, paymentExternalKey, voidTransactionExternalKey, null, ImmutableMap.<String, String>of(), requestOptions);
verifyPaymentTransaction(accountJson, voidPayment.getPaymentId(), paymentExternalKey, voidPayment.getTransactions().get(1),
voidTransactionExternalKey, null, "VOID", "SUCCESS");
}
@@ -579,7 +607,7 @@ public class TestPayment extends TestJaxrsBase {
mockPaymentControlProviderPlugin.setAborted(true);
try {
- killBillClient.createPayment(comboPaymentTransaction, Arrays.asList(MockPaymentControlProviderPlugin.PLUGIN_NAME), ImmutableMap.<String, String>of(), basicRequestOptions());
+ killBillClient.createPayment(comboPaymentTransaction, Arrays.asList(MockPaymentControlProviderPlugin.PLUGIN_NAME), ImmutableMap.<String, String>of(), requestOptions);
fail();
} catch (KillBillClientException e) {
assertEquals(e.getResponse().getStatusCode(), 422);
@@ -597,7 +625,7 @@ public class TestPayment extends TestJaxrsBase {
mockPaymentControlProviderPlugin.throwsException(new IllegalStateException());
try {
- killBillClient.createPayment(comboPaymentTransaction, Arrays.asList(MockPaymentControlProviderPlugin.PLUGIN_NAME), ImmutableMap.<String, String>of(), basicRequestOptions());
+ killBillClient.createPayment(comboPaymentTransaction, Arrays.asList(MockPaymentControlProviderPlugin.PLUGIN_NAME), ImmutableMap.<String, String>of(), requestOptions);
fail();
} catch (KillBillClientException e) {
assertEquals(e.getResponse().getStatusCode(), 500);
@@ -635,7 +663,7 @@ public class TestPayment extends TestJaxrsBase {
final ComboPaymentTransaction comboPaymentTransaction = new ComboPaymentTransaction(accountJson, paymentMethodJson, null, ImmutableList.<PluginProperty>of(), ImmutableList.<PluginProperty>of());
- final Payment payment = killBillClient.createPayment(comboPaymentTransaction, ImmutableMap.<String, String>of(), basicRequestOptions());
+ final Payment payment = killBillClient.createPayment(comboPaymentTransaction, ImmutableMap.<String, String>of(), requestOptions);
// Client returns null in case of a 404
Assert.assertNull(payment);
}
@@ -700,7 +728,7 @@ public class TestPayment extends TestJaxrsBase {
captureTransaction.setPaymentExternalKey(paymentExternalKey);
captureTransaction.setTransactionExternalKey(capture1TransactionExternalKey);
// captureAuthorization is using paymentId
- final Payment capturedPayment1 = killBillClient.captureAuthorization(captureTransaction, basicRequestOptions());
+ final Payment capturedPayment1 = killBillClient.captureAuthorization(captureTransaction, requestOptions);
verifyPayment(account, paymentMethodId, capturedPayment1, paymentExternalKey, authTransactionExternalKey, "AUTHORIZE", "SUCCESS",
BigDecimal.TEN, BigDecimal.TEN, BigDecimal.ONE, BigDecimal.ZERO, 2, paymentNb);
verifyPaymentTransaction(account, authPayment.getPaymentId(), paymentExternalKey, capturedPayment1.getTransactions().get(1),
@@ -711,7 +739,7 @@ public class TestPayment extends TestJaxrsBase {
captureTransaction.setTransactionExternalKey(capture2TransactionExternalKey);
// captureAuthorization is using externalKey
captureTransaction.setPaymentId(null);
- final Payment capturedPayment2 = killBillClient.captureAuthorization(captureTransaction, basicRequestOptions());
+ final Payment capturedPayment2 = killBillClient.captureAuthorization(captureTransaction, requestOptions);
verifyPayment(account, paymentMethodId, capturedPayment2, paymentExternalKey, authTransactionExternalKey, "AUTHORIZE", "SUCCESS",
BigDecimal.TEN, BigDecimal.TEN, new BigDecimal("2"), BigDecimal.ZERO, 3, paymentNb);
verifyPaymentTransaction(account, authPayment.getPaymentId(), paymentExternalKey, capturedPayment2.getTransactions().get(2),
@@ -725,7 +753,7 @@ public class TestPayment extends TestJaxrsBase {
refundTransaction.setCurrency(account.getCurrency());
refundTransaction.setPaymentExternalKey(paymentExternalKey);
refundTransaction.setTransactionExternalKey(refundTransactionExternalKey);
- final Payment refundPayment = killBillClient.refundPayment(refundTransaction, basicRequestOptions());
+ final Payment refundPayment = killBillClient.refundPayment(refundTransaction, requestOptions);
verifyPayment(account, paymentMethodId, refundPayment, paymentExternalKey, authTransactionExternalKey, "AUTHORIZE", "SUCCESS",
BigDecimal.TEN, BigDecimal.TEN, new BigDecimal("2"), new BigDecimal("2"), 4, paymentNb);
verifyPaymentTransaction(account, authPayment.getPaymentId(), paymentExternalKey, refundPayment.getTransactions().get(3),
@@ -750,7 +778,7 @@ public class TestPayment extends TestJaxrsBase {
authTransaction.setPaymentExternalKey(paymentExternalKey);
authTransaction.setTransactionExternalKey(transactionExternalKey);
authTransaction.setTransactionType(transactionType.toString());
- final Payment payment = killBillClient.createPayment(account.getAccountId(), paymentMethodId, authTransaction, pluginProperties, basicRequestOptions());
+ final Payment payment = killBillClient.createPayment(account.getAccountId(), paymentMethodId, authTransaction, pluginProperties, requestOptions);
verifyPayment(account, paymentMethodId, payment, paymentExternalKey, transactionExternalKey, transactionType.toString(), transactionStatus, transactionAmount, authAmount, BigDecimal.ZERO, BigDecimal.ZERO, 1, paymentNb);
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPaymentGateway.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPaymentGateway.java
index 6ae89fb..5c87db5 100644
--- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPaymentGateway.java
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPaymentGateway.java
@@ -42,7 +42,7 @@ public class TestPaymentGateway extends TestJaxrsBase {
final HostedPaymentPageFields hppFields = new HostedPaymentPageFields();
- final HostedPaymentPageFormDescriptor hostedPaymentPageFormDescriptor = killBillClient.buildFormDescriptor(hppFields, account.getAccountId(), null, ImmutableMap.<String, String>of(), basicRequestOptions());
+ final HostedPaymentPageFormDescriptor hostedPaymentPageFormDescriptor = killBillClient.buildFormDescriptor(hppFields, account.getAccountId(), null, ImmutableMap.<String, String>of(), requestOptions);
Assert.assertEquals(hostedPaymentPageFormDescriptor.getKbAccountId(), account.getAccountId());
}
@@ -58,7 +58,7 @@ public class TestPaymentGateway extends TestJaxrsBase {
final ComboHostedPaymentPage comboHostedPaymentPage = new ComboHostedPaymentPage(account, paymentMethod, ImmutableList.<PluginProperty>of(), hppFields);
- final HostedPaymentPageFormDescriptor hostedPaymentPageFormDescriptor = killBillClient.buildFormDescriptor(comboHostedPaymentPage, ImmutableMap.<String, String>of(), basicRequestOptions());
+ final HostedPaymentPageFormDescriptor hostedPaymentPageFormDescriptor = killBillClient.buildFormDescriptor(comboHostedPaymentPage, ImmutableMap.<String, String>of(), requestOptions);
Assert.assertNotNull(hostedPaymentPageFormDescriptor.getKbAccountId());
}
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPaymentPluginProperties.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPaymentPluginProperties.java
index e24c9a9..f31858f 100644
--- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPaymentPluginProperties.java
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPaymentPluginProperties.java
@@ -222,7 +222,7 @@ public class TestPaymentPluginProperties extends TestJaxrsBase {
completeTransactionByPaymentId.setPaymentId(initialPayment.getPaymentId());
completeTransactionByPaymentId.setProperties(bodyProperties);
- final RequestOptions basicRequestOptions = basicRequestOptions();
+ final RequestOptions basicRequestOptions = requestOptions;
final Multimap<String, String> params = LinkedListMultimap.create(basicRequestOptions.getQueryParams());
params.putAll(KillBillHttpClient.CONTROL_PLUGIN_NAME, ImmutableList.<String>of(PluginPropertiesVerificator.PLUGIN_NAME));
@@ -245,7 +245,7 @@ public class TestPaymentPluginProperties extends TestJaxrsBase {
authTransaction.setPaymentExternalKey(paymentExternalKey);
authTransaction.setTransactionExternalKey(transactionExternalKey);
authTransaction.setTransactionType(transactionType.toString());
- final Payment payment = killBillClient.createPayment(account.getAccountId(), paymentMethodId, authTransaction, pluginProperties, basicRequestOptions());
+ final Payment payment = killBillClient.createPayment(account.getAccountId(), paymentMethodId, authTransaction, pluginProperties, requestOptions);
return payment;
}
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 a180f70..ea4ab18 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
@@ -77,7 +77,7 @@ public class TestPerTenantConfig extends TestJaxrsBase {
perTenantProperties.put("org.killbill.payment.retry.days", "1,1,1");
final String perTenantConfig = mapper.writeValueAsString(perTenantProperties);
- final TenantKey tenantKey = killBillClient.postConfigurationPropertiesForTenant(perTenantConfig, basicRequestOptions());
+ final TenantKey tenantKey = killBillClient.postConfigurationPropertiesForTenant(perTenantConfig, requestOptions);
final Account accountJson = createAccountWithPMBundleAndSubscriptionAndWaitForFirstInvoice();
@@ -92,7 +92,7 @@ public class TestPerTenantConfig extends TestJaxrsBase {
//
// Now unregister special per tenant config and we the first retry occurs one day after (and still fails), it now sets a retry date of 8 days
//
- killBillClient.unregisterConfigurationForTenant(basicRequestOptions());
+ killBillClient.unregisterConfigurationForTenant(requestOptions);
// org.killbill.tenant.broadcast.rate has been set to 1s
crappyWaitForLackOfProperSynchonization(2000);
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestTag.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestTag.java
index 2fb7072..44a3f3d 100644
--- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestTag.java
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestTag.java
@@ -35,6 +35,7 @@ import org.killbill.billing.client.model.TagDefinition;
import org.killbill.billing.client.model.Tags;
import org.killbill.billing.util.api.AuditLevel;
import org.killbill.billing.util.tag.ControlTagType;
+import org.killbill.billing.util.tag.dao.SystemTags;
import org.testng.Assert;
import org.testng.annotations.Test;
@@ -54,18 +55,20 @@ public class TestTag extends TestJaxrsBase {
for (final TagDefinition tagDefinition : tagDefinitions) {
try {
- killBillClient.createTagDefinition(tagDefinition, createdBy, reason, comment);
+ killBillClient.createTagDefinition(tagDefinition, requestOptions);
fail();
} catch (final KillBillClientException e) {
}
}
}
+
+
@Test(groups = "slow", description = "Can create a TagDefinition")
public void testTagDefinitionOk() throws Exception {
final TagDefinition input = new TagDefinition(null, false, "blue", "relaxing color", ImmutableList.<ObjectType>of());
- final TagDefinition objFromJson = killBillClient.createTagDefinition(input, createdBy, reason, comment);
+ final TagDefinition objFromJson = killBillClient.createTagDefinition(input, requestOptions);
assertNotNull(objFromJson);
assertEquals(objFromJson.getName(), input.getName());
assertEquals(objFromJson.getDescription(), input.getDescription());
@@ -73,28 +76,32 @@ public class TestTag extends TestJaxrsBase {
@Test(groups = "slow", description = "Can create and delete TagDefinitions")
public void testMultipleTagDefinitionOk() throws Exception {
- List<TagDefinition> objFromJson = killBillClient.getTagDefinitions();
+ List<TagDefinition> objFromJson = killBillClient.getTagDefinitions(requestOptions);
final int sizeSystemTag = objFromJson.isEmpty() ? 0 : objFromJson.size();
+ for (final TagDefinition cur : objFromJson) {
+ Assert.assertFalse(SystemTags.isSystemTag(cur.getId()));
+ }
+
final TagDefinition inputBlue = new TagDefinition(null, false, "blue", "relaxing color", ImmutableList.<ObjectType>of());
- killBillClient.createTagDefinition(inputBlue, createdBy, reason, comment);
+ killBillClient.createTagDefinition(inputBlue, requestOptions);
final TagDefinition inputRed = new TagDefinition(null, false, "red", "hot color", ImmutableList.<ObjectType>of());
- killBillClient.createTagDefinition(inputRed, createdBy, reason, comment);
+ killBillClient.createTagDefinition(inputRed, requestOptions);
final TagDefinition inputYellow = new TagDefinition(null, false, "yellow", "vibrant color", ImmutableList.<ObjectType>of());
- killBillClient.createTagDefinition(inputYellow, createdBy, reason, comment);
+ killBillClient.createTagDefinition(inputYellow, requestOptions);
final TagDefinition inputGreen = new TagDefinition(null, false, "green", "super relaxing color", ImmutableList.<ObjectType>of());
- killBillClient.createTagDefinition(inputGreen, createdBy, reason, comment);
+ killBillClient.createTagDefinition(inputGreen, requestOptions);
- objFromJson = killBillClient.getTagDefinitions();
+ objFromJson = killBillClient.getTagDefinitions(requestOptions);
assertNotNull(objFromJson);
assertEquals(objFromJson.size(), 4 + sizeSystemTag);
- killBillClient.deleteTagDefinition(objFromJson.get(0).getId(), createdBy, reason, comment);
+ killBillClient.deleteTagDefinition(objFromJson.get(0).getId(), requestOptions);
- objFromJson = killBillClient.getTagDefinitions();
+ objFromJson = killBillClient.getTagDefinitions(requestOptions);
assertNotNull(objFromJson);
assertEquals(objFromJson.size(), 3 + sizeSystemTag);
}
@@ -112,22 +119,22 @@ public class TestTag extends TestJaxrsBase {
ProductCategory.BASE, BillingPeriod.MONTHLY, true);
for (final ControlTagType controlTagType : ControlTagType.values()) {
- killBillClient.createAccountTag(account.getAccountId(), controlTagType.getId(), createdBy, reason, comment);
+ killBillClient.createAccountTag(account.getAccountId(), controlTagType.getId(), requestOptions);
}
final TagDefinition bundleTagDefInput = new TagDefinition(null, false, "bundletagdef", "nothing special", ImmutableList.<ObjectType>of());
- final TagDefinition bundleTagDef = killBillClient.createTagDefinition(bundleTagDefInput, createdBy, reason, comment);
+ final TagDefinition bundleTagDef = killBillClient.createTagDefinition(bundleTagDefInput, requestOptions);
- killBillClient.createBundleTag(subscriptionJson.getBundleId(), bundleTagDef.getId(), createdBy, reason, comment);
+ killBillClient.createBundleTag(subscriptionJson.getBundleId(), bundleTagDef.getId(), requestOptions);
- final Tags allBundleTags = killBillClient.getBundleTags(subscriptionJson.getBundleId(), AuditLevel.FULL);
+ final Tags allBundleTags = killBillClient.getBundleTags(subscriptionJson.getBundleId(), AuditLevel.FULL, requestOptions);
Assert.assertEquals(allBundleTags.size(), 1);
- final Tags allAccountTags = killBillClient.getAllAccountTags(account.getAccountId(), null, AuditLevel.FULL);
+ final Tags allAccountTags = killBillClient.getAllAccountTags(account.getAccountId(), null, AuditLevel.FULL, requestOptions);
Assert.assertEquals(allAccountTags.size(), ControlTagType.values().length + 1);
- final Tags allBundleTagsForAccount = killBillClient.getAllAccountTags(account.getAccountId(), ObjectType.BUNDLE.name(), AuditLevel.FULL);
+ final Tags allBundleTagsForAccount = killBillClient.getAllAccountTags(account.getAccountId(), ObjectType.BUNDLE.name(), AuditLevel.FULL, requestOptions);
Assert.assertEquals(allBundleTagsForAccount.size(), 1);
}
@@ -136,15 +143,28 @@ public class TestTag extends TestJaxrsBase {
public void testSystemTagsPagination() throws Exception {
final Account account = createAccount();
for (final ControlTagType controlTagType : ControlTagType.values()) {
- killBillClient.createAccountTag(account.getAccountId(), controlTagType.getId(), createdBy, reason, comment);
+ killBillClient.createAccountTag(account.getAccountId(), controlTagType.getId(), requestOptions);
}
- final Tags allTags = killBillClient.getTags();
+ final Tags allTags = killBillClient.getTags(requestOptions);
Assert.assertEquals(allTags.size(), ControlTagType.values().length);
for (final ControlTagType controlTagType : ControlTagType.values()) {
- Assert.assertEquals(killBillClient.searchTags(controlTagType.toString()).size(), 1);
- Assert.assertEquals(killBillClient.searchTags(controlTagType.getDescription()).size(), 1);
+ Assert.assertEquals(killBillClient.searchTags(controlTagType.toString(), requestOptions).size(), 1);
+ Assert.assertEquals(killBillClient.searchTags(controlTagType.getDescription(), requestOptions).size(), 1);
+ }
+ }
+
+ @Test(groups = "slow", description = "Can create a TagDefinition")
+ public void testNotAllowedSystemTag() throws Exception {
+
+ final Account account = createAccount();
+
+ try {
+ killBillClient.createAccountTag(account.getAccountId(), SystemTags.PARK_TAG_DEFINITION_ID, requestOptions);
+ Assert.fail("Creating a tag associated with a system tag should fail");
+ } catch (final Exception e) {
+ Assert.assertTrue(true);
}
}
@@ -153,14 +173,14 @@ public class TestTag extends TestJaxrsBase {
final Account account = createAccount();
for (int i = 0; i < 5; i++) {
final TagDefinition tagDefinition = new TagDefinition(null, false, UUID.randomUUID().toString().substring(0, 5), UUID.randomUUID().toString(), ImmutableList.<ObjectType>of(ObjectType.ACCOUNT));
- final UUID tagDefinitionId = killBillClient.createTagDefinition(tagDefinition, createdBy, reason, comment).getId();
- killBillClient.createAccountTag(account.getAccountId(), tagDefinitionId, createdBy, reason, comment);
+ final UUID tagDefinitionId = killBillClient.createTagDefinition(tagDefinition, requestOptions).getId();
+ killBillClient.createAccountTag(account.getAccountId(), tagDefinitionId, requestOptions);
}
- final Tags allTags = killBillClient.getTags();
+ final Tags allTags = killBillClient.getTags(requestOptions);
Assert.assertEquals(allTags.size(), 5);
- Tags page = killBillClient.getTags(0L, 1L);
+ Tags page = killBillClient.getTags(0L, 1L, requestOptions);
for (int i = 0; i < 5; i++) {
Assert.assertNotNull(page);
Assert.assertEquals(page.size(), 1);
@@ -174,11 +194,11 @@ public class TestTag extends TestJaxrsBase {
doSearchTag(tag.getTagId().toString(), tag);
doSearchTag(tag.getTagDefinitionName(), tag);
- final TagDefinition tagDefinition = killBillClient.getTagDefinition(tag.getTagDefinitionId());
+ final TagDefinition tagDefinition = killBillClient.getTagDefinition(tag.getTagDefinitionId(), requestOptions);
doSearchTag(tagDefinition.getDescription(), tag);
}
- final Tags tags = killBillClient.searchTags(ObjectType.ACCOUNT.toString());
+ final Tags tags = killBillClient.searchTags(ObjectType.ACCOUNT.toString(), requestOptions);
Assert.assertEquals(tags.size(), 5);
Assert.assertEquals(tags.getPaginationCurrentOffset(), 0);
Assert.assertEquals(tags.getPaginationTotalNbRecords(), 5);
@@ -186,7 +206,7 @@ public class TestTag extends TestJaxrsBase {
}
private void doSearchTag(final String searchKey, @Nullable final Tag expectedTag) throws KillBillClientException {
- final Tags tags = killBillClient.searchTags(searchKey);
+ final Tags tags = killBillClient.searchTags(searchKey, requestOptions);
if (expectedTag == null) {
Assert.assertTrue(tags.isEmpty());
Assert.assertEquals(tags.getPaginationCurrentOffset(), 0);
diff --git a/profiles/killbill/src/test/resources/logback.travis.xml b/profiles/killbill/src/test/resources/logback.travis.xml
index 3202974..c838018 100644
--- a/profiles/killbill/src/test/resources/logback.travis.xml
+++ b/profiles/killbill/src/test/resources/logback.travis.xml
@@ -29,6 +29,12 @@
<logger name="jdbc.resultsettable" level="OFF"/>
<logger name="jdbc.connection" level="OFF"/>
+ <logger name="org.apache.shiro.realm.text.IniRealm" level="ERROR"/>
+ <logger name="org.slf4j.Logger" level="ERROR"/>
+
+ <logger name="org.killbill.billing.osgi" level="ERROR"/>
+ <logger name="org.killbill.bus" level="ERROR"/>
+
<root level="WARN">
<appender-ref ref="STDOUT"/>
</root>
diff --git a/profiles/killbill/src/test/resources/SpyCarBasicInvalidName.xml b/profiles/killbill/src/test/resources/SpyCarBasicInvalidName.xml
new file mode 100644
index 0000000..bb6f47f
--- /dev/null
+++ b/profiles/killbill/src/test/resources/SpyCarBasicInvalidName.xml
@@ -0,0 +1,188 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!--
+ ~ Copyright 2010-2013 Ning, Inc.
+ ~
+ ~ Ning licenses this file to you under the Apache License, version 2.0
+ ~ (the "License"); you may not use this file except in compliance with the
+ ~ License. You may obtain a copy of the License at:
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ ~ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ ~ License for the specific language governing permissions and limitations
+ ~ under the License.
+ -->
+
+<catalog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:noNamespaceSchemaLocation="CatalogSchema.xsd ">
+
+ <effectiveDate>2015-02-08T00:00:00+00:00</effectiveDate>
+ <catalogName>SpyCarBasicInvalidName</catalogName>
+
+ <recurringBillingMode>IN_ADVANCE</recurringBillingMode>
+
+ <currencies>
+ <currency>USD</currency>
+ <currency>GBP</currency>
+ </currencies>
+
+ <products>
+ <product name="Standard">
+ <category>BASE</category>
+ </product>
+ <product name="Sports">
+ <category>BASE</category>
+ </product>
+ <product name="Super">
+ <category>BASE</category>
+ </product>
+ </products>
+
+ <rules>
+ <changePolicy>
+ <changePolicyCase>
+ <policy>IMMEDIATE</policy>
+ </changePolicyCase>
+ </changePolicy>
+ <changeAlignment>
+ <changeAlignmentCase>
+ <alignment>START_OF_BUNDLE</alignment>
+ </changeAlignmentCase>
+ </changeAlignment>
+ <cancelPolicy>
+ <cancelPolicyCase>
+ <policy>IMMEDIATE</policy>
+ </cancelPolicyCase>
+ </cancelPolicy>
+ <createAlignment>
+ <createAlignmentCase>
+ <alignment>START_OF_BUNDLE</alignment>
+ </createAlignmentCase>
+ </createAlignment>
+ <billingAlignment>
+ <billingAlignmentCase>
+ <alignment>ACCOUNT</alignment>
+ </billingAlignmentCase>
+ </billingAlignment>
+ <priceList>
+ <priceListCase>
+ <toPriceList>DEFAULT</toPriceList>
+ </priceListCase>
+ </priceList>
+ </rules>
+
+ <plans>
+ <plan name="standard-monthly">
+ <product>Standard</product>
+ <initialPhases>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <fixed>
+ <fixedPrice> <!-- empty price implies $0 -->
+ </fixedPrice>
+
+ </fixed>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <recurring>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price>
+ <currency>GBP</currency>
+ <value>75.00</value>
+ </price>
+ <price>
+ <currency>USD</currency>
+ <value>100.00</value>
+ </price>
+ </recurringPrice>
+ </recurring>
+ </finalPhase>
+ </plan>
+ <plan name="sports-monthly">
+ <product>Sports</product>
+ <initialPhases>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <fixed>
+ <fixedPrice> <!-- empty price implies $0 -->
+ </fixedPrice>
+ </fixed>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <recurring>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price>
+ <currency>GBP</currency>
+ <value>375.00</value>
+ </price>
+ <price>
+ <currency>USD</currency>
+ <value>500.00</value>
+ </price>
+ </recurringPrice>
+ </recurring>
+ </finalPhase>
+ </plan>
+ <plan name="super-monthly">
+ <product>Super</product>
+ <initialPhases>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <fixed>
+ <fixedPrice> <!-- empty price implies $0 -->
+ </fixedPrice>
+
+ </fixed>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <recurring>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price>
+ <currency>GBP</currency>
+ <value>750.00</value>
+ </price>
+ <price>
+ <currency>USD</currency>
+ <value>1000.00</value>
+ </price>
+ </recurringPrice>
+ </recurring>
+ </finalPhase>
+ </plan>
+ </plans>
+ <priceLists>
+ <defaultPriceList name="DEFAULT">
+ <plans>
+ <plan>standard-monthly</plan>
+ <plan>sports-monthly</plan>
+ <plan>super-monthly</plan>
+ </plans>
+ </defaultPriceList>
+ </priceLists>
+</catalog>
diff --git a/profiles/killbill/src/test/resources/UsageExperimental.xml b/profiles/killbill/src/test/resources/UsageExperimental.xml
new file mode 100644
index 0000000..4d2e7e9
--- /dev/null
+++ b/profiles/killbill/src/test/resources/UsageExperimental.xml
@@ -0,0 +1,289 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!--
+ ~ Copyright 2014 The Billing Project, Inc.
+ ~
+ ~ Ning licenses this file to you under the Apache License, version 2.0
+ ~ (the "License"); you may not use this file except in compliance with the
+ ~ License. You may obtain a copy of the License at:
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ ~ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ ~ License for the specific language governing permissions and limitations
+ ~ under the License.
+ -->
+
+<catalog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:noNamespaceSchemaLocation="CatalogSchema.xsd ">
+
+ <effectiveDate>2013-02-08T00:00:00+00:00</effectiveDate>
+ <catalogName>Usage</catalogName>
+
+ <!-- TBD
+ Defines the billingMode for all recurring subscription in that catalog:
+ Goal is to avoid to end up in a situation where a user could switch plan and suddenly his
+ recurring billing goes from IN_ADVANCE to IN_ARREAR or the reverse.
+ -->
+ <recurringBillingMode>IN_ADVANCE</recurringBillingMode>
+
+ <currencies>
+ <currency>BTC</currency>
+ </currencies>
+
+ <units>
+ <unit name="members"/>
+ <unit name="cell-phone-minutes"/>
+ <unit name="fastrack-tokens"/>
+ <unit name="bandwith-meg-sec"/>
+ <unit name="Mbytes"/>
+ </units>
+
+ <products>
+ <product name="Dummy">
+ <category>BASE</category>
+ </product>
+ <product name="CapacityInAdvance">
+ <category>BASE</category>
+ </product>
+ <product name="ConsumableInAdvancePrepayCredit">
+ <category>BASE</category>
+ </product>
+ <product name="ConsumableInAdvanceTopUp">
+ <category>BASE</category>
+ </product>
+ <product name="CapacityInArrear">
+ <category>BASE</category>
+ </product>
+ <product name="ConsumableInArrear">
+ <category>BASE</category>
+ </product>
+ </products>
+
+ <rules>
+ <changePolicy>
+ <changePolicyCase>
+ <policy>IMMEDIATE</policy>
+ </changePolicyCase>
+ </changePolicy>
+ <cancelPolicy>
+ <cancelPolicyCase>
+ <policy>IMMEDIATE</policy>
+ </cancelPolicyCase>
+ </cancelPolicy>
+ </rules>
+
+ <plans>
+ <plan name="capacity-in-advance-monthly">
+ <product>CapacityInAdvance</product>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+
+ <usages>
+ <usage name="capacity-in-advance-monthly-usage1" billingMode="IN_ADVANCE" usageType="CAPACITY">
+ <billingPeriod>MONTHLY</billingPeriod>
+ <limits>
+ <limit>
+ <unit>members</unit>
+ <max>100</max>
+ </limit>
+ </limits>
+ <!-- could accept a fixed price and/or a recurring price -->
+ <recurringPrice>
+ <price>
+ <currency>BTC</currency>
+ <value>100.00</value>
+ </price>
+ </recurringPrice>
+ </usage>
+ </usages>
+ </finalPhase>
+ </plan>
+
+
+ <plan name="consumable-in-advance-prepay-credit-monthly">
+ <product>ConsumableInAdvancePrepayCredit</product>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <usages>
+ <usage name="consumable-in-advance-prepay-credit-monthly-usage1" billingMode="IN_ADVANCE" usageType="CONSUMABLE">
+ <billingPeriod>MONTHLY</billingPeriod>
+ <blocks>
+ <block>
+ <unit>cell-phone-minutes</unit>
+ <size>1000</size>
+ <!-- could be either fixed (with NO_BILLING_PERIOD) )or recurring:
+ * In billing period is NO_BILLING_PERIOD, we buy one block of units
+ * In billing period ha s been specified, we buy one block of units for each period
+ -->
+ <prices>
+ <price>
+ <currency>BTC</currency>
+ <value>0.10</value>
+ </price>
+ </prices>
+ </block>
+ </blocks>
+ <!-- We could instead define the price here as we did for capacity-in-advance if we want to 'bundle' linit/units -->
+ </usage>
+ </usages>
+ </finalPhase>
+ </plan>
+
+
+ <plan name="consumable-in-advance-topup">
+ <product>ConsumableInAdvanceTopUp</product>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+
+ <usages>
+ <usage name="consumable-in-advance-topup-usage1" billingMode="IN_ADVANCE" usageType="CONSUMABLE">
+ <billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+ <blocks>
+ <block type="TOP_UP">
+ <unit>fastrack-tokens</unit>
+ <size>10</size>
+ <prices>
+ <price>
+ <currency>BTC</currency>
+ <value>0.10</value>
+ </price>
+ </prices>
+ <minTopUpCredit>5</minTopUpCredit>
+ </block>
+ </blocks>
+ </usage>
+ </usages>
+ </finalPhase>
+ </plan>
+
+
+ <plan name="capacity-in-arrear">
+ <product>CapacityInArrear</product>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+
+ <usages>
+ <usage name="capacity-in-arrear-usage1" billingMode="IN_ARREAR" usageType="CAPACITY">
+ <billingPeriod>MONTHLY</billingPeriod>
+ <tiers>
+ <tier>
+ <limits>
+ <limit>
+ <unit>bandwith-meg-sec</unit>
+ <max>100</max>
+ </limit>
+ <limit>
+ <unit>members</unit>
+ <max>500</max>
+ </limit>
+ </limits>
+ <fixedPrice>
+ <price>
+ <currency>BTC</currency>
+ <value>0.007</value>
+ </price>
+ </fixedPrice>
+ <recurringPrice>
+ <price>
+ <currency>BTC</currency>
+ <value>0.8</value>
+ </price>
+ </recurringPrice>
+ </tier>
+ <tier>
+ <limits>
+ <limit>
+ <unit>bandwith-meg-sec</unit>
+ <max>100</max>
+ </limit>
+ <limit>
+ <unit>members</unit>
+ <max>1000</max>
+ </limit>
+ </limits>
+ <fixedPrice>
+ <price>
+ <currency>BTC</currency>
+ <value>0.4</value>
+ </price>
+ </fixedPrice>
+ <recurringPrice>
+ <price>
+ <currency>BTC</currency>
+ <value>1.2</value>
+ </price>
+ </recurringPrice>
+ </tier>
+ </tiers>
+ </usage>
+ </usages>
+ </finalPhase>
+ </plan>
+
+
+ <plan name="consumable-in-arrear">
+ <product>ConsumableInArrear</product>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <usages>
+ <usage name="consumable-in-arrear-usage1" billingMode="IN_ARREAR" usageType="CONSUMABLE">
+ <billingPeriod>MONTHLY</billingPeriod>
+ <tiers>
+ <tier>
+ <blocks>
+ <tieredBlock>
+ <unit>cell-phone-minutes</unit>
+ <size>1000</size>
+ <prices>
+ <price>
+ <currency>BTC</currency>
+ <value>0.5</value>
+ </price>
+ </prices>
+ <max>10000</max>
+ </tieredBlock>
+ <tieredBlock>
+ <unit>Mbytes</unit>
+ <size>512</size>
+ <prices>
+ <price>
+ <currency>BTC</currency>
+ <value>0.3</value>
+ </price>
+ </prices>
+ <max>512000</max>
+ </tieredBlock>
+ </blocks>
+ </tier>
+ </tiers>
+ </usage>
+ </usages>
+ </finalPhase>
+ </plan>
+
+
+ </plans>
+ <priceLists>
+ <defaultPriceList name="DEFAULT">
+ <plans>
+ <plan>capacity-in-advance-monthly</plan>
+ <plan>consumable-in-advance-prepay-credit-monthly</plan>
+ <plan>consumable-in-advance-topup</plan>
+ <plan>capacity-in-arrear</plan>
+ <plan>consumable-in-arrear</plan>
+ </plans>
+ </defaultPriceList>
+ </priceLists>
+</catalog>
profiles/killpay/pom.xml 2(+1 -1)
diff --git a/profiles/killpay/pom.xml b/profiles/killpay/pom.xml
index 56d67a4..030fee8 100644
--- a/profiles/killpay/pom.xml
+++ b/profiles/killpay/pom.xml
@@ -20,7 +20,7 @@
<parent>
<artifactId>killbill-profiles</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.18.2-SNAPSHOT</version>
+ <version>0.18.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-profiles-killpay</artifactId>
profiles/pom.xml 2(+1 -1)
diff --git a/profiles/pom.xml b/profiles/pom.xml
index 8474f5a..7764c49 100644
--- a/profiles/pom.xml
+++ b/profiles/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.18.2-SNAPSHOT</version>
+ <version>0.18.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-profiles</artifactId>
subscription/pom.xml 2(+1 -1)
diff --git a/subscription/pom.xml b/subscription/pom.xml
index 43e27d9..109e33b 100644
--- a/subscription/pom.xml
+++ b/subscription/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.18.2-SNAPSHOT</version>
+ <version>0.18.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-subscription</artifactId>
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 0d9d275..3b52dc5 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
@@ -38,8 +38,9 @@ import org.killbill.billing.catalog.api.PlanAlignmentCreate;
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.entitlement.api.Entitlement.EntitlementState;
import org.killbill.billing.subscription.api.user.SubscriptionBaseApiException;
-import org.killbill.billing.subscription.api.user.SubscriptionBaseTransitionData;
+import org.killbill.billing.subscription.api.user.SubscriptionBaseTransition;
import org.killbill.billing.subscription.api.user.DefaultSubscriptionBase;
import org.killbill.billing.subscription.exceptions.SubscriptionBaseError;
@@ -89,7 +90,6 @@ public class PlanAligner extends BaseAligner {
bundleStartDate,
plan,
initialPhase,
- priceList,
effectiveDate,
context);
final TimedPhase[] result = new TimedPhase[2];
@@ -104,18 +104,18 @@ public class PlanAligner extends BaseAligner {
* @param subscription the subscription in change (only start date, bundle start date, current phase, plan and pricelist
* are looked at)
* @param plan the current Plan
- * @param priceList the priceList on which we should change that subscription.
* @param effectiveDate the effective change date (driven by the catalog policy, i.e. when the change occurs)
+ * @param newPlanInitialPhaseType the phase on which to start when switching to new plan
* @return the current phase
* @throws CatalogApiException for catalog errors
* @throws org.killbill.billing.subscription.api.user.SubscriptionBaseApiException for subscription errors
*/
public TimedPhase getCurrentTimedPhaseOnChange(final DefaultSubscriptionBase subscription,
final Plan plan,
- final String priceList,
final DateTime effectiveDate,
+ final PhaseType newPlanInitialPhaseType,
final InternalTenantContext context) throws CatalogApiException, SubscriptionBaseApiException {
- return getTimedPhaseOnChange(subscription, plan, effectiveDate, WhichPhase.CURRENT, context);
+ return getTimedPhaseOnChange(subscription, plan, effectiveDate, newPlanInitialPhaseType, WhichPhase.CURRENT, context);
}
/**
@@ -124,18 +124,18 @@ public class PlanAligner extends BaseAligner {
* @param subscription the subscription in change (only start date, bundle start date, current phase, plan and pricelist
* are looked at)
* @param plan the current Plan
- * @param priceList the priceList on which we should change that subscription.
* @param effectiveDate the effective change date (driven by the catalog policy, i.e. when the change occurs)
+ * @param newPlanInitialPhaseType the phase on which to start when switching to new plan
* @return the next phase
* @throws CatalogApiException for catalog errors
* @throws org.killbill.billing.subscription.api.user.SubscriptionBaseApiException for subscription errors
*/
public TimedPhase getNextTimedPhaseOnChange(final DefaultSubscriptionBase subscription,
final Plan plan,
- final String priceList,
final DateTime effectiveDate,
+ final PhaseType newPlanInitialPhaseType,
final InternalTenantContext context) throws CatalogApiException, SubscriptionBaseApiException {
- return getTimedPhaseOnChange(subscription, plan, effectiveDate, WhichPhase.NEXT, context);
+ return getTimedPhaseOnChange(subscription, plan, effectiveDate, newPlanInitialPhaseType, WhichPhase.NEXT, context);
}
/**
@@ -145,39 +145,43 @@ public class PlanAligner extends BaseAligner {
* @return the next phase
*/
public TimedPhase getNextTimedPhase(final DefaultSubscriptionBase subscription, final DateTime effectiveDate, final InternalTenantContext context) {
+
+
try {
- final SubscriptionBaseTransitionData lastPlanTransition = subscription.getLastTransitionForCurrentPlan();
- if (effectiveDate.isBefore(lastPlanTransition.getEffectiveTransitionTime())) {
- throw new SubscriptionBaseError(String.format("Cannot specify an effectiveDate prior to last Plan Change, subscription = %s, effectiveDate = %s",
- subscription.getId(), effectiveDate));
+ final SubscriptionBaseTransition pendingOrLastPlanTransition;
+ if (subscription.getState() == EntitlementState.PENDING) {
+ pendingOrLastPlanTransition = subscription.getPendingTransition();
+ } else {
+ pendingOrLastPlanTransition = subscription.getLastTransitionForCurrentPlan();
}
- switch (lastPlanTransition.getTransitionType()) {
+
+ switch (pendingOrLastPlanTransition.getTransitionType()) {
// If we never had any Plan change, borrow the logic for createPlan alignment
case CREATE:
case TRANSFER:
final List<TimedPhase> timedPhases = getTimedPhaseOnCreate(subscription.getAlignStartDate(),
subscription.getBundleStartDate(),
- lastPlanTransition.getNextPlan(),
- lastPlanTransition.getNextPhase().getPhaseType(),
- lastPlanTransition.getNextPriceList().getName(),
+ pendingOrLastPlanTransition.getNextPlan(),
+ pendingOrLastPlanTransition.getNextPhase().getPhaseType(),
effectiveDate,
context);
return getTimedPhase(timedPhases, effectiveDate, WhichPhase.NEXT);
case CHANGE:
return getTimedPhaseOnChange(subscription.getAlignStartDate(),
subscription.getBundleStartDate(),
- lastPlanTransition.getPreviousPhase(),
- lastPlanTransition.getPreviousPlan(),
- lastPlanTransition.getNextPlan(),
+ pendingOrLastPlanTransition.getPreviousPhase(),
+ pendingOrLastPlanTransition.getPreviousPlan(),
+ pendingOrLastPlanTransition.getNextPlan(),
effectiveDate,
- lastPlanTransition.getEffectiveTransitionTime(),
+ pendingOrLastPlanTransition.getEffectiveTransitionTime(),
subscription.getAllTransitions().get(0).getNextPhase().getPhaseType(),
+ null,
WhichPhase.NEXT,
context);
default:
throw new SubscriptionBaseError(String.format("Unexpected initial transition %s for current plan %s on subscription %s",
- lastPlanTransition.getTransitionType(), subscription.getCurrentPlan(), subscription.getId()));
+ pendingOrLastPlanTransition.getTransitionType(), subscription.getCurrentPlan(), subscription.getId()));
}
} catch (Exception /* SubscriptionBaseApiException, CatalogApiException */ e) {
throw new SubscriptionBaseError(String.format("Could not compute next phase change for subscription %s", subscription.getId()), e);
@@ -188,7 +192,6 @@ public class PlanAligner extends BaseAligner {
final DateTime bundleStartDate,
final Plan plan,
@Nullable final PhaseType initialPhase,
- final String priceList,
final DateTime effectiveDate,
final InternalTenantContext context)
throws CatalogApiException, SubscriptionBaseApiException {
@@ -215,6 +218,7 @@ public class PlanAligner extends BaseAligner {
private TimedPhase getTimedPhaseOnChange(final DefaultSubscriptionBase subscription,
final Plan nextPlan,
final DateTime effectiveDate,
+ final PhaseType newPlanInitialPhaseType,
final WhichPhase which,
final InternalTenantContext context) throws CatalogApiException, SubscriptionBaseApiException {
return getTimedPhaseOnChange(subscription.getAlignStartDate(),
@@ -226,6 +230,7 @@ public class PlanAligner extends BaseAligner {
// This method is only called while doing the change, hence we want to pass the change effective date
effectiveDate,
subscription.getAllTransitions().get(0).getNextPhase().getPhaseType(),
+ newPlanInitialPhaseType,
which,
context);
}
@@ -238,6 +243,7 @@ public class PlanAligner extends BaseAligner {
final DateTime effectiveDate,
final DateTime lastOrCurrentChangeEffectiveDate,
final PhaseType originalInitialPhase,
+ @Nullable final PhaseType newPlanInitialPhaseType,
final WhichPhase which,
final InternalTenantContext context) throws CatalogApiException, SubscriptionBaseApiException {
final Catalog catalog = catalogService.getFullCatalog(true, true, context);
@@ -251,15 +257,17 @@ public class PlanAligner extends BaseAligner {
switch (alignment) {
case START_OF_SUBSCRIPTION:
planStartDate = subscriptionStartDate;
- initialPhase = isPlanContainPhaseType(nextPlan, originalInitialPhase) ? originalInitialPhase : null;
+ initialPhase = newPlanInitialPhaseType != null ? newPlanInitialPhaseType :
+ (isPlanContainPhaseType(nextPlan, originalInitialPhase) ? originalInitialPhase : null);
break;
case START_OF_BUNDLE:
planStartDate = bundleStartDate;
- initialPhase = isPlanContainPhaseType(nextPlan, originalInitialPhase) ? originalInitialPhase : null;
+ initialPhase = newPlanInitialPhaseType != null ? newPlanInitialPhaseType :
+ (isPlanContainPhaseType(nextPlan, originalInitialPhase) ? originalInitialPhase : null);
break;
case CHANGE_OF_PLAN:
planStartDate = lastOrCurrentChangeEffectiveDate;
- initialPhase = null;
+ initialPhase = newPlanInitialPhaseType;
break;
case CHANGE_OF_PRICELIST:
throw new SubscriptionBaseError(String.format("Not implemented yet %s", alignment));
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseApiService.java b/subscription/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseApiService.java
index 83c8117..bcba397 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseApiService.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseApiService.java
@@ -83,7 +83,7 @@ public interface SubscriptionBaseApiService {
List<PlanPhasePriceOverride> overrides, BillingActionPolicy policy, CallContext context)
throws SubscriptionBaseApiException;
- public int cancelAddOnsIfRequiredOnBasePlanEvent(final DefaultSubscriptionBase subscription, final SubscriptionBaseEvent event, final CallContext context) throws CatalogApiException;
+ public int handleBasePlanEvent(final DefaultSubscriptionBase subscription, final SubscriptionBaseEvent event, final CallContext context) throws CatalogApiException;
public PlanChangeResult getPlanChangeResult(final DefaultSubscriptionBase subscription, PlanSpecifier spec, final DateTime effectiveDate, TenantContext context) throws SubscriptionBaseApiException;
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 798a653..c72cf42 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
@@ -54,6 +54,7 @@ import org.killbill.billing.entitlement.api.Entitlement.EntitlementState;
import org.killbill.billing.entitlement.api.EntitlementAOStatusDryRun;
import org.killbill.billing.entitlement.api.EntitlementAOStatusDryRun.DryRunChangeReason;
import org.killbill.billing.entitlement.api.EntitlementSpecifier;
+import org.killbill.billing.entitlement.api.Subscription;
import org.killbill.billing.events.EffectiveSubscriptionInternalEvent;
import org.killbill.billing.invoice.api.DryRunArguments;
import org.killbill.billing.subscription.api.SubscriptionApiBase;
@@ -101,6 +102,7 @@ import org.killbill.notificationq.api.NotificationQueueService.NoSuchNotificatio
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
@@ -192,6 +194,7 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
return apiService.createPlan(new SubscriptionBuilder()
.setId(UUIDs.randomUUID())
.setBundleId(bundleId)
+ .setBundleExternalKey(bundle.getExternalKey())
.setCategory(plan.getProduct().getCategory())
.setBundleStartDate(bundleStartDate)
.setAlignStartDate(effectiveDate)
@@ -202,7 +205,7 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
}
}
- private List<SubscriptionSpecifier> verifyAndBuildSubscriptionSpecifiers(final UUID bundleId, final Iterable<EntitlementSpecifier> entitlements, final boolean isMigrated, final InternalCallContext context, final DateTime now, final DateTime effectiveDate, final Catalog catalog, final CallContext callContext) throws SubscriptionBaseApiException, CatalogApiException {
+ private List<SubscriptionSpecifier> verifyAndBuildSubscriptionSpecifiers(final UUID bundleId, final String externalKey, final Iterable<EntitlementSpecifier> entitlements, final boolean isMigrated, final InternalCallContext context, final DateTime now, final DateTime effectiveDate, final Catalog catalog, final CallContext callContext) throws SubscriptionBaseApiException, CatalogApiException {
final List<SubscriptionSpecifier> subscriptions = new ArrayList<SubscriptionSpecifier>();
boolean first = true;
final List<SubscriptionBase> subscriptionsForBundle = getSubscriptionsForBundle(bundleId, null, context);
@@ -248,6 +251,7 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
subscription.setBuilder(new SubscriptionBuilder()
.setId(UUIDs.randomUUID())
.setBundleId(bundleId)
+ .setBundleExternalKey(externalKey)
.setCategory(plan.getProduct().getCategory())
.setBundleStartDate(effectiveDate)
.setAlignStartDate(effectiveDate)
@@ -276,6 +280,7 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
bundle.getId(),
effectiveDate,
verifyAndBuildSubscriptionSpecifiers(bundle.getId(),
+ bundle.getExternalKey(),
entitlementWithAddOnsSpecifier.getEntitlementSpecifier(),
entitlementWithAddOnsSpecifier.isMigrated(),
context,
@@ -335,6 +340,33 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
public SubscriptionBaseBundle createBundleForAccount(final UUID accountId, final String bundleKey, final InternalCallContext context) throws SubscriptionBaseApiException {
final List<SubscriptionBaseBundle> existingBundles = dao.getSubscriptionBundlesForKey(bundleKey, context);
+
+ //
+ // Because the creation of the SubscriptionBundle is not atomic (with creation of Subscription/SubscriptionEvent), we verify if we were left
+ // with an empty SubscriptionBaseBundle form a past failing operation (See #684). We only allow reuse if such SubscriptionBaseBundle is fully
+ // empty (and don't allow use case where all Subscription are cancelled, which is the condition for that key to be re-used)
+ // Such condition should have been checked upstream (to decide whether that key is valid or not)
+ //
+ final SubscriptionBaseBundle existingBundleForAccount = Iterables.tryFind(existingBundles, new Predicate<SubscriptionBaseBundle>() {
+ @Override
+ public boolean apply(final SubscriptionBaseBundle input) {
+ return input.getAccountId().equals(accountId);
+ }
+ }).orNull();
+
+ // If Bundle already exists, and there is 0 Subscription, we reuse
+ if (existingBundleForAccount != null) {
+ try {
+ final Map<UUID, List<SubscriptionBase>> accountSubscriptions = dao.getSubscriptionsForAccount(context);
+ final List<SubscriptionBase> subscriptions = accountSubscriptions.get(existingBundleForAccount.getId());
+ if (subscriptions == null || subscriptions.size() == 0) {
+ return existingBundleForAccount;
+ }
+ } catch (final CatalogApiException e) {
+ throw new SubscriptionBaseApiException(e);
+ }
+ }
+
final DateTime now = clock.getUTCNow();
final DateTime originalCreatedDate = !existingBundles.isEmpty() ? existingBundles.get(0).getCreatedDate() : now;
final DefaultSubscriptionBaseBundle bundle = new DefaultSubscriptionBaseBundle(bundleKey, accountId, now, originalCreatedDate, now, now);
@@ -641,6 +673,7 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
final SubscriptionBuilder builder = new SubscriptionBuilder()
.setId(subscriptionId)
.setBundleId(bundleId)
+ .setBundleExternalKey(null)
.setCategory(plan.getProduct().getCategory())
.setBundleStartDate(bundleStartDate)
.setAlignStartDate(startEffectiveDate);
@@ -758,7 +791,8 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
}
}
- private DateTime getEffectiveDateForNewBCD(final int bcd, @Nullable final LocalDate effectiveFromDate, final InternalCallContext internalCallContext) {
+ @VisibleForTesting
+ DateTime getEffectiveDateForNewBCD(final int bcd, @Nullable final LocalDate effectiveFromDate, final InternalCallContext internalCallContext) {
if (internalCallContext.getAccountRecordId() == null) {
throw new IllegalStateException("Need to have a valid context with accountRecordId");
}
@@ -778,7 +812,7 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
final int lastDayOfNextMonth = startDatePlusOneMonth.dayOfMonth().getMaximumValue();
final int originalBCDORLastDayOfMonth = bcd <= lastDayOfNextMonth ? bcd : lastDayOfNextMonth;
requestedDate = new LocalDate(startDatePlusOneMonth.getYear(), startDatePlusOneMonth.getMonthOfYear(), originalBCDORLastDayOfMonth);
- } else if (bcd == currentDay) {
+ } else if (bcd == currentDay && effectiveFromDate == null) {
// will default to immediate event
requestedDate = null;
} else if (bcd <= lastDayOfMonth) {
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/transfer/DefaultSubscriptionBaseTransferApi.java b/subscription/src/main/java/org/killbill/billing/subscription/api/transfer/DefaultSubscriptionBaseTransferApi.java
index bd62893..0c45606 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/api/transfer/DefaultSubscriptionBaseTransferApi.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/api/transfer/DefaultSubscriptionBaseTransferApi.java
@@ -248,6 +248,7 @@ public class DefaultSubscriptionBaseTransferApi extends SubscriptionApiBase impl
final DefaultSubscriptionBase defaultSubscriptionBase = createSubscriptionForApiUse(new SubscriptionBuilder()
.setId(UUIDs.randomUUID())
.setBundleId(subscriptionBundleData.getId())
+ .setBundleExternalKey(subscriptionBundleData.getExternalKey())
.setCategory(productCategory)
.setBundleStartDate(effectiveTransferDate)
.setAlignStartDate(subscriptionAlignStartDate),
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultEffectiveSubscriptionEvent.java b/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultEffectiveSubscriptionEvent.java
index 4e4d7b7..cc03427 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultEffectiveSubscriptionEvent.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultEffectiveSubscriptionEvent.java
@@ -37,6 +37,7 @@ public class DefaultEffectiveSubscriptionEvent extends DefaultSubscriptionEvent
public DefaultEffectiveSubscriptionEvent(@JsonProperty("eventId") final UUID eventId,
@JsonProperty("subscriptionId") final UUID subscriptionId,
@JsonProperty("bundleId") final UUID bundleId,
+ @JsonProperty("bundleExternalKey") final String bundleExternalKey,
@JsonProperty("effectiveTransitionTime") final DateTime effectiveTransitionTime,
@JsonProperty("previousState") final EntitlementState previousState,
@JsonProperty("previousPlan") final String previousPlan,
@@ -55,7 +56,7 @@ public class DefaultEffectiveSubscriptionEvent extends DefaultSubscriptionEvent
@JsonProperty("searchKey1") final Long searchKey1,
@JsonProperty("searchKey2") final Long searchKey2,
@JsonProperty("userToken") final UUID userToken) {
- super(eventId, subscriptionId, bundleId, effectiveTransitionTime, effectiveTransitionTime, previousState, previousPlan,
+ super(eventId, subscriptionId, bundleId, bundleExternalKey, effectiveTransitionTime, effectiveTransitionTime, previousState, previousPlan,
previousPhase, previousPriceList, previousBillCycleDayLocal, nextState, nextPlan, nextPhase, nextPriceList, nextBillCycleDayLocal, totalOrdering,
transitionType, remainingEventsForUserOperation, startDate, searchKey1, searchKey2, userToken);
}
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultRequestedSubscriptionEvent.java b/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultRequestedSubscriptionEvent.java
index 1e918ef..f9ecaa3 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultRequestedSubscriptionEvent.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultRequestedSubscriptionEvent.java
@@ -34,6 +34,7 @@ public class DefaultRequestedSubscriptionEvent extends DefaultSubscriptionEvent
public DefaultRequestedSubscriptionEvent(@JsonProperty("eventId") final UUID eventId,
@JsonProperty("subscriptionId") final UUID subscriptionId,
@JsonProperty("bundleId") final UUID bundleId,
+ @JsonProperty("bundleExternalKey") final String bundleExternalKey,
@JsonProperty("requestedTransitionTime") final DateTime requestedTransitionTime,
@JsonProperty("effectiveTransitionTime") final DateTime effectiveTransitionTime,
@JsonProperty("previousState") final EntitlementState previousState,
@@ -53,7 +54,7 @@ public class DefaultRequestedSubscriptionEvent extends DefaultSubscriptionEvent
@JsonProperty("searchKey1") final Long searchKey1,
@JsonProperty("searchKey2") final Long searchKey2,
@JsonProperty("userToken") final UUID userToken) {
- super(eventId, subscriptionId, bundleId, requestedTransitionTime, effectiveTransitionTime, previousState, previousPlan,
+ super(eventId, subscriptionId, bundleId, bundleExternalKey, requestedTransitionTime, effectiveTransitionTime, previousState, previousPlan,
previousPhase, previousPriceList, previousBillCycleDayLocal, nextState, nextPlan, nextPhase, nextPriceList, nextBillCycleDayLocal, totalOrdering,
transitionType, remainingEventsForUserOperation, startDate, searchKey1, searchKey2, userToken);
}
@@ -64,7 +65,7 @@ public class DefaultRequestedSubscriptionEvent extends DefaultSubscriptionEvent
final Long searchKey1,
final Long searchKey2,
final UUID userToken) {
- this(nextEvent.getId(), nextEvent.getSubscriptionId(), subscription.getBundleId(), nextEvent.getEffectiveDate(), nextEvent.getEffectiveDate(),
+ this(nextEvent.getId(), nextEvent.getSubscriptionId(), subscription.getBundleId(), subscription.getBundleExternalKey(), nextEvent.getEffectiveDate(), nextEvent.getEffectiveDate(),
null, null, null, null, null, null, null, null, null, null, nextEvent.getTotalOrdering(), transitionType, 0, null, searchKey1, searchKey2, userToken);
}
}
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 91acbb2..e255419 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
@@ -83,6 +83,7 @@ public class DefaultSubscriptionBase extends EntityBase implements SubscriptionB
// Final subscription fields
//
private final UUID bundleId;
+ private final String bundleExternalKey;
private final DateTime alignStartDate;
private final DateTime bundleStartDate;
private final ProductCategory category;
@@ -118,6 +119,7 @@ public class DefaultSubscriptionBase extends EntityBase implements SubscriptionB
this.apiService = apiService;
this.clock = clock;
this.bundleId = builder.getBundleId();
+ this.bundleExternalKey = builder.getBundleExternalKey();
this.alignStartDate = builder.getAlignStartDate();
this.bundleStartDate = builder.getBundleStartDate();
this.category = builder.getCategory();
@@ -131,6 +133,7 @@ public class DefaultSubscriptionBase extends EntityBase implements SubscriptionB
this.apiService = apiService;
this.clock = clock;
this.bundleId = internalSubscription.getBundleId();
+ this.bundleExternalKey = internalSubscription.getBundleExternalKey();
this.alignStartDate = internalSubscription.getAlignStartDate();
this.bundleStartDate = internalSubscription.getBundleStartDate();
this.category = internalSubscription.getCategory();
@@ -145,6 +148,10 @@ public class DefaultSubscriptionBase extends EntityBase implements SubscriptionB
return bundleId;
}
+ public String getBundleExternalKey() {
+ return bundleExternalKey;
+ }
+
@Override
public DateTime getStartDate() {
return transitions.get(0).getEffectiveTransitionTime();
@@ -713,7 +720,7 @@ public class DefaultSubscriptionBase extends EntityBase implements SubscriptionB
nextPriceList = (nextPlan != null) ? catalog.findPriceListForPlan(nextPlanName, cur.getEffectiveDate(), getAlignStartDate()) : null;
final SubscriptionBaseTransitionData transition = new SubscriptionBaseTransitionData(
- cur.getId(), id, bundleId, cur.getType(), apiEventType,
+ cur.getId(), id, bundleId, bundleExternalKey, cur.getType(), apiEventType,
cur.getEffectiveDate(),
prevEventId, prevCreatedDate,
previousState, previousPlan, previousPhase,
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 19731b9..c7d3e41 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
@@ -33,9 +33,6 @@ import org.joda.time.DateTimeZone;
import org.joda.time.ReadableInstant;
import org.killbill.billing.ErrorCode;
import org.killbill.billing.ObjectType;
-import org.killbill.billing.account.api.AccountApiException;
-import org.killbill.billing.account.api.AccountInternalApi;
-import org.killbill.billing.account.api.ImmutableAccountInternalApi;
import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.catalog.api.BillingActionPolicy;
@@ -65,10 +62,12 @@ import org.killbill.billing.subscription.engine.dao.SubscriptionDao;
import org.killbill.billing.subscription.events.SubscriptionBaseEvent;
import org.killbill.billing.subscription.events.phase.PhaseEvent;
import org.killbill.billing.subscription.events.phase.PhaseEventData;
+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.events.user.ApiEventUncancel;
import org.killbill.billing.util.callcontext.CallContext;
import org.killbill.billing.util.callcontext.InternalCallContextFactory;
@@ -113,6 +112,11 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
final InternalCallContext internalCallContext = createCallContextFromBundleId(subscription.getBundleId(), context);
try {
+
+ if (subscription.getAlignStartDate().compareTo(effectiveDate) != 0) {
+ throw new RuntimeException("Subscription id = " + subscription.getId() + ", alignStartDate = " + subscription.getAlignStartDate() + ", effectiveDate = " + effectiveDate);
+ }
+
final List<SubscriptionBaseEvent> events = getEventsOnCreation(subscription.getBundleId(), subscription.getId(), subscription.getAlignStartDate(), subscription.getBundleStartDate(),
plan, initialPhase, realPriceList, effectiveDate, processedDate, internalCallContext);
dao.createSubscription(subscription, events, internalCallContext);
@@ -312,7 +316,14 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
uncancelEvents.add(uncancelEvent);
final InternalCallContext internalCallContext = createCallContextFromBundleId(subscription.getBundleId(), context);
- final TimedPhase nextTimedPhase = planAligner.getNextTimedPhase(subscription, now, internalCallContext);
+ //
+ // Used to compute effective for next phase (which was set unactive during cancellation).
+ // In case of a pending subscription we don't want to pass an effective date prior the CREATE event as we would end up with the wrong
+ // transition in PlanAligner (next transition would be CREATE instead of potential next PHASE)
+ //
+ final DateTime planAlignerEffectiveDate = subscription.getState() == EntitlementState.PENDING ? subscription.getStartDate() : now;
+
+ final TimedPhase nextTimedPhase = planAligner.getNextTimedPhase(subscription, planAlignerEffectiveDate, internalCallContext);
final PhaseEvent nextPhaseEvent = (nextTimedPhase != null) ?
PhaseEventData.createNextPhaseEvent(subscription.getId(), nextTimedPhase.getPhase().getName(), nextTimedPhase.getStartPhase()) :
null;
@@ -428,6 +439,17 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
final PlanPhasePriceOverridesWithCallContext overridesWithContext = new DefaultPlanPhasePriceOverridesWithCallContext(overrides, context);
final Plan newPlan = catalogService.getFullCatalog(true, true, internalCallContext).createOrFindPlan(spec, overridesWithContext, effectiveDate, subscription.getStartDate());
+ final PhaseType initialPhaseType;
+ if (overrides != null &&
+ overrides.size() == 1 &&
+ overrides.get(0).getPlanPhaseSpecifier() != null &&
+ overrides.get(0).getCurrency() == null) {
+ initialPhaseType = overrides.get(0).getPlanPhaseSpecifier().getPhaseType();
+ } else {
+ initialPhaseType = null;
+ }
+
+
if (ProductCategory.ADD_ON.toString().equalsIgnoreCase(newPlan.getProduct().getCategory().toString())) {
if (newPlan.getPlansAllowedInBundle() != -1
&& newPlan.getPlansAllowedInBundle() > 0
@@ -444,7 +466,7 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
final List<DefaultSubscriptionBase> addOnSubscriptionsToBeCancelled = new ArrayList<DefaultSubscriptionBase>();
final List<SubscriptionBaseEvent> addOnCancelEvents = new ArrayList<SubscriptionBaseEvent>();
- final List<SubscriptionBaseEvent> changeEvents = getEventsOnChangePlan(subscription, newPlan, newPlan.getPriceListName(), effectiveDate, true, addOnSubscriptionsToBeCancelled, addOnCancelEvents, internalCallContext);
+ final List<SubscriptionBaseEvent> changeEvents = getEventsOnChangePlan(subscription, newPlan, newPlan.getPriceListName(), effectiveDate, true, addOnSubscriptionsToBeCancelled, addOnCancelEvents, initialPhaseType, internalCallContext);
dao.changePlan(subscription, changeEvents, addOnSubscriptionsToBeCancelled, addOnCancelEvents, internalCallContext);
@@ -488,7 +510,7 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
final Collection<DefaultSubscriptionBase> addOnSubscriptionsToBeCancelled = new ArrayList<DefaultSubscriptionBase>();
final Collection<SubscriptionBaseEvent> addOnCancelEvents = new ArrayList<SubscriptionBaseEvent>();
- final List<SubscriptionBaseEvent> changeEvents = getEventsOnChangePlan(subscription, newPlan, newPriceList, effectiveDate, addCancellationAddOnForEventsIfRequired, addOnSubscriptionsToBeCancelled, addOnCancelEvents, internalTenantContext);
+ final List<SubscriptionBaseEvent> changeEvents = getEventsOnChangePlan(subscription, newPlan, newPriceList, effectiveDate, addCancellationAddOnForEventsIfRequired, addOnSubscriptionsToBeCancelled, addOnCancelEvents, null, internalTenantContext);
changeEvents.addAll(addOnCancelEvents);
return changeEvents;
}
@@ -498,8 +520,9 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
final boolean addCancellationAddOnForEventsIfRequired,
final Collection<DefaultSubscriptionBase> addOnSubscriptionsToBeCancelled,
final Collection<SubscriptionBaseEvent> addOnCancelEvents,
+ final PhaseType initialPhaseType,
final InternalTenantContext internalTenantContext) throws CatalogApiException, SubscriptionBaseApiException {
- final TimedPhase currentTimedPhase = planAligner.getCurrentTimedPhaseOnChange(subscription, newPlan, newPriceList, effectiveDate, internalTenantContext);
+ final TimedPhase currentTimedPhase = planAligner.getCurrentTimedPhaseOnChange(subscription, newPlan, effectiveDate, initialPhaseType, internalTenantContext);
final SubscriptionBaseEvent changeEvent = new ApiEventChange(new ApiEventBuilder()
.setSubscriptionId(subscription.getId())
@@ -509,7 +532,7 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
.setEffectiveDate(effectiveDate)
.setFromDisk(true));
- final TimedPhase nextTimedPhase = planAligner.getNextTimedPhaseOnChange(subscription, newPlan, newPriceList, effectiveDate, internalTenantContext);
+ final TimedPhase nextTimedPhase = planAligner.getNextTimedPhaseOnChange(subscription, newPlan, effectiveDate, initialPhaseType, internalTenantContext);
final PhaseEvent nextPhaseEvent = (nextTimedPhase != null) ?
PhaseEventData.createNextPhaseEvent(subscription.getId(),
nextTimedPhase.getPhase().getName(), nextTimedPhase.getStartPhase()) :
@@ -547,15 +570,21 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
}
@Override
- public int cancelAddOnsIfRequiredOnBasePlanEvent(final DefaultSubscriptionBase subscription, final SubscriptionBaseEvent event, final CallContext context) throws CatalogApiException {
- final Product baseProduct = (subscription.getState() == EntitlementState.CANCELLED) ? null : subscription.getCurrentPlan().getProduct();
+ public int handleBasePlanEvent(final DefaultSubscriptionBase subscription, final SubscriptionBaseEvent event, final CallContext context) throws CatalogApiException {
- final List<SubscriptionBaseEvent> cancelEvents = new LinkedList<SubscriptionBaseEvent>();
final InternalCallContext internalCallContext = createCallContextFromBundleId(subscription.getBundleId(), context);
- final List<DefaultSubscriptionBase> subscriptionsToBeCancelled = computeAddOnsToCancel(cancelEvents, baseProduct, subscription.getBundleId(), event.getEffectiveDate(), internalCallContext);
- dao.cancelSubscriptionsOnBasePlanEvent(subscription, event, subscriptionsToBeCancelled, cancelEvents, internalCallContext);
+ if (((ApiEvent) event).getApiEventType() == ApiEventType.CANCEL || ((ApiEvent) event).getApiEventType() == ApiEventType.CHANGE) {
+ final Product baseProduct = (subscription.getState() == EntitlementState.CANCELLED) ? null : subscription.getCurrentPlan().getProduct();
+
+ final List<SubscriptionBaseEvent> cancelEvents = new LinkedList<SubscriptionBaseEvent>();
+ final List<DefaultSubscriptionBase> subscriptionsToBeCancelled = computeAddOnsToCancel(cancelEvents, baseProduct, subscription.getBundleId(), event.getEffectiveDate(), internalCallContext);
+ dao.cancelSubscriptionsOnBasePlanEvent(subscription, event, subscriptionsToBeCancelled, cancelEvents, internalCallContext);
- return subscriptionsToBeCancelled.size();
+ return subscriptionsToBeCancelled.size();
+ } else {
+ dao.notifyOnBasePlanEvent(subscription, event, internalCallContext);
+ return 0;
+ }
}
private List<DefaultSubscriptionBase> computeAddOnsToCancel(final Collection<SubscriptionBaseEvent> cancelEvents, final CatalogEntity baseProduct, final UUID bundleId, final DateTime effectiveDate, final InternalCallContext internalCallContext) throws CatalogApiException {
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionEvent.java b/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionEvent.java
index 89f8aa7..71ab5da 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionEvent.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionEvent.java
@@ -34,6 +34,7 @@ public abstract class DefaultSubscriptionEvent extends BusEventBase implements S
private final Long totalOrdering;
private final UUID subscriptionId;
private final UUID bundleId;
+ private final String bundleExternalKey;
private final UUID eventId;
private final DateTime requestedTransitionTime;
private final DateTime effectiveTransitionTime;
@@ -59,6 +60,7 @@ public abstract class DefaultSubscriptionEvent extends BusEventBase implements S
this(in.getId(),
in.getSubscriptionId(),
in.getBundleId(),
+ in.getBundleExternalKey(),
in.getEffectiveTransitionTime(),
in.getEffectiveTransitionTime(),
in.getPreviousState(),
@@ -84,6 +86,7 @@ public abstract class DefaultSubscriptionEvent extends BusEventBase implements S
public DefaultSubscriptionEvent(@JsonProperty("eventId") final UUID eventId,
@JsonProperty("subscriptionId") final UUID subscriptionId,
@JsonProperty("bundleId") final UUID bundleId,
+ @JsonProperty("bundleExternalKey") final String bundleExternalKey,
@JsonProperty("requestedTransitionTime") final DateTime requestedTransitionTime,
@JsonProperty("effectiveTransitionTime") final DateTime effectiveTransitionTime,
@JsonProperty("previousState") final EntitlementState previousState,
@@ -107,6 +110,7 @@ public abstract class DefaultSubscriptionEvent extends BusEventBase implements S
this.eventId = eventId;
this.subscriptionId = subscriptionId;
this.bundleId = bundleId;
+ this.bundleExternalKey = bundleExternalKey;
this.requestedTransitionTime = requestedTransitionTime;
this.effectiveTransitionTime = effectiveTransitionTime;
this.previousState = previousState;
@@ -148,6 +152,11 @@ public abstract class DefaultSubscriptionEvent extends BusEventBase implements S
}
@Override
+ public String getBundleExternalKey() {
+ return bundleExternalKey;
+ }
+
+ @Override
public EntitlementState getPreviousState() {
return previousState;
}
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/user/SubscriptionBaseTransitionData.java b/subscription/src/main/java/org/killbill/billing/subscription/api/user/SubscriptionBaseTransitionData.java
index 511b142..b8beb50 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/api/user/SubscriptionBaseTransitionData.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/api/user/SubscriptionBaseTransitionData.java
@@ -33,6 +33,7 @@ public class SubscriptionBaseTransitionData implements SubscriptionBaseTransitio
private final Long totalOrdering;
private final UUID subscriptionId;
private final UUID bundleId;
+ private final String bundleExternalKey;
private final UUID eventId;
private final EventType eventType;
private final ApiEventType apiEventType;
@@ -59,6 +60,7 @@ public class SubscriptionBaseTransitionData implements SubscriptionBaseTransitio
public SubscriptionBaseTransitionData(final UUID eventId,
final UUID subscriptionId,
final UUID bundleId,
+ final String bundleExternalKey,
final EventType eventType,
final ApiEventType apiEventType,
final DateTime effectiveTransitionTime,
@@ -83,6 +85,7 @@ public class SubscriptionBaseTransitionData implements SubscriptionBaseTransitio
this.eventId = eventId;
this.subscriptionId = subscriptionId;
this.bundleId = bundleId;
+ this.bundleExternalKey = bundleExternalKey;
this.eventType = eventType;
this.apiEventType = apiEventType;
this.effectiveTransitionTime = effectiveTransitionTime;
@@ -117,6 +120,7 @@ public class SubscriptionBaseTransitionData implements SubscriptionBaseTransitio
this.eventId = input.getId();
this.subscriptionId = input.getSubscriptionId();
this.bundleId = input.getBundleId();
+ this.bundleExternalKey = input.getBundleExternalKey();
this.eventType = eventType;
this.apiEventType = apiEventType;
this.effectiveTransitionTime = input.getEffectiveTransitionTime();
@@ -156,6 +160,10 @@ public class SubscriptionBaseTransitionData implements SubscriptionBaseTransitio
return bundleId;
}
+ public String getBundleExternalKey() {
+ return bundleExternalKey;
+ }
+
@Override
public EntitlementState getPreviousState() {
return previousState;
@@ -285,6 +293,7 @@ public class SubscriptionBaseTransitionData implements SubscriptionBaseTransitio
sb.append(", totalOrdering=").append(totalOrdering);
sb.append(", subscriptionId=").append(subscriptionId);
sb.append(", bundleId=").append(bundleId);
+ sb.append(", bundleExternalKey=").append(bundleExternalKey);
sb.append(", eventId=").append(eventId);
sb.append(", eventType=").append(eventType);
sb.append(", effectiveTransitionTime=").append(effectiveTransitionTime);
@@ -322,6 +331,9 @@ public class SubscriptionBaseTransitionData implements SubscriptionBaseTransitio
if (bundleId != null ? !bundleId.equals(that.bundleId) : that.bundleId != null) {
return false;
}
+ if (bundleExternalKey != null ? !bundleExternalKey.equals(that.bundleExternalKey) : that.bundleExternalKey != null) {
+ return false;
+ }
if (effectiveTransitionTime != null ? effectiveTransitionTime.compareTo(that.effectiveTransitionTime) != 0 : that.effectiveTransitionTime != null) {
return false;
}
@@ -384,6 +396,7 @@ public class SubscriptionBaseTransitionData implements SubscriptionBaseTransitio
int result = totalOrdering != null ? totalOrdering.hashCode() : 0;
result = 31 * result + (subscriptionId != null ? subscriptionId.hashCode() : 0);
result = 31 * result + (bundleId != null ? bundleId.hashCode() : 0);
+ result = 31 * result + (bundleExternalKey != null ? bundleExternalKey.hashCode() : 0);
result = 31 * result + (eventId != null ? eventId.hashCode() : 0);
result = 31 * result + (eventType != null ? eventType.hashCode() : 0);
result = 31 * result + (apiEventType != null ? apiEventType.hashCode() : 0);
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/user/SubscriptionBuilder.java b/subscription/src/main/java/org/killbill/billing/subscription/api/user/SubscriptionBuilder.java
index 7beabf6..97df7fd 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/api/user/SubscriptionBuilder.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/api/user/SubscriptionBuilder.java
@@ -28,6 +28,7 @@ public class SubscriptionBuilder {
private UUID id;
private UUID bundleId;
+ private String bundleExternalKey;
private DateTime createdDate;
private DateTime updatedDate;
private DateTime alignStartDate;
@@ -42,6 +43,7 @@ public class SubscriptionBuilder {
public SubscriptionBuilder(final DefaultSubscriptionBase original) {
this.id = original.getId();
this.bundleId = original.getBundleId();
+ this.bundleExternalKey = original.getBundleExternalKey();
this.alignStartDate = original.getAlignStartDate();
this.bundleStartDate = original.getBundleStartDate();
this.category = original.getCategory();
@@ -69,6 +71,11 @@ public class SubscriptionBuilder {
return this;
}
+ public SubscriptionBuilder setBundleExternalKey(final String bundleExternalKey) {
+ this.bundleExternalKey = bundleExternalKey;
+ return this;
+ }
+
public SubscriptionBuilder setAlignStartDate(final DateTime alignStartDate) {
this.alignStartDate = alignStartDate;
return this;
@@ -109,6 +116,10 @@ public class SubscriptionBuilder {
return bundleId;
}
+ public String getBundleExternalKey() {
+ return bundleExternalKey;
+ }
+
public DateTime getAlignStartDate() {
return alignStartDate;
}
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/engine/core/DefaultSubscriptionBaseService.java b/subscription/src/main/java/org/killbill/billing/subscription/engine/core/DefaultSubscriptionBaseService.java
index fd0e273..183814e 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/engine/core/DefaultSubscriptionBaseService.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/engine/core/DefaultSubscriptionBaseService.java
@@ -204,7 +204,7 @@ public class DefaultSubscriptionBaseService implements EventListener, Subscripti
}
private boolean onBasePlanEvent(final DefaultSubscriptionBase baseSubscription, final SubscriptionBaseEvent event, final CallContext context) throws CatalogApiException {
- apiService.cancelAddOnsIfRequiredOnBasePlanEvent(baseSubscription, event, context);
+ apiService.handleBasePlanEvent(baseSubscription, event, context);
return true;
}
}
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 9c842a1..9bbc75d 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
@@ -55,7 +55,6 @@ import org.killbill.billing.subscription.api.user.DefaultEffectiveSubscriptionEv
import org.killbill.billing.subscription.api.user.DefaultRequestedSubscriptionEvent;
import org.killbill.billing.subscription.api.user.DefaultSubscriptionBase;
import org.killbill.billing.subscription.api.user.DefaultSubscriptionBaseBundle;
-import org.killbill.billing.subscription.api.user.DefaultSubscriptionBaseWithAddOns;
import org.killbill.billing.subscription.api.user.SubscriptionBaseBundle;
import org.killbill.billing.subscription.api.user.SubscriptionBaseTransitionData;
import org.killbill.billing.subscription.api.user.SubscriptionBuilder;
@@ -98,6 +97,7 @@ import org.killbill.notificationq.api.NotificationQueue;
import org.killbill.notificationq.api.NotificationQueueService;
import org.killbill.notificationq.api.NotificationQueueService.NoSuchNotificationQueue;
import org.skife.jdbi.v2.IDBI;
+import org.skife.jdbi.v2.sqlobject.customizers.OverrideStatementLocatorWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -305,8 +305,9 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
final SubscriptionBase shellSubscription = transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<SubscriptionBase>() {
@Override
public SubscriptionBase inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
- final SubscriptionModelDao model = entitySqlDaoWrapperFactory.become(SubscriptionSqlDao.class).getById(subscriptionId.toString(), context);
- return SubscriptionModelDao.toSubscription(model);
+ final SubscriptionModelDao subscriptionModel = entitySqlDaoWrapperFactory.become(SubscriptionSqlDao.class).getById(subscriptionId.toString(), context);
+ final SubscriptionBundleModelDao bundleModel = entitySqlDaoWrapperFactory.become(BundleSqlDao.class).getById(subscriptionModel.getBundleId().toString(), context);
+ return SubscriptionModelDao.toSubscription(subscriptionModel, bundleModel.getExternalKey());
}
});
return buildSubscription(shellSubscription, context);
@@ -321,11 +322,14 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<List<SubscriptionBase>>() {
@Override
public List<SubscriptionBase> inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
+
+ final SubscriptionBundleModelDao bundleModel = entitySqlDaoWrapperFactory.become(BundleSqlDao.class).getById(bundleId.toString(), context);
+
final List<SubscriptionModelDao> models = entitySqlDaoWrapperFactory.become(SubscriptionSqlDao.class).getSubscriptionsFromBundleId(bundleId.toString(), context);
return new ArrayList<SubscriptionBase>(Collections2.transform(models, new Function<SubscriptionModelDao, SubscriptionBase>() {
@Override
public SubscriptionBase apply(@Nullable final SubscriptionModelDao input) {
- return SubscriptionModelDao.toSubscription(input);
+ return SubscriptionModelDao.toSubscription(input, bundleModel.getExternalKey());
}
}));
}
@@ -364,11 +368,20 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
final List<SubscriptionBase> allSubscriptions = transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<List<SubscriptionBase>>() {
@Override
public List<SubscriptionBase> inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
- final List<SubscriptionModelDao> models = entitySqlDaoWrapperFactory.become(SubscriptionSqlDao.class).getByAccountRecordId(context);
- return new ArrayList<SubscriptionBase>(Collections2.transform(models, new Function<SubscriptionModelDao, SubscriptionBase>() {
+
+ final List<SubscriptionBundleModelDao> bundleModels = entitySqlDaoWrapperFactory.become(BundleSqlDao.class).getByAccountRecordId(context);
+
+ final List<SubscriptionModelDao> subscriptionModels = entitySqlDaoWrapperFactory.become(SubscriptionSqlDao.class).getByAccountRecordId(context);
+ return new ArrayList<SubscriptionBase>(Collections2.transform(subscriptionModels, new Function<SubscriptionModelDao, SubscriptionBase>() {
@Override
public SubscriptionBase apply(final SubscriptionModelDao input) {
- return SubscriptionModelDao.toSubscription(input);
+ final SubscriptionBundleModelDao bundleModel = Iterables.find(bundleModels, new Predicate<SubscriptionBundleModelDao>() {
+ @Override
+ public boolean apply(final SubscriptionBundleModelDao bundleInput) {
+ return bundleInput.getId().equals(input.getBundleId());
+ }
+ });
+ return SubscriptionModelDao.toSubscription(input, bundleModel.getExternalKey());
}
}));
}
@@ -557,6 +570,18 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
}
@Override
+ public void notifyOnBasePlanEvent(final DefaultSubscriptionBase subscription, final SubscriptionBaseEvent event, final InternalCallContext context) {
+ transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<Void>() {
+ @Override
+ public Void inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
+ notifyBusOfEffectiveImmediateChange(entitySqlDaoWrapperFactory, subscription, event, 0, context);
+ return null;
+ }
+ });
+
+ }
+
+ @Override
public void cancelSubscriptions(final List<DefaultSubscriptionBase> subscriptions, final List<SubscriptionBaseEvent> cancelEvents, final InternalCallContext context) {
transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<Void>() {
@Override
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/model/SubscriptionModelDao.java b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/model/SubscriptionModelDao.java
index b53a9cd..e0761b9 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/model/SubscriptionModelDao.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/model/SubscriptionModelDao.java
@@ -25,7 +25,6 @@ import org.killbill.billing.subscription.api.user.SubscriptionBuilder;
import org.killbill.billing.subscription.api.user.DefaultSubscriptionBase;
import org.killbill.billing.subscription.api.SubscriptionBase;
import org.killbill.billing.util.dao.TableName;
-import org.killbill.billing.entity.EntityBase;
import org.killbill.billing.util.entity.dao.EntityModelDao;
import org.killbill.billing.util.entity.dao.EntityModelDaoBase;
@@ -106,13 +105,14 @@ public class SubscriptionModelDao extends EntityModelDaoBase implements EntityMo
this.migrated = migrated;
}
- public static SubscriptionBase toSubscription(final SubscriptionModelDao src) {
+ public static SubscriptionBase toSubscription(final SubscriptionModelDao src, final String externalKey) {
if (src == null) {
return null;
}
return new DefaultSubscriptionBase(new SubscriptionBuilder()
.setId(src.getId())
.setBundleId(src.getBundleId())
+ .setBundleExternalKey(externalKey)
.setCategory(src.getCategory())
.setCreatedDate(src.getCreatedDate())
.setUpdatedDate(src.getUpdatedDate())
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionDao.java b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionDao.java
index 3430d2e..516d80c 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionDao.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionDao.java
@@ -87,6 +87,8 @@ public interface SubscriptionDao extends EntityDao<SubscriptionBundleModelDao, S
public void cancelSubscriptionsOnBasePlanEvent(DefaultSubscriptionBase subscription, SubscriptionBaseEvent event, List<DefaultSubscriptionBase> subscriptions, List<SubscriptionBaseEvent> cancelEvents, InternalCallContext context);
+ public void notifyOnBasePlanEvent(final DefaultSubscriptionBase subscription, final SubscriptionBaseEvent event, final InternalCallContext context);
+
public void cancelSubscriptions(List<DefaultSubscriptionBase> subscriptions, List<SubscriptionBaseEvent> cancelEvents, InternalCallContext context);
public void uncancelSubscription(DefaultSubscriptionBase subscription, List<SubscriptionBaseEvent> uncancelEvents, InternalCallContext context);
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/alignment/TestPlanAligner.java b/subscription/src/test/java/org/killbill/billing/subscription/alignment/TestPlanAligner.java
index 869e886..1fb56a3 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/alignment/TestPlanAligner.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/alignment/TestPlanAligner.java
@@ -33,7 +33,6 @@ import org.killbill.billing.subscription.events.SubscriptionBaseEvent;
import org.killbill.billing.subscription.events.user.ApiEventBase;
import org.killbill.billing.subscription.events.user.ApiEventBuilder;
import org.killbill.billing.subscription.events.user.ApiEventType;
-import org.killbill.billing.subscription.exceptions.SubscriptionBaseError;
import org.killbill.clock.DefaultClock;
import org.testng.Assert;
import org.testng.annotations.BeforeClass;
@@ -60,7 +59,12 @@ public class TestPlanAligner extends SubscriptionTestSuiteNoDB {
public void testCreationBundleAlignment() throws Exception {
final String productName = "pistol-monthly";
final PhaseType initialPhase = PhaseType.TRIAL;
- final DefaultSubscriptionBase defaultSubscriptionBase = createSubscriptionStartedInThePast(productName, initialPhase);
+
+ final DateTime now = clock.getUTCNow();
+ final DateTime bundleStartDate = now.minusHours(10);
+ final DateTime alignStartDate = bundleStartDate.plusHours(5);
+
+ final DefaultSubscriptionBase defaultSubscriptionBase = createSubscription(bundleStartDate, alignStartDate, productName, initialPhase);
// Make the creation effective now, after the bundle and the subscription started
final DateTime effectiveDate = clock.getUTCNow();
@@ -80,14 +84,6 @@ public class TestPlanAligner extends SubscriptionTestSuiteNoDB {
Assert.assertNull(phasesInThePast[0]);
Assert.assertEquals(phasesInThePast[1].getStartPhase(), defaultSubscriptionBase.getBundleStartDate());
- // Verify the next phase via the other API
- try {
- planAligner.getNextTimedPhase(defaultSubscriptionBase, effectiveDateInThePast, internalCallContext);
- Assert.fail("Can't use getNextTimedPhase(): the effective date is before the initial plan");
- } catch (SubscriptionBaseError e) {
- Assert.assertTrue(true);
- }
-
// Try a change plan now (simulate an IMMEDIATE policy)
final String newProductName = "shotgun-monthly";
final DateTime effectiveChangeDate = clock.getUTCNow();
@@ -105,7 +101,12 @@ public class TestPlanAligner extends SubscriptionTestSuiteNoDB {
public void testCreationSubscriptionAlignment() throws Exception {
final String productName = "laser-scope-monthly";
final PhaseType initialPhase = PhaseType.DISCOUNT;
- final DefaultSubscriptionBase defaultSubscriptionBase = createSubscriptionStartedInThePast(productName, initialPhase);
+
+ final DateTime now = clock.getUTCNow();
+ final DateTime bundleStartDate = now.minusHours(10);
+ final DateTime alignStartDate = bundleStartDate.plusHours(5);
+
+ final DefaultSubscriptionBase defaultSubscriptionBase = createSubscription(bundleStartDate, alignStartDate, productName, initialPhase);
// Look now, after the bundle and the subscription started
final DateTime effectiveDate = clock.getUTCNow();
@@ -125,14 +126,6 @@ public class TestPlanAligner extends SubscriptionTestSuiteNoDB {
Assert.assertNull(phasesInThePast[0]);
Assert.assertEquals(phasesInThePast[1].getStartPhase(), defaultSubscriptionBase.getStartDate());
- // Verify the next phase via the other API
- try {
- planAligner.getNextTimedPhase(defaultSubscriptionBase, effectiveDateInThePast, internalCallContext);
- Assert.fail("Can't use getNextTimedPhase(): the effective date is before the initial plan");
- } catch (SubscriptionBaseError e) {
- Assert.assertTrue(true);
- }
-
// Try a change plan (simulate END_OF_TERM policy)
final String newProductName = "telescopic-scope-monthly";
final DateTime effectiveChangeDate = defaultSubscriptionBase.getStartDate().plusMonths(1);
@@ -146,11 +139,241 @@ public class TestPlanAligner extends SubscriptionTestSuiteNoDB {
Assert.assertNull(newPhase);
}
- private DefaultSubscriptionBase createSubscriptionStartedInThePast(final String productName, final PhaseType phaseType) throws CatalogApiException {
+
+ //
+ // Scenario : change Plan with START_OF_SUBSCRIPTION after skipping TRIAL on Create to a new Plan that only has EVERGREEN
+ //
+ @Test(groups = "fast")
+ public void testCreateWithTargetPhaseType1() throws Exception {
+ final String productName = "pistol-monthly";
+
+ final DateTime now = clock.getUTCNow();
+ final DateTime bundleStartDate = now;
+ final DateTime alignStartDate = bundleStartDate;
+
+ final Plan plan = catalogService.getFullCatalog(true, true, internalCallContext).findPlan(productName, clock.getUTCNow());
+ final TimedPhase [] phases = planAligner.getCurrentAndNextTimedPhaseOnCreate(alignStartDate, bundleStartDate, plan, PhaseType.EVERGREEN, PriceListSet.DEFAULT_PRICELIST_NAME, now, internalCallContext);
+ Assert.assertEquals(phases.length, 2);
+ Assert.assertEquals(phases[0].getPhase().getPhaseType(), PhaseType.EVERGREEN);
+ Assert.assertEquals(phases[0].getStartPhase(), now);
+ Assert.assertNull(phases[1]);
+
+ final DefaultSubscriptionBase defaultSubscriptionBase = createSubscription(bundleStartDate, alignStartDate, productName, PhaseType.EVERGREEN);
+
+ final String newProductName = "pistol-monthly-notrial";
+ final Plan newPlan = catalogService.getFullCatalog(true, true, internalCallContext).findPlan(newProductName, clock.getUTCNow());
+ final DateTime effectiveChangeDate = defaultSubscriptionBase.getStartDate().plusMonths(15);
+
+ final TimedPhase currentPhase = planAligner.getCurrentTimedPhaseOnChange(defaultSubscriptionBase, newPlan, effectiveChangeDate, null, internalCallContext);
+ Assert.assertEquals(currentPhase.getStartPhase(), alignStartDate);
+ Assert.assertEquals(currentPhase.getPhase().getPhaseType(), PhaseType.EVERGREEN);
+ }
+
+
+ //
+ // Scenario : change Plan with START_OF_SUBSCRIPTION after skipping TRIAL on Create to a new Plan that has {TRIAL, EVERGREEN}
+ //
+ @Test(groups = "fast")
+ public void testCreateWithTargetPhaseType2() throws Exception {
+ final String productName = "pistol-monthly";
+
+ final DateTime now = clock.getUTCNow();
+ final DateTime bundleStartDate = now;
+ final DateTime alignStartDate = bundleStartDate;
+
+ final Plan plan = catalogService.getFullCatalog(true, true, internalCallContext).findPlan(productName, clock.getUTCNow());
+ final TimedPhase [] phases = planAligner.getCurrentAndNextTimedPhaseOnCreate(alignStartDate, bundleStartDate, plan, PhaseType.EVERGREEN, PriceListSet.DEFAULT_PRICELIST_NAME, now, internalCallContext);
+ Assert.assertEquals(phases.length, 2);
+ Assert.assertEquals(phases[0].getPhase().getPhaseType(), PhaseType.EVERGREEN);
+ Assert.assertEquals(phases[0].getStartPhase(), now);
+ Assert.assertNull(phases[1]);
+
+ final DefaultSubscriptionBase defaultSubscriptionBase = createSubscription(bundleStartDate, alignStartDate, productName, PhaseType.EVERGREEN);
+
+ final String newProductName = "shotgun-monthly";
+ final Plan newPlan = catalogService.getFullCatalog(true, true, internalCallContext).findPlan(newProductName, clock.getUTCNow());
+ final DateTime effectiveChangeDate = defaultSubscriptionBase.getStartDate().plusMonths(15);
+
+ final TimedPhase currentPhase = planAligner.getCurrentTimedPhaseOnChange(defaultSubscriptionBase, newPlan, effectiveChangeDate, null, internalCallContext);
+ Assert.assertEquals(currentPhase.getStartPhase(), alignStartDate);
+ Assert.assertEquals(currentPhase.getPhase().getPhaseType(), PhaseType.EVERGREEN);
+ }
+
+
+ //
+ // Scenario : change Plan with START_OF_SUBSCRIPTION after skipping TRIAL on Create to a new Plan that has {TRIAL, DISCOUNT, EVERGREEN}
+ //
+ @Test(groups = "fast")
+ public void testCreateWithTargetPhaseType3() throws Exception {
+ final String productName = "pistol-monthly";
+
+ final DateTime now = clock.getUTCNow();
+ final DateTime bundleStartDate = now;
+ final DateTime alignStartDate = bundleStartDate;
+
+ final Plan plan = catalogService.getFullCatalog(true, true, internalCallContext).findPlan(productName, clock.getUTCNow());
+ final TimedPhase [] phases = planAligner.getCurrentAndNextTimedPhaseOnCreate(alignStartDate, bundleStartDate, plan, PhaseType.EVERGREEN, PriceListSet.DEFAULT_PRICELIST_NAME, now, internalCallContext);
+ Assert.assertEquals(phases.length, 2);
+ Assert.assertEquals(phases[0].getPhase().getPhaseType(), PhaseType.EVERGREEN);
+ Assert.assertEquals(phases[0].getStartPhase(), now);
+ Assert.assertNull(phases[1]);
+
+ final DefaultSubscriptionBase defaultSubscriptionBase = createSubscription(bundleStartDate, alignStartDate, productName, PhaseType.EVERGREEN);
+
+ final String newProductName = "assault-rifle-annual-gunclub-discount";
+ final Plan newPlan = catalogService.getFullCatalog(true, true, internalCallContext).findPlan(newProductName, clock.getUTCNow());
+ final DateTime effectiveChangeDate = defaultSubscriptionBase.getStartDate().plusMonths(15);
+
+ // Because new Plan has an EVERGREEN PhaseType we end up directly on that PhaseType
+ final TimedPhase currentPhase = planAligner.getCurrentTimedPhaseOnChange(defaultSubscriptionBase, newPlan, effectiveChangeDate, null, internalCallContext);
+ Assert.assertEquals(currentPhase.getStartPhase(), alignStartDate);
+ Assert.assertEquals(currentPhase.getPhase().getPhaseType(), PhaseType.EVERGREEN);
+
+ }
+
+ //
+ // Scenario : change Plan with START_OF_SUBSCRIPTION after skipping TRIAL on Create to a new Plan that has {DISCOUNT, EVERGREEN}
+ //
+ @Test(groups = "fast")
+ public void testCreateWithTargetPhaseType4() throws Exception {
+ final String productName = "pistol-monthly";
+
+ final DateTime now = clock.getUTCNow();
+ final DateTime bundleStartDate = now;
+ final DateTime alignStartDate = bundleStartDate;
+
+ final Plan plan = catalogService.getFullCatalog(true, true, internalCallContext).findPlan(productName, clock.getUTCNow());
+ final TimedPhase [] phases = planAligner.getCurrentAndNextTimedPhaseOnCreate(alignStartDate, bundleStartDate, plan, PhaseType.EVERGREEN, PriceListSet.DEFAULT_PRICELIST_NAME, now, internalCallContext);
+ Assert.assertEquals(phases.length, 2);
+ Assert.assertEquals(phases[0].getPhase().getPhaseType(), PhaseType.EVERGREEN);
+ Assert.assertEquals(phases[0].getStartPhase(), now);
+ Assert.assertNull(phases[1]);
+
+ final DefaultSubscriptionBase defaultSubscriptionBase = createSubscription(bundleStartDate, alignStartDate, productName, PhaseType.EVERGREEN);
+
+ final String newProductName = "pistol-annual-gunclub-discount-notrial";
+ final Plan newPlan = catalogService.getFullCatalog(true, true, internalCallContext).findPlan(newProductName, clock.getUTCNow());
+ final DateTime effectiveChangeDate = defaultSubscriptionBase.getStartDate().plusMonths(15);
+
+ // Because new Plan has an EVERGREEN PhaseType we end up directly on that PhaseType
+ final TimedPhase currentPhase = planAligner.getCurrentTimedPhaseOnChange(defaultSubscriptionBase, newPlan, effectiveChangeDate, null, internalCallContext);
+ Assert.assertEquals(currentPhase.getStartPhase(), alignStartDate);
+ Assert.assertEquals(currentPhase.getPhase().getPhaseType(), PhaseType.EVERGREEN);
+ }
+
+ //
+ // Scenario : change Plan with START_OF_SUBSCRIPTION after skipping TRIAL on Create to a new Plan that has {TRIAL, FIXEDTERM}
+ //
+ @Test(groups = "fast")
+ public void testCreateWithTargetPhaseType5() throws Exception {
+ final String productName = "pistol-monthly";
+
+ final DateTime now = clock.getUTCNow();
+ final DateTime bundleStartDate = now;
+ final DateTime alignStartDate = bundleStartDate;
+
+ final Plan plan = catalogService.getFullCatalog(true, true, internalCallContext).findPlan(productName, clock.getUTCNow());
+ final TimedPhase [] phases = planAligner.getCurrentAndNextTimedPhaseOnCreate(alignStartDate, bundleStartDate, plan, PhaseType.EVERGREEN, PriceListSet.DEFAULT_PRICELIST_NAME, now, internalCallContext);
+ Assert.assertEquals(phases.length, 2);
+ Assert.assertEquals(phases[0].getPhase().getPhaseType(), PhaseType.EVERGREEN);
+ Assert.assertEquals(phases[0].getStartPhase(), now);
+ Assert.assertNull(phases[1]);
+
+ final DefaultSubscriptionBase defaultSubscriptionBase = createSubscription(bundleStartDate, alignStartDate, productName, PhaseType.EVERGREEN);
+
+ final String newProductName = "pistol-monthly-fixedterm";
+ final Plan newPlan = catalogService.getFullCatalog(true, true, internalCallContext).findPlan(newProductName, clock.getUTCNow());
+ final DateTime effectiveChangeDate = defaultSubscriptionBase.getStartDate().plusDays(5);
+
+ final TimedPhase currentPhase = planAligner.getCurrentTimedPhaseOnChange(defaultSubscriptionBase, newPlan, effectiveChangeDate, null, internalCallContext);
+
+ // Initial phase EVERGREEN does not exist in the new Plan so we ignore the original skipped Phase and proceed with default alignment (we only move the clock 5 days so we are still in TRIAL)
+ Assert.assertEquals(currentPhase.getStartPhase(), alignStartDate);
+ Assert.assertEquals(currentPhase.getPhase().getPhaseType(), PhaseType.TRIAL);
+
+ final TimedPhase nextPhase = planAligner.getNextTimedPhaseOnChange(defaultSubscriptionBase, newPlan, effectiveChangeDate, null, internalCallContext);
+ Assert.assertEquals(nextPhase.getStartPhase(), alignStartDate.plusDays(30));
+ Assert.assertEquals(nextPhase.getPhase().getPhaseType(), PhaseType.FIXEDTERM);
+ }
+
+
+ //
+ // Scenario : change Plan with START_OF_SUBSCRIPTION to a new Plan that has {TRIAL, DISCOUNT, EVERGREEN} and specifying a target PhaseType = DISCOUNT
+ //
+ @Test(groups = "fast")
+ public void testChangeWithTargetPhaseType1() throws Exception {
+ final String productName = "pistol-monthly";
+
+ final DateTime now = clock.getUTCNow();
+ final DateTime bundleStartDate = now;
+ final DateTime alignStartDate = bundleStartDate;
+
+ final Plan plan = catalogService.getFullCatalog(true, true, internalCallContext).findPlan(productName, clock.getUTCNow());
+ final TimedPhase [] phases = planAligner.getCurrentAndNextTimedPhaseOnCreate(alignStartDate, bundleStartDate, plan, null, PriceListSet.DEFAULT_PRICELIST_NAME, now, internalCallContext);
+ Assert.assertEquals(phases.length, 2);
+ Assert.assertEquals(phases[0].getPhase().getPhaseType(), PhaseType.TRIAL);
+ Assert.assertEquals(phases[0].getStartPhase(), now);
+ Assert.assertEquals(phases[1].getPhase().getPhaseType(), PhaseType.EVERGREEN);
+ Assert.assertEquals(phases[1].getStartPhase(), now.plusDays(30));
+
+ final DefaultSubscriptionBase defaultSubscriptionBase = createSubscription(bundleStartDate, alignStartDate, productName, PhaseType.TRIAL);
+
+ final String newProductName = "pistol-annual-gunclub-discount";
+ final Plan newPlan = catalogService.getFullCatalog(true, true, internalCallContext).findPlan(newProductName, clock.getUTCNow());
+ final DateTime effectiveChangeDate = defaultSubscriptionBase.getStartDate().plusDays(5);
+
+ final TimedPhase currentPhase = planAligner.getCurrentTimedPhaseOnChange(defaultSubscriptionBase, newPlan, effectiveChangeDate, PhaseType.DISCOUNT, internalCallContext);
+
+ // We end up straight on DISCOUNT but because we are using START_OF_SUBSCRIPTION alignment, such Phase starts with beginning of subscription
+ Assert.assertEquals(currentPhase.getStartPhase(), alignStartDate);
+ Assert.assertEquals(currentPhase.getPhase().getPhaseType(), PhaseType.DISCOUNT);
+
+ final TimedPhase nextPhase = planAligner.getNextTimedPhaseOnChange(defaultSubscriptionBase, newPlan, effectiveChangeDate, PhaseType.DISCOUNT, internalCallContext);
+ Assert.assertEquals(nextPhase.getStartPhase(), alignStartDate.plusMonths(6));
+ Assert.assertEquals(nextPhase.getPhase().getPhaseType(), PhaseType.EVERGREEN);
+ }
+
+ //
+ // Scenario : change Plan with CHANGE_OF_PLAN to a new Plan that has {DISCOUNT, EVERGREEN} and specifying a target PhaseType = EVERGREEN
+ //
+ @Test(groups = "fast")
+ public void testChangeWithTargetPhaseType2() throws Exception {
+ final String productName = "pistol-monthly";
+
+ final DateTime now = clock.getUTCNow();
+ final DateTime bundleStartDate = now;
+ final DateTime alignStartDate = bundleStartDate;
+
+ final Plan plan = catalogService.getFullCatalog(true, true, internalCallContext).findPlan(productName, clock.getUTCNow());
+ final TimedPhase [] phases = planAligner.getCurrentAndNextTimedPhaseOnCreate(alignStartDate, bundleStartDate, plan, null, PriceListSet.DEFAULT_PRICELIST_NAME, now, internalCallContext);
+ Assert.assertEquals(phases.length, 2);
+ Assert.assertEquals(phases[0].getPhase().getPhaseType(), PhaseType.TRIAL);
+ Assert.assertEquals(phases[0].getStartPhase(), now);
+ Assert.assertEquals(phases[1].getPhase().getPhaseType(), PhaseType.EVERGREEN);
+ Assert.assertEquals(phases[1].getStartPhase(), now.plusDays(30));
+
+ final DefaultSubscriptionBase defaultSubscriptionBase = createSubscription(bundleStartDate, alignStartDate, productName, PhaseType.TRIAL);
+
+ final String newProductName = "assault-rifle-annual-rescue";
+ final Plan newPlan = catalogService.getFullCatalog(true, true, internalCallContext).findPlan(newProductName, clock.getUTCNow());
+ final DateTime effectiveChangeDate = defaultSubscriptionBase.getStartDate().plusDays(5);
+
+ final TimedPhase currentPhase = planAligner.getCurrentTimedPhaseOnChange(defaultSubscriptionBase, newPlan, effectiveChangeDate, PhaseType.EVERGREEN, internalCallContext);
+
+ // We end up straight on EVERGREEN Phase and because we are CHANGE_OF_PLAN aligned the start is at the effective date of the change
+ Assert.assertEquals(currentPhase.getStartPhase(), alignStartDate.plusDays(5));
+ Assert.assertEquals(currentPhase.getPhase().getPhaseType(), PhaseType.EVERGREEN);
+
+ final TimedPhase nextPhase = planAligner.getNextTimedPhaseOnChange(defaultSubscriptionBase, newPlan, effectiveChangeDate, PhaseType.EVERGREEN, internalCallContext);
+ Assert.assertNull(nextPhase);
+ }
+
+
+ private DefaultSubscriptionBase createSubscription(final DateTime bundleStartDate, final DateTime alignStartDate, final String productName, final PhaseType phaseType) throws CatalogApiException {
final SubscriptionBuilder builder = new SubscriptionBuilder();
- builder.setBundleStartDate(clock.getUTCNow().minusHours(10));
+ builder.setBundleStartDate(bundleStartDate);
// Make sure to set the dates apart
- builder.setAlignStartDate(new DateTime(builder.getBundleStartDate().plusHours(5)));
+ builder.setAlignStartDate(new DateTime(alignStartDate));
// Create the transitions
final DefaultSubscriptionBase defaultSubscriptionBase = new DefaultSubscriptionBase(builder, null, clock);
@@ -221,7 +444,7 @@ public class TestPlanAligner extends SubscriptionTestSuiteNoDB {
// The date is used for different catalog versions - we don't care here
final Plan newPlan = catalogService.getFullCatalog(true, true, internalCallContext).findPlan(newProductName, clock.getUTCNow());
- return planAligner.getNextTimedPhaseOnChange(defaultSubscriptionBase, newPlan, priceList, effectiveChangeDate, internalCallContext);
+ return planAligner.getNextTimedPhaseOnChange(defaultSubscriptionBase, newPlan, effectiveChangeDate, null, internalCallContext);
}
private TimedPhase[] getTimedPhasesOnCreate(final String productName,
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/api/svcs/TestEffectiveDateForNewBCD.java b/subscription/src/test/java/org/killbill/billing/subscription/api/svcs/TestEffectiveDateForNewBCD.java
new file mode 100644
index 0000000..f00851a
--- /dev/null
+++ b/subscription/src/test/java/org/killbill/billing/subscription/api/svcs/TestEffectiveDateForNewBCD.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.subscription.api.svcs;
+
+import org.joda.time.DateTime;
+import org.joda.time.LocalDate;
+import org.killbill.billing.subscription.SubscriptionTestSuiteNoDB;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+//
+// Test all possible combinations of input effectiveDate and new BCD:
+// (We call ComputedEffectiveDay the original day in the month extracted from the input effectiveDate)
+//
+// - effectiveDate = {null (present), past, future}
+// - newBCD {< ComputedEffectiveDay, equals ComputedEffectiveDay, > ComputedEffectiveDay, > End Of Month}
+//
+// => 12 possible tests
+//
+public class TestEffectiveDateForNewBCD extends SubscriptionTestSuiteNoDB {
+
+ @Test(groups = "fast")
+ public void testNullEffectiveDateWithBCDPriorComputedEffectiveDay() throws Exception {
+
+ // Set by test as : 2012-05-07T00:03:42.000Z
+ final DateTime now = clock.getUTCNow();
+
+ int newBCD = 3;
+ // effectiveDate = 2012-05-07T00:03:42.000Z => ComputedEffectiveDay = 7
+ LocalDate effectiveDate = null;
+
+ // newBCD < ComputedEffectiveDay
+ final DateTime result = ((DefaultSubscriptionInternalApi) subscriptionInternalApi).getEffectiveDateForNewBCD(newBCD, effectiveDate, internalCallContext);
+
+ Assert.assertEquals(result, internalCallContext.toUTCDateTime(new LocalDate("2012-06-03")));
+ }
+
+ @Test(groups = "fast")
+ public void testNullEffectiveDateWithBCDEqualsComputedEffectiveDay() throws Exception {
+
+ // Set by test as : 2012-05-07T00:03:42.000Z
+ final DateTime now = clock.getUTCNow();
+
+ int newBCD = 7;
+ // effectiveDate = 2012-05-07T00:03:42.000Z => ComputedEffectiveDay = 7
+ LocalDate effectiveDate = null;
+
+ // newBCD == ComputedEffectiveDay
+ final DateTime result = ((DefaultSubscriptionInternalApi) subscriptionInternalApi).getEffectiveDateForNewBCD(newBCD, effectiveDate, internalCallContext);
+
+ final DateTime nowAfterCall = clock.getUTCNow();
+
+ // In that case because we want the event to fire right NOW, we don't use the account reference time but instead use the clock.getUTCNOW(). In case test
+ // takes longer than 1 mSec we need to check a (very small) range of dates
+ Assert.assertTrue(result.compareTo(now) >= 0);
+ Assert.assertTrue(result.compareTo(nowAfterCall) <= 0);
+ }
+
+ @Test(groups = "fast")
+ public void testNullEffectiveDateWithBCDAfterComputedEffectiveDay() throws Exception {
+
+ // Set by test as : 2012-05-07T00:03:42.000Z => New time = 2012-06-07T00:03:42.000Z (and june only has 30 days)
+ clock.addMonths(1);
+
+ int newBCD = 31;
+ // effectiveDate = 2012-06-07T00:03:42.000Z => ComputedEffectiveDay = 7
+ LocalDate effectiveDate = null;
+
+ // newBCD > 30 (max day in June)
+ final DateTime result = ((DefaultSubscriptionInternalApi) subscriptionInternalApi).getEffectiveDateForNewBCD(newBCD, effectiveDate, internalCallContext);
+
+ Assert.assertEquals(result, internalCallContext.toUTCDateTime(new LocalDate("2012-06-30")));
+ }
+
+ @Test(groups = "fast")
+ public void testNullEffectiveDateWithBCDAfterEndOfTheMonth() throws Exception {
+
+ // Set by test as : 2012-05-07T00:03:42.000Z
+ final DateTime now = clock.getUTCNow();
+
+ int newBCD = 10;
+ // effectiveDate = 2012-05-07T00:03:42.000Z => ComputedEffectiveDay = 7
+ LocalDate effectiveDate = null;
+
+ // newBCD < ComputedEffectiveDay
+ final DateTime result = ((DefaultSubscriptionInternalApi) subscriptionInternalApi).getEffectiveDateForNewBCD(newBCD, effectiveDate, internalCallContext);
+
+ Assert.assertEquals(result, internalCallContext.toUTCDateTime(new LocalDate("2012-05-10")));
+ }
+
+ @Test(groups = "fast")
+ public void testFutureEffectiveDateWithBCDPriorComputedEffectiveDay() throws Exception {
+
+ // Set by test as : 2012-05-07T00:03:42.000Z
+ final DateTime now = clock.getUTCNow();
+
+ int newBCD = 3;
+ // effectiveDate = 2012-05-07T00:03:42.000Z => ComputedEffectiveDay = 7
+ LocalDate effectiveDate = new LocalDate(2012, 7, 7);
+
+ // newBCD < ComputedEffectiveDay
+ final DateTime result = ((DefaultSubscriptionInternalApi) subscriptionInternalApi).getEffectiveDateForNewBCD(newBCD, effectiveDate, internalCallContext);
+
+ Assert.assertEquals(result, internalCallContext.toUTCDateTime(new LocalDate("2012-08-03")));
+ }
+
+ @Test(groups = "fast")
+ public void testFutureEffectiveDateWithBCDEqualsComputedEffectiveDay() throws Exception {
+
+ // Set by test as : 2012-05-07T00:03:42.000Z
+ final DateTime now = clock.getUTCNow();
+
+ int newBCD = 3;
+ // effectiveDate = 2012-05-07T00:03:42.000Z => ComputedEffectiveDay = 7
+ LocalDate effectiveDate = new LocalDate(2012, 7, 3);
+
+ // newBCD == ComputedEffectiveDay
+ final DateTime result = ((DefaultSubscriptionInternalApi) subscriptionInternalApi).getEffectiveDateForNewBCD(newBCD, effectiveDate, internalCallContext);
+
+ Assert.assertEquals(result, internalCallContext.toUTCDateTime(new LocalDate("2012-07-03")));
+ }
+
+ @Test(groups = "fast")
+ public void testFutureEffectiveDateWithBCDAfterComputedEffectiveDay() throws Exception {
+
+ // Set by test as : 2012-05-07T00:03:42.000Z
+ final DateTime now = clock.getUTCNow();
+
+ int newBCD = 10;
+ // effectiveDate = 2012-05-07T00:03:42.000Z => ComputedEffectiveDay = 7
+ LocalDate effectiveDate = new LocalDate(2012, 7, 3);
+
+ // newBCD > ComputedEffectiveDay
+ final DateTime result = ((DefaultSubscriptionInternalApi) subscriptionInternalApi).getEffectiveDateForNewBCD(newBCD, effectiveDate, internalCallContext);
+
+ Assert.assertEquals(result, internalCallContext.toUTCDateTime(new LocalDate("2012-07-10")));
+ }
+
+ @Test(groups = "fast")
+ public void testFutureEffectiveDateWithBCDAfterEndOfTheMonth() throws Exception {
+
+ // Set by test as : 2012-05-07T00:03:42.000Z
+ final DateTime now = clock.getUTCNow();
+
+ int newBCD = 31;
+ // effectiveDate = 2012-06-03 => ComputedEffectiveDay = 3
+ LocalDate effectiveDate = new LocalDate(2012, 6, 3);
+
+ // newBCD > 30 (max day in June)
+ final DateTime result = ((DefaultSubscriptionInternalApi) subscriptionInternalApi).getEffectiveDateForNewBCD(newBCD, effectiveDate, internalCallContext);
+
+ Assert.assertEquals(result, internalCallContext.toUTCDateTime(new LocalDate("2012-06-30")));
+ }
+
+ @Test(groups = "fast")
+ public void testPastEffectiveDateWithBCDPriorComputedEffectiveDay() throws Exception {
+
+ // Set by test as : 2012-05-07T00:03:42.000Z
+ final DateTime now = clock.getUTCNow();
+
+ int newBCD = 3;
+ // effectiveDate = 2012-05-07T00:03:42.000Z => ComputedEffectiveDay = 7
+ LocalDate effectiveDate = new LocalDate(2012, 2, 7);
+
+ // newBCD < ComputedEffectiveDay
+ final DateTime result = ((DefaultSubscriptionInternalApi) subscriptionInternalApi).getEffectiveDateForNewBCD(newBCD, effectiveDate, internalCallContext);
+
+ Assert.assertEquals(result, internalCallContext.toUTCDateTime(new LocalDate("2012-03-03")));
+ }
+
+ @Test(groups = "fast")
+ public void testPastEffectiveDateWithBCDEqualsComputedEffectiveDay() throws Exception {
+
+ // Set by test as : 2012-05-07T00:03:42.000Z
+ final DateTime now = clock.getUTCNow();
+
+ int newBCD = 3;
+ // effectiveDate = 2012-05-07T00:03:42.000Z => ComputedEffectiveDay = 7
+ LocalDate effectiveDate = new LocalDate(2012, 2, 3);
+
+ // newBCD == ComputedEffectiveDay
+ final DateTime result = ((DefaultSubscriptionInternalApi) subscriptionInternalApi).getEffectiveDateForNewBCD(newBCD, effectiveDate, internalCallContext);
+
+ Assert.assertEquals(result, internalCallContext.toUTCDateTime(new LocalDate("2012-02-03")));
+ }
+
+ @Test(groups = "fast")
+ public void testPastEffectiveDateWithBCDAfterComputedEffectiveDay() throws Exception {
+
+ // Set by test as : 2012-05-07T00:03:42.000Z
+ final DateTime now = clock.getUTCNow();
+
+ int newBCD = 10;
+ // effectiveDate = 2012-05-07T00:03:42.000Z => ComputedEffectiveDay = 7
+ LocalDate effectiveDate = new LocalDate(2012, 2, 3);
+
+ // newBCD > ComputedEffectiveDay
+ final DateTime result = ((DefaultSubscriptionInternalApi) subscriptionInternalApi).getEffectiveDateForNewBCD(newBCD, effectiveDate, internalCallContext);
+
+ Assert.assertEquals(result, internalCallContext.toUTCDateTime(new LocalDate("2012-02-10")));
+ }
+
+ @Test(groups = "fast")
+ public void testPastEffectiveDateWithBCDAfterEndOfTheMonth() throws Exception {
+
+ // Set by test as : 2012-05-07T00:03:42.000Z
+ final DateTime now = clock.getUTCNow();
+
+ int newBCD = 31;
+ // effectiveDate = 2012-02-03 => ComputedEffectiveDay = 3
+ LocalDate effectiveDate = new LocalDate(2012, 2, 3);
+
+ // newBCD > 30 (max day in June)
+ final DateTime result = ((DefaultSubscriptionInternalApi) subscriptionInternalApi).getEffectiveDateForNewBCD(newBCD, effectiveDate, internalCallContext);
+
+ Assert.assertEquals(result, internalCallContext.toUTCDateTime(new LocalDate("2012-2-29")));
+ }
+
+}
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/api/TestEventJson.java b/subscription/src/test/java/org/killbill/billing/subscription/api/TestEventJson.java
index 772055c..941b6cb 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/api/TestEventJson.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/api/TestEventJson.java
@@ -36,7 +36,7 @@ public class TestEventJson extends SubscriptionTestSuiteNoDB {
@Test(groups = "fast")
public void testSubscriptionEvent() throws Exception {
- final EffectiveSubscriptionInternalEvent e = new DefaultEffectiveSubscriptionEvent(UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID(), new DateTime(),
+ final EffectiveSubscriptionInternalEvent e = new DefaultEffectiveSubscriptionEvent(UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID(), null, new DateTime(),
EntitlementState.ACTIVE, "pro", "TRIAL", "DEFAULT", null, EntitlementState.CANCELLED, null, null, null, null, 3L,
SubscriptionBaseTransitionType.CANCEL, 0, new DateTime(), 1L, 2L, null);
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 af4ddbb..be86546 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
@@ -43,6 +43,7 @@ import org.testng.Assert;
import org.testng.annotations.Test;
import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertNull;
import static org.testng.Assert.assertTrue;
@@ -384,6 +385,48 @@ public class TestUserApiCancel extends SubscriptionTestSuiteWithEmbeddedDB {
subscription = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(subscription.getId(), internalCallContext);
Assert.assertEquals(subscription.getAllTransitions().get(subscription.getAllTransitions().size() - 1).getTransitionType(), SubscriptionBaseTransitionType.CANCEL);
Assert.assertEquals(new LocalDate(subscription.getAllTransitions().get(subscription.getAllTransitions().size() - 1).getEffectiveTransitionTime(), accountData.getTimeZone()), new LocalDate(2016, 12, 1));
+ }
+
+ @Test(groups = "slow")
+ public void testCancelUncancelFutureSubscription() throws SubscriptionBaseApiException {
+ final DateTime init = clock.getUTCNow();
+
+ final String productName = "Shotgun";
+ final BillingPeriod term = BillingPeriod.MONTHLY;
+ final String planSetName = PriceListSet.DEFAULT_PRICELIST_NAME;
+
+
+ final DateTime futureCreationDate = init.plusDays(10);
+
+ DefaultSubscriptionBase subscription = (DefaultSubscriptionBase) subscriptionInternalApi.createSubscription(bundle.getId(),
+ testUtil.getProductSpecifier(productName, planSetName, term, null), null, futureCreationDate, false, internalCallContext);
+ assertListenerStatus();
+ assertNotNull(subscription);
+ assertEquals(subscription.getState(), EntitlementState.PENDING);
+
+ // Cancel / Uncancel a few times to make sure this works and we end up on a stable state
+ for (int i = 0; i < 3; i++) {
+ subscription.cancelWithPolicy(BillingActionPolicy.IMMEDIATE, null, -1, callContext);
+
+ subscription = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(subscription.getId(), internalCallContext);
+ assertEquals(subscription.getState(), EntitlementState.PENDING);
+
+ subscription.uncancel(callContext);
+ }
+
+ // Now check we are on the right state (as if nothing had happened)
+ testListener.pushExpectedEvents(NextEvent.CREATE, NextEvent.UNCANCEL, NextEvent.UNCANCEL, NextEvent.UNCANCEL);
+ clock.addDays(10);
+ assertListenerStatus();
+
+ subscription = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(subscription.getId(), internalCallContext);
+ assertEquals(subscription.getState(), EntitlementState.ACTIVE);
+
+ testListener.pushExpectedEvent(NextEvent.PHASE);
+ clock.addMonths(1);
+ assertListenerStatus();
+
+
}
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/engine/dao/MockSubscriptionDaoMemory.java b/subscription/src/test/java/org/killbill/billing/subscription/engine/dao/MockSubscriptionDaoMemory.java
index 8fc4b4b..271bcad 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/engine/dao/MockSubscriptionDaoMemory.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/engine/dao/MockSubscriptionDaoMemory.java
@@ -351,6 +351,11 @@ public class MockSubscriptionDaoMemory extends MockEntityDaoBase<SubscriptionBun
}
@Override
+ public void notifyOnBasePlanEvent(final DefaultSubscriptionBase subscription, final SubscriptionBaseEvent event, final InternalCallContext context) {
+ notifyBusOfEffectiveImmediateChange(subscription, event, subscriptions.size(), context);
+ }
+
+ @Override
public void cancelSubscriptions(final List<DefaultSubscriptionBase> subscriptions, final List<SubscriptionBaseEvent> cancelEvents, final InternalCallContext context) {
synchronized (events) {
for (int i = 0; i < subscriptions.size(); i++) {
tenant/pom.xml 2(+1 -1)
diff --git a/tenant/pom.xml b/tenant/pom.xml
index 82faa89..d12ae39 100644
--- a/tenant/pom.xml
+++ b/tenant/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.18.2-SNAPSHOT</version>
+ <version>0.18.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-tenant</artifactId>
usage/pom.xml 2(+1 -1)
diff --git a/usage/pom.xml b/usage/pom.xml
index 8cade0f..386fbcb 100644
--- a/usage/pom.xml
+++ b/usage/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.18.2-SNAPSHOT</version>
+ <version>0.18.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-usage</artifactId>
util/pom.xml 2(+1 -1)
diff --git a/util/pom.xml b/util/pom.xml
index de7529f..c7237a3 100644
--- a/util/pom.xml
+++ b/util/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.18.2-SNAPSHOT</version>
+ <version>0.18.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-util</artifactId>
diff --git a/util/src/main/java/org/killbill/billing/util/config/definition/JaxrsConfig.java b/util/src/main/java/org/killbill/billing/util/config/definition/JaxrsConfig.java
index 95fe22d..ba941a2 100644
--- a/util/src/main/java/org/killbill/billing/util/config/definition/JaxrsConfig.java
+++ b/util/src/main/java/org/killbill/billing/util/config/definition/JaxrsConfig.java
@@ -19,6 +19,7 @@ package org.killbill.billing.util.config.definition;
import org.skife.config.Config;
import org.skife.config.Default;
+import org.skife.config.DefaultNull;
import org.skife.config.Description;
import org.skife.config.TimeSpan;
@@ -39,4 +40,13 @@ public interface JaxrsConfig extends KillbillConfig {
@Description("Type of return for the jaxrs response location URL")
boolean isJaxrsLocationFullUrl();
+ @Config("org.killbill.jaxrs.location.useForwardHeaders")
+ @Default("true")
+ @Description("Whether to respect X-Forwarded headers for redirect URLs")
+ boolean isJaxrsLocationUseForwardHeaders();
+
+ @Config("org.killbill.jaxrs.location.host")
+ @DefaultNull
+ @Description("Base host address to use for redirect URLs")
+ String getJaxrsLocationHost();
}
diff --git a/util/src/main/java/org/killbill/billing/util/export/dao/CSVExportOutputStream.java b/util/src/main/java/org/killbill/billing/util/export/dao/CSVExportOutputStream.java
index 154415e..1bde787 100644
--- a/util/src/main/java/org/killbill/billing/util/export/dao/CSVExportOutputStream.java
+++ b/util/src/main/java/org/killbill/billing/util/export/dao/CSVExportOutputStream.java
@@ -66,6 +66,8 @@ public class CSVExportOutputStream extends OutputStream implements DatabaseExpor
// Remove quoting of character which applies (somewhat arbitrarily, Tatu???) for string whose length is greater than MAX_QUOTE_CHECK = 24 -- See CVSWriter#_mayNeedQuotes
builder.disableQuoteChar();
+ builder.setColumnSeparator('|');
+
for (final ColumnInfo columnInfo : columnsForTable) {
builder.addColumn(columnInfo.getColumnName(), getColumnTypeFromSqlType(columnInfo.getDataType()));
}
diff --git a/util/src/main/java/org/killbill/billing/util/security/shiro/dao/DefaultUserDao.java b/util/src/main/java/org/killbill/billing/util/security/shiro/dao/DefaultUserDao.java
index 07c8737..7dc71e7 100644
--- a/util/src/main/java/org/killbill/billing/util/security/shiro/dao/DefaultUserDao.java
+++ b/util/src/main/java/org/killbill/billing/util/security/shiro/dao/DefaultUserDao.java
@@ -66,7 +66,7 @@ public class DefaultUserDao implements UserDao {
password, salt.toBase64(), securityConfig.getShiroNbHashIterations()).toBase64();
final DateTime createdDate = clock.getUTCNow();
- dbi.inTransaction(new TransactionCallback<Void>() {
+ inTransactionWithExceptionHandling(new TransactionCallback<Void>() {
@Override
public Void inTransaction(final Handle handle, final TransactionStatus status) throws Exception {
final UserRolesSqlDao userRolesSqlDao = handle.attach(UserRolesSqlDao.class);
@@ -96,9 +96,9 @@ public class DefaultUserDao implements UserDao {
}
@Override
- public void addRoleDefinition(final String role, final List<String> permissions, final String createdBy) {
+ public void addRoleDefinition(final String role, final List<String> permissions, final String createdBy) throws SecurityApiException {
final DateTime createdDate = clock.getUTCNow();
- dbi.inTransaction(new TransactionCallback<Void>() {
+ inTransactionWithExceptionHandling(new TransactionCallback<Void>() {
@Override
public Void inTransaction(final Handle handle, final TransactionStatus status) throws Exception {
final RolesPermissionsSqlDao rolesPermissionsSqlDao = handle.attach(RolesPermissionsSqlDao.class);
@@ -132,7 +132,7 @@ public class DefaultUserDao implements UserDao {
final String hashedPasswordBase64 = new SimpleHash(KillbillCredentialsMatcher.HASH_ALGORITHM_NAME,
password, salt.toBase64(), securityConfig.getShiroNbHashIterations()).toBase64();
- dbi.inTransaction(new TransactionCallback<Void>() {
+ inTransactionWithExceptionHandling(new TransactionCallback<Void>() {
@Override
public Void inTransaction(final Handle handle, final TransactionStatus status) throws Exception {
@@ -150,7 +150,7 @@ public class DefaultUserDao implements UserDao {
@Override
public void updateUserRoles(final String username, final List<String> roles, final String updatedBy) throws SecurityApiException {
- dbi.inTransaction(new TransactionCallback<Void>() {
+ inTransactionWithExceptionHandling(new TransactionCallback<Void>() {
@Override
public Void inTransaction(final Handle handle, final TransactionStatus status) throws Exception {
final DateTime updatedDate = clock.getUTCNow();
@@ -192,7 +192,7 @@ public class DefaultUserDao implements UserDao {
@Override
public void invalidateUser(final String username, final String updatedBy) throws SecurityApiException {
- dbi.inTransaction(new TransactionCallback<Void>() {
+ inTransactionWithExceptionHandling(new TransactionCallback<Void>() {
@Override
public Void inTransaction(final Handle handle, final TransactionStatus status) throws Exception {
final DateTime updatedDate = clock.getUTCNow();
@@ -206,4 +206,24 @@ public class DefaultUserDao implements UserDao {
}
});
}
+
+ private <T> T inTransactionWithExceptionHandling(final TransactionCallback<T> callback) throws SecurityApiException {
+ // Similar to EntitySqlDaoTransactionalJdbiWrapper#execute
+ try {
+ return dbi.inTransaction(callback);
+ } catch (final RuntimeException e) {
+ throwSecurityApiException(e);
+ return null;
+ }
+ }
+
+ private void throwSecurityApiException(final Throwable e) throws SecurityApiException {
+ if (e.getCause() != null && e.getCause().getClass().isAssignableFrom(SecurityApiException.class)) {
+ throw (SecurityApiException) e.getCause();
+ } else if (e.getCause() != null) {
+ throwSecurityApiException(e.getCause());
+ } else {
+ throw e instanceof RuntimeException ? (RuntimeException) e : new RuntimeException(e);
+ }
+ }
}
diff --git a/util/src/main/java/org/killbill/billing/util/security/shiro/dao/UserDao.java b/util/src/main/java/org/killbill/billing/util/security/shiro/dao/UserDao.java
index bb0ab0d..142e13d 100644
--- a/util/src/main/java/org/killbill/billing/util/security/shiro/dao/UserDao.java
+++ b/util/src/main/java/org/killbill/billing/util/security/shiro/dao/UserDao.java
@@ -27,7 +27,7 @@ public interface UserDao {
public List<UserRolesModelDao> getUserRoles(String username);
- public void addRoleDefinition(String role, List<String> permissions, String createdBy);
+ public void addRoleDefinition(String role, List<String> permissions, String createdBy) throws SecurityApiException;
public List<RolesPermissionsModelDao> getRoleDefinition(String role);
diff --git a/util/src/main/java/org/killbill/billing/util/tag/api/DefaultTagUserApi.java b/util/src/main/java/org/killbill/billing/util/tag/api/DefaultTagUserApi.java
index 068c90a..b461cc2 100644
--- a/util/src/main/java/org/killbill/billing/util/tag/api/DefaultTagUserApi.java
+++ b/util/src/main/java/org/killbill/billing/util/tag/api/DefaultTagUserApi.java
@@ -37,6 +37,7 @@ import org.killbill.billing.util.tag.DefaultTagDefinition;
import org.killbill.billing.util.tag.DescriptiveTag;
import org.killbill.billing.util.tag.Tag;
import org.killbill.billing.util.tag.TagDefinition;
+import org.killbill.billing.util.tag.dao.SystemTags;
import org.killbill.billing.util.tag.dao.TagDao;
import org.killbill.billing.util.tag.dao.TagDefinitionDao;
import org.killbill.billing.util.tag.dao.TagDefinitionModelDao;
@@ -74,7 +75,7 @@ public class DefaultTagUserApi implements TagUserApi {
@Override
public List<TagDefinition> getTagDefinitions(final TenantContext context) {
- return ImmutableList.<TagDefinition>copyOf(Collections2.transform(tagDefinitionDao.getTagDefinitions(internalCallContextFactory.createInternalTenantContextWithoutAccountRecordId(context)),
+ return ImmutableList.<TagDefinition>copyOf(Collections2.transform(tagDefinitionDao.getTagDefinitions(false, internalCallContextFactory.createInternalTenantContextWithoutAccountRecordId(context)),
new Function<TagDefinitionModelDao, TagDefinition>() {
@Override
public TagDefinition apply(final TagDefinitionModelDao input) {
@@ -125,6 +126,12 @@ public class DefaultTagUserApi implements TagUserApi {
@Override
public void addTag(final UUID objectId, final ObjectType objectType, final UUID tagDefinitionId, final CallContext context) throws TagApiException {
+
+ if (SystemTags.isSystemTag(tagDefinitionId)) {
+ // TODO Create a proper ErrorCode instaed
+ throw new IllegalStateException(String.format("Failed to add tag for tagDefinitionId='%s': System tags are reserved for the system.", tagDefinitionId));
+ }
+
final InternalCallContext internalContext = internalCallContextFactory.createInternalCallContext(objectId, objectType, context);
final TagModelDao tag = new TagModelDao(context.getCreatedDate(), tagDefinitionId, objectId, objectType);
try {
diff --git a/util/src/main/java/org/killbill/billing/util/tag/dao/DefaultTagDefinitionDao.java b/util/src/main/java/org/killbill/billing/util/tag/dao/DefaultTagDefinitionDao.java
index e406380..6c78854 100644
--- a/util/src/main/java/org/killbill/billing/util/tag/dao/DefaultTagDefinitionDao.java
+++ b/util/src/main/java/org/killbill/billing/util/tag/dao/DefaultTagDefinitionDao.java
@@ -68,7 +68,7 @@ public class DefaultTagDefinitionDao extends EntityDaoBase<TagDefinitionModelDao
}
@Override
- public List<TagDefinitionModelDao> getTagDefinitions(final InternalTenantContext context) {
+ public List<TagDefinitionModelDao> getTagDefinitions(final boolean includeSystemTags, final InternalTenantContext context) {
return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<List<TagDefinitionModelDao>>() {
@Override
public List<TagDefinitionModelDao> inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
@@ -79,7 +79,7 @@ public class DefaultTagDefinitionDao extends EntityDaoBase<TagDefinitionModelDao
Iterators.addAll(definitionList, all);
// Add invoice tag definitions
- definitionList.addAll(SystemTags.all());
+ definitionList.addAll(SystemTags.get(includeSystemTags));
return definitionList;
}
});
diff --git a/util/src/main/java/org/killbill/billing/util/tag/dao/SystemTags.java b/util/src/main/java/org/killbill/billing/util/tag/dao/SystemTags.java
index adede89..5879654 100644
--- a/util/src/main/java/org/killbill/billing/util/tag/dao/SystemTags.java
+++ b/util/src/main/java/org/killbill/billing/util/tag/dao/SystemTags.java
@@ -22,9 +22,13 @@ import java.util.LinkedList;
import java.util.List;
import java.util.UUID;
+import javax.annotation.Nullable;
+
import org.killbill.billing.util.tag.ControlTagType;
+import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
public class SystemTags {
@@ -35,8 +39,10 @@ public class SystemTags {
// Note! TagSqlDao.sql.stg needs to be kept in sync (see userAndSystemTagDefinitions)
private static final List<TagDefinitionModelDao> SYSTEM_DEFINED_TAG_DEFINITIONS = ImmutableList.<TagDefinitionModelDao>of(new TagDefinitionModelDao(PARK_TAG_DEFINITION_ID, null, null, PARK_TAG_DEFINITION_NAME, "Accounts with invalid invoicing state"));
- public static Collection<TagDefinitionModelDao> all() {
- final Collection<TagDefinitionModelDao> all = new LinkedList<TagDefinitionModelDao>(SYSTEM_DEFINED_TAG_DEFINITIONS);
+ public static Collection<TagDefinitionModelDao> get(final boolean includeSystemTags) {
+ final Collection<TagDefinitionModelDao> all = includeSystemTags ?
+ new LinkedList<TagDefinitionModelDao>(SYSTEM_DEFINED_TAG_DEFINITIONS) :
+ new LinkedList<TagDefinitionModelDao>();
for (final ControlTagType controlTag : ControlTagType.values()) {
all.add(new TagDefinitionModelDao(controlTag));
}
@@ -59,6 +65,15 @@ public class SystemTags {
return null;
}
+ public static boolean isSystemTag(final UUID tagDefinitionId) {
+ return Iterables.any(SYSTEM_DEFINED_TAG_DEFINITIONS, new Predicate<TagDefinitionModelDao>() {
+ @Override
+ public boolean apply(final TagDefinitionModelDao input) {
+ return input.getId().equals(tagDefinitionId);
+ }
+ });
+ }
+
public static TagDefinitionModelDao lookup(final UUID tagDefinitionId) {
for (final ControlTagType t : ControlTagType.values()) {
if (t.getId().equals(tagDefinitionId)) {
diff --git a/util/src/main/java/org/killbill/billing/util/tag/dao/TagDefinitionDao.java b/util/src/main/java/org/killbill/billing/util/tag/dao/TagDefinitionDao.java
index cfa2447..84f3bed 100644
--- a/util/src/main/java/org/killbill/billing/util/tag/dao/TagDefinitionDao.java
+++ b/util/src/main/java/org/killbill/billing/util/tag/dao/TagDefinitionDao.java
@@ -28,7 +28,7 @@ import org.killbill.billing.util.tag.TagDefinition;
public interface TagDefinitionDao extends EntityDao<TagDefinitionModelDao, TagDefinition, TagDefinitionApiException> {
- public List<TagDefinitionModelDao> getTagDefinitions(InternalTenantContext context);
+ public List<TagDefinitionModelDao> getTagDefinitions(boolean includeSystemTags, InternalTenantContext context);
public TagDefinitionModelDao getByName(String definitionName, InternalTenantContext context);
diff --git a/util/src/main/java/org/killbill/billing/util/tag/DefaultTagInternalApi.java b/util/src/main/java/org/killbill/billing/util/tag/DefaultTagInternalApi.java
index 9db3025..e4af036 100644
--- a/util/src/main/java/org/killbill/billing/util/tag/DefaultTagInternalApi.java
+++ b/util/src/main/java/org/killbill/billing/util/tag/DefaultTagInternalApi.java
@@ -51,7 +51,7 @@ public class DefaultTagInternalApi implements TagInternalApi {
@Override
public List<TagDefinition> getTagDefinitions(final InternalTenantContext context) {
- return ImmutableList.<TagDefinition>copyOf(Collections2.transform(tagDefinitionDao.getTagDefinitions(context),
+ return ImmutableList.<TagDefinition>copyOf(Collections2.transform(tagDefinitionDao.getTagDefinitions(true, context),
new Function<TagDefinitionModelDao, TagDefinition>() {
@Override
public TagDefinition apply(final TagDefinitionModelDao input) {
diff --git a/util/src/main/resources/org/killbill/billing/util/entity/dao/EntitySqlDao.sql.stg b/util/src/main/resources/org/killbill/billing/util/entity/dao/EntitySqlDao.sql.stg
index d05e54e..6f35450 100644
--- a/util/src/main/resources/org/killbill/billing/util/entity/dao/EntitySqlDao.sql.stg
+++ b/util/src/main/resources/org/killbill/billing/util/entity/dao/EntitySqlDao.sql.stg
@@ -151,10 +151,15 @@ get(offset, rowCount, orderBy) ::= <<
select
<allTableFields("t.")>
from <tableName()> t
-where <CHECK_TENANT("t.")>
-<andCheckSoftDeletionWithComma("t.")>
+join (
+ select <recordIdField()>
+ from <tableName()>
+ where <CHECK_TENANT()>
+ <andCheckSoftDeletionWithComma()>
+ order by <orderBy>
+ limit :rowCount offset :offset
+) optimization on <recordIdField("optimization.")> = <recordIdField("t.")>
order by t.<orderBy>
-limit :rowCount offset :offset
;
>>
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 be41d8d..95ac19d 100644
--- a/util/src/test/java/org/killbill/billing/api/TestApiListener.java
+++ b/util/src/test/java/org/killbill/billing/api/TestApiListener.java
@@ -144,6 +144,9 @@ public class TestApiListener {
@Subscribe
public void handleSubscriptionEvents(final EffectiveSubscriptionInternalEvent eventEffective) {
log.info(String.format("Got subscription event %s", eventEffective.toString()));
+
+ Assert.assertNotNull(eventEffective.getBundleExternalKey());
+
switch (eventEffective.getTransitionType()) {
case TRANSFER:
assertEqualsNicely(NextEvent.TRANSFER);
diff --git a/util/src/test/java/org/killbill/billing/mock/MockEffectiveSubscriptionEvent.java b/util/src/test/java/org/killbill/billing/mock/MockEffectiveSubscriptionEvent.java
index 60f76fa..24e3b48 100644
--- a/util/src/test/java/org/killbill/billing/mock/MockEffectiveSubscriptionEvent.java
+++ b/util/src/test/java/org/killbill/billing/mock/MockEffectiveSubscriptionEvent.java
@@ -34,6 +34,7 @@ public class MockEffectiveSubscriptionEvent extends BusEventBase implements Effe
private final Long totalOrdering;
private final UUID subscriptionId;
private final UUID bundleId;
+ private final String bundleExternalKey;
private final UUID eventId;
private final DateTime requestedTransitionTime;
private final DateTime effectiveTransitionTime;
@@ -57,6 +58,7 @@ public class MockEffectiveSubscriptionEvent extends BusEventBase implements Effe
public MockEffectiveSubscriptionEvent(@JsonProperty("eventId") final UUID eventId,
@JsonProperty("subscriptionId") final UUID subscriptionId,
@JsonProperty("bundleId") final UUID bundleId,
+ @JsonProperty("bundleExternalKey") final String bundleExternalKey,
@JsonProperty("requestedTransitionTime") final DateTime requestedTransitionTime,
@JsonProperty("effectiveTransitionTime") final DateTime effectiveTransitionTime,
@JsonProperty("previousState") final EntitlementState previousState,
@@ -80,6 +82,7 @@ public class MockEffectiveSubscriptionEvent extends BusEventBase implements Effe
this.eventId = eventId;
this.subscriptionId = subscriptionId;
this.bundleId = bundleId;
+ this.bundleExternalKey = bundleExternalKey;
this.requestedTransitionTime = requestedTransitionTime;
this.effectiveTransitionTime = effectiveTransitionTime;
this.previousState = previousState;
@@ -121,6 +124,10 @@ public class MockEffectiveSubscriptionEvent extends BusEventBase implements Effe
return bundleId;
}
+ @Override
+ public String getBundleExternalKey() {
+ return bundleExternalKey;
+ }
@Override
public EntitlementState getPreviousState() {
diff --git a/util/src/test/java/org/killbill/billing/util/dao/TestStringTemplateInheritance.java b/util/src/test/java/org/killbill/billing/util/dao/TestStringTemplateInheritance.java
index 7f1627c..cc50c48 100644
--- a/util/src/test/java/org/killbill/billing/util/dao/TestStringTemplateInheritance.java
+++ b/util/src/test/java/org/killbill/billing/util/dao/TestStringTemplateInheritance.java
@@ -123,9 +123,14 @@ public class TestStringTemplateInheritance extends UtilTestSuiteNoDB {
", t.account_record_id\r?\n" +
", t.tenant_record_id\r?\n" +
"from kombucha t\r?\n" +
- "where t.tenant_record_id = :tenantRecordId\r?\n" +
+ "join \\(\r?\n" +
+ " select record_id\r?\n" +
+ " from kombucha\r?\n" +
+ " where tenant_record_id = :tenantRecordId\r?\n" +
+ " order by record_id\r?\n" +
+ " limit :rowCount offset :offset\r?\n" +
+ "\\) optimization on optimization.record_id = t.record_id\r?\n" +
"order by t.record_id\r?\n" +
- "limit :rowCount offset :offset\r?\n" +
";");
assertPattern(kombucha.getInstanceOf("test").toString(), "select\r?\n" +
" t.record_id\r?\n" +
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 b2d2785..90a96a4 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
@@ -60,10 +60,10 @@ public class TestCSVExportOutputStream extends UtilTestSuiteNoDB {
"last_name", "dupont",
"age", "30"));
- Assert.assertEquals(out.toString(), "-- " + tableName + " first_name,last_name,age\n" +
- "jean,dupond,35\n" +
- "jack,dujardin,40\n" +
- "pierre,schmitt,12\n" +
- "stephane,dupont,30\n");
+ Assert.assertEquals(out.toString(), "-- " + tableName + " first_name|last_name|age\n" +
+ "jean|dupond|35\n" +
+ "jack|dujardin|40\n" +
+ "pierre|schmitt|12\n" +
+ "stephane|dupont|30\n");
}
}
diff --git a/util/src/test/java/org/killbill/billing/util/export/dao/TestDatabaseExportDao.java b/util/src/test/java/org/killbill/billing/util/export/dao/TestDatabaseExportDao.java
index a4ffe97..e9b497f 100644
--- a/util/src/test/java/org/killbill/billing/util/export/dao/TestDatabaseExportDao.java
+++ b/util/src/test/java/org/killbill/billing/util/export/dao/TestDatabaseExportDao.java
@@ -80,13 +80,13 @@ public class TestDatabaseExportDao extends UtilTestSuiteWithEmbeddedDB {
// Verify new dump
final String newDump = getDump();
- Assert.assertEquals(newDump, "-- accounts record_id,id,external_key,email,name,first_name_length,currency,billing_cycle_day_local,payment_method_id,time_zone,locale,address1,address2,company_name,city,state_or_province,country,postal_code,phone,migrated,is_notified_for_invoices,created_date,created_by,updated_date,updated_by,tenant_record_id\n" +
- String.format("%s,%s,,%s,%s,%s,,,,,,,,,,,,,,false,%s,%s,%s,%s,%s,%s", internalCallContext.getAccountRecordId(), accountId, accountEmail, accountName, firstNameLength,
+ Assert.assertEquals(newDump, "-- accounts record_id|id|external_key|email|name|first_name_length|currency|billing_cycle_day_local|payment_method_id|time_zone|locale|address1|address2|company_name|city|state_or_province|country|postal_code|phone|migrated|is_notified_for_invoices|created_date|created_by|updated_date|updated_by|tenant_record_id\n" +
+ String.format("%s|%s||%s|%s|%s||||||||||||||false|%s|%s|%s|%s|%s|%s", internalCallContext.getAccountRecordId(), accountId, accountEmail, accountName, firstNameLength,
isNotifiedForInvoices, "1970-05-24T18:33:02.000+0000", createdBy, "1982-02-18T20:03:42.000+0000", updatedBy, internalCallContext.getTenantRecordId()) + "\n" +
- "-- " + tableNameA + " record_id,a_column,account_record_id,tenant_record_id\n" +
- "1,a," + internalCallContext.getAccountRecordId() + "," + internalCallContext.getTenantRecordId() + "\n" +
- "-- " + tableNameB + " record_id,b_column,account_record_id,tenant_record_id\n" +
- "1,b," + internalCallContext.getAccountRecordId() + "," + internalCallContext.getTenantRecordId() + "\n");
+ "-- " + tableNameA + " record_id|a_column|account_record_id|tenant_record_id\n" +
+ "1|a|" + internalCallContext.getAccountRecordId() + "|" + internalCallContext.getTenantRecordId() + "\n" +
+ "-- " + tableNameB + " record_id|b_column|account_record_id|tenant_record_id\n" +
+ "1|b|" + internalCallContext.getAccountRecordId() + "|" + internalCallContext.getTenantRecordId() + "\n");
}
diff --git a/util/src/test/java/org/killbill/billing/util/tag/dao/MockTagDefinitionDao.java b/util/src/test/java/org/killbill/billing/util/tag/dao/MockTagDefinitionDao.java
index c602091..f382b4f 100644
--- a/util/src/test/java/org/killbill/billing/util/tag/dao/MockTagDefinitionDao.java
+++ b/util/src/test/java/org/killbill/billing/util/tag/dao/MockTagDefinitionDao.java
@@ -34,7 +34,7 @@ public class MockTagDefinitionDao extends MockEntityDaoBase<TagDefinitionModelDa
private final Map<String, TagDefinitionModelDao> tags = new ConcurrentHashMap<String, TagDefinitionModelDao>();
@Override
- public List<TagDefinitionModelDao> getTagDefinitions(final InternalTenantContext context) {
+ public List<TagDefinitionModelDao> getTagDefinitions(final boolean dummy, final InternalTenantContext context) {
return new ArrayList<TagDefinitionModelDao>(tags.values());
}
diff --git a/util/src/test/java/org/killbill/billing/util/tag/dao/TestDefaultTagDao.java b/util/src/test/java/org/killbill/billing/util/tag/dao/TestDefaultTagDao.java
index b110370..d648fcd 100644
--- a/util/src/test/java/org/killbill/billing/util/tag/dao/TestDefaultTagDao.java
+++ b/util/src/test/java/org/killbill/billing/util/tag/dao/TestDefaultTagDao.java
@@ -68,8 +68,8 @@ public class TestDefaultTagDao extends UtilTestSuiteWithEmbeddedDB {
result = tagDefinitionDao.getByIds(uuids, internalCallContext);
assertEquals(result.size(), 4);
- result = tagDefinitionDao.getTagDefinitions(internalCallContext);
- assertEquals(result.size(), 3 + SystemTags.all().size());
+ result = tagDefinitionDao.getTagDefinitions(true, internalCallContext);
+ assertEquals(result.size(), 3 + SystemTags.get(true).size());
}
@Test(groups = "slow")
diff --git a/util/src/test/java/org/killbill/billing/util/tag/TestTagStore.java b/util/src/test/java/org/killbill/billing/util/tag/TestTagStore.java
index 6ff114f..8de750a 100644
--- a/util/src/test/java/org/killbill/billing/util/tag/TestTagStore.java
+++ b/util/src/test/java/org/killbill/billing/util/tag/TestTagStore.java
@@ -144,7 +144,7 @@ public class TestTagStore extends UtilTestSuiteWithEmbeddedDB {
@Test(groups = "slow")
public void testGetTagDefinitions() {
- final List<TagDefinitionModelDao> definitionList = tagDefinitionDao.getTagDefinitions(internalCallContext);
+ final List<TagDefinitionModelDao> definitionList = tagDefinitionDao.getTagDefinitions(false, internalCallContext);
assertTrue(definitionList.size() >= ControlTagType.values().length);
}
}