killbill-uncached
Changes
.circleci/config.yml 219(+219 -0)
.DS_Store 0(+0 -0)
.gitignore 5(+5 -0)
.travis.yml 37(+11 -26)
account/pom.xml 8(+2 -6)
account/src/main/java/org/killbill/billing/account/api/user/DefaultAccountCreationEvent.java 19(+18 -1)
account/src/main/resources/org/killbill/billing/account/migration/V20170915165117__external_key_not_null.sql 2(+2 -0)
account/src/main/resources/org/killbill/billing/account/migration/V20171108184350__reference_time.sql 4(+4 -0)
account/src/test/java/org/killbill/billing/account/api/user/TestDefaultAccountUserApiWithMocks.java 4(+3 -1)
api/pom.xml 2(+1 -1)
api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseTransitionType.java 4(+4 -0)
beatrix/pom.xml 6(+1 -5)
beatrix/src/test/java/org/killbill/billing/beatrix/integration/BeatrixIntegrationModule.java 2(+0 -2)
beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueIntegration.java 14(+9 -5)
beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestCatalogRetireElements.java 264(+260 -4)
beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationBase.java 136(+92 -44)
beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationDryRunInvoice.java 554(+554 -0)
beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationInvoice.java 333(+45 -288)
beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationInvoiceWithRepairLogic.java 10(+1 -9)
beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationParentInvoice.java 237(+234 -3)
beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithAutoInvoiceDraft.java 274(+274 -0)
beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithAutoInvoiceOffTag.java 21(+4 -17)
beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithCatalogUpdate.java 17(+8 -9)
beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestInvoiceSystemDisabling.java 2(+1 -1)
beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestMigrationSubscriptions.java 177(+171 -6)
beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestPaymentWithControl.java 74(+68 -6)
beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithFakeKPMPlugin.java 46(+19 -27)
beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithInvoicePlugin.java 266(+266 -0)
beatrix/src/test/java/org/killbill/billing/beatrix/integration/usage/TestConsumableInArrear.java 6(+1 -5)
catalog/pom.xml 6(+1 -5)
catalog/src/main/java/org/killbill/billing/catalog/dao/CatalogOverrideBlockDefinitionSqlDao.java 4(+2 -2)
catalog/src/main/java/org/killbill/billing/catalog/dao/CatalogOverridePhaseDefinitionSqlDao.java 4(+2 -2)
catalog/src/main/java/org/killbill/billing/catalog/dao/CatalogOverridePhaseUsageSqlDao.java 4(+2 -2)
catalog/src/main/java/org/killbill/billing/catalog/dao/CatalogOverridePlanDefinitionSqlDao.java 4(+2 -2)
catalog/src/main/java/org/killbill/billing/catalog/dao/CatalogOverridePlanPhaseSqlDao.java 11(+6 -5)
catalog/src/main/java/org/killbill/billing/catalog/dao/CatalogOverrideTierDefinitionSqlDao.java 4(+2 -2)
catalog/src/main/java/org/killbill/billing/catalog/dao/CatalogOverrideUsageDefinitionSqlDao.java 4(+2 -2)
catalog/src/main/java/org/killbill/billing/catalog/dao/PlanPhaseKeysCollectionBinder.java 57(+0 -57)
catalog/src/main/java/org/killbill/billing/catalog/StandaloneCatalogWithPriceOverride.java 19(+1 -18)
catalog/src/main/resources/org/killbill/billing/catalog/dao/CatalogOverridePhaseDefinitionSqlDao.sql.stg 8(+3 -5)
catalog/src/main/resources/org/killbill/billing/catalog/dao/CatalogOverridePlanDefinitionSqlDao.sql.stg 6(+2 -4)
catalog/src/main/resources/org/killbill/billing/catalog/dao/CatalogOverridePlanPhaseSqlDao.sql.stg 9(+3 -6)
catalog/src/main/resources/org/killbill/billing/catalog/migration/V20161220000000__unit_price_override.sql 15(+7 -8)
catalog/src/test/java/org/killbill/billing/catalog/plugin/TestModelVersionedPluginCatalog.java 8(+0 -8)
catalog/src/test/resources/catalogTest.xml 48(+47 -1)
currency/pom.xml 2(+1 -1)
entitlement/pom.xml 8(+2 -6)
entitlement/src/main/java/org/killbill/billing/entitlement/api/BlockingStateOrdering.java 105(+93 -12)
entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlementApi.java 69(+53 -16)
entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlementContext.java 2(+1 -1)
entitlement/src/main/java/org/killbill/billing/entitlement/api/SubscriptionEventOrdering.java 142(+5 -137)
entitlement/src/main/java/org/killbill/billing/entitlement/api/svcs/DefaultEntitlementApiBase.java 5(+3 -2)
entitlement/src/main/java/org/killbill/billing/entitlement/api/svcs/DefaultEntitlementInternalApi.java 3(+2 -1)
entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/DefaultEventsStream.java 59(+45 -14)
entitlement/src/main/java/org/killbill/billing/entitlement/logging/EntitlementLoggingHelper.java 10(+10 -0)
entitlement/src/main/resources/org/killbill/billing/entitlement/dao/BlockingStateSqlDao.sql.stg 17(+8 -9)
entitlement/src/test/java/org/killbill/billing/entitlement/api/TestBlockingStateOrdering.java 378(+378 -0)
entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultEntitlement.java 18(+9 -9)
entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultEntitlementApi.java 45(+24 -21)
entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultSubscriptionApi.java 28(+14 -14)
entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultSubscriptionBundleTimeline.java 216(+4 -212)
entitlement/src/test/java/org/killbill/billing/entitlement/api/TestEntitlementDateHelper.java 9(+5 -4)
entitlement/src/test/java/org/killbill/billing/entitlement/api/TestRegessionSubscriptionApi.java 2(+1 -1)
entitlement/src/test/java/org/killbill/billing/entitlement/dao/TestDefaultBlockingStateDao.java 2(+1 -1)
entitlement/src/test/java/org/killbill/billing/entitlement/engine/core/TestEntitlementUtils.java 8(+4 -4)
entitlement/src/test/java/org/killbill/billing/entitlement/EntitlementTestSuiteWithEmbeddedDB.java 10(+3 -7)
invoice/pom.xml 8(+2 -6)
invoice/src/main/java/org/killbill/billing/invoice/generator/DefaultInvoiceGenerator.java 36(+28 -8)
invoice/src/main/java/org/killbill/billing/invoice/generator/FixedAndRecurringInvoiceItemGenerator.java 9(+4 -5)
invoice/src/main/java/org/killbill/billing/invoice/generator/UsageInvoiceItemGenerator.java 6(+3 -3)
invoice/src/main/java/org/killbill/billing/invoice/notification/DefaultNextBillingDateNotifier.java 47(+31 -16)
invoice/src/main/java/org/killbill/billing/invoice/notification/DefaultNextBillingDatePoster.java 48(+32 -16)
invoice/src/main/java/org/killbill/billing/invoice/notification/EmailInvoiceNotifier.java 118(+0 -118)
invoice/src/main/java/org/killbill/billing/invoice/notification/NextBillingDateNotificationKey.java 25(+24 -1)
invoice/src/main/java/org/killbill/billing/invoice/notification/ParentInvoiceCommitmentPoster.java 11(+7 -4)
invoice/src/main/java/org/killbill/billing/invoice/provider/DefaultNoOpInvoiceProviderPlugin.java 5(+1 -4)
invoice/src/main/java/org/killbill/billing/invoice/provider/NoOpInvoiceProviderPluginProvider.java 2(+1 -1)
invoice/src/main/java/org/killbill/billing/invoice/template/formatters/DefaultInvoiceFormatter.java 10(+10 -0)
invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalUsageInArrear.java 4(+3 -1)
invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoiceParentChildrenSqlDao.sql.stg 16(+8 -8)
invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoicePaymentSqlDao.sql.stg 32(+16 -16)
invoice/src/test/java/org/killbill/billing/invoice/api/migration/TestDefaultInvoiceMigrationApi.java 4(+2 -2)
invoice/src/test/java/org/killbill/billing/invoice/api/user/TestDefaultInvoiceUserApi.java 78(+34 -44)
invoice/src/test/java/org/killbill/billing/invoice/api/user/TestInvoiceFlagBehaviors.java 194(+194 -0)
invoice/src/test/java/org/killbill/billing/invoice/generator/TestDefaultInvoiceGenerator.java 109(+68 -41)
invoice/src/test/java/org/killbill/billing/invoice/model/TestExternalChargeInvoiceItem.java 11(+6 -5)
invoice/src/test/java/org/killbill/billing/invoice/notification/TestNextBillingDateNotificationKey.java 33(+31 -2)
invoice/src/test/java/org/killbill/billing/invoice/notification/TestNextBillingDateNotifier.java 4(+3 -1)
invoice/src/test/java/org/killbill/billing/invoice/usage/TestContiguousIntervalConsumableInArrear.java 93(+93 -0)
jaxrs/pom.xml 7(+6 -1)
junction/pom.xml 2(+1 -1)
junction/src/main/java/org/killbill/billing/junction/plumbing/billing/BlockingCalculator.java 174(+75 -99)
junction/src/main/java/org/killbill/billing/junction/plumbing/billing/BlockingStateService.java 69(+69 -0)
junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultBillingEventSet.java 18(+13 -5)
junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultInternalBillingApi.java 51(+35 -16)
junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DisabledDuration.java 124(+124 -0)
junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBlockingCalculator.java 159(+113 -46)
junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBlockingStateService.java 399(+399 -0)
junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestDefaultInternalBillingApi.java 55(+37 -18)
junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestDisabledDuration.java 205(+205 -0)
NEWS 15(+15 -0)
overdue/pom.xml 6(+1 -5)
overdue/src/main/java/org/killbill/billing/overdue/applicator/OverdueStateApplicator.java 123(+31 -92)
overdue/src/test/java/org/killbill/billing/overdue/notification/TestDefaultOverdueCheckPoster.java 2(+1 -1)
overdue/src/test/java/org/killbill/billing/overdue/notification/TestOverdueCheckNotifier.java 5(+3 -2)
payment/pom.xml 6(+1 -5)
payment/src/main/java/org/killbill/billing/payment/core/janitor/IncompletePaymentAttemptTask.java 1(+1 -0)
payment/src/main/java/org/killbill/billing/payment/core/janitor/IncompletePaymentTransactionTask.java 3(+2 -1)
payment/src/main/java/org/killbill/billing/payment/core/PaymentPluginServiceRegistration.java 7(+5 -2)
payment/src/main/java/org/killbill/billing/payment/core/PluginControlPaymentProcessor.java 27(+19 -8)
payment/src/main/java/org/killbill/billing/payment/core/sm/control/AuthorizeControlOperation.java 1(+1 -0)
payment/src/main/java/org/killbill/billing/payment/core/sm/control/CaptureControlOperation.java 1(+1 -0)
payment/src/main/java/org/killbill/billing/payment/core/sm/control/ChargebackControlOperation.java 1(+1 -0)
payment/src/main/java/org/killbill/billing/payment/core/sm/control/ChargebackReversalControlOperation.java 1(+1 -0)
payment/src/main/java/org/killbill/billing/payment/core/sm/control/CompletionControlOperation.java 1(+1 -0)
payment/src/main/java/org/killbill/billing/payment/core/sm/control/ControlPluginRunner.java 58(+39 -19)
payment/src/main/java/org/killbill/billing/payment/core/sm/control/CreditControlOperation.java 1(+1 -0)
payment/src/main/java/org/killbill/billing/payment/core/sm/control/DefaultControlCompleted.java 11(+8 -3)
payment/src/main/java/org/killbill/billing/payment/core/sm/control/OperationControlCallback.java 5(+5 -0)
payment/src/main/java/org/killbill/billing/payment/core/sm/control/PaymentStateControlContext.java 3(+2 -1)
payment/src/main/java/org/killbill/billing/payment/core/sm/control/PurchaseControlOperation.java 1(+1 -0)
payment/src/main/java/org/killbill/billing/payment/core/sm/control/RefundControlOperation.java 1(+1 -0)
payment/src/main/java/org/killbill/billing/payment/core/sm/control/VoidControlOperation.java 1(+1 -0)
payment/src/main/java/org/killbill/billing/payment/core/sm/payments/ChargebackInitiated.java 2(+1 -1)
payment/src/main/java/org/killbill/billing/payment/core/sm/PluginControlPaymentAutomatonRunner.java 15(+12 -3)
payment/src/main/java/org/killbill/billing/payment/dao/TransactionStatusCollectionBinder.java 57(+0 -57)
payment/src/main/java/org/killbill/billing/payment/invoice/InvoicePaymentControlPluginApi.java 81(+44 -37)
payment/src/main/java/org/killbill/billing/payment/retry/DefaultPriorPaymentControlResult.java 14(+11 -3)
payment/src/test/java/org/killbill/billing/payment/api/TestPaymentGatewayApiWithPaymentControl.java 2(+1 -1)
payment/src/test/java/org/killbill/billing/payment/core/janitor/TestCompletionTaskBase.java 91(+91 -0)
payment/src/test/java/org/killbill/billing/payment/core/janitor/TestIncompletePaymentTransactionTaskWithDB.java 79(+76 -3)
payment/src/test/java/org/killbill/billing/payment/core/sm/control/TestControlPluginRunner.java 1(+1 -0)
payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryablePaymentAutomatonRunner.java 5(+3 -2)
payment/src/test/java/org/killbill/billing/payment/core/sm/TestPaymentAutomatonDAOHelper.java 1(+1 -0)
payment/src/test/java/org/killbill/billing/payment/core/sm/TestPaymentEnteringStateCallback.java 1(+1 -0)
payment/src/test/java/org/killbill/billing/payment/core/sm/TestPaymentLeavingStateCallback.java 1(+1 -0)
payment/src/test/java/org/killbill/billing/payment/core/TestPaymentMethodProcessorNoDB.java 6(+3 -3)
payment/src/test/java/org/killbill/billing/payment/core/TestPaymentMethodProcessorRefreshWithDB.java 4(+2 -2)
payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentControlProviderPlugin.java 14(+11 -3)
pom.xml 4(+2 -2)
profiles/killbill/pom.xml 6(+1 -5)
profiles/killbill/src/main/java/org/killbill/billing/server/listeners/CleanupListener.java 30(+20 -10)
profiles/killbill/src/main/java/org/killbill/billing/server/listeners/KillbillGuiceListener.java 14(+13 -1)
profiles/killbill/src/main/java/org/killbill/billing/server/log/obfuscators/LoggingFilterObfuscator.java 57(+57 -0)
profiles/killbill/src/main/java/org/killbill/billing/server/log/obfuscators/ObfuscatorConverter.java 1(+1 -0)
profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillBillEmbeddedDBProvider.java 1(+1 -0)
profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillbillServerModule.java 41(+23 -18)
profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillBillShiroWebModule.java 4(+4 -0)
profiles/killbill/src/main/java/org/killbill/billing/server/notifications/PushNotificationKey.java 10(+9 -1)
profiles/killbill/src/main/java/org/killbill/billing/server/notifications/PushNotificationListener.java 11(+9 -2)
profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPaymentPluginProperties.java 26(+21 -5)
profiles/killbill/src/test/java/org/killbill/billing/server/log/obfuscators/TestLoggingFilterObfuscator.java 81(+81 -0)
profiles/killpay/pom.xml 2(+1 -1)
profiles/killpay/src/main/java/org/killbill/billing/server/modules/KillpayServerModule.java 16(+11 -5)
profiles/pom.xml 2(+1 -1)
README.md 2(+1 -1)
subscription/pom.xml 8(+2 -6)
subscription/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseApiService.java 47(+32 -15)
subscription/src/main/java/org/killbill/billing/subscription/api/svcs/DefaultSubscriptionInternalApi.java 239(+96 -143)
subscription/src/main/java/org/killbill/billing/subscription/api/timeline/DefaultSubscriptionBaseTimelineApi.java 13(+9 -4)
subscription/src/main/java/org/killbill/billing/subscription/api/transfer/DefaultSubscriptionBaseTransferApi.java 36(+20 -16)
subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBase.java 32(+23 -9)
subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBaseApiService.java 289(+172 -117)
subscription/src/main/java/org/killbill/billing/subscription/engine/addon/AddonUtils.java 25(+12 -13)
subscription/src/main/java/org/killbill/billing/subscription/engine/core/DefaultSubscriptionBaseService.java 26(+17 -9)
subscription/src/main/java/org/killbill/billing/subscription/engine/dao/BundleSqlDao.java 36(+21 -15)
subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java 291(+176 -115)
subscription/src/main/java/org/killbill/billing/subscription/engine/dao/model/SubscriptionBundleModelDao.java 15(+13 -2)
subscription/src/main/java/org/killbill/billing/subscription/engine/dao/model/SubscriptionEventModelDao.java 25(+24 -1)
subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionDao.java 36(+19 -17)
subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionEventSqlDao.java 16(+8 -8)
subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionSqlDao.java 10(+5 -5)
subscription/src/main/java/org/killbill/billing/subscription/events/user/ApiEventBuilder.java 2(+2 -0)
subscription/src/main/java/org/killbill/billing/subscription/events/user/ApiEventUndoChange.java 15(+8 -7)
subscription/src/main/resources/org/killbill/billing/subscription/engine/dao/BundleSqlDao.sql.stg 41(+28 -13)
subscription/src/main/resources/org/killbill/billing/subscription/engine/dao/SubscriptionEventSqlDao.sql.stg 28(+14 -14)
subscription/src/main/resources/org/killbill/billing/subscription/engine/dao/SubscriptionSqlDao.sql.stg 10(+5 -5)
subscription/src/main/resources/org/killbill/billing/subscription/migration/V20170920200757__bundle_external_key.sql 2(+2 -0)
subscription/src/test/java/org/killbill/billing/subscription/alignment/TestPlanAligner.java 44(+22 -22)
subscription/src/test/java/org/killbill/billing/subscription/api/transfer/TestDefaultSubscriptionTransferApi.java 6(+4 -2)
subscription/src/test/java/org/killbill/billing/subscription/api/transfer/TestTransfer.java 13(+6 -7)
subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiAddOn.java 24(+12 -12)
subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiChangePlan.java 125(+99 -26)
subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiCreate.java 24(+22 -2)
subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiError.java 19(+11 -8)
subscription/src/test/java/org/killbill/billing/subscription/DefaultSubscriptionTestInitializer.java 6(+4 -2)
subscription/src/test/java/org/killbill/billing/subscription/engine/dao/MockSubscriptionDaoMemory.java 71(+40 -31)
subscription/src/test/java/org/killbill/billing/subscription/engine/dao/MockSubscriptionDaoSql.java 4(+2 -2)
subscription/src/test/java/org/killbill/billing/subscription/engine/dao/TestSubscriptionDao.java 169(+169 -0)
subscription/src/test/java/org/killbill/billing/subscription/engine/dao/TestSubscriptionModelDao.java 51(+51 -0)
subscription/src/test/java/org/killbill/billing/subscription/SubscriptionTestInitializer.java 3(+2 -1)
subscription/src/test/java/org/killbill/billing/subscription/SubscriptionTestSuiteNoDB.java 3(+2 -1)
subscription/src/test/java/org/killbill/billing/subscription/SubscriptionTestSuiteWithEmbeddedDB.java 9(+2 -7)
tenant/pom.xml 8(+2 -6)
usage/pom.xml 12(+6 -6)
util/pom.xml 29(+19 -10)
util/src/main/java/org/killbill/billing/util/customfield/api/DefaultCustomFieldUserApi.java 69(+36 -33)
util/src/main/java/org/killbill/billing/util/entity/dao/EntitySqlDaoStringTemplate.java 145(+0 -145)
util/src/main/java/org/killbill/billing/util/entity/dao/EntitySqlDaoTransactionalJdbiWrapper.java 6(+4 -2)
util/src/main/java/org/killbill/billing/util/entity/dao/EntitySqlDaoWrapperInvocationHandler.java 25(+21 -4)
util/src/main/java/org/killbill/billing/util/security/shiro/dao/RolesPermissionsSqlDao.java 16(+14 -2)
util/src/main/java/org/killbill/billing/util/security/shiro/realm/KillBillJndiLdapRealm.java 95(+61 -34)
util/src/main/java/org/killbill/billing/util/security/shiro/realm/KillBillOktaRealm.java 241(+241 -0)
util/src/main/resources/org/killbill/billing/util/customfield/dao/CustomFieldSqlDao.sql.stg 16(+12 -4)
util/src/main/resources/org/killbill/billing/util/migration/V20171011170256__tag_definition_object_types.sql 2(+2 -0)
util/src/main/resources/org/killbill/billing/util/security/shiro/dao/JDBCSessionSqlDao.sql.stg 2(+0 -2)
util/src/main/resources/org/killbill/billing/util/security/shiro/dao/RolesPermissionsSqlDao.sql.stg 22(+16 -6)
util/src/main/resources/org/killbill/billing/util/security/shiro/dao/UserRolesSqlDao.sql.stg 8(+3 -5)
util/src/main/resources/org/killbill/billing/util/validation/dao/DatabaseSchemaSqlDao.sql.stg 2(+0 -2)
util/src/test/java/org/killbill/billing/util/callcontext/TestInternalCallContextFactory.java 8(+5 -3)
util/src/test/java/org/killbill/billing/util/customfield/api/TestDefaultCustomFieldUserApi.java 113(+107 -6)
util/src/test/java/org/killbill/billing/util/dao/TestStringTemplateInheritanceWithJdbi.java 16(+0 -16)
util/src/test/java/org/killbill/billing/util/security/shiro/realm/TestKillBillJdbcRealm.java 30(+30 -0)
util/src/test/java/org/killbill/billing/util/security/shiro/realm/TestKillBillOktaRealm.java 62(+62 -0)
Details
.circleci/config.yml 219(+219 -0)
diff --git a/.circleci/config.yml b/.circleci/config.yml
new file mode 100644
index 0000000..ca7b9fc
--- /dev/null
+++ b/.circleci/config.yml
@@ -0,0 +1,219 @@
+defaults: &defaults
+ working_directory: ~/repo
+ environment:
+ MAVEN_OPTS: -server -showversion -XX:+PrintCommandLineFlags -XX:+UseCodeCacheFlushing -Xms1024M -Xmx2048M -XX:+CMSClassUnloadingEnabled -XX:-OmitStackTraceInFastThrow -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSConcurrentMTEnabled -XX:+CMSParallelRemarkEnabled -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=70 -XX:+ScavengeBeforeFullGC -XX:+CMSScavengeBeforeRemark -XX:NewSize=600m -XX:MaxNewSize=900m -XX:SurvivorRatio=10 -XX:+DisableExplicitGC -Djava.security.egd=file:/dev/./urandom
+
+version: 2
+jobs:
+ build:
+ <<: *defaults
+ docker:
+ - image: killbill/kbbuild:0.1.0
+ steps:
+ - checkout
+ - restore_cache:
+ key: v1-dependencies-{{ .Branch }}-{{ checksum "pom.xml" }}
+ - run:
+ name: Setup dependencies
+ command: |
+ if [ "${CIRCLE_BRANCH}" != "master" ]; then
+ for i in killbill-oss-parent killbill-api killbill-plugin-api killbill-commons killbill-platform; do
+ if [ -n "$(git ls-remote --heads https://github.com/killbill/$i.git ${CIRCLE_BRANCH})" ]; then
+ echo "*** Setting up $i"
+ mkdir -p /home/killbill/$i
+ git clone https://github.com/killbill/$i.git /home/killbill/$i
+ pushd /home/killbill/$i
+ git checkout -b ${CIRCLE_BRANCH} origin/${CIRCLE_BRANCH}
+ mvn clean install -DskipTests=true
+ popd
+ fi
+ done
+ fi
+ - run: mvn -DskipTests=true clean install dependency:go-offline
+ - save_cache:
+ paths:
+ - ~/.m2
+ key: v1-dependencies-{{ .Branch }}-{{ checksum "pom.xml" }}
+
+ test-h2:
+ <<: *defaults
+ docker:
+ - image: killbill/kbbuild:0.1.0
+ steps:
+ - checkout
+ - restore_cache:
+ key: v1-dependencies-{{ .Branch }}-{{ checksum "pom.xml" }}
+ - run: mvn clean install -Ptravis
+ - run:
+ name: Save test results
+ command: |
+ mkdir -p ~/junit/
+ find . -type f -regex ".*/target/surefire-reports/.*xml" -exec cp {} ~/junit/ \;
+ when: always
+ - store_test_results:
+ path: ~/junit
+ - store_artifacts:
+ path: ~/junit
+ test-mysql:
+ <<: *defaults
+ docker:
+ - image: killbill/kbbuild:0.1.0
+ - image: killbill/mariadb:0.18
+ environment:
+ - MYSQL_ROOT_PASSWORD=root
+ steps:
+ - checkout
+ - restore_cache:
+ key: v1-dependencies-{{ .Branch }}-{{ checksum "pom.xml" }}
+ - run:
+ name: Setup latest DDL
+ command: |
+ set +e
+ count=0
+ until mysqladmin ping -h 127.0.0.1 -u root --password=root --silent; do
+ if [[ "$count" == "25" ]]; then
+ exit 1
+ fi
+ (( count++ ))
+
+ printf '.'
+ sleep 5
+ done
+
+ set -e
+ ./bin/db-helper -a create --driver mysql -u root -p root -t yes -h 127.0.0.1
+ - run: mvn clean install -Plocaltest-mysql
+ - run:
+ name: Save test results
+ command: |
+ mkdir -p ~/junit/
+ find . -type f -regex ".*/target/surefire-reports/.*xml" -exec cp {} ~/junit/ \;
+ when: always
+ - store_test_results:
+ path: ~/junit
+ - store_artifacts:
+ path: ~/junit
+ test-postgresql:
+ <<: *defaults
+ docker:
+ - image: killbill/kbbuild:0.1.0
+ - image: killbill/postgresql:0.18
+ environment:
+ - POSTGRES_PASSWORD=postgres
+ steps:
+ - checkout
+ - restore_cache:
+ key: v1-dependencies-{{ .Branch }}-{{ checksum "pom.xml" }}
+ - run:
+ name: Setup latest DDL
+ command: ./bin/db-helper -a create --driver postgres -u postgres -p postgres -t yes
+ - run: mvn clean install -Plocaltest-postgresql
+ - run:
+ name: Save test results
+ command: |
+ mkdir -p ~/junit/
+ find . -type f -regex ".*/target/surefire-reports/.*xml" -exec cp {} ~/junit/ \;
+ when: always
+ - store_test_results:
+ path: ~/junit
+ - store_artifacts:
+ path: ~/junit
+
+ integration-tests:
+ <<: *defaults
+ docker:
+ - image: killbill/kbbuild:0.1.0
+ - image: killbill/mariadb:0.18
+ environment:
+ - MYSQL_ROOT_PASSWORD=root
+ steps:
+ - checkout
+ - restore_cache:
+ key: v1-dependencies-{{ .Branch }}-{{ checksum "pom.xml" }}
+ - run:
+ name: Setup latest DDL
+ command: ./bin/db-helper -a create --driver mysql -u root -p root -t yes -h 127.0.0.1
+ - run:
+ name: Run integration tests
+ command: |
+ set +e
+
+ mvn clean install -DskipTests=true
+ nohup ./bin/start-server -s &
+
+ mkdir -p /home/killbill/killbill-integration-tests
+ git clone https://github.com/killbill/killbill-integration-tests.git /home/killbill/killbill-integration-tests
+ pushd /home/killbill/killbill-integration-tests
+ if [ "${CIRCLE_BRANCH}" != "master" ]; then
+ if [ -n "$(git ls-remote --heads https://github.com/killbill/killbill-integration-tests.git ${CIRCLE_BRANCH})" ]; then
+ git checkout -b ${CIRCLE_BRANCH} origin/${CIRCLE_BRANCH}
+ fi
+ fi
+ bundle install
+
+ count=0
+ until $(curl --output /dev/null --silent --fail http://127.0.0.1:8080/1.0/healthcheck); do
+ if [[ "$count" == "25" ]]; then
+ exit 1
+ fi
+ (( count++ ))
+
+ printf '.'
+ sleep 5
+ done
+
+ set -e
+ mkdir -p /tmp/test-results
+ bundle exec rake test:core | tee /tmp/test-results/test.txt 2>&1
+ - store_test_results:
+ path: /tmp/test-results
+ - store_artifacts:
+ path: /tmp/test-results
+
+workflows:
+ version: 2
+ build-and-test:
+ jobs:
+ - build:
+ filters:
+ branches:
+ only:
+ - master
+ - work-for-release-0.19.x
+ - circle-ci-experiment
+ - test-h2:
+ requires:
+ - build
+ filters:
+ branches:
+ only:
+ - master
+ - circle-ci-experiment
+ - test-mysql:
+ requires:
+ - build
+ filters:
+ branches:
+ only:
+ - master
+ - work-for-release-0.19.x
+ - circle-ci-experiment
+ - test-postgresql:
+ requires:
+ - build
+ filters:
+ branches:
+ only:
+ - master
+ - circle-ci-experiment
+ - integration-tests:
+ requires:
+ - test-h2
+ - test-mysql
+ - test-postgresql
+ filters:
+ branches:
+ only:
+ - master
+ - work-for-release-0.19.x
+ - circle-ci-experiment
.DS_Store 0(+0 -0)
diff --git a/.DS_Store b/.DS_Store
new file mode 100644
index 0000000..5008ddf
Binary files /dev/null and b/.DS_Store differ
.gitignore 5(+5 -0)
diff --git a/.gitignore b/.gitignore
index dea7666..251e19e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -66,6 +66,11 @@ modules.xml
.idea/vcs.xml
.idea/dbnavigator.xml
+# Eclipse metadata
+.classpath
+.project
+.settings/
+
#
# https://github.com/github/gitignore/blob/master/Java.gitignore
#
.travis.yml 37(+11 -26)
diff --git a/.travis.yml b/.travis.yml
index bdf121e..feec69b 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -8,45 +8,30 @@ cache:
dist: trusty
before_install:
+ - grep -q -z '<artifactId>killbill-oss-parent</artifactId>[[:space:]]\+<groupId>org.kill-bill.billing</groupId>[[:space:]]\+<version>[0-9\.]*-SNAPSHOT</version>' pom.xml && echo "killbill-oss-parent SNAPSHOT has been found, exiting." && exit 0 || true
- echo "<settings><profiles><profile><repositories><repository><id>central</id><name>bintray</name><url>http://jcenter.bintray.com</url></repository></repositories><id>bintray</id></profile></profiles><activeProfiles><activeProfile>bintray</activeProfile></activeProfiles></settings>" > /var/tmp/settings.xml
- - mvn -N io.takari:maven:wrapper -Dmaven=3.3.9
+ - |
+ function keep_alive() {
+ while true; do
+ echo -en "\a"
+ sleep 5
+ done
+ }
+ keep_alive &
before_script:
- jdk_switcher use $JDK
-script: if [[ -v COMMAND ]]; then $COMMAND; else travis_retry ./mvnw -q -Djava.security.egd=file:/dev/./urandom -Dorg.slf4j.simpleLogger.defaultLogLevel=WARN -Dorg.slf4j.simpleLogger.log.org.killbill.billing.util.cache=ERROR -Dorg.slf4j.simpleLogger.log.org.killbill.billing.lifecycle=ERROR -Dlogback.configurationFile=$PWD/profiles/killbill/src/test/resources/logback.travis.xml clean install $PHASE -pl '!beatrix,!profiles,!profiles/killbill,!profiles/killpay' 2>&1 | egrep -v 'Download|Install|[ \t]*at [ \ta-zA-Z0-9\.\:\(\)]+'; [ ${PIPESTATUS[0]} == 0 ]; fi
-# Remove --quiet to avoid timeouts
-install: mvn -U install -DskipTests=true --settings /var/tmp/settings.xml | egrep -v 'Download|Install'
+script: 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'
+install: mvn -q -U install -DskipTests=true --settings /var/tmp/settings.xml
notifications:
email:
- kill-bill-commits@googlegroups.com
-env:
- global:
- - MAVEN_OPTS="-Xmx512m -XX:MaxPermSize=192m"
-
matrix:
include:
- - env: PHASE="-Ptravis,jdk16" JDK=oraclejdk8
- - env: PHASE="-Ptravis,jdk16" JDK=openjdk8
- - env: PHASE="-Ptravis,jdk17" JDK=oraclejdk8
- - env: PHASE="-Ptravis,jdk17" JDK=openjdk8
- - env: PHASE="-Ptravis,jdk18" JDK=oraclejdk8
- env: PHASE="-Ptravis,jdk18" JDK=openjdk8
- - env: PHASE="-Pmysql,jdk16" JDK=oraclejdk8
- - env: PHASE="-Pmysql,jdk16" JDK=openjdk8
- - env: PHASE="-Pmysql,jdk17" JDK=oraclejdk8
- - env: PHASE="-Pmysql,jdk17" JDK=openjdk8
- - env: PHASE="-Pmysql,jdk18" JDK=oraclejdk8
- env: PHASE="-Pmysql,jdk18" JDK=openjdk8
- - env: PHASE="-Ppostgresql,jdk16" JDK=oraclejdk8
- - env: PHASE="-Ppostgresql,jdk16" JDK=openjdk8
- - env: PHASE="-Ppostgresql,jdk17" JDK=oraclejdk8
- - env: PHASE="-Ppostgresql,jdk17" JDK=openjdk8
- - env: PHASE="-Ppostgresql,jdk18" JDK=oraclejdk8
- env: PHASE="-Ppostgresql,jdk18" JDK=openjdk8
fast_finish: true
-
-after_success:
- - '[ "${TRAVIS_PULL_REQUEST}" = "false" ] && echo "<settings><servers><server><id>sonatype-nexus-snapshots</id><username>\${env.OSSRH_USER}</username><password>\${env.OSSRH_PASS}</password></server></servers></settings>" > ~/settings.xml && mvn deploy -DskipTests=true --settings ~/settings.xml | egrep "WARN|ERR|\[INFO\]\ ---|Upload" | egrep -v "[0-9]+/[0-9]+ KB" ; rm -f ~/settings.xml'
account/pom.xml 8(+2 -6)
diff --git a/account/pom.xml b/account/pom.xml
index 8f1eaa0..0a2e7c5 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.19.0-SNAPSHOT</version>
+ <version>0.19.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-account</artifactId>
@@ -79,7 +79,7 @@
</dependency>
<dependency>
<groupId>org.antlr</groupId>
- <artifactId>stringtemplate</artifactId>
+ <artifactId>ST4</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
@@ -88,10 +88,6 @@
<scope>test</scope>
</dependency>
<dependency>
- <groupId>org.jdbi</groupId>
- <artifactId>jdbi</artifactId>
- </dependency>
- <dependency>
<groupId>org.kill-bill.billing</groupId>
<artifactId>killbill-api</artifactId>
</dependency>
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 ca107c9..40647a9 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
@@ -42,6 +42,7 @@ public class DefaultAccount extends EntityBase implements Account {
private final Boolean isPaymentDelegatedToParent;
private final Integer billCycleDayLocal;
private final UUID paymentMethodId;
+ private final DateTime referenceTime;
private final DateTimeZone timeZone;
private final String locale;
private final String address1;
@@ -73,6 +74,7 @@ public class DefaultAccount extends EntityBase implements Account {
data.isPaymentDelegatedToParent(),
data.getBillCycleDayLocal(),
data.getPaymentMethodId(),
+ data.getReferenceTime(),
data.getTimeZone(),
data.getLocale(),
data.getAddress1(),
@@ -93,7 +95,7 @@ public class DefaultAccount extends EntityBase implements Account {
final String name, final Integer firstNameLength, final Currency currency,
final UUID parentAccountId, final Boolean isPaymentDelegatedToParent,
final Integer billCycleDayLocal, final UUID paymentMethodId,
- final DateTimeZone timeZone, final String locale,
+ final DateTime referenceTime, final DateTimeZone timeZone, final String locale,
final String address1, final String address2, final String companyName,
final String city, final String stateOrProvince, final String country,
final String postalCode, final String phone, final String notes,
@@ -110,6 +112,7 @@ public class DefaultAccount extends EntityBase implements Account {
isPaymentDelegatedToParent,
billCycleDayLocal,
paymentMethodId,
+ referenceTime,
timeZone,
locale,
address1,
@@ -130,7 +133,7 @@ public class DefaultAccount extends EntityBase implements Account {
final String name, final Integer firstNameLength, final Currency currency,
final UUID parentAccountId, final Boolean isPaymentDelegatedToParent,
final Integer billCycleDayLocal, final UUID paymentMethodId,
- final DateTimeZone timeZone, final String locale,
+ final DateTime referenceTime, final DateTimeZone timeZone, final String locale,
final String address1, final String address2, final String companyName,
final String city, final String stateOrProvince, final String country,
final String postalCode, final String phone, final String notes,
@@ -145,6 +148,7 @@ public class DefaultAccount extends EntityBase implements Account {
this.isPaymentDelegatedToParent = isPaymentDelegatedToParent != null ? isPaymentDelegatedToParent : false;
this.billCycleDayLocal = billCycleDayLocal == null ? DEFAULT_BILLING_CYCLE_DAY_LOCAL : billCycleDayLocal;
this.paymentMethodId = paymentMethodId;
+ this.referenceTime = referenceTime;
this.timeZone = timeZone;
this.locale = locale;
this.address1 = address1;
@@ -173,6 +177,7 @@ public class DefaultAccount extends EntityBase implements Account {
accountModelDao.getIsPaymentDelegatedToParent(),
accountModelDao.getBillingCycleDayLocal(),
accountModelDao.getPaymentMethodId(),
+ accountModelDao.getReferenceTime(),
accountModelDao.getTimeZone(),
accountModelDao.getLocale(),
accountModelDao.getAddress1(),
@@ -366,7 +371,7 @@ public class DefaultAccount extends EntityBase implements Account {
@Override
public DateTime getReferenceTime() {
- return AccountDateTimeUtils.getReferenceDateTime(this);
+ return referenceTime;
}
@Override
@@ -381,6 +386,7 @@ public class DefaultAccount extends EntityBase implements Account {
", isPaymentDelegatedToParent=" + isPaymentDelegatedToParent +
", billCycleDayLocal=" + billCycleDayLocal +
", paymentMethodId=" + paymentMethodId +
+ ", referenceTime=" + referenceTime +
", timezone=" + timeZone +
", locale=" + locale +
", address1=" + address1 +
@@ -468,6 +474,9 @@ public class DefaultAccount extends EntityBase implements Account {
if (stateOrProvince != null ? !stateOrProvince.equals(that.stateOrProvince) : that.stateOrProvince != null) {
return false;
}
+ if (referenceTime != null ? referenceTime.compareTo(that.referenceTime) != 0 : that.referenceTime != null) {
+ return false;
+ }
if (timeZone != null ? !timeZone.equals(that.timeZone) : that.timeZone != null) {
return false;
}
@@ -490,6 +499,7 @@ public class DefaultAccount extends EntityBase implements Account {
result = 31 * result + (isPaymentDelegatedToParent != null ? isPaymentDelegatedToParent.hashCode() : 0);
result = 31 * result + billCycleDayLocal;
result = 31 * result + (paymentMethodId != null ? paymentMethodId.hashCode() : 0);
+ result = 31 * result + (referenceTime != null ? referenceTime.hashCode() : 0);
result = 31 * result + (timeZone != null ? timeZone.hashCode() : 0);
result = 31 * result + (locale != null ? locale.hashCode() : 0);
result = 31 * result + (address1 != null ? address1.hashCode() : 0);
@@ -514,8 +524,8 @@ public class DefaultAccount extends EntityBase implements Account {
// 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)
+ // * ignoreNullInput = false (case where we allow to reset values)
+ // * ignoreNullInput = true (case where we DON'T allow to reset values and so if such value is null we ignore the check)
//
//
if ((ignoreNullInput || externalKey != null) &&
@@ -545,6 +555,11 @@ public class DefaultAccount extends EntityBase implements Account {
timeZone, currentAccount.getTimeZone()));
}
+ if (referenceTime != null && currentAccount.getReferenceTime().withMillisOfDay(0).compareTo(referenceTime.withMillisOfDay(0)) != 0) {
+ throw new IllegalArgumentException(String.format("Killbill doesn't support updating the account referenceTime yet: new=%s, current=%s",
+ referenceTime, currentAccount.getReferenceTime()));
+ }
+
}
}
diff --git a/account/src/main/java/org/killbill/billing/account/api/DefaultImmutableAccountData.java b/account/src/main/java/org/killbill/billing/account/api/DefaultImmutableAccountData.java
index df7a78b..fdce2f1 100644
--- a/account/src/main/java/org/killbill/billing/account/api/DefaultImmutableAccountData.java
+++ b/account/src/main/java/org/killbill/billing/account/api/DefaultImmutableAccountData.java
@@ -61,7 +61,7 @@ public class DefaultImmutableAccountData implements ImmutableAccountData, Extern
account.getCurrency(),
account.getTimeZone(),
AccountDateTimeUtils.getFixedOffsetTimeZone(account),
- AccountDateTimeUtils.getReferenceDateTime(account));
+ account.getReferenceTime());
}
public DefaultImmutableAccountData(final AccountModelDao account) {
@@ -70,7 +70,7 @@ public class DefaultImmutableAccountData implements ImmutableAccountData, Extern
account.getCurrency(),
account.getTimeZone(),
AccountDateTimeUtils.getFixedOffsetTimeZone(account),
- AccountDateTimeUtils.getReferenceDateTime(account));
+ account.getReferenceTime());
}
@Override
diff --git a/account/src/main/java/org/killbill/billing/account/api/DefaultMutableAccountData.java b/account/src/main/java/org/killbill/billing/account/api/DefaultMutableAccountData.java
index 1f1c6fc..6829226 100644
--- a/account/src/main/java/org/killbill/billing/account/api/DefaultMutableAccountData.java
+++ b/account/src/main/java/org/killbill/billing/account/api/DefaultMutableAccountData.java
@@ -20,6 +20,7 @@ package org.killbill.billing.account.api;
import java.util.UUID;
+import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.killbill.billing.catalog.api.Currency;
@@ -37,6 +38,7 @@ public class DefaultMutableAccountData implements MutableAccountData {
private Boolean isPaymentDelegatedToParent;
private int billCycleDayLocal;
private UUID paymentMethodId;
+ private DateTime referenceTime;
private DateTimeZone timeZone;
private String locale;
private String address1;
@@ -54,8 +56,8 @@ public class DefaultMutableAccountData implements MutableAccountData {
public DefaultMutableAccountData(final String externalKey, final String email, final String name,
final int firstNameLength, final Currency currency,
final UUID parentAccountId, final Boolean isPaymentDelegatedToParent,
- final int billCycleDayLocal, final UUID paymentMethodId, final DateTimeZone timeZone,
- final String locale, final String address1, final String address2,
+ final int billCycleDayLocal, final UUID paymentMethodId, final DateTime referenceTime,
+ final DateTimeZone timeZone, final String locale, final String address1, final String address2,
final String companyName, final String city, final String stateOrProvince,
final String country, final String postalCode, final String phone,
final String notes, final boolean isMigrated, final boolean isNotifiedForInvoices) {
@@ -68,6 +70,7 @@ public class DefaultMutableAccountData implements MutableAccountData {
this.isPaymentDelegatedToParent = isPaymentDelegatedToParent;
this.billCycleDayLocal = billCycleDayLocal;
this.paymentMethodId = paymentMethodId;
+ this.referenceTime = referenceTime;
this.timeZone = timeZone;
this.locale = locale;
this.address1 = address1;
@@ -93,6 +96,7 @@ public class DefaultMutableAccountData implements MutableAccountData {
this.isPaymentDelegatedToParent = accountData.isPaymentDelegatedToParent();
this.billCycleDayLocal = accountData.getBillCycleDayLocal() == null ? DEFAULT_BILLING_CYCLE_DAY_LOCAL : accountData.getBillCycleDayLocal();
this.paymentMethodId = accountData.getPaymentMethodId();
+ this.referenceTime = accountData.getReferenceTime();
this.timeZone = accountData.getTimeZone();
this.locale = accountData.getLocale();
this.address1 = accountData.getAddress1();
@@ -174,11 +178,22 @@ public class DefaultMutableAccountData implements MutableAccountData {
}
@Override
+ public DateTime getReferenceTime() {
+ return referenceTime;
+ }
+
+
+ @Override
public void setPaymentMethodId(final UUID paymentMethodId) {
this.paymentMethodId = paymentMethodId;
}
@Override
+ public void setReferenceTime(final DateTime dateTime) {
+ this.referenceTime = referenceTime;
+ }
+
+ @Override
public DateTimeZone getTimeZone() {
return timeZone;
}
diff --git a/account/src/main/java/org/killbill/billing/account/api/user/DefaultAccountCreationEvent.java b/account/src/main/java/org/killbill/billing/account/api/user/DefaultAccountCreationEvent.java
index 1ddfd15..54da302 100644
--- a/account/src/main/java/org/killbill/billing/account/api/user/DefaultAccountCreationEvent.java
+++ b/account/src/main/java/org/killbill/billing/account/api/user/DefaultAccountCreationEvent.java
@@ -20,7 +20,10 @@ package org.killbill.billing.account.api.user;
import java.util.UUID;
+import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
+import org.joda.time.format.DateTimeFormatter;
+import org.joda.time.format.ISODateTimeFormat;
import org.killbill.billing.account.api.AccountData;
import org.killbill.billing.account.dao.AccountModelDao;
import org.killbill.billing.catalog.api.Currency;
@@ -34,6 +37,8 @@ import com.google.common.base.Strings;
public class DefaultAccountCreationEvent extends BusEventBase implements AccountCreationInternalEvent {
+ private static final DateTimeFormatter DATE_TIME_FORMATTER = ISODateTimeFormat.dateTimeParser();
+
private final UUID id;
private final AccountData data;
@@ -113,6 +118,7 @@ public class DefaultAccountCreationEvent extends BusEventBase implements Account
private final UUID parentAccountId;
private final Boolean isPaymentDelegatedToParent;
private final UUID paymentMethodId;
+ private final String referenceTime;
private final String timeZone;
private final String locale;
private final String address1;
@@ -137,6 +143,7 @@ public class DefaultAccountCreationEvent extends BusEventBase implements Account
d.getParentAccountId(),
d.getIsPaymentDelegatedToParent(),
d.getPaymentMethodId(),
+ d.getReferenceTime() != null ? d.getReferenceTime().toString() : null,
d.getTimeZone() != null ? d.getTimeZone().getID() : null,
d.getLocale(),
d.getAddress1(),
@@ -162,6 +169,7 @@ public class DefaultAccountCreationEvent extends BusEventBase implements Account
@JsonProperty("parentAccountId") final UUID parentAccountId,
@JsonProperty("isPaymentDelegatedToParent") final Boolean isPaymentDelegatedToParent,
@JsonProperty("paymentMethodId") final UUID paymentMethodId,
+ @JsonProperty("referenceTime") final String referenceTime,
@JsonProperty("timeZone") final String timeZone,
@JsonProperty("locale") final String locale,
@JsonProperty("address1") final String address1,
@@ -184,6 +192,7 @@ public class DefaultAccountCreationEvent extends BusEventBase implements Account
this.parentAccountId = parentAccountId;
this.isPaymentDelegatedToParent = isPaymentDelegatedToParent;
this.paymentMethodId = paymentMethodId;
+ this.referenceTime = referenceTime;
this.timeZone = timeZone;
this.locale = locale;
this.address1 = address1;
@@ -315,6 +324,11 @@ public class DefaultAccountCreationEvent extends BusEventBase implements Account
}
@Override
+ public DateTime getReferenceTime() {
+ return DATE_TIME_FORMATTER.parseDateTime(referenceTime);
+ }
+
+ @Override
@JsonIgnore
public Boolean isMigrated() {
return isMigrated;
@@ -414,10 +428,12 @@ public class DefaultAccountCreationEvent extends BusEventBase implements Account
if (stateOrProvince != null ? !stateOrProvince.equals(that.stateOrProvince) : that.stateOrProvince != null) {
return false;
}
+ if (referenceTime != null ? referenceTime.compareTo(that.referenceTime) != 0 : that.referenceTime != null) {
+ return false;
+ }
if (timeZone != null ? !timeZone.equals(that.timeZone) : that.timeZone != null) {
return false;
}
-
return true;
}
@@ -432,6 +448,7 @@ public class DefaultAccountCreationEvent extends BusEventBase implements Account
result = 31 * result + (parentAccountId != null ? parentAccountId.hashCode() : 0);
result = 31 * result + (isPaymentDelegatedToParent != null ? isPaymentDelegatedToParent.hashCode() : 0);
result = 31 * result + (paymentMethodId != null ? paymentMethodId.hashCode() : 0);
+ result = 31 * result + (referenceTime != null ? referenceTime.hashCode() : 0);
result = 31 * result + (timeZone != null ? timeZone.hashCode() : 0);
result = 31 * result + (locale != null ? locale.hashCode() : 0);
result = 31 * result + (address1 != null ? address1.hashCode() : 0);
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 c1474e4..798d720 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
@@ -101,7 +101,6 @@ public class DefaultAccountUserApi extends DefaultAccountApiBase implements Acco
}
final AccountModelDao account = new AccountModelDao(data);
-
if (null != account.getExternalKey() && account.getExternalKey().length() > 255) {
throw new AccountApiException(ErrorCode.EXTERNAL_KEY_LIMIT_EXCEEDED);
}
@@ -166,7 +165,7 @@ public class DefaultAccountUserApi extends DefaultAccountApiBase implements Acco
input.validateAccountUpdateInput(currentAccount, true);
- final AccountModelDao updatedAccountModelDao = new AccountModelDao(currentAccount.getId(), input);
+ final AccountModelDao updatedAccountModelDao = new AccountModelDao(currentAccount.getId(), input);
accountDao.update(updatedAccountModelDao, internalCallContextFactory.createInternalCallContext(updatedAccountModelDao.getId(), context));
}
diff --git a/account/src/main/java/org/killbill/billing/account/dao/AccountEmailSqlDao.java b/account/src/main/java/org/killbill/billing/account/dao/AccountEmailSqlDao.java
index 80856ce..5108df8 100644
--- a/account/src/main/java/org/killbill/billing/account/dao/AccountEmailSqlDao.java
+++ b/account/src/main/java/org/killbill/billing/account/dao/AccountEmailSqlDao.java
@@ -20,7 +20,7 @@ import java.util.List;
import java.util.UUID;
import org.skife.jdbi.v2.sqlobject.Bind;
-import org.skife.jdbi.v2.sqlobject.BindBean;
+import org.killbill.commons.jdbi.binder.SmartBindBean;
import org.skife.jdbi.v2.sqlobject.SqlQuery;
import org.skife.jdbi.v2.sqlobject.SqlUpdate;
@@ -30,17 +30,17 @@ import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.util.entity.dao.Audited;
import org.killbill.billing.util.entity.dao.EntitySqlDao;
-import org.killbill.billing.util.entity.dao.EntitySqlDaoStringTemplate;
+import org.killbill.commons.jdbi.template.KillBillSqlDaoStringTemplate;
-@EntitySqlDaoStringTemplate
+@KillBillSqlDaoStringTemplate
public interface AccountEmailSqlDao extends EntitySqlDao<AccountEmailModelDao, AccountEmail> {
@SqlUpdate
@Audited(ChangeType.DELETE)
- public void markEmailAsDeleted(@BindBean final AccountEmailModelDao accountEmail,
- @BindBean final InternalCallContext context);
+ public void markEmailAsDeleted(@SmartBindBean final AccountEmailModelDao accountEmail,
+ @SmartBindBean final InternalCallContext context);
@SqlQuery
public List<AccountEmailModelDao> getEmailByAccountId(@Bind("accountId") final UUID accountId,
- @BindBean final InternalTenantContext context);
+ @SmartBindBean final InternalTenantContext context);
}
diff --git a/account/src/main/java/org/killbill/billing/account/dao/AccountModelDao.java b/account/src/main/java/org/killbill/billing/account/dao/AccountModelDao.java
index b0e392f..5d2ddfa 100644
--- a/account/src/main/java/org/killbill/billing/account/dao/AccountModelDao.java
+++ b/account/src/main/java/org/killbill/billing/account/dao/AccountModelDao.java
@@ -48,6 +48,7 @@ public class AccountModelDao extends EntityModelDaoBase implements TimeZoneAware
private Boolean isPaymentDelegatedToParent;
private int billingCycleDayLocal;
private UUID paymentMethodId;
+ private DateTime referenceTime;
private DateTimeZone timeZone;
private String locale;
private String address1;
@@ -68,7 +69,7 @@ public class AccountModelDao extends EntityModelDaoBase implements TimeZoneAware
public AccountModelDao(final UUID id, final DateTime createdDate, final DateTime updatedDate, final String externalKey,
final String email, final String name, final Integer firstNameLength, final Currency currency,
final UUID parentAccountId, final Boolean isPaymentDelegatedToParent,
- final int billingCycleDayLocal, final UUID paymentMethodId, final DateTimeZone timeZone,
+ final int billingCycleDayLocal, final UUID paymentMethodId, final DateTime referenceTime, final DateTimeZone timeZone,
final String locale, final String address1, final String address2, final String companyName,
final String city, final String stateOrProvince, final String country, final String postalCode,
final String phone, final String notes, final Boolean migrated, final Boolean notifiedForInvoices) {
@@ -82,6 +83,7 @@ public class AccountModelDao extends EntityModelDaoBase implements TimeZoneAware
this.isPaymentDelegatedToParent = isPaymentDelegatedToParent;
this.billingCycleDayLocal = billingCycleDayLocal;
this.paymentMethodId = paymentMethodId;
+ this.referenceTime = referenceTime;
this.timeZone = MoreObjects.firstNonNull(timeZone, DateTimeZone.UTC);
this.locale = locale;
this.address1 = address1;
@@ -97,7 +99,7 @@ public class AccountModelDao extends EntityModelDaoBase implements TimeZoneAware
this.isNotifiedForInvoices = notifiedForInvoices;
}
- public AccountModelDao(final UUID id, @Nullable final DateTime createdDate, final DateTime updatedDate, final AccountData account) {
+ private AccountModelDao(final UUID id, @Nullable final DateTime createdDate, @Nullable final DateTime updatedDate, final AccountData account) {
this(id,
createdDate,
updatedDate,
@@ -110,6 +112,7 @@ public class AccountModelDao extends EntityModelDaoBase implements TimeZoneAware
account.isPaymentDelegatedToParent(),
MoreObjects.firstNonNull(account.getBillCycleDayLocal(), DEFAULT_BILLING_CYCLE_DAY_LOCAL),
account.getPaymentMethodId(),
+ account.getReferenceTime() != null ? account.getReferenceTime() : createdDate,
account.getTimeZone(),
account.getLocale(),
account.getAddress1(),
@@ -126,14 +129,17 @@ public class AccountModelDao extends EntityModelDaoBase implements TimeZoneAware
MoreObjects.firstNonNull(account.isNotifiedForInvoices(), false));
}
- public AccountModelDao(final UUID id, final AccountData account) {
- this(id, null, null, account);
+
+ public AccountModelDao(final UUID accountId, final AccountData account) {
+ this(accountId, null, null, account);
}
+
public AccountModelDao(final AccountData account) {
- this(UUIDs.randomUUID(), account);
+ this(UUIDs.randomUUID(), null, null, account);
}
+
@Override
public void setRecordId(final Long recordId) {
super.setRecordId(recordId);
@@ -216,6 +222,15 @@ public class AccountModelDao extends EntityModelDaoBase implements TimeZoneAware
}
@Override
+ public DateTime getReferenceTime() {
+ return referenceTime;
+ }
+
+ public void setReferenceTime(final DateTime referenceTime) {
+ this.referenceTime = referenceTime;
+ }
+
+ @Override
public DateTimeZone getTimeZone() {
return timeZone;
}
@@ -335,6 +350,7 @@ public class AccountModelDao extends EntityModelDaoBase implements TimeZoneAware
sb.append(", isPaymentDelegatedToParent=").append(isPaymentDelegatedToParent);
sb.append(", billingCycleDayLocal=").append(billingCycleDayLocal);
sb.append(", paymentMethodId=").append(paymentMethodId);
+ sb.append(", referenceTime=").append(referenceTime);
sb.append(", timeZone=").append(timeZone);
sb.append(", locale='").append(locale).append('\'');
sb.append(", address1='").append(address1).append('\'');
@@ -429,10 +445,12 @@ public class AccountModelDao extends EntityModelDaoBase implements TimeZoneAware
if (stateOrProvince != null ? !stateOrProvince.equals(that.stateOrProvince) : that.stateOrProvince != null) {
return false;
}
+ if (referenceTime != null ? referenceTime.compareTo(that.referenceTime) != 0 : that.referenceTime != null) {
+ return false;
+ }
if (timeZone != null ? !timeZone.equals(that.timeZone) : that.timeZone != null) {
return false;
}
-
return true;
}
@@ -448,6 +466,7 @@ public class AccountModelDao extends EntityModelDaoBase implements TimeZoneAware
result = 31 * result + (isPaymentDelegatedToParent != null ? isPaymentDelegatedToParent.hashCode() : 0);
result = 31 * result + billingCycleDayLocal;
result = 31 * result + (paymentMethodId != null ? paymentMethodId.hashCode() : 0);
+ result = 31 * result + (referenceTime != null ? referenceTime.hashCode() : 0);
result = 31 * result + (timeZone != null ? timeZone.hashCode() : 0);
result = 31 * result + (locale != null ? locale.hashCode() : 0);
result = 31 * result + (address1 != null ? address1.hashCode() : 0);
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 77bea77..1506e64 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
@@ -27,44 +27,44 @@ import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.util.audit.ChangeType;
import org.killbill.billing.util.entity.dao.Audited;
import org.killbill.billing.util.entity.dao.EntitySqlDao;
-import org.killbill.billing.util.entity.dao.EntitySqlDaoStringTemplate;
+import org.killbill.commons.jdbi.template.KillBillSqlDaoStringTemplate;
import org.skife.jdbi.v2.sqlobject.Bind;
-import org.skife.jdbi.v2.sqlobject.BindBean;
+import org.killbill.commons.jdbi.binder.SmartBindBean;
import org.skife.jdbi.v2.sqlobject.SqlQuery;
import org.skife.jdbi.v2.sqlobject.SqlUpdate;
-@EntitySqlDaoStringTemplate
+@KillBillSqlDaoStringTemplate
public interface AccountSqlDao extends EntitySqlDao<AccountModelDao, Account> {
@SqlQuery
public AccountModelDao getAccountByKey(@Bind("externalKey") final String key,
- @BindBean final InternalTenantContext context);
+ @SmartBindBean final InternalTenantContext context);
@SqlQuery
public UUID getIdFromKey(@Bind("externalKey") final String key,
- @BindBean final InternalTenantContext context);
+ @SmartBindBean final InternalTenantContext context);
@SqlQuery
public Integer getBCD(@Bind("id") String accountId,
- @BindBean final InternalTenantContext context);
+ @SmartBindBean final InternalTenantContext context);
@SqlUpdate
@Audited(ChangeType.UPDATE)
- public void update(@BindBean final AccountModelDao account,
- @BindBean final InternalCallContext context);
+ public void update(@SmartBindBean final AccountModelDao account,
+ @SmartBindBean final InternalCallContext context);
@SqlUpdate
@Audited(ChangeType.UPDATE)
public Object updatePaymentMethod(@Bind("id") String accountId,
@Bind("paymentMethodId") String paymentMethodId,
- @BindBean final InternalCallContext context);
+ @SmartBindBean final InternalCallContext context);
@SqlQuery
List<AccountModelDao> getAccountsByParentId(@Bind("parentAccountId") UUID parentAccountId,
- @BindBean final InternalTenantContext context);
+ @SmartBindBean final InternalTenantContext context);
@SqlQuery
public AccountModelDao luckySearch(@Bind("searchKey") final String searchKey,
- @BindBean final InternalTenantContext context);
+ @SmartBindBean 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 3054b9e..919ccf3 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
@@ -57,6 +57,7 @@ import org.skife.jdbi.v2.IDBI;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.inject.Inject;
@@ -81,6 +82,14 @@ public class DefaultAccountDao extends EntityDaoBase<AccountModelDao, Account, A
@Override
public void create(final AccountModelDao entity, final InternalCallContext context) throws AccountApiException {
+
+ // We don't enforce the created_date for the Account because it is extracted from context
+ // so, if there is no referenceTime specified we have to set it from the InternalCallContext#created_date
+ //
+ if (entity.getReferenceTime() == null) {
+ entity.setReferenceTime(context.getCreatedDate());
+ }
+
final AccountModelDao refreshedEntity = transactionalSqlDao.execute(getCreateEntitySqlDaoTransactionWrapper(entity, context));
// Populate the caches only after the transaction has been committed, in case of rollbacks
transactionalSqlDao.populateCaches(refreshedEntity);
diff --git a/account/src/main/resources/org/killbill/billing/account/dao/AccountEmailSqlDao.sql.stg b/account/src/main/resources/org/killbill/billing/account/dao/AccountEmailSqlDao.sql.stg
index fa5d62b..e54ad7d 100644
--- a/account/src/main/resources/org/killbill/billing/account/dao/AccountEmailSqlDao.sql.stg
+++ b/account/src/main/resources/org/killbill/billing/account/dao/AccountEmailSqlDao.sql.stg
@@ -1,4 +1,4 @@
-group AccountEmailSqlDao: EntitySqlDao;
+import "org/killbill/billing/util/entity/dao/EntitySqlDao.sql.stg"
tableName() ::= "account_emails"
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 937b12f..ddc38f6 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
@@ -1,4 +1,4 @@
-group AccountSqlDao: EntitySqlDao;
+import "org/killbill/billing/util/entity/dao/EntitySqlDao.sql.stg"
tableName() ::= "accounts"
@@ -14,6 +14,7 @@ tableFields(prefix) ::= <<
, <prefix>parent_account_id
, <prefix>is_payment_delegated_to_parent
, <prefix>payment_method_id
+, <prefix>reference_time
, <prefix>time_zone
, <prefix>locale
, <prefix>address1
@@ -43,6 +44,7 @@ tableValues() ::= <<
, :parentAccountId
, :isPaymentDelegatedToParent
, :paymentMethodId
+, :referenceTime
, :timeZone
, :locale
, :address1
@@ -64,7 +66,7 @@ tableValues() ::= <<
/** The accounts table doesn't have an account_record_id column (it's the record_id) **/
accountRecordIdFieldWithComma(prefix) ::= ""
-accountRecordIdValueWithComma(prefix) ::= ""
+accountRecordIdValueWithComma() ::= ""
update() ::= <<
update accounts set
@@ -91,7 +93,7 @@ update accounts set
, updated_date = :updatedDate
, updated_by = :updatedBy
where id = :id
-<AND_CHECK_TENANT()>
+<AND_CHECK_TENANT("")>
;
>>
@@ -101,19 +103,19 @@ updatePaymentMethod() ::= <<
SET payment_method_id = :paymentMethodId
, updated_date = :updatedDate
, updated_by = :updatedBy
- WHERE id = :id <AND_CHECK_TENANT()>;
+ WHERE id = :id <AND_CHECK_TENANT("")>;
>>
getAccountByKey() ::= <<
- select <allTableFields()>
+ select <allTableFields("")>
from accounts
- where external_key = :externalKey <AND_CHECK_TENANT()>;
+ where external_key = :externalKey <AND_CHECK_TENANT("")>;
>>
getBCD() ::= <<
select billing_cycle_day_local
from accounts
- where id = :id <AND_CHECK_TENANT()>;
+ where id = :id <AND_CHECK_TENANT("")>;
>>
searchQuery(prefix) ::= <<
@@ -130,50 +132,50 @@ select
<allTableFields("t.")>
from <tableName()> t
join (
- select distinct <recordIdField()>
+ select distinct <recordIdField("")>
from (
(
- select <recordIdField()>
+ select <recordIdField("")>
from <tableName()>
- where <idField()> = :searchKey
- <andCheckSoftDeletionWithComma()>
- <AND_CHECK_TENANT()>
+ where <idField("")> = :searchKey
+ <andCheckSoftDeletionWithComma("")>
+ <AND_CHECK_TENANT("")>
limit 1
)
union all
(
- select <recordIdField()>
+ select <recordIdField("")>
from <tableName()>
where name = :searchKey
- <andCheckSoftDeletionWithComma()>
- <AND_CHECK_TENANT()>
+ <andCheckSoftDeletionWithComma("")>
+ <AND_CHECK_TENANT("")>
limit 1
)
union all
(
- select <recordIdField()>
+ select <recordIdField("")>
from <tableName()>
where email = :searchKey
- <andCheckSoftDeletionWithComma()>
- <AND_CHECK_TENANT()>
+ <andCheckSoftDeletionWithComma("")>
+ <AND_CHECK_TENANT("")>
limit 1
)
union all
(
- select <recordIdField()>
+ select <recordIdField("")>
from <tableName()>
where external_key = :searchKey
- <andCheckSoftDeletionWithComma()>
- <AND_CHECK_TENANT()>
+ <andCheckSoftDeletionWithComma("")>
+ <AND_CHECK_TENANT("")>
limit 1
)
union all
(
- select <recordIdField()>
+ select <recordIdField("")>
from <tableName()>
where company_name = :searchKey
- <andCheckSoftDeletionWithComma()>
- <AND_CHECK_TENANT()>
+ <andCheckSoftDeletionWithComma("")>
+ <AND_CHECK_TENANT("")>
limit 1
)
) search_with_idx
@@ -185,14 +187,14 @@ limit 1
getIdFromKey() ::= <<
SELECT id
FROM accounts
- WHERE external_key = :externalKey <AND_CHECK_TENANT()>;
+ WHERE external_key = :externalKey <AND_CHECK_TENANT("")>;
>>
getAccountsByParentId() ::= <<
- select <allTableFields()>
+ select <allTableFields("")>
from accounts
where parent_account_id = :parentAccountId
- <AND_CHECK_TENANT()>
- <defaultOrderBy()>
+ <AND_CHECK_TENANT("")>
+ <defaultOrderBy("")>
;
>>
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 195eb5b..55019dc 100644
--- a/account/src/main/resources/org/killbill/billing/account/ddl.sql
+++ b/account/src/main/resources/org/killbill/billing/account/ddl.sql
@@ -4,7 +4,7 @@ DROP TABLE IF EXISTS accounts;
CREATE TABLE accounts (
record_id serial unique,
id varchar(36) NOT NULL,
- external_key varchar(255) NULL,
+ external_key varchar(255) NOT NULL,
email varchar(128) DEFAULT NULL,
name varchar(100) DEFAULT NULL,
first_name_length int DEFAULT NULL,
@@ -13,6 +13,7 @@ CREATE TABLE accounts (
parent_account_id varchar(36) DEFAULT NULL,
is_payment_delegated_to_parent boolean DEFAULT FALSE,
payment_method_id varchar(36) DEFAULT NULL,
+ reference_time datetime NOT NULL,
time_zone varchar(50) NOT NULL,
locale varchar(5) DEFAULT NULL,
address1 varchar(100) DEFAULT NULL,
@@ -41,20 +42,22 @@ 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 (
record_id serial unique,
id varchar(36) NOT NULL,
target_record_id bigint /*! unsigned */ not null,
- external_key varchar(255) NULL,
+ external_key varchar(255) NOT NULL,
email varchar(128) DEFAULT NULL,
name varchar(100) DEFAULT NULL,
first_name_length int DEFAULT NULL,
currency varchar(3) DEFAULT NULL,
billing_cycle_day_local int DEFAULT NULL,
parent_account_id varchar(36) DEFAULT NULL,
- payment_method_id varchar(36) DEFAULT NULL,
is_payment_delegated_to_parent boolean default false,
+ payment_method_id varchar(36) DEFAULT NULL,
+ reference_time datetime NOT NULL,
time_zone varchar(50) NOT NULL,
locale varchar(5) DEFAULT NULL,
address1 varchar(100) DEFAULT NULL,
diff --git a/account/src/main/resources/org/killbill/billing/account/migration/V20170915165117__external_key_not_null.sql b/account/src/main/resources/org/killbill/billing/account/migration/V20170915165117__external_key_not_null.sql
new file mode 100644
index 0000000..73b42fd
--- /dev/null
+++ b/account/src/main/resources/org/killbill/billing/account/migration/V20170915165117__external_key_not_null.sql
@@ -0,0 +1,2 @@
+alter table accounts modify external_key varchar(255) NOT NULL;
+alter table account_history modify external_key varchar(255) NOT NULL;
diff --git a/account/src/main/resources/org/killbill/billing/account/migration/V20171108184350__reference_time.sql b/account/src/main/resources/org/killbill/billing/account/migration/V20171108184350__reference_time.sql
new file mode 100644
index 0000000..68d8e2a
--- /dev/null
+++ b/account/src/main/resources/org/killbill/billing/account/migration/V20171108184350__reference_time.sql
@@ -0,0 +1,4 @@
+alter table accounts add column reference_time datetime NOT NULL after payment_method_id;
+alter table account_history add column reference_time datetime NOT NULL after payment_method_id;
+update accounts set reference_time = created_date;
+update account_history set reference_time = created_date;
\ No newline at end of file
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 8b53264..f3cf489 100644
--- a/account/src/test/java/org/killbill/billing/account/AccountTestUtils.java
+++ b/account/src/test/java/org/killbill/billing/account/AccountTestUtils.java
@@ -19,6 +19,7 @@ package org.killbill.billing.account;
import java.util.Locale;
import java.util.UUID;
+import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.killbill.billing.account.api.AccountData;
import org.killbill.billing.account.api.DefaultMutableAccountData;
@@ -65,27 +66,27 @@ public abstract class AccountTestUtils {
}
public static AccountModelDao createTestAccount() {
- return createTestAccount(30, 31, UUID.randomUUID().toString().substring(0, 4));
+ return createTestAccount(31, UUID.randomUUID().toString().substring(0, 4));
}
public static AccountModelDao createTestAccount(final String phone) {
- return createTestAccount(30, 31, phone);
+ return createTestAccount(31, phone);
}
public static AccountModelDao createTestAccount(final int billCycleDay) {
- return createTestAccount(billCycleDay, billCycleDay, UUID.randomUUID().toString().substring(0, 4));
+ return createTestAccount(billCycleDay, UUID.randomUUID().toString().substring(0, 4));
}
- private static AccountModelDao createTestAccount(final int billCycleDayUTC, final int billCycleDayLocal, final String phone) {
- final AccountData accountData = createAccountData(billCycleDayUTC, billCycleDayLocal, phone);
+ private static AccountModelDao createTestAccount(final int billCycleDayLocal, final String phone) {
+ final AccountData accountData = createAccountData(billCycleDayLocal, phone);
return new AccountModelDao(UUID.randomUUID(), accountData);
}
public static MutableAccountData createAccountData() {
- return createAccountData(30, 31, UUID.randomUUID().toString().substring(0, 4));
+ return createAccountData(31, UUID.randomUUID().toString().substring(0, 4));
}
- private static MutableAccountData createAccountData(final int billCycleDayUTC, final int billCycleDayLocal, final String phone) {
+ private static MutableAccountData createAccountData(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();
@@ -104,7 +105,7 @@ public abstract class AccountTestUtils {
final String notes = UUID.randomUUID().toString();
return new DefaultMutableAccountData(externalKey, email, name, firstNameLength, currency, null, false,
- billCycleDayLocal, paymentMethodId, timeZone,
+ billCycleDayLocal, paymentMethodId, null, timeZone,
locale, address1, address2, companyName, city, stateOrProvince,
country, postalCode, phone, notes, false, true);
}
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 f5d36ef..76627a0 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
@@ -138,7 +138,7 @@ public class TestDefaultAccountUserApi extends AccountTestSuiteWithEmbeddedDB {
// Update the address and leave other fields null
final MutableAccountData mutableAccountData = new DefaultMutableAccountData(null, null, null, 0, null, null, false, 0, null,
- null, null, null, null, null, null,
+ clock.getUTCNow(), null, null, null, null, null, null,
null, null, null, null, null, false, false);
final String newAddress1 = UUID.randomUUID().toString();
mutableAccountData.setAddress1(newAddress1);
diff --git a/account/src/test/java/org/killbill/billing/account/api/user/TestDefaultAccountUserApiWithMocks.java b/account/src/test/java/org/killbill/billing/account/api/user/TestDefaultAccountUserApiWithMocks.java
index 44e370c..28e1ba0 100644
--- a/account/src/test/java/org/killbill/billing/account/api/user/TestDefaultAccountUserApiWithMocks.java
+++ b/account/src/test/java/org/killbill/billing/account/api/user/TestDefaultAccountUserApiWithMocks.java
@@ -20,6 +20,7 @@ package org.killbill.billing.account.api.user;
import java.util.UUID;
+import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.killbill.billing.account.AccountTestSuiteNoDB;
import org.killbill.billing.account.api.AccountData;
@@ -66,6 +67,7 @@ public class TestDefaultAccountUserApiWithMocks extends AccountTestSuiteNoDB {
final Currency currency = Currency.BRL;
final Integer billCycleDay = Integer.MAX_VALUE;
final UUID paymentMethodId = UUID.randomUUID();
+ final DateTime referenceTime = clock.getUTCNow();
final DateTimeZone timeZone = DateTimeZone.UTC;
final String locale = UUID.randomUUID().toString();
final String address1 = UUID.randomUUID().toString();
@@ -80,7 +82,7 @@ public class TestDefaultAccountUserApiWithMocks extends AccountTestSuiteNoDB {
final Boolean isMigrated = true;
final Boolean isNotifiedForInvoices = false;
final AccountData data = new DefaultAccount(id, externalKey, email, name, firstNameLength, currency, null, false, billCycleDay,
- paymentMethodId, timeZone, locale, address1, address2, companyName,
+ paymentMethodId, referenceTime, timeZone, locale, address1, address2, companyName,
city, stateOrProvince, country, postalCode, phone, notes, isMigrated, isNotifiedForInvoices);
accountUserApi.createAccount(data, callContext);
diff --git a/account/src/test/java/org/killbill/billing/account/api/user/TestEventJson.java b/account/src/test/java/org/killbill/billing/account/api/user/TestEventJson.java
index c5176da..8e142cb 100644
--- a/account/src/test/java/org/killbill/billing/account/api/user/TestEventJson.java
+++ b/account/src/test/java/org/killbill/billing/account/api/user/TestEventJson.java
@@ -20,6 +20,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
+import org.joda.time.DateTime;
import org.testng.Assert;
import org.testng.annotations.Test;
@@ -51,7 +52,7 @@ public class TestEventJson extends AccountTestSuiteNoDB {
@Test(groups = "fast", description="Test Account event serialization")
public void testAccountCreationEvent() throws Exception {
final DefaultAccountData data = new DefaultAccountData("dsfdsf", "bobo", 3, "bobo@yahoo.com", 12, "USD", null, false, UUID.randomUUID(),
- "UTC", "US", "21 avenue", "", "Gling", "San Franciso", "CA", "94110", "USA", "4126789887", "notes", false, false);
+ new DateTime().toString(), "UTC", "US", "21 avenue", "", "Gling", "San Franciso", "CA", "94110", "USA", "4126789887", "notes", false, false);
final DefaultAccountCreationEvent e = new DefaultAccountCreationEvent(data, UUID.randomUUID(), 1L, 2L, null);
final String json = mapper.writeValueAsString(e);
diff --git a/account/src/test/java/org/killbill/billing/account/dao/TestAccountDao.java b/account/src/test/java/org/killbill/billing/account/dao/TestAccountDao.java
index e8ea8f0..3db923f 100644
--- a/account/src/test/java/org/killbill/billing/account/dao/TestAccountDao.java
+++ b/account/src/test/java/org/killbill/billing/account/dao/TestAccountDao.java
@@ -65,7 +65,7 @@ public class TestAccountDao extends AccountTestSuiteWithEmbeddedDB {
final String email = UUID.randomUUID().toString();
final String name = UUID.randomUUID().toString();
final AccountData accountData = new DefaultMutableAccountData(null, email, name, 0, null, null, false,
- 0, null, null, null, null,
+ 0, null, clock.getUTCNow(), null, null, null,
null, null, null, null, null,
null, null, null, false, true);
final AccountModelDao account = new AccountModelDao(UUID.randomUUID(), accountData);
@@ -184,7 +184,7 @@ public class TestAccountDao extends AccountTestSuiteWithEmbeddedDB {
@Test(groups = "slow", description = "Test Account DAO: tags")
public void testTags() throws TagApiException, TagDefinitionApiException {
final AccountModelDao account = createTestAccount();
- final TagDefinitionModelDao tagDefinition = tagDefinitionDao.create(UUID.randomUUID().toString().substring(0, 4), UUID.randomUUID().toString(), internalCallContext);
+ final TagDefinitionModelDao tagDefinition = tagDefinitionDao.create(UUID.randomUUID().toString().substring(0, 4), UUID.randomUUID().toString(), ObjectType.ACCOUNT.name(), internalCallContext);
final Tag tag = new DescriptiveTag(tagDefinition.getId(), ObjectType.ACCOUNT, account.getId(), internalCallContext.getCreatedDate());
tagDao.create(new TagModelDao(tag), internalCallContext);
api/pom.xml 2(+1 -1)
diff --git a/api/pom.xml b/api/pom.xml
index 9c403e2..d937a42 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.19.0-SNAPSHOT</version>
+ <version>0.19.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-internal-api</artifactId>
diff --git a/api/src/main/java/org/killbill/billing/callcontext/DefaultCallContext.java b/api/src/main/java/org/killbill/billing/callcontext/DefaultCallContext.java
index 865f58f..2d43bcd 100644
--- a/api/src/main/java/org/killbill/billing/callcontext/DefaultCallContext.java
+++ b/api/src/main/java/org/killbill/billing/callcontext/DefaultCallContext.java
@@ -20,9 +20,9 @@ import java.util.UUID;
import org.joda.time.DateTime;
-import org.killbill.clock.Clock;
import org.killbill.billing.util.callcontext.CallOrigin;
import org.killbill.billing.util.callcontext.UserType;
+import org.killbill.clock.Clock;
public class DefaultCallContext extends CallContextBase {
diff --git a/api/src/main/java/org/killbill/billing/callcontext/TimeAwareContext.java b/api/src/main/java/org/killbill/billing/callcontext/TimeAwareContext.java
index 2d23d02..fd936d2 100644
--- a/api/src/main/java/org/killbill/billing/callcontext/TimeAwareContext.java
+++ b/api/src/main/java/org/killbill/billing/callcontext/TimeAwareContext.java
@@ -29,18 +29,18 @@ public class TimeAwareContext {
private final DateTimeZone fixedOffsetTimeZone;
private final DateTime referenceDateTime;
- private final LocalTime referenceTime;
+ private final LocalTime referenceLocalTime;
public TimeAwareContext(@Nullable final DateTimeZone fixedOffsetTimeZone, @Nullable final DateTime referenceDateTime) {
this.fixedOffsetTimeZone = fixedOffsetTimeZone;
this.referenceDateTime = referenceDateTime;
- this.referenceTime = computeReferenceTime(referenceDateTime);
+ this.referenceLocalTime = computeReferenceTime(referenceDateTime);
}
public DateTime toUTCDateTime(final LocalDate localDate) {
validateContext();
- return ClockUtil.toUTCDateTime(localDate, getReferenceTime(), getFixedOffsetTimeZone());
+ return ClockUtil.toUTCDateTime(localDate, getReferenceLocalTime(), getFixedOffsetTimeZone());
}
public LocalDate toLocalDate(final DateTime dateTime) {
@@ -50,8 +50,8 @@ public class TimeAwareContext {
}
private void validateContext() {
- if (getFixedOffsetTimeZone() == null || getReferenceTime() == null) {
- throw new IllegalArgumentException(String.format("Context mis-configured: fixedOffsetTimeZone=%s, referenceTime=%s", getFixedOffsetTimeZone(), getReferenceTime()));
+ if (getFixedOffsetTimeZone() == null || getReferenceLocalTime() == null) {
+ throw new IllegalArgumentException(String.format("Context mis-configured: fixedOffsetTimeZone=%s, referenceLocalTime=%s", getFixedOffsetTimeZone(), getReferenceLocalTime()));
}
}
@@ -72,7 +72,7 @@ public class TimeAwareContext {
}
//@VisibleForTesting
- public LocalTime getReferenceTime() {
- return referenceTime;
+ public LocalTime getReferenceLocalTime() {
+ return referenceLocalTime;
}
}
diff --git a/api/src/main/java/org/killbill/billing/entitlement/EventsStream.java b/api/src/main/java/org/killbill/billing/entitlement/EventsStream.java
index f29f04e..20027f2 100644
--- a/api/src/main/java/org/killbill/billing/entitlement/EventsStream.java
+++ b/api/src/main/java/org/killbill/billing/entitlement/EventsStream.java
@@ -62,6 +62,8 @@ public interface EventsStream {
boolean isSubscriptionCancelled();
+ boolean isBlockChange(final DateTime effectiveDate);
+
int getDefaultBillCycleDayLocal();
Collection<BlockingState> getPendingEntitlementCancellationEvents();
diff --git a/api/src/main/java/org/killbill/billing/junction/BillingEventSet.java b/api/src/main/java/org/killbill/billing/junction/BillingEventSet.java
index b44faa9..3f760c2 100644
--- a/api/src/main/java/org/killbill/billing/junction/BillingEventSet.java
+++ b/api/src/main/java/org/killbill/billing/junction/BillingEventSet.java
@@ -30,7 +30,9 @@ public interface BillingEventSet extends SortedSet<BillingEvent> {
public boolean isAccountAutoInvoiceOff();
- public BillingMode getRecurringBillingMode();
+ public boolean isAccountAutoInvoiceDraft();
+
+ public boolean isAccountAutoInvoiceReuseDraft();
public List<UUID> getSubscriptionIdsWithAutoInvoiceOff();
diff --git a/api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBase.java b/api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBase.java
index b581b4c..29b10da 100644
--- a/api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBase.java
+++ b/api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBase.java
@@ -27,6 +27,7 @@ import org.killbill.billing.catalog.api.BillingPeriod;
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.PlanPhaseSpecifier;
import org.killbill.billing.catalog.api.PlanSpecifier;
import org.killbill.billing.catalog.api.PriceList;
import org.killbill.billing.catalog.api.Product;
@@ -54,15 +55,18 @@ public interface SubscriptionBase extends Entity, Blockable {
throws SubscriptionBaseApiException;
// Return the effective date of the change
- public DateTime changePlan(final PlanSpecifier spec, final List<PlanPhasePriceOverride> overrides, final CallContext context)
+ public DateTime changePlan(final PlanPhaseSpecifier spec, final List<PlanPhasePriceOverride> overrides, final CallContext context)
+ throws SubscriptionBaseApiException;
+
+ public boolean undoChangePlan(final CallContext context)
throws SubscriptionBaseApiException;
// Return the effective date of the change
- public DateTime changePlanWithDate(final PlanSpecifier spec, final List<PlanPhasePriceOverride> overrides, final DateTime requestedDate, final CallContext context)
+ public DateTime changePlanWithDate(final PlanPhaseSpecifier spec, final List<PlanPhasePriceOverride> overrides, final DateTime requestedDate, final CallContext context)
throws SubscriptionBaseApiException;
// Return the effective date of the change
- public DateTime changePlanWithPolicy(final PlanSpecifier spec, final List<PlanPhasePriceOverride> overrides,
+ public DateTime changePlanWithPolicy(final PlanPhaseSpecifier spec, final List<PlanPhasePriceOverride> overrides,
final BillingActionPolicy policy, final CallContext context)
throws SubscriptionBaseApiException;
diff --git a/api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseInternalApi.java b/api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseInternalApi.java
index 3bc9cb9..a896c01 100644
--- a/api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseInternalApi.java
+++ b/api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseInternalApi.java
@@ -47,11 +47,11 @@ public interface SubscriptionBaseInternalApi {
final boolean isMigrated, InternalCallContext context) throws SubscriptionBaseApiException;
public List<SubscriptionBaseWithAddOns> createBaseSubscriptionsWithAddOns(UUID accountId, Iterable<BaseEntitlementWithAddOnsSpecifier> baseEntitlementWithAddOnsSpecifier,
- InternalCallContext contextWithValidAccountRecordId) throws SubscriptionBaseApiException;
+ boolean renameCancelledBundleIfExist, InternalCallContext contextWithValidAccountRecordId) throws SubscriptionBaseApiException;
public void cancelBaseSubscriptions(Iterable<SubscriptionBase> subscriptions, BillingActionPolicy policy, int accountBillCycleDayLocal, InternalCallContext context) throws SubscriptionBaseApiException;
- public SubscriptionBaseBundle createBundleForAccount(UUID accountId, String bundleName, InternalCallContext context)
+ public SubscriptionBaseBundle createBundleForAccount(UUID accountId, String bundleName, boolean renameCancelledBundleIfExist, InternalCallContext context)
throws SubscriptionBaseApiException;
public List<SubscriptionBaseBundle> getBundlesForAccountAndKey(UUID accountId, String bundleKey, InternalTenantContext context)
@@ -86,18 +86,13 @@ public interface SubscriptionBaseInternalApi {
public List<EffectiveSubscriptionInternalEvent> getBillingTransitions(SubscriptionBase subscription, InternalTenantContext context);
- public DateTime getDryRunChangePlanEffectiveDate(SubscriptionBase subscription, PlanSpecifier spec, DateTime requestedDate, BillingActionPolicy policy, List<PlanPhasePriceOverride> overrides, InternalCallContext context) throws SubscriptionBaseApiException, CatalogApiException;
+ public DateTime getDryRunChangePlanEffectiveDate(SubscriptionBase subscription, PlanPhaseSpecifier spec, DateTime requestedDate, BillingActionPolicy policy, List<PlanPhasePriceOverride> overrides, InternalCallContext context) throws SubscriptionBaseApiException, CatalogApiException;
public List<EntitlementAOStatusDryRun> getDryRunChangePlanStatus(UUID subscriptionId, @Nullable String baseProductName,
DateTime requestedDate, InternalTenantContext context) throws SubscriptionBaseApiException;
public void updateExternalKey(UUID bundleId, String newExternalKey, InternalCallContext context);
- public Iterable<DateTime> getFutureNotificationsForAccount(InternalCallContext context);
-
- public Map<UUID, DateTime> getNextFutureEventForSubscriptions(final SubscriptionBaseTransitionType eventType, final InternalCallContext internalCallContext);
-
-
public void updateBCD(final UUID subscriptionId, final int bcd, @Nullable final LocalDate effectiveFromDate, final InternalCallContext internalCallContext) throws SubscriptionBaseApiException;
public int getDefaultBillCycleDayLocal(final Map<UUID, Integer> bcdCache, final SubscriptionBase subscription, final SubscriptionBase baseSubscription, final PlanPhaseSpecifier planPhaseSpecifier, final int accountBillCycleDayLocal, final DateTime effectiveDate, final InternalTenantContext context) throws SubscriptionBaseApiException;
diff --git a/api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseTransitionType.java b/api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseTransitionType.java
index 94606d0..2dcf5bd 100644
--- a/api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseTransitionType.java
+++ b/api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseTransitionType.java
@@ -41,6 +41,10 @@ public enum SubscriptionBaseTransitionType {
*/
UNCANCEL,
/**
+ * Occurs when a user undo a pending change before it reached its effective date
+ */
+ UNDO_CHANGE,
+ /**
* Generated by the system to mark a change of phase
*/
PHASE,
beatrix/pom.xml 6(+1 -5)
diff --git a/beatrix/pom.xml b/beatrix/pom.xml
index b534e04..95fd17f 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.19.0-SNAPSHOT</version>
+ <version>0.19.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-beatrix</artifactId>
@@ -115,10 +115,6 @@
<scope>test</scope>
</dependency>
<dependency>
- <groupId>org.jdbi</groupId>
- <artifactId>jdbi</artifactId>
- </dependency>
- <dependency>
<groupId>org.kill-bill.billing</groupId>
<artifactId>killbill-account</artifactId>
<type>test-jar</type>
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 913eb94..0c86b09 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
@@ -58,6 +58,7 @@ import org.killbill.billing.lifecycle.glue.BusModule;
import org.killbill.billing.notification.plugin.api.BlockingStateMetadata;
import org.killbill.billing.notification.plugin.api.BroadcastMetadata;
import org.killbill.billing.notification.plugin.api.ExtBusEventType;
+import org.killbill.billing.notification.plugin.api.InvoicePaymentMetadata;
import org.killbill.billing.notification.plugin.api.PaymentMetadata;
import org.killbill.billing.notification.plugin.api.SubscriptionMetadata;
import org.killbill.billing.notification.plugin.api.SubscriptionMetadata.ActionType;
@@ -86,7 +87,7 @@ public class BeatrixListener {
private final PersistentBus externalBus;
private final InternalCallContextFactory internalCallContextFactory;
- protected final ObjectMapper objectMapper;
+ protected ObjectMapper objectMapper;
@Inject
public BeatrixListener(@Named(BusModule.EXTERNAL_BUS_NAMED) final PersistentBus externalBus,
@@ -225,6 +226,8 @@ public class BeatrixListener {
objectType = ObjectType.INVOICE;
objectId = realEventInvPay.getInvoiceId();
eventBusType = ExtBusEventType.INVOICE_PAYMENT_SUCCESS;
+ final InvoicePaymentMetadata invoicePaymentInfoMetaDataObj = new InvoicePaymentMetadata(realEventInvPay.getPaymentId(), realEventInvPay.getType(), realEventInvPay.getPaymentDate(), realEventInvPay.getAmount(), realEventInvPay.getCurrency(), realEventInvPay.getLinkedInvoicePaymentId(), realEventInvPay.getPaymentCookieId(), realEventInvPay.getProcessedCurrency());
+ metaData = objectMapper.writeValueAsString(invoicePaymentInfoMetaDataObj);
break;
case INVOICE_PAYMENT_ERROR:
@@ -232,6 +235,8 @@ public class BeatrixListener {
objectType = ObjectType.INVOICE;
objectId = realEventInvPayErr.getInvoiceId();
eventBusType = ExtBusEventType.INVOICE_PAYMENT_FAILED;
+ final InvoicePaymentMetadata invoicePaymentErrorMetaDataObj = new InvoicePaymentMetadata(realEventInvPayErr.getPaymentId(), realEventInvPayErr.getType(), realEventInvPayErr.getPaymentDate(), realEventInvPayErr.getAmount(), realEventInvPayErr.getCurrency(), realEventInvPayErr.getLinkedInvoicePaymentId(), realEventInvPayErr.getPaymentCookieId(), realEventInvPayErr.getProcessedCurrency());
+ metaData = objectMapper.writeValueAsString(invoicePaymentErrorMetaDataObj);
break;
case PAYMENT_INFO:
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/extbus/TestBeatrixListener.java b/beatrix/src/test/java/org/killbill/billing/beatrix/extbus/TestBeatrixListener.java
new file mode 100644
index 0000000..ffa9563
--- /dev/null
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/extbus/TestBeatrixListener.java
@@ -0,0 +1,789 @@
+/*
+ * 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.extbus;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.killbill.billing.ObjectType;
+import org.killbill.billing.callcontext.InternalCallContext;
+import org.killbill.billing.catalog.api.Currency;
+import org.killbill.billing.events.AccountChangeInternalEvent;
+import org.killbill.billing.events.AccountCreationInternalEvent;
+import org.killbill.billing.events.BroadcastInternalEvent;
+import org.killbill.billing.events.BusInternalEvent;
+import org.killbill.billing.events.BusInternalEvent.BusInternalEventType;
+import org.killbill.billing.events.ControlTagCreationInternalEvent;
+import org.killbill.billing.events.ControlTagDeletionInternalEvent;
+import org.killbill.billing.events.CustomFieldCreationEvent;
+import org.killbill.billing.events.CustomFieldDeletionEvent;
+import org.killbill.billing.events.InvoiceAdjustmentInternalEvent;
+import org.killbill.billing.events.InvoiceCreationInternalEvent;
+import org.killbill.billing.events.InvoiceNotificationInternalEvent;
+import org.killbill.billing.events.InvoicePaymentErrorInternalEvent;
+import org.killbill.billing.events.InvoicePaymentInfoInternalEvent;
+import org.killbill.billing.events.InvoicePaymentInternalEvent;
+import org.killbill.billing.events.OverdueChangeInternalEvent;
+import org.killbill.billing.events.PaymentErrorInternalEvent;
+import org.killbill.billing.events.PaymentInfoInternalEvent;
+import org.killbill.billing.events.PaymentInternalEvent;
+import org.killbill.billing.events.PaymentPluginErrorInternalEvent;
+import org.killbill.billing.events.TenantConfigChangeInternalEvent;
+import org.killbill.billing.events.TenantConfigDeletionInternalEvent;
+import org.killbill.billing.events.UserTagCreationInternalEvent;
+import org.killbill.billing.events.UserTagDeletionInternalEvent;
+import org.killbill.billing.invoice.api.InvoicePaymentType;
+import org.killbill.billing.notification.plugin.api.BroadcastMetadata;
+import org.killbill.billing.notification.plugin.api.ExtBusEventType;
+import org.killbill.billing.notification.plugin.api.InvoicePaymentMetadata;
+import org.killbill.billing.notification.plugin.api.PaymentMetadata;
+import org.killbill.billing.payment.api.TransactionStatus;
+import org.killbill.billing.payment.api.TransactionType;
+import org.killbill.billing.util.callcontext.CallOrigin;
+import org.killbill.billing.util.callcontext.InternalCallContextFactory;
+import org.killbill.billing.util.callcontext.TenantContext;
+import org.killbill.billing.util.callcontext.UserType;
+import org.killbill.bus.api.BusEvent;
+import org.killbill.bus.api.PersistentBus;
+import org.killbill.bus.api.PersistentBus.EventBusException;
+import org.mockito.ArgumentCaptor;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyObject;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNull;
+
+public class TestBeatrixListener {
+
+ private static final Long SEARCH_KEY_2 = 9L;
+ private static final Long SEARCH_KEY_1 = 10L;
+ private static final UUID USER_TOKEN = UUID.randomUUID();
+ private static final Long ACCOUNT_RECORD_ID = 11L;
+ private static final Long TENANT_RECORD_ID = 12L;
+ private static final UUID OBJECT_ID = UUID.randomUUID();
+ private static final UUID TENANT_ID = UUID.randomUUID();
+ private static final UUID ACCOUNT_ID = UUID.randomUUID();
+ private static final String METADATA = "metadata";
+
+ private static final UUID PAYMENT_ID = UUID.randomUUID();
+ private static final DateTime PAYMENT_DATE = DateTime.now();
+ private static final BigDecimal PAYMENT_AMOUNT = BigDecimal.valueOf(13);
+ private static final UUID LINKED_INVOICE_PAYMENT_ID = UUID.randomUUID();
+ private static final String PAYMENT_COOKIE_ID = "payment cookie id";
+
+ private static final UUID PAYMENT_TRANSACTION_ID = UUID.randomUUID();
+ private static final DateTime PAYMENT_EFFECTIVE_DATE = DateTime.now();
+
+ private static final String SERVICE_NAME = "service name";
+ private static final String BROADCAST_EVENT_TYPE = "broadcast event type";
+ private static final String BROADCAST_EVENT_JSON = "{\"key\":\"value\"}";
+
+ private BeatrixListener beatrixListener;
+ private PersistentBus externalBus;
+ private InternalCallContextFactory internalCallContextFactory;
+ private TenantContext tenantContext;
+ private ObjectMapper objectMapper;
+
+ @BeforeMethod(groups = "fast")
+ public void setUp() throws Exception {
+ externalBus = mock(PersistentBus.class);
+ internalCallContextFactory = mock(InternalCallContextFactory.class);
+ beatrixListener = new BeatrixListener(externalBus, internalCallContextFactory);
+
+ objectMapper = mock(ObjectMapper.class);
+ beatrixListener.objectMapper = objectMapper;
+
+ InternalCallContext internalContext = new InternalCallContext(
+ TENANT_RECORD_ID,
+ ACCOUNT_RECORD_ID,
+ null, null,
+ USER_TOKEN,
+ null, null, null, null, null, null, null
+ );
+ when(internalCallContextFactory.createInternalCallContext(
+ SEARCH_KEY_2,
+ SEARCH_KEY_1,
+ "BeatrixListener",
+ CallOrigin.INTERNAL,
+ UserType.SYSTEM,
+ USER_TOKEN)).thenReturn(internalContext);
+
+ tenantContext = mock(TenantContext.class);
+ when(tenantContext.getTenantId()).thenReturn(TENANT_ID);
+ when(internalCallContextFactory.createTenantContext(internalContext)).thenReturn(tenantContext);
+ }
+
+ @Test(groups = "fast")
+ public void testAccountCreate() throws Exception {
+ AccountCreationInternalEvent event = mock(AccountCreationInternalEvent.class);
+ provideCommonBusEventInfo(event);
+ when(event.getBusEventType()).thenReturn(BusInternalEventType.ACCOUNT_CREATE);
+ when(event.getId()).thenReturn(ACCOUNT_ID);
+
+ ArgumentCaptor<BusEvent> eventCaptor = ArgumentCaptor.forClass(BusEvent.class);
+
+ beatrixListener.handleAllInternalKillbillEvents(event);
+
+ verify(externalBus, times(1)).post(eventCaptor.capture());
+
+ DefaultBusExternalEvent postedEvent = (DefaultBusExternalEvent)eventCaptor.getValue();
+ assertEquals(postedEvent.getObjectId(), ACCOUNT_ID);
+ assertEquals(postedEvent.getObjectType(), ObjectType.ACCOUNT);
+ assertEquals(postedEvent.getEventType(), ExtBusEventType.ACCOUNT_CREATION);
+ assertNull(postedEvent.getMetaData());
+ assertCommonFieldsWithAccountId(postedEvent);
+ }
+
+ @Test(groups = "fast")
+ public void testAccountChange() throws Exception {
+ AccountChangeInternalEvent event = mock(AccountChangeInternalEvent.class);
+ provideCommonBusEventInfo(event);
+ when(event.getBusEventType()).thenReturn(BusInternalEventType.ACCOUNT_CHANGE);
+ when(event.getAccountId()).thenReturn(ACCOUNT_ID);
+
+ when(internalCallContextFactory.getAccountId(
+ ACCOUNT_ID,
+ ObjectType.ACCOUNT,
+ tenantContext)
+ ).thenReturn(ACCOUNT_ID);
+
+ ArgumentCaptor<BusEvent> eventCaptor = ArgumentCaptor.forClass(BusEvent.class);
+
+ beatrixListener.handleAllInternalKillbillEvents(event);
+
+ verify(externalBus).post(eventCaptor.capture());
+
+ DefaultBusExternalEvent postedEvent = (DefaultBusExternalEvent)eventCaptor.getValue();
+ assertEquals(postedEvent.getObjectId(), ACCOUNT_ID);
+ assertEquals(postedEvent.getObjectType(), ObjectType.ACCOUNT);
+ assertEquals(postedEvent.getEventType(), ExtBusEventType.ACCOUNT_CHANGE);
+ assertNull(postedEvent.getMetaData());
+ assertCommonFieldsWithAccountId(postedEvent);
+ }
+
+ @Test(groups = "fast")
+ public void testInvoiceCreation() throws Exception {
+ InvoiceCreationInternalEvent event = mock(InvoiceCreationInternalEvent.class);
+ provideCommonBusEventInfo(event);
+ when(event.getBusEventType()).thenReturn(BusInternalEventType.INVOICE_CREATION);
+ when(event.getInvoiceId()).thenReturn(OBJECT_ID);
+
+ when(internalCallContextFactory.getAccountId(
+ OBJECT_ID,
+ ObjectType.INVOICE,
+ tenantContext)
+ ).thenReturn(ACCOUNT_ID);
+
+ ArgumentCaptor<BusEvent> eventCaptor = ArgumentCaptor.forClass(BusEvent.class);
+
+ beatrixListener.handleAllInternalKillbillEvents(event);
+
+ verify(externalBus).post(eventCaptor.capture());
+
+ DefaultBusExternalEvent postedEvent = (DefaultBusExternalEvent)eventCaptor.getValue();
+ assertEquals(postedEvent.getObjectId(), OBJECT_ID);
+ assertEquals(postedEvent.getObjectType(), ObjectType.INVOICE);
+ assertEquals(postedEvent.getEventType(), ExtBusEventType.INVOICE_CREATION);
+ assertNull(postedEvent.getMetaData());
+ assertCommonFieldsWithAccountId(postedEvent);
+ }
+
+ @Test(groups = "fast")
+ public void testInvoiceNotification() throws Exception {
+ InvoiceNotificationInternalEvent event = mock(InvoiceNotificationInternalEvent.class);
+ provideCommonBusEventInfo(event);
+ when(event.getBusEventType()).thenReturn(BusInternalEventType.INVOICE_NOTIFICATION);
+ when(event.getAccountId()).thenReturn(ACCOUNT_ID);
+
+ ArgumentCaptor<BusEvent> eventCaptor = ArgumentCaptor.forClass(BusEvent.class);
+
+ beatrixListener.handleAllInternalKillbillEvents(event);
+
+ verify(externalBus).post(eventCaptor.capture());
+
+ DefaultBusExternalEvent postedEvent = (DefaultBusExternalEvent)eventCaptor.getValue();
+ assertNull(postedEvent.getObjectId());
+ assertEquals(postedEvent.getObjectType(), ObjectType.INVOICE);
+ assertEquals(postedEvent.getEventType(), ExtBusEventType.INVOICE_NOTIFICATION);
+ assertNull(postedEvent.getMetaData());
+ assertCommonFieldsWithAccountId(postedEvent);
+ }
+
+ @Test(groups = "fast")
+ public void testInvoiceAdjustment() throws Exception {
+ InvoiceAdjustmentInternalEvent event = mock(InvoiceAdjustmentInternalEvent.class);
+ provideCommonBusEventInfo(event);
+ when(event.getBusEventType()).thenReturn(BusInternalEventType.INVOICE_ADJUSTMENT);
+ when(event.getInvoiceId()).thenReturn(OBJECT_ID);
+
+ when(internalCallContextFactory.getAccountId(
+ OBJECT_ID,
+ ObjectType.INVOICE,
+ tenantContext)
+ ).thenReturn(ACCOUNT_ID);
+
+ ArgumentCaptor<BusEvent> eventCaptor = ArgumentCaptor.forClass(BusEvent.class);
+
+ beatrixListener.handleAllInternalKillbillEvents(event);
+
+ verify(externalBus).post(eventCaptor.capture());
+
+ DefaultBusExternalEvent postedEvent = (DefaultBusExternalEvent)eventCaptor.getValue();
+ assertEquals(postedEvent.getObjectId(), OBJECT_ID);
+ assertEquals(postedEvent.getObjectType(), ObjectType.INVOICE);
+ assertEquals(postedEvent.getEventType(), ExtBusEventType.INVOICE_ADJUSTMENT);
+ assertNull(postedEvent.getMetaData());
+ assertCommonFieldsWithAccountId(postedEvent);
+ }
+
+ @Test(groups = "fast")
+ public void testInvoicePaymentInfo() throws Exception {
+ InvoicePaymentInfoInternalEvent event = mock(InvoicePaymentInfoInternalEvent.class);
+ provideCommonBusEventInfo(event);
+ when(event.getBusEventType()).thenReturn(BusInternalEventType.INVOICE_PAYMENT_INFO);
+ when(event.getInvoiceId()).thenReturn(OBJECT_ID);
+ provideCommonInvoicePaymentInfo(event);
+
+ ArgumentCaptor<InvoicePaymentMetadata> metadataCaptor = ArgumentCaptor.forClass(InvoicePaymentMetadata.class);
+ when(objectMapper.writeValueAsString(metadataCaptor.capture())).thenReturn(METADATA);
+
+ when(internalCallContextFactory.getAccountId(
+ OBJECT_ID,
+ ObjectType.INVOICE,
+ tenantContext)
+ ).thenReturn(ACCOUNT_ID);
+
+ ArgumentCaptor<BusEvent> eventCaptor = ArgumentCaptor.forClass(BusEvent.class);
+
+ beatrixListener.handleAllInternalKillbillEvents(event);
+
+ verify(externalBus).post(eventCaptor.capture());
+
+ DefaultBusExternalEvent postedEvent = (DefaultBusExternalEvent)eventCaptor.getValue();
+ assertEquals(postedEvent.getObjectId(), OBJECT_ID);
+ assertEquals(postedEvent.getObjectType(), ObjectType.INVOICE);
+ assertEquals(postedEvent.getEventType(), ExtBusEventType.INVOICE_PAYMENT_SUCCESS);
+ assertEquals(postedEvent.getMetaData(), METADATA);
+ assertCommonFieldsWithAccountId(postedEvent);
+
+ InvoicePaymentMetadata invoicePaymentMetadata = metadataCaptor.getValue();
+ assertInvoicePaymentMetadataFields(invoicePaymentMetadata);
+ }
+
+ @Test(groups = "fast")
+ public void testInvoicePaymentError() throws Exception {
+ InvoicePaymentErrorInternalEvent event = mock(InvoicePaymentErrorInternalEvent.class);
+ provideCommonBusEventInfo(event);
+ when(event.getBusEventType()).thenReturn(BusInternalEventType.INVOICE_PAYMENT_ERROR);
+ when(event.getInvoiceId()).thenReturn(OBJECT_ID);
+ provideCommonInvoicePaymentInfo(event);
+
+ ArgumentCaptor<InvoicePaymentMetadata> metadataCaptor = ArgumentCaptor.forClass(InvoicePaymentMetadata.class);
+ when(objectMapper.writeValueAsString(metadataCaptor.capture())).thenReturn(METADATA);
+
+ when(internalCallContextFactory.getAccountId(
+ OBJECT_ID,
+ ObjectType.INVOICE,
+ tenantContext)
+ ).thenReturn(ACCOUNT_ID);
+
+ ArgumentCaptor<BusEvent> eventCaptor = ArgumentCaptor.forClass(BusEvent.class);
+
+ beatrixListener.handleAllInternalKillbillEvents(event);
+
+ verify(externalBus).post(eventCaptor.capture());
+
+ DefaultBusExternalEvent postedEvent = (DefaultBusExternalEvent)eventCaptor.getValue();
+ assertEquals(postedEvent.getObjectId(), OBJECT_ID);
+ assertEquals(postedEvent.getObjectType(), ObjectType.INVOICE);
+ assertEquals(postedEvent.getEventType(), ExtBusEventType.INVOICE_PAYMENT_FAILED);
+ assertEquals(postedEvent.getMetaData(), METADATA);
+ assertCommonFieldsWithAccountId(postedEvent);
+
+ InvoicePaymentMetadata invoicePaymentMetadata = metadataCaptor.getValue();
+ assertInvoicePaymentMetadataFields(invoicePaymentMetadata);
+ }
+
+ @Test(groups = "fast")
+ public void testPaymentInfo() throws Exception {
+ PaymentInfoInternalEvent event = mock(PaymentInfoInternalEvent.class);
+ provideCommonBusEventInfo(event);
+ when(event.getBusEventType()).thenReturn(BusInternalEventType.PAYMENT_INFO);
+ when(event.getPaymentId()).thenReturn(OBJECT_ID);
+ provideCommonPaymentInfo(event);
+
+ ArgumentCaptor<PaymentMetadata> metadataCaptor = ArgumentCaptor.forClass(PaymentMetadata.class);
+ when(objectMapper.writeValueAsString(metadataCaptor.capture())).thenReturn(METADATA);
+
+ when(internalCallContextFactory.getAccountId(
+ OBJECT_ID,
+ ObjectType.PAYMENT,
+ tenantContext)
+ ).thenReturn(ACCOUNT_ID);
+
+ ArgumentCaptor<BusEvent> eventCaptor = ArgumentCaptor.forClass(BusEvent.class);
+
+ beatrixListener.handleAllInternalKillbillEvents(event);
+
+ verify(externalBus).post(eventCaptor.capture());
+
+ DefaultBusExternalEvent postedEvent = (DefaultBusExternalEvent)eventCaptor.getValue();
+ assertEquals(postedEvent.getObjectId(), OBJECT_ID);
+ assertEquals(postedEvent.getObjectType(), ObjectType.PAYMENT);
+ assertEquals(postedEvent.getEventType(), ExtBusEventType.PAYMENT_SUCCESS);
+ assertEquals(postedEvent.getMetaData(), METADATA);
+ assertCommonFieldsWithAccountId(postedEvent);
+
+ PaymentMetadata paymentMetadata = metadataCaptor.getValue();
+ assertPaymentMetadataFields(paymentMetadata);
+ }
+
+ @Test(groups = "fast")
+ public void testPaymentError() throws Exception {
+ PaymentErrorInternalEvent event = mock(PaymentErrorInternalEvent.class);
+ provideCommonBusEventInfo(event);
+ when(event.getBusEventType()).thenReturn(BusInternalEventType.PAYMENT_ERROR);
+ when(event.getPaymentId()).thenReturn(OBJECT_ID);
+ when(event.getAccountId()).thenReturn(ACCOUNT_ID);
+ provideCommonPaymentInfo(event);
+
+ ArgumentCaptor<PaymentMetadata> metadataCaptor = ArgumentCaptor.forClass(PaymentMetadata.class);
+ when(objectMapper.writeValueAsString(metadataCaptor.capture())).thenReturn(METADATA);
+
+ ArgumentCaptor<BusEvent> eventCaptor = ArgumentCaptor.forClass(BusEvent.class);
+
+ beatrixListener.handleAllInternalKillbillEvents(event);
+
+ verify(externalBus).post(eventCaptor.capture());
+
+ DefaultBusExternalEvent postedEvent = (DefaultBusExternalEvent)eventCaptor.getValue();
+ assertEquals(postedEvent.getObjectId(), OBJECT_ID);
+ assertEquals(postedEvent.getObjectType(), ObjectType.PAYMENT);
+ assertEquals(postedEvent.getEventType(), ExtBusEventType.PAYMENT_FAILED);
+ assertEquals(postedEvent.getMetaData(), METADATA);
+ assertCommonFieldsWithAccountId(postedEvent);
+
+ PaymentMetadata paymentMetadata = metadataCaptor.getValue();
+ assertPaymentMetadataFields(paymentMetadata);
+ }
+
+ @Test(groups = "fast")
+ public void testPaymentPluginError() throws Exception {
+ PaymentPluginErrorInternalEvent event = mock(PaymentPluginErrorInternalEvent.class);
+ provideCommonBusEventInfo(event);
+ when(event.getBusEventType()).thenReturn(BusInternalEventType.PAYMENT_PLUGIN_ERROR);
+ when(event.getPaymentId()).thenReturn(OBJECT_ID);
+ provideCommonPaymentInfo(event);
+
+ ArgumentCaptor<PaymentMetadata> metadataCaptor = ArgumentCaptor.forClass(PaymentMetadata.class);
+ when(objectMapper.writeValueAsString(metadataCaptor.capture())).thenReturn(METADATA);
+
+ when(internalCallContextFactory.getAccountId(
+ OBJECT_ID,
+ ObjectType.PAYMENT,
+ tenantContext)
+ ).thenReturn(ACCOUNT_ID);
+
+ ArgumentCaptor<BusEvent> eventCaptor = ArgumentCaptor.forClass(BusEvent.class);
+
+ beatrixListener.handleAllInternalKillbillEvents(event);
+
+ verify(externalBus).post(eventCaptor.capture());
+
+ DefaultBusExternalEvent postedEvent = (DefaultBusExternalEvent)eventCaptor.getValue();
+ assertEquals(postedEvent.getObjectId(), OBJECT_ID);
+ assertEquals(postedEvent.getObjectType(), ObjectType.PAYMENT);
+ assertEquals(postedEvent.getEventType(), ExtBusEventType.PAYMENT_FAILED);
+ assertEquals(postedEvent.getMetaData(), METADATA);
+ assertCommonFieldsWithAccountId(postedEvent);
+
+ PaymentMetadata paymentMetadata = metadataCaptor.getValue();
+ assertPaymentMetadataFields(paymentMetadata);
+ }
+
+ @Test(groups = "fast")
+ public void testOverdueChange() throws Exception {
+ OverdueChangeInternalEvent event = mock(OverdueChangeInternalEvent.class);
+ provideCommonBusEventInfo(event);
+ when(event.getBusEventType()).thenReturn(BusInternalEventType.OVERDUE_CHANGE);
+ when(event.getOverdueObjectId()).thenReturn(OBJECT_ID);
+
+ when(internalCallContextFactory.getAccountId(
+ OBJECT_ID,
+ ObjectType.ACCOUNT,
+ tenantContext)
+ ).thenReturn(ACCOUNT_ID);
+
+ ArgumentCaptor<BusEvent> eventCaptor = ArgumentCaptor.forClass(BusEvent.class);
+
+ beatrixListener.handleAllInternalKillbillEvents(event);
+
+ verify(externalBus).post(eventCaptor.capture());
+
+ DefaultBusExternalEvent postedEvent = (DefaultBusExternalEvent)eventCaptor.getValue();
+ assertEquals(postedEvent.getObjectId(), OBJECT_ID);
+ assertEquals(postedEvent.getObjectType(), ObjectType.ACCOUNT);
+ assertEquals(postedEvent.getEventType(), ExtBusEventType.OVERDUE_CHANGE);
+ assertNull(postedEvent.getMetaData());
+ assertCommonFieldsWithAccountId(postedEvent);
+ }
+
+ @Test(groups = "fast")
+ public void testUserTagCreation() throws Exception {
+ UserTagCreationInternalEvent event = mock(UserTagCreationInternalEvent.class);
+ provideCommonBusEventInfo(event);
+ when(event.getBusEventType()).thenReturn(BusInternalEventType.USER_TAG_CREATION);
+ when(event.getTagId()).thenReturn(OBJECT_ID);
+
+ when(internalCallContextFactory.getAccountId(
+ OBJECT_ID,
+ ObjectType.TAG,
+ tenantContext)
+ ).thenReturn(ACCOUNT_ID);
+
+ ArgumentCaptor<BusEvent> eventCaptor = ArgumentCaptor.forClass(BusEvent.class);
+
+ beatrixListener.handleAllInternalKillbillEvents(event);
+
+ verify(externalBus).post(eventCaptor.capture());
+
+ DefaultBusExternalEvent postedEvent = (DefaultBusExternalEvent)eventCaptor.getValue();
+ assertEquals(postedEvent.getObjectId(), OBJECT_ID);
+ assertEquals(postedEvent.getObjectType(), ObjectType.TAG);
+ assertEquals(postedEvent.getEventType(), ExtBusEventType.TAG_CREATION);
+ assertNull(postedEvent.getMetaData());
+ assertCommonFieldsWithAccountId(postedEvent);
+ }
+
+ @Test(groups = "fast")
+ public void testControlTagCreation() throws Exception {
+ ControlTagCreationInternalEvent event = mock(ControlTagCreationInternalEvent.class);
+ provideCommonBusEventInfo(event);
+ when(event.getBusEventType()).thenReturn(BusInternalEventType.CONTROL_TAG_CREATION);
+ when(event.getTagId()).thenReturn(OBJECT_ID);
+
+ when(internalCallContextFactory.getAccountId(
+ OBJECT_ID,
+ ObjectType.TAG,
+ tenantContext)
+ ).thenReturn(ACCOUNT_ID);
+
+ ArgumentCaptor<BusEvent> eventCaptor = ArgumentCaptor.forClass(BusEvent.class);
+
+ beatrixListener.handleAllInternalKillbillEvents(event);
+
+ verify(externalBus).post(eventCaptor.capture());
+
+ DefaultBusExternalEvent postedEvent = (DefaultBusExternalEvent)eventCaptor.getValue();
+ assertEquals(postedEvent.getObjectId(), OBJECT_ID);
+ assertEquals(postedEvent.getObjectType(), ObjectType.TAG);
+ assertEquals(postedEvent.getEventType(), ExtBusEventType.TAG_CREATION);
+ assertNull(postedEvent.getMetaData());
+ assertCommonFieldsWithAccountId(postedEvent);
+ }
+
+ @Test(groups = "fast")
+ public void testUserTagDeletion() throws Exception {
+ UserTagDeletionInternalEvent event = mock(UserTagDeletionInternalEvent.class);
+ provideCommonBusEventInfo(event);
+ when(event.getBusEventType()).thenReturn(BusInternalEventType.USER_TAG_DELETION);
+ when(event.getTagId()).thenReturn(OBJECT_ID);
+
+ when(internalCallContextFactory.getAccountId(
+ OBJECT_ID,
+ ObjectType.TAG,
+ tenantContext)
+ ).thenReturn(ACCOUNT_ID);
+
+ ArgumentCaptor<BusEvent> eventCaptor = ArgumentCaptor.forClass(BusEvent.class);
+
+ beatrixListener.handleAllInternalKillbillEvents(event);
+
+ verify(externalBus).post(eventCaptor.capture());
+
+ DefaultBusExternalEvent postedEvent = (DefaultBusExternalEvent)eventCaptor.getValue();
+ assertEquals(postedEvent.getObjectId(), OBJECT_ID);
+ assertEquals(postedEvent.getObjectType(), ObjectType.TAG);
+ assertEquals(postedEvent.getEventType(), ExtBusEventType.TAG_DELETION);
+ assertNull(postedEvent.getMetaData());
+ assertCommonFieldsWithAccountId(postedEvent);
+ }
+
+ @Test(groups = "fast")
+ public void testControlTagDeletion() throws Exception {
+ ControlTagDeletionInternalEvent event = mock(ControlTagDeletionInternalEvent.class);
+ provideCommonBusEventInfo(event);
+ when(event.getBusEventType()).thenReturn(BusInternalEventType.CONTROL_TAG_DELETION);
+ when(event.getTagId()).thenReturn(OBJECT_ID);
+
+ when(internalCallContextFactory.getAccountId(
+ OBJECT_ID,
+ ObjectType.TAG,
+ tenantContext)
+ ).thenReturn(ACCOUNT_ID);
+
+ ArgumentCaptor<BusEvent> eventCaptor = ArgumentCaptor.forClass(BusEvent.class);
+
+ beatrixListener.handleAllInternalKillbillEvents(event);
+
+ verify(externalBus).post(eventCaptor.capture());
+
+ DefaultBusExternalEvent postedEvent = (DefaultBusExternalEvent)eventCaptor.getValue();
+ assertEquals(postedEvent.getObjectId(), OBJECT_ID);
+ assertEquals(postedEvent.getObjectType(), ObjectType.TAG);
+ assertEquals(postedEvent.getEventType(), ExtBusEventType.TAG_DELETION);
+ assertNull(postedEvent.getMetaData());
+ assertCommonFieldsWithAccountId(postedEvent);
+ }
+
+ @Test(groups = "fast")
+ public void testCustomFieldCreation() throws Exception {
+ CustomFieldCreationEvent event = mock(CustomFieldCreationEvent.class);
+ provideCommonBusEventInfo(event);
+ when(event.getBusEventType()).thenReturn(BusInternalEventType.CUSTOM_FIELD_CREATION);
+ when(event.getCustomFieldId()).thenReturn(OBJECT_ID);
+
+ when(internalCallContextFactory.getAccountId(
+ OBJECT_ID,
+ ObjectType.CUSTOM_FIELD,
+ tenantContext)
+ ).thenReturn(ACCOUNT_ID);
+
+ ArgumentCaptor<BusEvent> eventCaptor = ArgumentCaptor.forClass(BusEvent.class);
+
+ beatrixListener.handleAllInternalKillbillEvents(event);
+
+ verify(externalBus).post(eventCaptor.capture());
+
+ DefaultBusExternalEvent postedEvent = (DefaultBusExternalEvent)eventCaptor.getValue();
+ assertEquals(postedEvent.getObjectId(), OBJECT_ID);
+ assertEquals(postedEvent.getObjectType(), ObjectType.CUSTOM_FIELD);
+ assertEquals(postedEvent.getEventType(), ExtBusEventType.CUSTOM_FIELD_CREATION);
+ assertNull(postedEvent.getMetaData());
+ assertCommonFieldsWithAccountId(postedEvent);
+ }
+
+ @Test(groups = "fast")
+ public void testCustomFieldDeletion() throws Exception {
+ CustomFieldDeletionEvent event = mock(CustomFieldDeletionEvent.class);
+ provideCommonBusEventInfo(event);
+ when(event.getBusEventType()).thenReturn(BusInternalEventType.CUSTOM_FIELD_DELETION);
+ when(event.getCustomFieldId()).thenReturn(OBJECT_ID);
+
+ when(internalCallContextFactory.getAccountId(
+ OBJECT_ID,
+ ObjectType.CUSTOM_FIELD,
+ tenantContext)
+ ).thenReturn(ACCOUNT_ID);
+
+ ArgumentCaptor<BusEvent> eventCaptor = ArgumentCaptor.forClass(BusEvent.class);
+
+ beatrixListener.handleAllInternalKillbillEvents(event);
+
+ verify(externalBus).post(eventCaptor.capture());
+
+ DefaultBusExternalEvent postedEvent = (DefaultBusExternalEvent)eventCaptor.getValue();
+ assertEquals(postedEvent.getObjectId(), OBJECT_ID);
+ assertEquals(postedEvent.getObjectType(), ObjectType.CUSTOM_FIELD);
+ assertEquals(postedEvent.getEventType(), ExtBusEventType.CUSTOM_FIELD_DELETION);
+ assertNull(postedEvent.getMetaData());
+ assertCommonFieldsWithAccountId(postedEvent);
+ }
+
+ @Test(groups = "fast")
+ public void testTenantConfigChange() throws Exception {
+ TenantConfigChangeInternalEvent event = mock(TenantConfigChangeInternalEvent.class);
+ provideCommonBusEventInfo(event);
+ when(event.getBusEventType()).thenReturn(BusInternalEventType.TENANT_CONFIG_CHANGE);
+ when(event.getId()).thenReturn(OBJECT_ID);
+ when(event.getKey()).thenReturn(METADATA);
+
+ ArgumentCaptor<BusEvent> eventCaptor = ArgumentCaptor.forClass(BusEvent.class);
+
+ beatrixListener.handleAllInternalKillbillEvents(event);
+
+ verify(externalBus).post(eventCaptor.capture());
+
+ DefaultBusExternalEvent postedEvent = (DefaultBusExternalEvent)eventCaptor.getValue();
+ assertEquals(postedEvent.getObjectId(), OBJECT_ID);
+ assertEquals(postedEvent.getObjectType(), ObjectType.TENANT_KVS);
+ assertEquals(postedEvent.getEventType(), ExtBusEventType.TENANT_CONFIG_CHANGE);
+ assertEquals(postedEvent.getMetaData(), METADATA);
+ assertCommonFieldsWithNoAccountId(postedEvent);
+ }
+
+ @Test(groups = "fast")
+ public void testTenantConfigDeletion() throws Exception {
+ TenantConfigDeletionInternalEvent event = mock(TenantConfigDeletionInternalEvent.class);
+ provideCommonBusEventInfo(event);
+ when(event.getBusEventType()).thenReturn(BusInternalEventType.TENANT_CONFIG_DELETION);
+ when(event.getKey()).thenReturn(METADATA);
+
+ ArgumentCaptor<BusEvent> eventCaptor = ArgumentCaptor.forClass(BusEvent.class);
+
+ beatrixListener.handleAllInternalKillbillEvents(event);
+
+ verify(externalBus).post(eventCaptor.capture());
+
+ DefaultBusExternalEvent postedEvent = (DefaultBusExternalEvent)eventCaptor.getValue();
+ assertNull(postedEvent.getObjectId());
+ assertEquals(postedEvent.getObjectType(), ObjectType.TENANT_KVS);
+ assertEquals(postedEvent.getEventType(), ExtBusEventType.TENANT_CONFIG_DELETION);
+ assertEquals(postedEvent.getMetaData(), METADATA);
+ assertCommonFieldsWithNoAccountId(postedEvent);
+ }
+
+ @Test(groups = "fast")
+ public void testBroadcastService() throws Exception {
+ BroadcastInternalEvent event = mock(BroadcastInternalEvent.class);
+ provideCommonBusEventInfo(event);
+ when(event.getBusEventType()).thenReturn(BusInternalEventType.BROADCAST_SERVICE);
+ when(event.getServiceName()).thenReturn(SERVICE_NAME);
+ when(event.getType()).thenReturn(BROADCAST_EVENT_TYPE);
+ when(event.getJsonEvent()).thenReturn(BROADCAST_EVENT_JSON);
+
+ ArgumentCaptor<BroadcastMetadata> metadataCaptor = ArgumentCaptor.forClass(BroadcastMetadata.class);
+ when(objectMapper.writeValueAsString(metadataCaptor.capture())).thenReturn(METADATA);
+
+ ArgumentCaptor<BusEvent> eventCaptor = ArgumentCaptor.forClass(BusEvent.class);
+
+ beatrixListener.handleAllInternalKillbillEvents(event);
+
+ verify(externalBus).post(eventCaptor.capture());
+
+ DefaultBusExternalEvent postedEvent = (DefaultBusExternalEvent)eventCaptor.getValue();
+ assertNull(postedEvent.getObjectId());
+ assertEquals(postedEvent.getObjectType(), ObjectType.SERVICE_BROADCAST);
+ assertEquals(postedEvent.getEventType(), ExtBusEventType.BROADCAST_SERVICE);
+ assertCommonFieldsWithNoAccountId(postedEvent);
+
+ BroadcastMetadata broadcastMetadata = metadataCaptor.getValue();
+ assertEquals(broadcastMetadata.getService(), SERVICE_NAME);
+ assertEquals(broadcastMetadata.getCommandType(), BROADCAST_EVENT_TYPE);
+ assertEquals(broadcastMetadata.getEventJson(), BROADCAST_EVENT_JSON);
+ }
+
+ @Test(groups = "fast")
+ public void testInvalidInternalEvent() throws Exception {
+ BusInternalEvent event = mock(BusInternalEvent.class);
+ when(event.getBusEventType()).thenReturn(BusInternalEventType.BUNDLE_REPAIR);
+
+ beatrixListener.handleAllInternalKillbillEvents(event);
+
+ verify(externalBus, never()).post(any(BusEvent.class));
+ }
+
+ @Test(groups = "fast")
+ public void testJsonProcessingException() throws Exception {
+ InvoicePaymentInfoInternalEvent event = mock(InvoicePaymentInfoInternalEvent.class);
+ when(event.getBusEventType()).thenReturn(BusInternalEventType.INVOICE_PAYMENT_INFO);
+ when(objectMapper.writeValueAsString(anyObject())).thenThrow(JsonProcessingException.class);
+
+ // Just make sure exception gets swallowed.
+ beatrixListener.handleAllInternalKillbillEvents(event);
+ }
+
+ @Test(groups = "fast", expectedExceptions = RuntimeException.class)
+ public void testEventBusException() throws Exception {
+ AccountCreationInternalEvent event = mock(AccountCreationInternalEvent.class);
+ provideCommonBusEventInfo(event);
+ when(event.getBusEventType()).thenReturn(BusInternalEventType.ACCOUNT_CREATE);
+ when(event.getId()).thenReturn(ACCOUNT_ID);
+
+ doThrow(EventBusException.class).when(externalBus).post(any(BusEvent.class));
+
+ beatrixListener.handleAllInternalKillbillEvents(event);
+ }
+
+
+
+ private void provideCommonBusEventInfo(BusInternalEvent event) {
+ when(event.getSearchKey2()).thenReturn(SEARCH_KEY_2);
+ when(event.getSearchKey1()).thenReturn(SEARCH_KEY_1);
+ when(event.getUserToken()).thenReturn(USER_TOKEN);
+ }
+
+ private void assertCommonFieldsWithAccountId(DefaultBusExternalEvent postedEvent) {
+ assertEquals(postedEvent.getAccountId(), ACCOUNT_ID);
+ assertCommonFields(postedEvent);
+ }
+
+ private void assertCommonFieldsWithNoAccountId(DefaultBusExternalEvent postedEvent) {
+ assertNull(postedEvent.getAccountId());
+ assertCommonFields(postedEvent);
+ }
+
+ private void assertCommonFields(DefaultBusExternalEvent postedEvent) {
+ assertEquals(postedEvent.getTenantId(), TENANT_ID);
+ assertEquals(postedEvent.getSearchKey1(), ACCOUNT_RECORD_ID);
+ assertEquals(postedEvent.getSearchKey2(), TENANT_RECORD_ID);
+ assertEquals(postedEvent.getUserToken(), USER_TOKEN);
+ }
+
+ private void provideCommonInvoicePaymentInfo(InvoicePaymentInternalEvent event) {
+ when(event.getPaymentId()).thenReturn(PAYMENT_ID);
+ when(event.getType()).thenReturn(InvoicePaymentType.ATTEMPT);
+ when(event.getPaymentDate()).thenReturn(PAYMENT_DATE);
+ when(event.getAmount()).thenReturn(PAYMENT_AMOUNT);
+ when(event.getCurrency()).thenReturn(Currency.USD);
+ when(event.getLinkedInvoicePaymentId()).thenReturn(LINKED_INVOICE_PAYMENT_ID);
+ when(event.getPaymentCookieId()).thenReturn(PAYMENT_COOKIE_ID);
+ when(event.getProcessedCurrency()).thenReturn(Currency.EUR);
+ }
+
+ private void assertInvoicePaymentMetadataFields(InvoicePaymentMetadata invoicePaymentMetadata) {
+ assertEquals(invoicePaymentMetadata.getPaymentId(), PAYMENT_ID);
+ assertEquals(invoicePaymentMetadata.getInvoicePaymentType(), InvoicePaymentType.ATTEMPT);
+ assertEquals(invoicePaymentMetadata.getPaymentDate(), PAYMENT_DATE);
+ assertEquals(invoicePaymentMetadata.getAmount(), PAYMENT_AMOUNT);
+ assertEquals(invoicePaymentMetadata.getCurrency(), Currency.USD);
+ assertEquals(invoicePaymentMetadata.getLinkedInvoicePaymentId(), LINKED_INVOICE_PAYMENT_ID);
+ assertEquals(invoicePaymentMetadata.getPaymentCookieId(), PAYMENT_COOKIE_ID);
+ assertEquals(invoicePaymentMetadata.getProcessedCurrency(), Currency.EUR);
+ }
+
+ private void provideCommonPaymentInfo(PaymentInternalEvent event) {
+ when(event.getPaymentTransactionId()).thenReturn(PAYMENT_TRANSACTION_ID);
+ when(event.getAmount()).thenReturn(PAYMENT_AMOUNT);
+ when(event.getCurrency()).thenReturn(Currency.USD);
+ when(event.getStatus()).thenReturn(TransactionStatus.SUCCESS);
+ when(event.getTransactionType()).thenReturn(TransactionType.PURCHASE);
+ when(event.getEffectiveDate()).thenReturn(PAYMENT_EFFECTIVE_DATE);
+ }
+
+ private void assertPaymentMetadataFields(PaymentMetadata paymentMetadata) {
+ assertEquals(paymentMetadata.getPaymentTransactionId(), PAYMENT_TRANSACTION_ID);
+ assertEquals(paymentMetadata.getAmount(), PAYMENT_AMOUNT);
+ assertEquals(paymentMetadata.getCurrency(), Currency.USD);
+ assertEquals(paymentMetadata.getStatus(), TransactionStatus.SUCCESS);
+ assertEquals(paymentMetadata.getTransactionType(), TransactionType.PURCHASE);
+ assertEquals(paymentMetadata.getEffectiveDate(), PAYMENT_EFFECTIVE_DATE);
+ }
+}
\ No newline at end of file
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/BeatrixIntegrationModule.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/BeatrixIntegrationModule.java
index 5b501ad..0fae546 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/BeatrixIntegrationModule.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/BeatrixIntegrationModule.java
@@ -46,7 +46,6 @@ import org.killbill.billing.tenant.glue.DefaultTenantModule;
import org.killbill.billing.usage.glue.UsageModule;
import org.killbill.billing.util.config.definition.InvoiceConfig;
import org.killbill.billing.util.config.definition.PaymentConfig;
-import org.killbill.billing.util.email.EmailModule;
import org.killbill.billing.util.email.templates.TemplateModule;
import org.killbill.billing.util.glue.AuditModule;
import org.killbill.billing.util.glue.BroadcastModule;
@@ -88,7 +87,6 @@ public class BeatrixIntegrationModule extends KillBillModule {
install(new GlobalLockerModule(configSource));
install(new CacheModule(configSource));
install(new ConfigModule(configSource));
- install(new EmailModule(configSource));
install(new CallContextModule(configSource));
install(new TagStoreModule(configSource));
install(new CustomFieldModule(configSource));
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 7dd3961..fdc849d 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
@@ -29,11 +29,13 @@ import java.util.UUID;
import org.joda.time.DateTime;
import org.joda.time.LocalDate;
import org.killbill.billing.ErrorCode;
+import org.killbill.billing.api.FlakyRetryAnalyzer;
import org.killbill.billing.api.TestApiListener.NextEvent;
import org.killbill.billing.beatrix.integration.BeatrixIntegrationModule;
import org.killbill.billing.beatrix.util.InvoiceChecker.ExpectedInvoiceItemCheck;
import org.killbill.billing.catalog.api.BillingPeriod;
import org.killbill.billing.catalog.api.Currency;
+import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
import org.killbill.billing.catalog.api.PlanSpecifier;
import org.killbill.billing.catalog.api.PriceListSet;
import org.killbill.billing.catalog.api.ProductCategory;
@@ -695,10 +697,10 @@ public class TestOverdueIntegration extends TestOverdueBase {
// 2012-05-06 => Create an external charge on a new invoice
addDaysAndCheckForCompletion(5);
- final InvoiceItem externalCharge = new ExternalChargeInvoiceItem(null, account.getId(), bundle.getId(), "For overdue", new LocalDate(2012, 5, 6), BigDecimal.TEN, Currency.USD);
+ final InvoiceItem externalCharge = new ExternalChargeInvoiceItem(null, account.getId(), bundle.getId(), "For overdue", new LocalDate(2012, 5, 6), new LocalDate(2012, 6, 6), BigDecimal.TEN, Currency.USD);
invoiceUserApi.insertExternalCharges(account.getId(), clock.getUTCToday(), ImmutableList.<InvoiceItem>of(externalCharge), false, callContext).get(0);
assertListenerStatus();
- invoiceChecker.checkInvoice(account.getId(), 2, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 6), null, InvoiceItemType.EXTERNAL_CHARGE, BigDecimal.TEN));
+ invoiceChecker.checkInvoice(account.getId(), 2, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 6), new LocalDate(2012, 6, 6), InvoiceItemType.EXTERNAL_CHARGE, BigDecimal.TEN));
// 2012-05-31 => DAY 30 have to get out of trial before first payment
addDaysAndCheckForCompletion(25, NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
@@ -953,7 +955,8 @@ public class TestOverdueIntegration extends TestOverdueBase {
checkODState(OverdueWrapper.CLEAR_STATE_NAME);
}
- @Test(groups = "slow", description = "Test overdue state with number of unpaid invoices condition")
+ // Flaky, see https://github.com/killbill/killbill/issues/782
+ @Test(groups = "slow", description = "Test overdue state with number of unpaid invoices condition", retryAnalyzer = FlakyRetryAnalyzer.class)
public void testOverdueStateWithNumberOfUnpaidInvoicesCondition() throws Exception {
// 2012-05-01T00:03:42.000Z
clock.setTime(new DateTime(2012, 5, 1, 0, 3, 42, 0));
@@ -1025,7 +1028,8 @@ public class TestOverdueIntegration extends TestOverdueBase {
checkODState(OverdueWrapper.CLEAR_STATE_NAME);
}
- @Test(groups = "slow", description = "Test overdue state with total unpaid invoice balance condition")
+ // Flaky, see https://github.com/killbill/killbill/issues/782
+ @Test(groups = "slow", description = "Test overdue state with total unpaid invoice balance condition", retryAnalyzer = FlakyRetryAnalyzer.class)
public void testOverdueStateWithTotalUnpaidInvoiceBalanceCondition() throws Exception {
// 2012-05-01T00:03:42.000Z
clock.setTime(new DateTime(2012, 5, 1, 0, 3, 42, 0));
@@ -1195,7 +1199,7 @@ public class TestOverdueIntegration extends TestOverdueBase {
private void checkChangePlanWithOverdueState(final Entitlement entitlement, final boolean shouldFail, final boolean expectedPayment) {
if (shouldFail) {
try {
- entitlement.changePlan(new PlanSpecifier("Pistol", term, PriceListSet.DEFAULT_PRICELIST_NAME), null, ImmutableList.<PluginProperty>of(), callContext);
+ entitlement.changePlan(new PlanPhaseSpecifier("Pistol", term, PriceListSet.DEFAULT_PRICELIST_NAME), null, ImmutableList.<PluginProperty>of(), callContext);
} catch (EntitlementApiException e) {
assertTrue(e.getCause() instanceof BlockingApiException || e.getCode() == ErrorCode.SUB_CHANGE_NON_ACTIVE.getCode(),
String.format("Cause is %s, message is %s", e.getCause(), e.getMessage()));
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestCatalogRetireElements.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestCatalogRetireElements.java
index f893c7e..62a00e8 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestCatalogRetireElements.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestCatalogRetireElements.java
@@ -24,11 +24,13 @@ import org.killbill.billing.ErrorCode;
import org.killbill.billing.account.api.Account;
import org.killbill.billing.api.TestApiListener.NextEvent;
import org.killbill.billing.catalog.api.BillingPeriod;
+import org.killbill.billing.catalog.api.PhaseType;
import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
import org.killbill.billing.catalog.api.PriceListSet;
import org.killbill.billing.catalog.api.ProductCategory;
import org.killbill.billing.entitlement.api.DefaultEntitlement;
import org.killbill.billing.entitlement.api.Entitlement;
+import org.killbill.billing.entitlement.api.Entitlement.EntitlementState;
import org.killbill.billing.entitlement.api.EntitlementApiException;
import org.killbill.billing.invoice.api.Invoice;
import org.killbill.billing.payment.api.PluginProperty;
@@ -82,7 +84,7 @@ public class TestCatalogRetireElements extends TestIntegrationBase {
final PlanPhaseSpecifier spec = new PlanPhaseSpecifier(productName, term, PriceListSet.DEFAULT_PRICELIST_NAME, null);
try {
- entitlementApi.createBaseEntitlement(account.getId(), spec, "externalKey2", null, null, null, false, ImmutableList.<PluginProperty>of(), callContext);
+ entitlementApi.createBaseEntitlement(account.getId(), spec, "externalKey2", null, null, null, false, true, ImmutableList.<PluginProperty>of(), callContext);
fail(); // force to fail is there is not an exception
} catch (final EntitlementApiException e) {
assertEquals(e.getCode(), ErrorCode.CAT_PLAN_NOT_FOUND.getCode());
@@ -101,6 +103,260 @@ public class TestCatalogRetireElements extends TestIntegrationBase {
}
@Test(groups = "slow")
+ public void testRetirePlanWithUncancel() throws Exception {
+ // Catalog v1 starts in 2011-01-01
+ // Catalog v2 starts in 2015-12-01
+ final LocalDate today = new LocalDate(2015, 10, 5);
+
+ // Set clock to the initial start date - we implicitly assume here that the account timezone is UTC
+ clock.setDay(today);
+
+ final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(1));
+
+ final String productName = "Pistol";
+ final BillingPeriod term = BillingPeriod.MONTHLY;
+
+ Entitlement bpEntitlement =
+ createBaseEntitlementAndCheckForCompletion(account.getId(), "externalKey", productName,
+ ProductCategory.BASE, term, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
+
+ assertNotNull(bpEntitlement);
+ assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext).size(), 1);
+ assertEquals(bpEntitlement.getState(), EntitlementState.ACTIVE);
+
+ // Move out a month.
+ busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+ clock.addMonths(1);
+ assertListenerStatus();
+
+ // Cancel entitlement
+ bpEntitlement = bpEntitlement.cancelEntitlementWithDate(new LocalDate("2016-05-01"), true, ImmutableList.<PluginProperty>of(), callContext);
+ assertEquals(bpEntitlement.getState(), EntitlementState.ACTIVE);
+ assertListenerStatus();
+
+ // Move out a month.
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+ clock.addMonths(1);
+ assertListenerStatus();
+
+ // Catalog v2 should start now.
+
+ final PlanPhaseSpecifier spec = new PlanPhaseSpecifier(productName, term, PriceListSet.DEFAULT_PRICELIST_NAME, null);
+ try {
+ entitlementApi.createBaseEntitlement(account.getId(), spec, "externalKey2", null, null, null, false, true, ImmutableList.<PluginProperty>of(), callContext);
+ fail(); // force to fail is there is not an exception
+ } catch (final EntitlementApiException e) {
+ assertEquals(e.getCode(), ErrorCode.CAT_PLAN_NOT_FOUND.getCode());
+ }
+
+ // Uncancel entitlement
+ busHandler.pushExpectedEvents(NextEvent.UNCANCEL);
+ bpEntitlement.uncancelEntitlement(ImmutableList.<PluginProperty>of(), callContext);
+ assertListenerStatus();
+
+ // Move out a month and verify 'Pistol' plan continue working as expected.
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+ clock.addMonths(1);
+ assertListenerStatus();
+
+ final List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ assertEquals(invoices.size(), 4);
+ for (final Invoice invoice : invoices) {
+ assertEquals(invoice.getInvoiceItems().get(0).getPlanName(), "pistol-monthly");
+ }
+ }
+
+ @Test(groups = "slow")
+ public void testRetirePlanAfterChange() throws Exception {
+ // Catalog v1 starts in 2011-01-01
+ // Catalog v3 starts in 2016-01-01
+ final LocalDate today = new LocalDate(2015, 7, 5);
+
+ // Set clock to the initial start date - we implicitly assume here that the account timezone is UTC
+ clock.setDay(today);
+
+ final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(null));
+
+ final String productName = "Pistol";
+ final BillingPeriod term = BillingPeriod.MONTHLY;
+ final PlanPhaseSpecifier spec1 = new PlanPhaseSpecifier(productName, term, "DEFAULT", null);
+
+ busHandler.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
+ Entitlement bpEntitlement = entitlementApi.createBaseEntitlement(account.getId(), spec1, "externalKey", null, null, null, false, true, ImmutableList.<PluginProperty>of(), callContext);
+ assertListenerStatus();
+
+ assertNotNull(bpEntitlement);
+ assertEquals(bpEntitlement.getLastActivePhase().getPhaseType(), PhaseType.TRIAL);
+ assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext).size(), 1);
+
+ // Move out after trial (2015-08-04)
+ busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+ clock.addDays(30);
+ assertListenerStatus();
+
+ assertEquals(entitlementApi.getEntitlementForId(bpEntitlement.getId(), callContext).getLastActivePhase().getPhaseType(), PhaseType.EVERGREEN);
+ assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext).size(), 2);
+
+ // 2015-08-05
+ clock.addDays(1);
+ assertListenerStatus();
+
+ // Change to discount phase in SpecialDiscount pricelist (CBA generated, no payment)
+ // Note that we need to trigger a CHANGE outside a TRIAL phase to generate a CHANGE event (otherwise, a CREATE is generated)
+ final PlanPhaseSpecifier spec2 = new PlanPhaseSpecifier(productName, term, "SpecialDiscount", null);
+ busHandler.pushExpectedEvents(NextEvent.CHANGE, NextEvent.INVOICE);
+ bpEntitlement = bpEntitlement.changePlanWithDate(spec2, null, clock.getUTCToday(), ImmutableList.<PluginProperty>of(), callContext);
+ assertListenerStatus();
+
+ assertEquals(entitlementApi.getEntitlementForId(bpEntitlement.getId(), callContext).getLastActivePhase().getPhaseType(), PhaseType.DISCOUNT);
+ assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext).size(), 3);
+
+ // Move out after discount phase (happens on 2015-11-04)
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+ // 2015-09-05
+ clock.addMonths(1);
+ assertListenerStatus();
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+ // 2015-10-05
+ clock.addMonths(1);
+ assertListenerStatus();
+ busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.NULL_INVOICE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+ // 2015-11-05
+ clock.addMonths(1);
+ // This verifies the PlanAligner.getNextTimedPhase codepath with a CHANGE transition type
+ assertListenerStatus();
+
+ assertEquals(entitlementApi.getEntitlementForId(bpEntitlement.getId(), callContext).getLastActivePhase().getPhaseType(), PhaseType.EVERGREEN);
+
+ // Move out a month (2015-12-05)
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+ clock.addMonths(1);
+ assertListenerStatus();
+
+ // Move out a month (2016-01-01)
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+ clock.addMonths(1);
+ assertListenerStatus();
+
+ // Catalog v3 should start now.
+
+ try {
+ entitlementApi.createBaseEntitlement(account.getId(), spec2, "externalKey2", null, null, null, false, true, ImmutableList.<PluginProperty>of(), callContext);
+ fail(); // force to fail is there is not an exception
+ } catch (final EntitlementApiException e) {
+ assertEquals(e.getCode(), ErrorCode.CAT_NO_SUCH_PRODUCT.getCode());
+ }
+
+ // Move out a month and verify 'Pistol' discounted plan continues working as expected.
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+ clock.addMonths(1);
+ assertListenerStatus();
+
+ final List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ assertEquals(invoices.size(), 9);
+ }
+
+ @Test(groups = "slow")
+ public void testRetirePlanWithUncancelAfterChange() throws Exception {
+ // Catalog v1 starts in 2011-01-01
+ // Catalog v3 starts in 2016-01-01
+ final LocalDate today = new LocalDate(2015, 7, 5);
+
+ // Set clock to the initial start date - we implicitly assume here that the account timezone is UTC
+ clock.setDay(today);
+
+ final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(null));
+
+ final String productName = "Pistol";
+ final BillingPeriod term = BillingPeriod.MONTHLY;
+ final PlanPhaseSpecifier spec1 = new PlanPhaseSpecifier(productName, term, "DEFAULT", null);
+
+ busHandler.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
+ Entitlement bpEntitlement = entitlementApi.createBaseEntitlement(account.getId(), spec1, "externalKey", null, null, null, false, true, ImmutableList.<PluginProperty>of(), callContext);
+ assertListenerStatus();
+
+ assertNotNull(bpEntitlement);
+ assertEquals(bpEntitlement.getLastActivePhase().getPhaseType(), PhaseType.TRIAL);
+ assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext).size(), 1);
+
+ // Move out after trial (2015-08-04)
+ busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+ clock.addDays(30);
+ assertListenerStatus();
+
+ assertEquals(entitlementApi.getEntitlementForId(bpEntitlement.getId(), callContext).getLastActivePhase().getPhaseType(), PhaseType.EVERGREEN);
+ assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext).size(), 2);
+
+ // 2015-08-05
+ clock.addDays(1);
+ assertListenerStatus();
+
+ // Change to discount phase in SpecialDiscount pricelist (CBA generated, no payment)
+ // Note that we need to trigger a CHANGE outside a TRIAL phase to generate a CHANGE event (otherwise, a CREATE is generated)
+ final PlanPhaseSpecifier spec2 = new PlanPhaseSpecifier(productName, term, "SpecialDiscount", null);
+ busHandler.pushExpectedEvents(NextEvent.CHANGE, NextEvent.INVOICE);
+ bpEntitlement = bpEntitlement.changePlanWithDate(spec2, null, clock.getUTCToday(), ImmutableList.<PluginProperty>of(), callContext);
+ assertListenerStatus();
+
+ assertEquals(entitlementApi.getEntitlementForId(bpEntitlement.getId(), callContext).getLastActivePhase().getPhaseType(), PhaseType.DISCOUNT);
+ assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext).size(), 3);
+
+ // Cancel entitlement
+ bpEntitlement = bpEntitlement.cancelEntitlementWithDate(new LocalDate("2016-05-01"), true, ImmutableList.<PluginProperty>of(), callContext);
+ assertEquals(bpEntitlement.getState(), EntitlementState.ACTIVE);
+ assertListenerStatus();
+
+ // Move out after discount phase (happens on 2015-11-04)
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+ // 2015-09-05
+ clock.addMonths(1);
+ assertListenerStatus();
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+ // 2015-10-05
+ clock.addMonths(1);
+ assertListenerStatus();
+ busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.NULL_INVOICE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+ // 2015-11-05
+ clock.addMonths(1);
+ // This verifies the PlanAligner.getNextTimedPhase codepath with a CHANGE transition type
+ assertListenerStatus();
+
+ assertEquals(entitlementApi.getEntitlementForId(bpEntitlement.getId(), callContext).getLastActivePhase().getPhaseType(), PhaseType.EVERGREEN);
+
+ // Move out a month (2015-12-05)
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+ clock.addMonths(1);
+ assertListenerStatus();
+
+ // Uncancel entitlement
+ busHandler.pushExpectedEvents(NextEvent.UNCANCEL);
+ bpEntitlement.uncancelEntitlement(ImmutableList.<PluginProperty>of(), callContext);
+ assertListenerStatus();
+
+ // Move out a month (2016-01-01)
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+ clock.addMonths(1);
+ assertListenerStatus();
+
+ // Catalog v3 should start now.
+
+ try {
+ entitlementApi.createBaseEntitlement(account.getId(), spec2, "externalKey2", null, null, null, false, true, ImmutableList.<PluginProperty>of(), callContext);
+ fail(); // force to fail is there is not an exception
+ } catch (final EntitlementApiException e) {
+ assertEquals(e.getCode(), ErrorCode.CAT_NO_SUCH_PRODUCT.getCode());
+ }
+
+ // Move out a month and verify 'Pistol' discounted plan continues working as expected.
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+ clock.addMonths(1);
+ assertListenerStatus();
+
+ final List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ assertEquals(invoices.size(), 9);
+ }
+
+ @Test(groups = "slow")
public void testRetireProduct() throws Exception {
// Catalog v1 starts in 2011-01-01
// Catalog v3 starts in 2016-01-01
@@ -138,7 +394,7 @@ public class TestCatalogRetireElements extends TestIntegrationBase {
final PlanPhaseSpecifier spec = new PlanPhaseSpecifier(productName, term, PriceListSet.DEFAULT_PRICELIST_NAME, null);
try {
- entitlementApi.createBaseEntitlement(account.getId(), spec, "externalKey2", null, null, null, false, ImmutableList.<PluginProperty>of(), callContext);
+ entitlementApi.createBaseEntitlement(account.getId(), spec, "externalKey2", null, null, null, false, true, ImmutableList.<PluginProperty>of(), callContext);
fail(); // force to fail is there is not an exception
} catch (final EntitlementApiException e) {
assertTrue(e.getLocalizedMessage().startsWith("Could not find any product named 'Pistol'"));
@@ -174,7 +430,7 @@ public class TestCatalogRetireElements extends TestIntegrationBase {
final PlanPhaseSpecifier spec = new PlanPhaseSpecifier(productName, term, "SpecialDiscount", null);
busHandler.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
- final Entitlement bpEntitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, "externalKey", null, null, null, false, ImmutableList.<PluginProperty>of(), callContext);
+ final Entitlement bpEntitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, "externalKey", null, null, null, false, true, ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
assertNotNull(bpEntitlement);
@@ -193,7 +449,7 @@ public class TestCatalogRetireElements extends TestIntegrationBase {
// PriceList "SpecialDiscount" at this point.
try {
- entitlementApi.createBaseEntitlement(account.getId(), spec, "externalKey2", null, null, null, false, ImmutableList.<PluginProperty>of(), callContext);
+ entitlementApi.createBaseEntitlement(account.getId(), spec, "externalKey2", null, null, null, false, true, ImmutableList.<PluginProperty>of(), callContext);
fail(); // force to fail is there is not an exception
} catch (final EntitlementApiException e) {
assertTrue(e.getLocalizedMessage().startsWith("Could not find any product named 'Pistol'"));
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegration.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegration.java
index 89507b5..2265bbf 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegration.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegration.java
@@ -82,7 +82,7 @@ public class TestIntegration extends TestIntegrationBase {
SubscriptionEventType.START_BILLING, null, null, null, null);
Invoice dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), clock.getUTCToday(), dryRun, callContext);
expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2012, 4, 1), null, InvoiceItemType.FIXED, new BigDecimal("0")));
- invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, callContext, expectedInvoices);
+ invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, expectedInvoices);
final DefaultEntitlement bpSubscription = createBaseEntitlementAndCheckForCompletion(account.getId(), "bundleKey", "Shotgun", ProductCategory.BASE, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
// Check bundle after BP got created otherwise we get an error from auditApi.
@@ -97,7 +97,7 @@ public class TestIntegration extends TestIntegrationBase {
SubscriptionEventType.START_BILLING, null, bpSubscription.getBundleId(), null, null);
dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), clock.getUTCToday(), dryRun, callContext);
expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2012, 4, 1), new LocalDate(2012, 5, 1), InvoiceItemType.RECURRING, new BigDecimal("399.95")));
- invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, callContext, expectedInvoices);
+ invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, expectedInvoices);
addAOEntitlementAndCheckForCompletion(bpSubscription.getBundleId(), "Telescopic-Scope", ProductCategory.ADD_ON, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
@@ -116,7 +116,7 @@ public class TestIntegration extends TestIntegrationBase {
// The second invoice should be adjusted for the AO (we paid for the full period) and since we paid we should also see a CBA
expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2012, 4, 1), new LocalDate(2012, 4, 1), InvoiceItemType.CBA_ADJ, new BigDecimal("399.95"),
false /* Avoid checking dates for CBA because code is using context and context createdDate is wrong in the test as we reset the clock too late, bummer... */ ));
- invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, callContext, expectedInvoices);
+ invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, expectedInvoices);
cancelEntitlementAndCheckForCompletion(bpSubscription, NextEvent.BLOCK, NextEvent.BLOCK, NextEvent.CANCEL, NextEvent.CANCEL, NextEvent.INVOICE);
@@ -157,7 +157,7 @@ public class TestIntegration extends TestIntegrationBase {
subscription.getId(), subscription.getBundleId(), null, null);
Invoice dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), clock.getUTCToday(), dryRun, callContext);
expectedInvoices.add(new ExpectedInvoiceItemCheck(initialCreationDate.toLocalDate(), null, InvoiceItemType.FIXED, new BigDecimal("0")));
- invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, callContext, expectedInvoices);
+ invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, expectedInvoices);
clock.addDeltaFromReality(1000); // Make sure CHANGE does not collide with CREATE
@@ -181,7 +181,7 @@ public class TestIntegration extends TestIntegrationBase {
// Verify first next targetDate
dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), new LocalDate(nextDate, testTimeZone), dryRun, callContext);
- invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, callContext, expectedInvoices);
+ invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, expectedInvoices);
setDateAndCheckForCompletion(nextDate, NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
@@ -207,7 +207,7 @@ public class TestIntegration extends TestIntegrationBase {
dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), new LocalDate(nextDate, testTimeZone), dryRun, callContext);
expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2012, 3, 31), new LocalDate(2012, 4, 30), InvoiceItemType.RECURRING, new BigDecimal("29.95")));
- invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, callContext, expectedInvoices);
+ invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, expectedInvoices);
addDaysAndCheckForCompletion(31, NextEvent.CHANGE, NextEvent.NULL_INVOICE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
invoiceChecker.checkInvoice(account.getId(), invoiceItemCount++, callContext, expectedInvoices);
@@ -421,49 +421,6 @@ public class TestIntegration extends TestIntegrationBase {
checkNoMoreInvoiceToGenerate(account);
}
- @Test(groups = {"stress"}, enabled = false)
- public void stressTest() throws Exception {
- final int maxIterations = 100;
- for (int curIteration = 0; curIteration < maxIterations; curIteration++) {
- if (curIteration != 0) {
- beforeMethod();
- }
-
- log.info("################################ ITERATION " + curIteration + " #########################");
- afterMethod();
- beforeMethod();
- testBasePlanCompleteWithBillingDayInPast();
- Thread.sleep(1000);
- afterMethod();
- beforeMethod();
- testBasePlanCompleteWithBillingDayAlignedWithTrial();
- Thread.sleep(1000);
- afterMethod();
- beforeMethod();
- testBasePlanCompleteWithBillingDayInFuture();
- if (curIteration < maxIterations - 1) {
- afterMethod();
- Thread.sleep(1000);
- }
- }
- }
-
- @Test(groups = {"stress"}, enabled = false)
- public void stressTestDebug() throws Exception {
- final int maxIterations = 100;
- for (int curIteration = 0; curIteration < maxIterations; curIteration++) {
- log.info("################################ ITERATION " + curIteration + " #########################");
- if (curIteration != 0) {
- beforeMethod();
- }
- testAddonsWithMultipleAlignments();
- if (curIteration < maxIterations - 1) {
- afterMethod();
- Thread.sleep(1000);
- }
- }
- }
-
@Test(groups = "slow")
public void testAddonsWithMultipleAlignments() throws Exception {
final DateTime initialDate = new DateTime(2012, 4, 25, 0, 13, 42, 0, testTimeZone);
@@ -510,6 +467,8 @@ public class TestIntegration extends TestIntegrationBase {
checkNoMoreInvoiceToGenerate(account);
}
+
+
@Test(groups = "slow")
public void testCreateMultipleBPWithSameExternalKey() throws Exception {
final DateTime initialDate = new DateTime(2012, 4, 25, 0, 13, 42, 0, testTimeZone);
@@ -531,17 +490,37 @@ public class TestIntegration extends TestIntegrationBase {
final String newProductName = "Pistol";
final DefaultEntitlement newBaseEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "bundleKey", newProductName, ProductCategory.BASE, term, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
- final SubscriptionBundle newBundle = subscriptionApi.getActiveSubscriptionBundleForExternalKey("bundleKey", callContext);
+ final List<SubscriptionBundle> bundles = subscriptionApi.getSubscriptionBundlesForExternalKey("bundleKey", callContext);
+ Assert.assertEquals(bundles.size(), 2);
+
+ final SubscriptionBundle newBundle = subscriptionApi.getActiveSubscriptionBundleForExternalKey("bundleKey", callContext);
assertNotEquals(initialBundle.getId(), newBundle.getId());
assertEquals(initialBundle.getAccountId(), newBundle.getAccountId());
assertEquals(initialBundle.getExternalKey(), newBundle.getExternalKey());
- final Entitlement refreshedBseEntitlement = entitlementApi.getEntitlementForId(baseEntitlement.getId(), callContext);
-
- assertEquals(refreshedBseEntitlement.getState(), EntitlementState.CANCELLED);
+ final Entitlement refreshedBaseEntitlement = entitlementApi.getEntitlementForId(baseEntitlement.getId(), callContext);
+ assertEquals(refreshedBaseEntitlement.getState(), EntitlementState.CANCELLED);
assertEquals(newBaseEntitlement.getState(), EntitlementState.ACTIVE);
+
+ // One more time
+ busHandler.pushExpectedEvents(NextEvent.BLOCK, NextEvent.CANCEL, NextEvent.NULL_INVOICE);
+ newBaseEntitlement.cancelEntitlementWithPolicy(EntitlementActionPolicy.IMMEDIATE, ImmutableList.<PluginProperty>of(), callContext);
+ assertListenerStatus();
+
+ final String newerProductName = "Shotgun";
+ final DefaultEntitlement newerBaseEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "bundleKey", newerProductName, ProductCategory.BASE, term, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
+ assertEquals(newerBaseEntitlement.getState(), EntitlementState.ACTIVE);
+
+ final List<SubscriptionBundle> bundlesAgain = subscriptionApi.getSubscriptionBundlesForExternalKey("bundleKey", callContext);
+ Assert.assertEquals(bundlesAgain.size(), 3);
+
+ final SubscriptionBundle newerBundle = subscriptionApi.getActiveSubscriptionBundleForExternalKey("bundleKey", callContext);
+ assertNotEquals(initialBundle.getId(), newerBundle.getId());
+ assertEquals(initialBundle.getAccountId(), newerBundle.getAccountId());
+ assertEquals(initialBundle.getExternalKey(), newerBundle.getExternalKey());
+
checkNoMoreInvoiceToGenerate(account);
}
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationBase.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationBase.java
index 06e6817..07a8c84 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationBase.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationBase.java
@@ -18,15 +18,18 @@
package org.killbill.billing.beatrix.integration;
-import com.google.common.base.Function;
-import com.google.common.base.Joiner;
-import com.google.common.base.Predicate;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
-import com.google.inject.Guice;
-import com.google.inject.Injector;
-import com.google.inject.Stage;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.Callable;
+
+import javax.annotation.Nullable;
+import javax.inject.Inject;
+import javax.inject.Named;
+
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.LocalDate;
@@ -122,30 +125,31 @@ import org.skife.config.TimeSpan;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.Assert;
+import org.testng.IHookCallBack;
+import org.testng.IHookable;
+import org.testng.ITestResult;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
-import javax.annotation.Nullable;
-import javax.inject.Inject;
-import javax.inject.Named;
-import java.math.BigDecimal;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-import java.util.UUID;
-import java.util.concurrent.Callable;
+import com.google.common.base.Function;
+import com.google.common.base.Joiner;
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Stage;
-import static org.awaitility.Awaitility.await;
import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.awaitility.Awaitility.await;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.fail;
-
-public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB {
+public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB implements IHookable {
protected static final DateTimeZone testTimeZone = DateTimeZone.UTC;
protected static final Logger log = LoggerFactory.getLogger(TestIntegrationBase.class);
@@ -295,6 +299,7 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB {
protected ConfigurableInvoiceConfig invoiceConfig;
+ @Override
protected void assertListenerStatus() {
busHandler.assertListenerStatus();
}
@@ -329,16 +334,10 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB {
lifecycle.fireStartupSequencePostEventRegistration();
paymentPlugin.clear();
-
- // Make sure we start with a clean state
- assertListenerStatus();
}
@AfterMethod(groups = "slow")
public void afterMethod() throws Exception {
- // Make sure we finish in a clean state
- assertListenerStatus();
-
lifecycle.fireShutdownSequencePriorEventUnRegistration();
busService.getBus().unregister(busHandler);
lifecycle.fireShutdownSequencePostEventUnRegistration();
@@ -435,7 +434,7 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB {
.isNotifiedForInvoices(false)
.externalKey(UUID.randomUUID().toString().substring(1, 8))
.currency(Currency.USD)
- .paymentMethodId(UUID.randomUUID())
+ .referenceTime(clock.getUTCNow())
.timeZone(DateTimeZone.UTC);
if (billingDay != null) {
builder.billingCycleDayLocal(billingDay);
@@ -454,6 +453,7 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB {
.billingCycleDayLocal(billingDay)
.currency(Currency.USD)
.paymentMethodId(UUID.randomUUID())
+ .referenceTime(clock.getUTCNow())
.timeZone(DateTimeZone.UTC)
.parentAccountId(parentAccountId)
.isPaymentDelegatedToParent(isPaymentDelegatedToParent)
@@ -504,7 +504,7 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB {
final List<PluginProperty> properties = new ArrayList<PluginProperty>();
final PluginProperty prop1 = new PluginProperty(InvoicePaymentControlPluginApi.PROP_IPCD_INVOICE_ID, invoice.getId().toString(), false);
properties.add(prop1);
- return paymentApi.createPurchaseWithPaymentControl(account, account.getPaymentMethodId(), null, amount, currency, UUID.randomUUID().toString(),
+ return paymentApi.createPurchaseWithPaymentControl(account, account.getPaymentMethodId(), null, amount, currency, null, UUID.randomUUID().toString(),
UUID.randomUUID().toString(), properties, PAYMENT_OPTIONS, callContext);
} catch (final PaymentApiException e) {
fail(e.toString());
@@ -523,7 +523,7 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB {
final PluginProperty prop1 = new PluginProperty(InvoicePaymentControlPluginApi.PROP_IPCD_INVOICE_ID, invoice.getId().toString(), false);
properties.add(prop1);
- return paymentApi.createPurchaseWithPaymentControl(account, account.getPaymentMethodId(), null, invoice.getBalance(), invoice.getCurrency(), UUID.randomUUID().toString(),
+ return paymentApi.createPurchaseWithPaymentControl(account, account.getPaymentMethodId(), null, invoice.getBalance(), invoice.getCurrency(), null, UUID.randomUUID().toString(),
UUID.randomUUID().toString(), properties, PAYMENT_OPTIONS, callContext);
} catch (final PaymentApiException e) {
fail(e.toString());
@@ -543,7 +543,7 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB {
final PluginProperty prop1 = new PluginProperty(InvoicePaymentControlPluginApi.PROP_IPCD_INVOICE_ID, invoice.getId().toString(), false);
properties.add(prop1);
- return paymentApi.createPurchaseWithPaymentControl(account, account.getPaymentMethodId(), null, invoice.getBalance(), invoice.getCurrency(), UUID.randomUUID().toString(),
+ return paymentApi.createPurchaseWithPaymentControl(account, account.getPaymentMethodId(), null, invoice.getBalance(), invoice.getCurrency(), null, UUID.randomUUID().toString(),
UUID.randomUUID().toString(), properties, EXTERNAL_PAYMENT_OPTIONS, callContext);
} catch (final PaymentApiException e) {
fail(e.toString());
@@ -562,7 +562,7 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB {
@Override
public Payment apply(@Nullable final Void input) {
try {
- return paymentApi.createRefundWithPaymentControl(account, payment.getId(), amount, currency, UUID.randomUUID().toString(),
+ return paymentApi.createRefundWithPaymentControl(account, payment.getId(), amount, currency, null, UUID.randomUUID().toString(),
PLUGIN_PROPERTIES, PAYMENT_OPTIONS, callContext);
} catch (final PaymentApiException e) {
fail(e.toString());
@@ -588,7 +588,7 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB {
properties.add(prop2);
try {
- return paymentApi.createRefundWithPaymentControl(account, payment.getId(), amount, currency, UUID.randomUUID().toString(),
+ return paymentApi.createRefundWithPaymentControl(account, payment.getId(), amount, currency, null, UUID.randomUUID().toString(),
properties, PAYMENT_OPTIONS, callContext);
} catch (final PaymentApiException e) {
fail(e.toString());
@@ -607,7 +607,7 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB {
@Override
public Payment apply(@Nullable final Void input) {
try {
- return paymentApi.createChargebackWithPaymentControl(account, payment.getId(), amount, currency, UUID.randomUUID().toString(),
+ return paymentApi.createChargebackWithPaymentControl(account, payment.getId(), amount, currency, null, UUID.randomUUID().toString(),
PAYMENT_OPTIONS, callContext);
} catch (final PaymentApiException e) {
fail(e.toString());
@@ -634,7 +634,7 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB {
@Override
public Payment apply(@Nullable final Void input) {
try {
- return paymentApi.createChargebackReversalWithPaymentControl(account, payment.getId(), chargebackTransactionExternalKey, PAYMENT_OPTIONS, callContext);
+ return paymentApi.createChargebackReversalWithPaymentControl(account, payment.getId(), null, chargebackTransactionExternalKey, PAYMENT_OPTIONS, callContext);
} catch (final PaymentApiException e) {
fail(e.toString());
return null;
@@ -659,7 +659,7 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB {
public Entitlement apply(@Nullable final Void dontcare) {
try {
final PlanPhaseSpecifier spec = new PlanPhaseSpecifier(productName, billingPeriod, PriceListSet.DEFAULT_PRICELIST_NAME, null);
- final Entitlement entitlement = entitlementApi.createBaseEntitlement(accountId, spec, bundleExternalKey, overrides, null, null, false, ImmutableList.<PluginProperty>of(), callContext);
+ final Entitlement entitlement = entitlementApi.createBaseEntitlement(accountId, spec, bundleExternalKey, overrides, null, null, false, true, ImmutableList.<PluginProperty>of(), callContext);
assertNotNull(entitlement);
return entitlement;
} catch (final EntitlementApiException e) {
@@ -717,9 +717,9 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB {
// Need to fetch again to get latest CTD updated from the system
Entitlement refreshedEntitlement = entitlementApi.getEntitlementForId(entitlement.getId(), callContext);
if (billingPolicy == null) {
- refreshedEntitlement = refreshedEntitlement.changePlan(new PlanSpecifier(productName, billingPeriod, priceList), null, ImmutableList.<PluginProperty>of(), callContext);
+ refreshedEntitlement = refreshedEntitlement.changePlan(new PlanPhaseSpecifier(productName, billingPeriod, priceList), null, ImmutableList.<PluginProperty>of(), callContext);
} else {
- refreshedEntitlement = refreshedEntitlement.changePlanOverrideBillingPolicy(new PlanSpecifier(productName, billingPeriod, priceList), null, null, billingPolicy, ImmutableList.<PluginProperty>of(), callContext);
+ refreshedEntitlement = refreshedEntitlement.changePlanOverrideBillingPolicy(new PlanPhaseSpecifier(productName, billingPeriod, priceList), null, null, billingPolicy, ImmutableList.<PluginProperty>of(), callContext);
}
return refreshedEntitlement;
} catch (final EntitlementApiException e) {
@@ -800,18 +800,46 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB {
}
protected void add_AUTO_PAY_OFF_Tag(final UUID id, final ObjectType type) throws TagDefinitionApiException, TagApiException {
+ add_account_Tag(id, ControlTagType.AUTO_PAY_OFF, type);
+ }
+
+ protected void add_AUTO_INVOICING_OFF_Tag(final UUID id, final ObjectType type) throws TagDefinitionApiException, TagApiException {
+ add_account_Tag(id, ControlTagType.AUTO_INVOICING_OFF, type);
+ }
+
+ protected void add_AUTO_INVOICING_DRAFT_Tag(final UUID id, final ObjectType type) throws TagDefinitionApiException, TagApiException {
+ add_account_Tag(id, ControlTagType.AUTO_INVOICING_DRAFT, type);
+ }
+
+ protected void add_AUTO_INVOICING_REUSE_DRAFT_Tag(final UUID id, final ObjectType type) throws TagDefinitionApiException, TagApiException {
+ add_account_Tag(id, ControlTagType.AUTO_INVOICING_REUSE_DRAFT, type);
+ }
+
+ private void add_account_Tag(final UUID id, final ControlTagType controlTagType, final ObjectType type) throws TagDefinitionApiException, TagApiException {
busHandler.pushExpectedEvent(NextEvent.TAG);
- tagUserApi.addTag(id, type, ControlTagType.AUTO_PAY_OFF.getId(), callContext);
+ tagUserApi.addTag(id, type, controlTagType.getId(), callContext);
assertListenerStatus();
-
- final List<Tag> tags = tagUserApi.getTagsForObject(id, type, false, callContext);
- assertEquals(tags.size(), 1);
+ tagUserApi.getTagsForObject(id, type, false, callContext);
}
protected void remove_AUTO_PAY_OFF_Tag(final UUID id, final ObjectType type, final NextEvent... additionalEvents) throws TagDefinitionApiException, TagApiException {
+ remove_account_Tag(id, ControlTagType.AUTO_PAY_OFF, type, additionalEvents);
+ }
+
+
+ protected void remove_AUTO_INVOICING_OFF_Tag(final UUID id, final ObjectType type, final NextEvent... additionalEvents) throws TagDefinitionApiException, TagApiException {
+ remove_account_Tag(id, ControlTagType.AUTO_INVOICING_OFF, type, additionalEvents);
+ }
+
+ protected void remove_AUTO_INVOICING_DRAFT_Tag(final UUID id, final ObjectType type, final NextEvent... additionalEvents) throws TagDefinitionApiException, TagApiException {
+ remove_account_Tag(id, ControlTagType.AUTO_INVOICING_DRAFT, type, additionalEvents);
+ }
+
+
+ private void remove_account_Tag(final UUID id, final ControlTagType controlTagType, final ObjectType type, final NextEvent... additionalEvents) throws TagDefinitionApiException, TagApiException {
busHandler.pushExpectedEvent(NextEvent.TAG);
busHandler.pushExpectedEvents(additionalEvents);
- tagUserApi.removeTag(id, type, ControlTagType.AUTO_PAY_OFF.getId(), callContext);
+ tagUserApi.removeTag(id, type, controlTagType.getId(), callContext);
assertListenerStatus();
}
@@ -987,6 +1015,16 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB {
}
@Override
+ public List<String> getInvoicePluginNames() {
+ return defaultInvoiceConfig.getInvoicePluginNames();
+ }
+
+ @Override
+ public List<String> getInvoicePluginNames(final InternalTenantContext tenantContext) {
+ return defaultInvoiceConfig.getInvoicePluginNames();
+ }
+
+ @Override
public boolean isEmailNotificationsEnabled() {
return defaultInvoiceConfig.isEmailNotificationsEnabled();
}
@@ -997,6 +1035,16 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB {
}
@Override
+ public String getParentAutoCommitUtcTime() {
+ return defaultInvoiceConfig.getParentAutoCommitUtcTime();
+ }
+
+ @Override
+ public String getParentAutoCommitUtcTime(final InternalTenantContext tenantContext) {
+ return defaultInvoiceConfig.getParentAutoCommitUtcTime();
+ }
+
+ @Override
public boolean isInvoicingSystemEnabled(final InternalTenantContext tenantContext) {
return isInvoicingSystemEnabled();
}
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationDryRunInvoice.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationDryRunInvoice.java
new file mode 100644
index 0000000..559a626
--- /dev/null
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationDryRunInvoice.java
@@ -0,0 +1,554 @@
+/*
+ * 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;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.joda.time.DateTime;
+import org.joda.time.LocalDate;
+import org.killbill.billing.ErrorCode;
+import org.killbill.billing.account.api.Account;
+import org.killbill.billing.api.TestApiListener.NextEvent;
+import org.killbill.billing.beatrix.util.InvoiceChecker.ExpectedInvoiceItemCheck;
+import org.killbill.billing.catalog.DefaultPlanPhasePriceOverride;
+import org.killbill.billing.catalog.api.BillingActionPolicy;
+import org.killbill.billing.catalog.api.BillingPeriod;
+import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
+import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
+import org.killbill.billing.catalog.api.PriceListSet;
+import org.killbill.billing.catalog.api.ProductCategory;
+import org.killbill.billing.entitlement.api.DefaultEntitlement;
+import org.killbill.billing.entitlement.api.Entitlement;
+import org.killbill.billing.entitlement.api.SubscriptionEventType;
+import org.killbill.billing.invoice.api.DryRunArguments;
+import org.killbill.billing.invoice.api.DryRunType;
+import org.killbill.billing.invoice.api.Invoice;
+import org.killbill.billing.invoice.api.InvoiceApiException;
+import org.killbill.billing.invoice.api.InvoiceItemType;
+import org.killbill.billing.payment.api.PluginProperty;
+import org.killbill.billing.subscription.api.user.DefaultSubscriptionBase;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+
+import static com.tc.util.Assert.fail;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+
+public class TestIntegrationDryRunInvoice extends TestIntegrationBase {
+
+ private static final DryRunArguments DRY_RUN_UPCOMING_INVOICE_ARG = new TestDryRunArguments(DryRunType.UPCOMING_INVOICE);
+ private static final DryRunArguments DRY_RUN_TARGET_DATE_ARG = new TestDryRunArguments(DryRunType.TARGET_DATE);
+
+ //
+ // Basic test with one subscription that verifies the behavior of using invoice dryRun api with no date
+ //
+ @Test(groups = "slow")
+ public void testDryRunWithNoTargetDate() throws Exception {
+ final int billingDay = 14;
+ final DateTime initialCreationDate = new DateTime(2015, 5, 15, 0, 0, 0, 0, testTimeZone);
+ // set clock to the initial start date
+ clock.setTime(initialCreationDate);
+
+ log.info("Beginning test with BCD of " + billingDay);
+ final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(billingDay));
+
+ int invoiceNumber = 1;
+
+ //
+ // CREATE SUBSCRIPTION AND EXPECT BOTH EVENTS: NextEvent.CREATE, NextEvent.BLOCK NextEvent.INVOICE
+ //
+ DefaultEntitlement baseEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "bundleKey", "Shotgun", ProductCategory.BASE, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
+ DefaultSubscriptionBase subscription = subscriptionDataFromSubscription(baseEntitlement.getSubscriptionBase());
+ invoiceChecker.checkInvoice(account.getId(), invoiceNumber++, callContext, new ExpectedInvoiceItemCheck(initialCreationDate.toLocalDate(), null, InvoiceItemType.FIXED, new BigDecimal("0")));
+ // No end date for the trial item (fixed price of zero), and CTD should be today (i.e. when the trial started)
+ invoiceChecker.checkChargedThroughDate(subscription.getId(), clock.getUTCToday(), callContext);
+
+ final List<ExpectedInvoiceItemCheck> expectedInvoices = new ArrayList<ExpectedInvoiceItemCheck>();
+ expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 6, 14), new LocalDate(2015, 7, 14), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
+
+ // This will verify that the upcoming Phase is found and the invoice is generated at the right date, with correct items
+ Invoice dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), null, DRY_RUN_UPCOMING_INVOICE_ARG, callContext);
+ invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, expectedInvoices);
+
+ // Move through time and verify we get the same invoice
+ busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+ clock.addDays(30);
+ assertListenerStatus();
+ List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoiceChecker.checkInvoice(invoices.get(1).getId(), callContext, expectedInvoices);
+ expectedInvoices.clear();
+
+ // This will verify that the upcoming invoice notification is found and the invoice is generated at the right date, with correct items
+ expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 7, 14), new LocalDate(2015, 8, 14), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
+ dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), null, DRY_RUN_UPCOMING_INVOICE_ARG, callContext);
+ invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, expectedInvoices);
+
+ // Move through time and verify we get the same invoice
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+ clock.addMonths(1);
+ assertListenerStatus();
+ invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoiceChecker.checkInvoice(invoices.get(2).getId(), callContext, expectedInvoices);
+ expectedInvoices.clear();
+
+ // One more time, this will verify that the upcoming invoice notification is found and the invoice is generated at the right date, with correct items
+ expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 8, 14), new LocalDate(2015, 9, 14), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
+ dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), null, DRY_RUN_UPCOMING_INVOICE_ARG, callContext);
+ invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, expectedInvoices);
+ }
+
+ //
+ // More sophisticated test with two non aligned subscriptions that verifies the behavior of using invoice dryRun api with no date
+ // - The first subscription is an annual (SUBSCRIPTION aligned) whose billingDate is the first (we start on Jan 2nd to take into account the 30 days trial)
+ // - The second and third subscriptions are monthly (ACCOUNT aligned) whose billingDate are the 14 (we start on Dec 15 to also take into account the 30 days trial)
+ //
+ // The test verifies that the dryRun invoice with supplied date will always take into account the 'closest' invoice that should be generated
+ //
+ @Test(groups = "slow")
+ public void testDryRunWithNoTargetDateAndMultipleNonAlignedSubscriptions() throws Exception {
+ // Set in such a way that annual billing date will be the 1st
+ final DateTime initialCreationDate = new DateTime(2014, 1, 2, 0, 0, 0, 0, testTimeZone);
+ clock.setTime(initialCreationDate);
+
+ // billing date for the monthly
+ final int billingDay = 14;
+
+ final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(billingDay));
+
+ int invoiceNumber = 1;
+
+ // Create ANNUAL BP
+ DefaultEntitlement baseEntitlementAnnual = createBaseEntitlementAndCheckForCompletion(account.getId(), "bundleKeyAnnual", "Shotgun", ProductCategory.BASE, BillingPeriod.ANNUAL, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
+ DefaultSubscriptionBase subscriptionAnnual = subscriptionDataFromSubscription(baseEntitlementAnnual.getSubscriptionBase());
+ invoiceChecker.checkInvoice(account.getId(), invoiceNumber++, callContext, new ExpectedInvoiceItemCheck(initialCreationDate.toLocalDate(), null, InvoiceItemType.FIXED, new BigDecimal("0")));
+ // No end date for the trial item (fixed price of zero), and CTD should be today (i.e. when the trial started)
+ invoiceChecker.checkChargedThroughDate(subscriptionAnnual.getId(), clock.getUTCToday(), callContext);
+
+ // Verify next dryRun invoice and then move the clock to that date to also verify real invoice is the same
+ final List<ExpectedInvoiceItemCheck> expectedInvoices = new ArrayList<ExpectedInvoiceItemCheck>();
+ expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2014, 2, 1), new LocalDate(2015, 2, 1), InvoiceItemType.RECURRING, new BigDecimal("2399.95")));
+
+ Invoice dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), null, DRY_RUN_UPCOMING_INVOICE_ARG, callContext);
+ invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, expectedInvoices);
+
+ busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+ // 2014-2-1
+ clock.addDays(30);
+ assertListenerStatus();
+ invoiceChecker.checkInvoice(account.getId(), invoiceNumber++, callContext, expectedInvoices);
+ expectedInvoices.clear();
+
+ // Since we only have one subscription next dryRun will show the annual
+ expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 2, 1), new LocalDate(2016, 2, 1), InvoiceItemType.RECURRING, new BigDecimal("2399.95")));
+ dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), null, DRY_RUN_UPCOMING_INVOICE_ARG, callContext);
+ invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, expectedInvoices);
+ expectedInvoices.clear();
+
+ // 2014-12-15
+ final DateTime secondSubscriptionCreationDate = new DateTime(2014, 12, 15, 0, 0, 0, 0, testTimeZone);
+ clock.setTime(secondSubscriptionCreationDate);
+
+ // Create the first monthly
+ DefaultEntitlement baseEntitlementMonthly1 = createBaseEntitlementAndCheckForCompletion(account.getId(), "bundleKey1", "Shotgun", ProductCategory.BASE, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
+ DefaultSubscriptionBase subscriptionMonthly1 = subscriptionDataFromSubscription(baseEntitlementMonthly1.getSubscriptionBase());
+ invoiceChecker.checkInvoice(account.getId(), invoiceNumber++, callContext, new ExpectedInvoiceItemCheck(secondSubscriptionCreationDate.toLocalDate(), null, InvoiceItemType.FIXED, new BigDecimal("0")));
+ // No end date for the trial item (fixed price of zero), and CTD should be today (i.e. when the trial started)
+ invoiceChecker.checkChargedThroughDate(subscriptionMonthly1.getId(), clock.getUTCToday(), callContext);
+
+ // Create the second monthly
+ DefaultEntitlement baseEntitlementMonthly2 = createBaseEntitlementAndCheckForCompletion(account.getId(), "bundleKey2", "Pistol", ProductCategory.BASE, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
+ DefaultSubscriptionBase subscriptionMonthly2 = subscriptionDataFromSubscription(baseEntitlementMonthly2.getSubscriptionBase());
+ invoiceChecker.checkInvoice(account.getId(), invoiceNumber++, callContext, new ExpectedInvoiceItemCheck(secondSubscriptionCreationDate.toLocalDate(), null, InvoiceItemType.FIXED, new BigDecimal("0")));
+ // No end date for the trial item (fixed price of zero), and CTD should be today (i.e. when the trial started)
+ invoiceChecker.checkChargedThroughDate(subscriptionMonthly2.getId(), clock.getUTCToday(), callContext);
+
+ // Verify next dryRun invoice and then move the clock to that date to also verify real invoice is the same
+ expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 1, 14), new LocalDate(2015, 2, 14), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
+ expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 1, 14), new LocalDate(2015, 2, 14), InvoiceItemType.RECURRING, new BigDecimal("29.95")));
+ dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), null, DRY_RUN_UPCOMING_INVOICE_ARG, callContext);
+ invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, expectedInvoices);
+
+ busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.PHASE, NextEvent.INVOICE, NextEvent.NULL_INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+ // 2015-1-14
+ clock.addDays(30);
+ assertListenerStatus();
+ invoiceChecker.checkInvoice(account.getId(), invoiceNumber++, callContext, expectedInvoices);
+ expectedInvoices.clear();
+
+ // We test first the next expected invoice for a specific subscription: We can see the targetDate is 2015-2-14 and not 2015-2-1
+ final DryRunArguments dryRunUpcomingInvoiceWithFilterArg1 = new TestDryRunArguments(DryRunType.UPCOMING_INVOICE, null, null, null, null, null, null, subscriptionMonthly1.getId(), null, null, null);
+ expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 2, 14), new LocalDate(2015, 3, 14), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
+ expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 2, 14), new LocalDate(2015, 3, 14), InvoiceItemType.RECURRING, new BigDecimal("29.95")));
+ dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), null, dryRunUpcomingInvoiceWithFilterArg1, callContext);
+ assertEquals(dryRunInvoice.getTargetDate(), new LocalDate(2015, 2, 14));
+ invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, expectedInvoices);
+ expectedInvoices.clear();
+
+ final DryRunArguments dryRunUpcomingInvoiceWithFilterArg2 = new TestDryRunArguments(DryRunType.UPCOMING_INVOICE, null, null, null, null, null, null, subscriptionMonthly2.getId(), null, null, null);
+ expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 2, 14), new LocalDate(2015, 3, 14), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
+ expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 2, 14), new LocalDate(2015, 3, 14), InvoiceItemType.RECURRING, new BigDecimal("29.95")));
+ dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), null, dryRunUpcomingInvoiceWithFilterArg2, callContext);
+ assertEquals(dryRunInvoice.getTargetDate(), new LocalDate(2015, 2, 14));
+ invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, expectedInvoices);
+ expectedInvoices.clear();
+
+ // Then we test first the next expected invoice at the account level
+ expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 2, 1), new LocalDate(2016, 2, 1), InvoiceItemType.RECURRING, new BigDecimal("2399.95")));
+ dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), null, DRY_RUN_UPCOMING_INVOICE_ARG, callContext);
+ invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, expectedInvoices);
+
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+ // 2015-2-1
+ clock.addDays(18);
+ assertListenerStatus();
+ invoiceChecker.checkInvoice(account.getId(), invoiceNumber++, callContext, expectedInvoices);
+ expectedInvoices.clear();
+
+ expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 2, 14), new LocalDate(2015, 3, 14), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
+ expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 2, 14), new LocalDate(2015, 3, 14), InvoiceItemType.RECURRING, new BigDecimal("29.95")));
+ dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), null, DRY_RUN_UPCOMING_INVOICE_ARG, callContext);
+ invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, expectedInvoices);
+ }
+
+ @Test(groups = "slow")
+ public void testDryRunWithPendingSubscription() throws Exception {
+
+ final LocalDate initialDate = new LocalDate(2017, 4, 1);
+ clock.setDay(initialDate);
+
+ // Create account with non BCD to force junction BCD logic to activate
+ final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(null));
+
+ final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Shotgun", BillingPeriod.ANNUAL, PriceListSet.DEFAULT_PRICELIST_NAME, null);
+
+ final LocalDate futureDate = new LocalDate(2017, 5, 1);
+
+ // No CREATE event as this is set in the future
+ final Entitlement createdEntitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, futureDate, futureDate, false, true, ImmutableList.<PluginProperty>of(), callContext);
+ assertEquals(createdEntitlement.getState(), Entitlement.EntitlementState.PENDING);
+ assertEquals(createdEntitlement.getEffectiveStartDate().compareTo(futureDate), 0);
+ assertEquals(createdEntitlement.getEffectiveEndDate(), null);
+ assertListenerStatus();
+
+ // Generate a dryRun invoice on the billing startDate
+ final Invoice dryRunInvoice1 = invoiceUserApi.triggerInvoiceGeneration(createdEntitlement.getAccountId(), futureDate, DRY_RUN_TARGET_DATE_ARG, callContext);
+ assertEquals(dryRunInvoice1.getInvoiceItems().size(), 1);
+ assertEquals(dryRunInvoice1.getInvoiceItems().get(0).getInvoiceItemType(), InvoiceItemType.FIXED);
+ assertEquals(dryRunInvoice1.getInvoiceItems().get(0).getAmount().compareTo(BigDecimal.ZERO), 0);
+ assertEquals(dryRunInvoice1.getInvoiceItems().get(0).getStartDate(), futureDate);
+ assertEquals(dryRunInvoice1.getInvoiceItems().get(0).getPlanName(), "shotgun-annual");
+
+ // Generate a dryRun invoice with a plan change
+ final DryRunArguments dryRunSubscriptionActionArg = new TestDryRunArguments(DryRunType.SUBSCRIPTION_ACTION, "Pistol", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null,
+ SubscriptionEventType.CHANGE, createdEntitlement.getId(), createdEntitlement.getBundleId(), futureDate, BillingActionPolicy.IMMEDIATE);
+
+ // First one day prior subscription starts
+ try {
+ invoiceUserApi.triggerInvoiceGeneration(createdEntitlement.getAccountId(), futureDate.minusDays(1), dryRunSubscriptionActionArg, callContext);
+ fail("Should fail to trigger dryRun invoice prior subscription starts");
+ } catch (final InvoiceApiException e) {
+ assertEquals(e.getCode(), ErrorCode.INVOICE_NOTHING_TO_DO.getCode());
+ }
+
+ // Second, on the startDate
+ final Invoice dryRunInvoice2 = invoiceUserApi.triggerInvoiceGeneration(createdEntitlement.getAccountId(), futureDate, dryRunSubscriptionActionArg, callContext);
+ assertEquals(dryRunInvoice2.getInvoiceItems().size(), 1);
+ assertEquals(dryRunInvoice2.getInvoiceItems().get(0).getInvoiceItemType(), InvoiceItemType.FIXED);
+ assertEquals(dryRunInvoice2.getInvoiceItems().get(0).getAmount().compareTo(BigDecimal.ZERO), 0);
+ assertEquals(dryRunInvoice2.getInvoiceItems().get(0).getStartDate(), futureDate);
+ assertEquals(dryRunInvoice2.getInvoiceItems().get(0).getPlanName(), "pistol-monthly");
+
+ // Check BCD is not yet set
+ final Account refreshedAccount1 = accountUserApi.getAccountById(account.getId(), callContext);
+ assertEquals(refreshedAccount1.getBillCycleDayLocal(), new Integer(0));
+
+ busHandler.pushExpectedEvents(NextEvent.INVOICE);
+ final Invoice realInvoice = invoiceUserApi.triggerInvoiceGeneration(createdEntitlement.getAccountId(), futureDate, null, callContext);
+ assertListenerStatus();
+
+ assertEquals(realInvoice.getInvoiceItems().size(), 1);
+ assertEquals(realInvoice.getInvoiceItems().get(0).getInvoiceItemType(), InvoiceItemType.FIXED);
+ assertEquals(realInvoice.getInvoiceItems().get(0).getAmount().compareTo(BigDecimal.ZERO), 0);
+ assertEquals(realInvoice.getInvoiceItems().get(0).getStartDate(), futureDate);
+ assertEquals(realInvoice.getInvoiceItems().get(0).getPlanName(), "shotgun-annual");
+
+ // Check BCD is now set
+ final Account refreshedAccount2 = accountUserApi.getAccountById(account.getId(), callContext);
+ assertEquals(refreshedAccount2.getBillCycleDayLocal(), new Integer(31));
+
+ // Move clock past startDate to check nothing happens
+ busHandler.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK, NextEvent.NULL_INVOICE);
+ clock.addDays(31);
+ assertListenerStatus();
+
+ // Move clock after PHASE event
+ busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.INVOICE_PAYMENT, NextEvent.PAYMENT);
+ clock.addMonths(12);
+ assertListenerStatus();
+ }
+
+ @Test(groups = "slow")
+ public void testDryRunWithPendingCancelledSubscription() throws Exception {
+
+ final LocalDate initialDate = new LocalDate(2017, 4, 1);
+ clock.setDay(initialDate);
+
+ // Create account with non BCD to force junction BCD logic to activate
+ final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(null));
+
+ final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("pistol-monthly-notrial", null);
+
+ final LocalDate futureStartDate = new LocalDate(2017, 5, 1);
+
+ // No CREATE event as this is set in the future
+ final Entitlement createdEntitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, futureStartDate, futureStartDate, false, true, ImmutableList.<PluginProperty>of(), callContext);
+ assertEquals(createdEntitlement.getState(), Entitlement.EntitlementState.PENDING);
+ assertEquals(createdEntitlement.getEffectiveStartDate().compareTo(futureStartDate), 0);
+ assertEquals(createdEntitlement.getEffectiveEndDate(), null);
+ assertListenerStatus();
+
+ // Generate an invoice using a future targetDate
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.INVOICE_PAYMENT, NextEvent.PAYMENT);
+ final Invoice firstInvoice = invoiceUserApi.triggerInvoiceGeneration(createdEntitlement.getAccountId(), futureStartDate, null, callContext);
+ assertListenerStatus();
+
+ assertEquals(firstInvoice.getInvoiceItems().size(), 1);
+ assertEquals(firstInvoice.getInvoiceItems().get(0).getInvoiceItemType(), InvoiceItemType.RECURRING);
+ assertEquals(firstInvoice.getInvoiceItems().get(0).getAmount().compareTo(new BigDecimal("19.95")), 0);
+ assertEquals(firstInvoice.getInvoiceItems().get(0).getStartDate(), futureStartDate);
+ assertEquals(firstInvoice.getInvoiceItems().get(0).getPlanName(), "pistol-monthly-notrial");
+
+ // Cancel subscription on its pending startDate
+ createdEntitlement.cancelEntitlementWithDate(futureStartDate, true, ImmutableList.<PluginProperty>of(), callContext);
+
+ final List<ExpectedInvoiceItemCheck> expectedInvoices = new ArrayList<ExpectedInvoiceItemCheck>();
+ expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2017, 5, 1), new LocalDate(2017, 6, 1), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-19.95")));
+ expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2017, 4, 1), new LocalDate(2017, 4, 1), InvoiceItemType.CBA_ADJ, new BigDecimal("19.95")));
+
+ final Invoice dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), null, DRY_RUN_UPCOMING_INVOICE_ARG, callContext);
+ assertEquals(dryRunInvoice.getTargetDate(), new LocalDate(2017, 5, 1));
+ invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, expectedInvoices);
+ expectedInvoices.clear();
+
+ // Move to startDate/cancel Date
+ busHandler.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK, NextEvent.CANCEL, NextEvent.BLOCK, NextEvent.NULL_INVOICE, NextEvent.INVOICE);
+ clock.addMonths(1);
+ assertListenerStatus();
+
+ final List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ assertEquals(invoices.size(), 2);
+
+ expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2017, 5, 1), new LocalDate(2017, 6, 1), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-19.95")));
+ expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2017, 5, 1), new LocalDate(2017, 5, 1), InvoiceItemType.CBA_ADJ, new BigDecimal("19.95")));
+ invoiceChecker.checkInvoice(invoices.get(1).getId(), callContext, expectedInvoices);
+ }
+
+
+ @Test(groups = "slow", description = "See https://github.com/killbill/killbill/issues/774")
+ public void testDryRunTargetDateWithIntermediateInvoice() throws Exception {
+ final DateTime initialCreationDate = new DateTime(2014, 1, 2, 0, 0, 0, 0, testTimeZone);
+ clock.setTime(initialCreationDate);
+
+ // billing date for the monthly
+ final int billingDay = 14;
+
+ final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(billingDay));
+
+ // Create first ANNUAL BP -> BCD = 1
+ createBaseEntitlementAndCheckForCompletion(account.getId(), "bundleKeyAnnual1", "Shotgun", ProductCategory.BASE, BillingPeriod.ANNUAL, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
+
+ // 2014-1-4
+ clock.addDays(2);
+ // Create second ANNUAL BP -> BCD = 3
+ createBaseEntitlementAndCheckForCompletion(account.getId(), "bundleKeyAnnual2", "Pistol", ProductCategory.BASE, BillingPeriod.ANNUAL, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
+
+ // 2014-2-1
+ busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+ clock.addDays(28);
+ assertListenerStatus();
+
+ // 2014-2-3
+ busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+ clock.addDays(2);
+ assertListenerStatus();
+
+ // 2014-12-15
+ final DateTime monthlySubscriptionCreationDate = new DateTime(2014, 12, 15, 0, 0, 0, 0, testTimeZone);
+ clock.setTime(monthlySubscriptionCreationDate);
+
+ // Create the monthly
+ createBaseEntitlementAndCheckForCompletion(account.getId(), "bundleKey", "Shotgun", ProductCategory.BASE, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
+
+ // 2015-1-14
+ busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+ clock.addDays(30);
+ assertListenerStatus();
+
+ final List<ExpectedInvoiceItemCheck> expectedInvoices = new ArrayList<ExpectedInvoiceItemCheck>();
+
+ // At this point (2015-1-14), we have 3 pending invoice notifications:
+ //
+ // - The 1st ANNUAL on 2015-2-1
+ // - The 2nd ANNUAL on 2015-2-3
+ // - The MONTHLY on 2015-2-14
+ //
+ // 1. We verify that a DryRunType.TARGET_DATE for 2015-2-14 leads to an invoice that **only** contains the MONTHLY item (fix for #774)
+ //
+ expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 2, 14), new LocalDate(2015, 3, 14), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
+ Invoice dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), new LocalDate(2015, 2, 14), DRY_RUN_TARGET_DATE_ARG, callContext);
+ assertEquals(dryRunInvoice.getTargetDate(), new LocalDate(2015, 2, 14));
+ invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, expectedInvoices);
+ expectedInvoices.clear();
+
+ // 2. We verify that a DryRunType.TARGET_DATE for 2015-2-3 leads to an invoice that **only** contains the 2nd ANNUAL item (fix for #774)
+ //
+ expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 2, 3), new LocalDate(2016, 2, 3), InvoiceItemType.RECURRING, new BigDecimal("199.95")));
+ dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), new LocalDate(2015, 2, 3), DRY_RUN_TARGET_DATE_ARG, callContext);
+ assertEquals(dryRunInvoice.getTargetDate(), new LocalDate(2015, 2, 3));
+ invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, expectedInvoices);
+ expectedInvoices.clear();
+
+ // 3. We verify that UPCOMING_INVOICE leads to next invoice fo the ANNUAL
+ expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 2, 1), new LocalDate(2016, 2, 1), InvoiceItemType.RECURRING, new BigDecimal("2399.95")));
+ dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), null, DRY_RUN_UPCOMING_INVOICE_ARG, callContext);
+ assertEquals(dryRunInvoice.getTargetDate(), new LocalDate(2015, 2, 1));
+ invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, expectedInvoices);
+ expectedInvoices.clear();
+
+ }
+
+ @Test(groups = "slow")
+ public void testDryRunWithAOs() throws Exception {
+ final LocalDate initialDate = new LocalDate(2017, 12, 1);
+ clock.setDay(initialDate);
+
+ // Create account with non BCD to force junction BCD logic to activate
+ final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(null));
+
+ // No CREATE event as this is set in the future
+ busHandler.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+ final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("pistol-monthly-notrial", null);
+ final Entitlement baseEntitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, null, null, false, true, ImmutableList.<PluginProperty>of(), callContext);
+ assertListenerStatus();
+
+ final DefaultEntitlement aoEntitlement = addAOEntitlementAndCheckForCompletion(baseEntitlement.getBundleId(), "Refurbish-Maintenance", ProductCategory.ADD_ON, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+ clock.addMonths(1);
+ assertListenerStatus();
+
+ final List<ExpectedInvoiceItemCheck> expectedInvoices = new ArrayList<ExpectedInvoiceItemCheck>();
+ expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2018, 2, 1), new LocalDate(2018, 3, 1), InvoiceItemType.RECURRING, new BigDecimal("19.95")));
+ expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2018, 2, 1), new LocalDate(2018, 3, 1), InvoiceItemType.RECURRING, new BigDecimal("199.95")));
+
+ // Specify AO subscriptionId filter
+ final DryRunArguments dryRunUpcomingInvoiceWithFilterArg1 = new TestDryRunArguments(DryRunType.UPCOMING_INVOICE, null, null, null, null, null, null, aoEntitlement.getId(), null, null, null);
+ Invoice dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), null, dryRunUpcomingInvoiceWithFilterArg1, callContext);
+ assertEquals(dryRunInvoice.getTargetDate(), new LocalDate(2018, 2, 1));
+ invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, expectedInvoices);
+
+ // Specify BP subscriptionId filter
+ final DryRunArguments dryRunUpcomingInvoiceWithFilterArg2 = new TestDryRunArguments(DryRunType.UPCOMING_INVOICE, null, null, null, null, null, null, baseEntitlement.getId(), null, null, null);
+ dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), null, dryRunUpcomingInvoiceWithFilterArg2, callContext);
+ assertEquals(dryRunInvoice.getTargetDate(), new LocalDate(2018, 2, 1));
+ invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, expectedInvoices);
+
+ // Specify bundleId filter
+ final DryRunArguments dryRunUpcomingInvoiceWithFilterArg3 = new TestDryRunArguments(DryRunType.UPCOMING_INVOICE, null, null, null, null, null, null, null, baseEntitlement.getBundleId(), null, null);
+ dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), null, dryRunUpcomingInvoiceWithFilterArg3, callContext);
+ assertEquals(dryRunInvoice.getTargetDate(), new LocalDate(2018, 2, 1));
+ invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, expectedInvoices);
+
+ }
+
+ @Test(groups = "slow")
+ public void testDryRunWithUpcomingSubscriptionEvents() throws Exception {
+
+ final DateTime initialDate = new DateTime(2017, 11, 1, 0, 3, 42, 0, testTimeZone);
+
+ // set clock to the initial start date
+ clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis());
+
+ final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(1));
+
+ final String productName = "Shotgun";
+ final BillingPeriod term = BillingPeriod.MONTHLY;
+
+ final DefaultEntitlement baseEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "bundleKey", productName, ProductCategory.BASE, term, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
+ assertNotNull(baseEntitlement);
+
+ // Verify the next invoice based on the PHASE event is correctly seen in the dryRun scenario
+ final List<ExpectedInvoiceItemCheck> expectedInvoices = new ArrayList<ExpectedInvoiceItemCheck>();
+ expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2017, 12, 1), new LocalDate(2018, 1, 1), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
+ Invoice dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), null, DRY_RUN_UPCOMING_INVOICE_ARG, callContext);
+ invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, expectedInvoices);
+ expectedInvoices.clear();
+
+ busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+ clock.addDays(30);
+ assertListenerStatus();
+
+ invoiceChecker.checkInvoice(account.getId(), 2, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2017, 12, 1), new LocalDate(2018, 1, 1), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
+
+ // Future pause the subscription
+ LocalDate effectivePauseDate = new LocalDate(2017, 12, 15);
+ entitlementApi.pause(baseEntitlement.getBundleId(), effectivePauseDate, ImmutableList.<PluginProperty>of(), callContext);
+
+ // Verify the next invoice based on the PAUSE event is correctly seen in the dryRun scenario
+ expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2017, 12, 15), new LocalDate(2018, 1, 1), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-137.07")));
+ expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2017, 12, 1), new LocalDate(2017, 12, 1), InvoiceItemType.CBA_ADJ, new BigDecimal("137.07")));
+ dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), null, DRY_RUN_UPCOMING_INVOICE_ARG, callContext);
+ invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, expectedInvoices);
+ expectedInvoices.clear();
+
+ // Hit the pause effective date 2017-12-15)
+ busHandler.pushExpectedEvents(NextEvent.BLOCK, NextEvent.INVOICE);
+ clock.addDays(14);
+ assertListenerStatus();
+
+ // Unfortunately we can't reuse *exactly the items from the dryRun invoice because the effective date for the CBA is set with current date.
+ expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2017, 12, 15), new LocalDate(2018, 1, 1), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-137.07")));
+ expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2017, 12, 15), new LocalDate(2017, 12, 15), InvoiceItemType.CBA_ADJ, new BigDecimal("137.07")));
+ invoiceChecker.checkInvoice(account.getId(), 3, callContext, expectedInvoices);
+ expectedInvoices.clear();
+
+ // Future resume the subscription
+ LocalDate effectiveResumeDate = new LocalDate(2017, 12, 25);
+ entitlementApi.resume(baseEntitlement.getBundleId(), effectiveResumeDate, ImmutableList.<PluginProperty>of(), callContext);
+
+ // Verify the next invoice based on the RESUME event is correctly seen in the dryRun scenario
+ expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2017, 12, 25), new LocalDate(2018, 1, 1), InvoiceItemType.RECURRING, new BigDecimal("56.44")));
+ expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2017, 12, 15), new LocalDate(2017, 12, 15), InvoiceItemType.CBA_ADJ, new BigDecimal("-56.44")));
+ dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), null, DRY_RUN_UPCOMING_INVOICE_ARG, callContext);
+ invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, expectedInvoices);
+ expectedInvoices.clear();
+
+ busHandler.pushExpectedEvents(NextEvent.BLOCK, NextEvent.INVOICE);
+ clock.addDays(10);
+ assertListenerStatus();
+
+ invoiceChecker.checkInvoice(account.getId(), 4, callContext,
+ new ExpectedInvoiceItemCheck(new LocalDate(2017, 12, 25), new LocalDate(2018, 1, 1), InvoiceItemType.RECURRING, new BigDecimal("56.44")),
+ new ExpectedInvoiceItemCheck(new LocalDate(2017, 12, 25), new LocalDate(2017, 12, 25), InvoiceItemType.CBA_ADJ, new BigDecimal("-56.44")));
+
+ }
+
+}
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationInvoice.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationInvoice.java
index 1f481ea..c9c9157 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationInvoice.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationInvoice.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2014-2016 Groupon, Inc
- * Copyright 2014-2016 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
@@ -25,201 +25,34 @@ import java.util.UUID;
import org.joda.time.DateTime;
import org.joda.time.LocalDate;
-import org.killbill.billing.ErrorCode;
import org.killbill.billing.ObjectType;
import org.killbill.billing.account.api.Account;
import org.killbill.billing.api.TestApiListener.NextEvent;
import org.killbill.billing.beatrix.util.InvoiceChecker.ExpectedInvoiceItemCheck;
-import org.killbill.billing.catalog.api.BillingActionPolicy;
+import org.killbill.billing.catalog.DefaultPlanPhasePriceOverride;
import org.killbill.billing.catalog.api.BillingPeriod;
import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
-import org.killbill.billing.catalog.api.PriceListSet;
import org.killbill.billing.catalog.api.ProductCategory;
+import org.killbill.billing.catalog.api.UsagePriceOverride;
import org.killbill.billing.entitlement.api.DefaultEntitlement;
import org.killbill.billing.entitlement.api.Entitlement;
-import org.killbill.billing.entitlement.api.EntitlementApiException;
-import org.killbill.billing.entitlement.api.SubscriptionEventType;
-import org.killbill.billing.invoice.api.DryRunArguments;
-import org.killbill.billing.invoice.api.DryRunType;
import org.killbill.billing.invoice.api.Invoice;
-import org.killbill.billing.invoice.api.InvoiceApiException;
import org.killbill.billing.invoice.api.InvoiceItem;
import org.killbill.billing.invoice.api.InvoiceItemType;
import org.killbill.billing.invoice.model.ExternalChargeInvoiceItem;
import org.killbill.billing.payment.api.Payment;
import org.killbill.billing.payment.api.PluginProperty;
import org.killbill.billing.subscription.api.user.DefaultSubscriptionBase;
-import org.testng.Assert;
import org.testng.annotations.Test;
import com.google.common.collect.ImmutableList;
-import static com.tc.util.Assert.fail;
import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertNull;
import static org.testng.Assert.assertTrue;
public class TestIntegrationInvoice extends TestIntegrationBase {
- //
- // Basic test with one subscription that verifies the behavior of using invoice dryRun api with no date
- //
- @Test(groups = "slow")
- public void testDryRunWithNoTargetDate() throws Exception {
- final int billingDay = 14;
- final DateTime initialCreationDate = new DateTime(2015, 5, 15, 0, 0, 0, 0, testTimeZone);
- // set clock to the initial start date
- clock.setTime(initialCreationDate);
-
- log.info("Beginning test with BCD of " + billingDay);
- final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(billingDay));
-
- int invoiceItemCount = 1;
-
- //
- // CREATE SUBSCRIPTION AND EXPECT BOTH EVENTS: NextEvent.CREATE, NextEvent.BLOCK NextEvent.INVOICE
- //
- DefaultEntitlement baseEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "bundleKey", "Shotgun", ProductCategory.BASE, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
- DefaultSubscriptionBase subscription = subscriptionDataFromSubscription(baseEntitlement.getSubscriptionBase());
- invoiceChecker.checkInvoice(account.getId(), invoiceItemCount++, callContext, new ExpectedInvoiceItemCheck(initialCreationDate.toLocalDate(), null, InvoiceItemType.FIXED, new BigDecimal("0")));
- // No end date for the trial item (fixed price of zero), and CTD should be today (i.e. when the trial started)
- invoiceChecker.checkChargedThroughDate(subscription.getId(), clock.getUTCToday(), callContext);
-
- final List<ExpectedInvoiceItemCheck> expectedInvoices = new ArrayList<ExpectedInvoiceItemCheck>();
- expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 6, 14), new LocalDate(2015, 7, 14), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
-
- // This will verify that the upcoming Phase is found and the invoice is generated at the right date, with correct items
- DryRunArguments dryRun = new TestDryRunArguments(DryRunType.UPCOMING_INVOICE);
- Invoice dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), null, dryRun, callContext);
- invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, callContext, expectedInvoices);
-
- // Move through time and verify we get the same invoice
- busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
- clock.addDays(30);
- assertListenerStatus();
- List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
- invoiceChecker.checkInvoice(invoices.get(1).getId(), callContext, expectedInvoices);
- expectedInvoices.clear();
-
- // This will verify that the upcoming invoice notification is found and the invoice is generated at the right date, with correct items
- expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 7, 14), new LocalDate(2015, 8, 14), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
- dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), null, dryRun, callContext);
- invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, callContext, expectedInvoices);
-
-
- // Move through time and verify we get the same invoice
- busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
- clock.addMonths(1);
- assertListenerStatus();
- invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
- invoiceChecker.checkInvoice(invoices.get(2).getId(), callContext, expectedInvoices);
- expectedInvoices.clear();
-
- // One more time, this will verify that the upcoming invoice notification is found and the invoice is generated at the right date, with correct items
- expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 8, 14), new LocalDate(2015, 9, 14), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
- dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), null, dryRun, callContext);
- invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, callContext, expectedInvoices);
- }
-
- //
- // More sophisticated test with two non aligned subscriptions that verifies the behavior of using invoice dryRun api with no date
- // - The first subscription is an annual (SUBSCRIPTION aligned) whose billingDate is the first (we start on Jan 2nd to take into account the 30 days trial)
- // - The second subscription is a monthly (ACCOUNT aligned) whose billingDate is the 14 (we start on Dec 15 to also take into account the 30 days trial)
- //
- // The test verifies that the dryRun invoice with supplied date will always take into account the 'closest' invoice that should be generated
- //
- @Test(groups = "slow")
- public void testDryRunWithNoTargetDateAndMultipleNonAlignedSubscriptions() throws Exception {
- // Set in such a way that annual billing date will be the 1st
- final DateTime initialCreationDate = new DateTime(2014, 1, 2, 0, 0, 0, 0, testTimeZone);
- clock.setTime(initialCreationDate);
-
- // billing date for the monthly
- final int billingDay = 14;
-
- final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(billingDay));
-
- int invoiceItemCount = 1;
-
- // Create ANNUAL BP
- DefaultEntitlement baseEntitlementAnnual = createBaseEntitlementAndCheckForCompletion(account.getId(), "bundleKeyAnnual", "Shotgun", ProductCategory.BASE, BillingPeriod.ANNUAL, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
- DefaultSubscriptionBase subscriptionAnnual = subscriptionDataFromSubscription(baseEntitlementAnnual.getSubscriptionBase());
- invoiceChecker.checkInvoice(account.getId(), invoiceItemCount++, callContext, new ExpectedInvoiceItemCheck(initialCreationDate.toLocalDate(), null, InvoiceItemType.FIXED, new BigDecimal("0")));
- // No end date for the trial item (fixed price of zero), and CTD should be today (i.e. when the trial started)
- invoiceChecker.checkChargedThroughDate(subscriptionAnnual.getId(), clock.getUTCToday(), callContext);
-
-
- // Verify next dryRun invoice and then move the clock to that date to also verify real invoice is the same
- final List<ExpectedInvoiceItemCheck> expectedInvoices = new ArrayList<ExpectedInvoiceItemCheck>();
- expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2014, 2, 1), new LocalDate(2015, 2, 1), InvoiceItemType.RECURRING, new BigDecimal("2399.95")));
-
- DryRunArguments dryRun = new TestDryRunArguments(DryRunType.UPCOMING_INVOICE);
- Invoice dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), null, dryRun, callContext);
- invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, callContext, expectedInvoices);
-
- busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
- // 2014-2-1
- clock.addDays(30);
- assertListenerStatus();
- invoiceChecker.checkInvoice(account.getId(), invoiceItemCount++, callContext, expectedInvoices);
- expectedInvoices.clear();
-
- // Since we only have one subscription next dryRun will show the annual
- expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 2, 1), new LocalDate(2016, 2, 1), InvoiceItemType.RECURRING, new BigDecimal("2399.95")));
- dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), null, dryRun, callContext);
- invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, callContext, expectedInvoices);
- expectedInvoices.clear();
-
- // 2014-12-15
- final DateTime secondSubscriptionCreationDate = new DateTime(2014, 12, 15, 0, 0, 0, 0, testTimeZone);
- clock.setTime(secondSubscriptionCreationDate);
-
- // Create the monthly
- DefaultEntitlement baseEntitlementMonthly = createBaseEntitlementAndCheckForCompletion(account.getId(), "bundleKey", "Shotgun", ProductCategory.BASE, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
- DefaultSubscriptionBase subscriptionMonthly = subscriptionDataFromSubscription(baseEntitlementMonthly.getSubscriptionBase());
- invoiceChecker.checkInvoice(account.getId(), invoiceItemCount++, callContext, new ExpectedInvoiceItemCheck(secondSubscriptionCreationDate.toLocalDate(), null, InvoiceItemType.FIXED, new BigDecimal("0")));
- // No end date for the trial item (fixed price of zero), and CTD should be today (i.e. when the trial started)
- invoiceChecker.checkChargedThroughDate(subscriptionMonthly.getId(), clock.getUTCToday(), callContext);
-
- // Verify next dryRun invoice and then move the clock to that date to also verify real invoice is the same
- expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 1, 14), new LocalDate(2015, 2, 14), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
- dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), null, dryRun, callContext);
- invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, callContext, expectedInvoices);
-
- busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
- // 2015-1-14
- clock.addDays(30);
- assertListenerStatus();
- invoiceChecker.checkInvoice(account.getId(), invoiceItemCount++, callContext, expectedInvoices);
- expectedInvoices.clear();
-
-
- // We test first the next expected invoice for a specific subscription: We can see the targetDate is 2015-2-14 and not 2015-2-1
- final DryRunArguments dryRunWIthSubscription = new TestDryRunArguments(DryRunType.UPCOMING_INVOICE, null, null, null, null, null, null, subscriptionMonthly.getId(), null, null, null);
- expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 2, 14), new LocalDate(2015, 3, 14), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
- dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), null, dryRunWIthSubscription, callContext);
- assertEquals(dryRunInvoice.getTargetDate(), new LocalDate(2015, 2, 14));
- invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, callContext, expectedInvoices);
- expectedInvoices.clear();
-
- // Then we test first the next expected invoice at the account level
- expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 2, 1), new LocalDate(2016, 2, 1), InvoiceItemType.RECURRING, new BigDecimal("2399.95")));
- dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), null, dryRun, callContext);
- invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, callContext, expectedInvoices);
-
- busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
- // 2015-2-1
- clock.addDays(18);
- assertListenerStatus();
- invoiceChecker.checkInvoice(account.getId(), invoiceItemCount++, callContext, expectedInvoices);
- expectedInvoices.clear();
-
- expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 2, 14), new LocalDate(2015, 3, 14), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
- dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), null, dryRun, callContext);
- invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, callContext, expectedInvoices);
- }
-
@Test(groups = "slow")
public void testApplyCreditOnExistingBalance() throws Exception {
final DateTime initialCreationDate = new DateTime(2015, 5, 15, 0, 0, 0, 0, testTimeZone);
@@ -269,7 +102,6 @@ public class TestIntegrationInvoice extends TestIntegrationBase {
final BigDecimal accountBalance3 = invoiceUserApi.getAccountBalance(account.getId(), callContext);
assertTrue(accountBalance3.compareTo(BigDecimal.ZERO) == 0);
-
final List<Payment> payments = paymentApi.getAccountPayments(account.getId(), false, false, ImmutableList.<PluginProperty>of(), callContext);
assertEquals(payments.size(), 1);
@@ -290,7 +122,7 @@ public class TestIntegrationInvoice extends TestIntegrationBase {
int invoiceItemCount = 1;
- DefaultEntitlement baseEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "bundleKey", "Shotgun", ProductCategory.BASE, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
+ DefaultEntitlement baseEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "bundleKey", "Shotgun", ProductCategory.BASE, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
DefaultSubscriptionBase subscription = subscriptionDataFromSubscription(baseEntitlement.getSubscriptionBase());
final List<ExpectedInvoiceItemCheck> expectedInvoices = new ArrayList<ExpectedInvoiceItemCheck>();
@@ -311,7 +143,7 @@ public class TestIntegrationInvoice extends TestIntegrationBase {
// add create external charge
final LocalDate date = clock.getToday(account.getTimeZone());
final List<InvoiceItem> invoiceItemList = new ArrayList<InvoiceItem>();
- ExternalChargeInvoiceItem item = new ExternalChargeInvoiceItem(null, account.getId(), subscription.getBundleId(), "", date, BigDecimal.TEN, account.getCurrency());
+ ExternalChargeInvoiceItem item = new ExternalChargeInvoiceItem(null, account.getId(), subscription.getBundleId(), "", date, date, BigDecimal.TEN, account.getCurrency());
invoiceItemList.add(item);
final List<InvoiceItem> draftInvoiceItems = invoiceUserApi.insertExternalCharges(account.getId(), date, invoiceItemList, false, callContext);
@@ -337,8 +169,7 @@ public class TestIntegrationInvoice extends TestIntegrationBase {
assertEquals(accountPayments.get(2).getPurchasedAmount(), new BigDecimal("10.00"));
}
-
- @Test(groups = "slow", description= "See https://github.com/killbill/killbill/issues/127#issuecomment-292445089")
+ @Test(groups = "slow", description = "See https://github.com/killbill/killbill/issues/127#issuecomment-292445089")
public void testIntegrationWithBCDLargerThanEndMonth() throws Exception {
final int billingDay = 31;
@@ -350,7 +181,7 @@ public class TestIntegrationInvoice extends TestIntegrationBase {
final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Blowdart", BillingPeriod.MONTHLY, "notrial", null);
busHandler.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE, NextEvent.INVOICE_PAYMENT, NextEvent.PAYMENT);
- entitlementApi.createBaseEntitlement(account.getId(), spec, "bundleExternalKey", ImmutableList.<PlanPhasePriceOverride>of(), null, null, false, ImmutableList.<PluginProperty>of(), callContext);
+ entitlementApi.createBaseEntitlement(account.getId(), spec, "bundleExternalKey", ImmutableList.<PlanPhasePriceOverride>of(), null, null, false, true, ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
// 2017-02-28
@@ -370,140 +201,66 @@ public class TestIntegrationInvoice extends TestIntegrationBase {
assertListenerStatus();
}
- @Test(groups = "slow")
- public void testDryRunWithPendingSubscription() throws Exception {
-
- final LocalDate initialDate = new LocalDate(2017, 4, 1);
- clock.setDay(initialDate);
- // Create account with non BCD to force junction BCD logic to activate
- final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(null));
+ @Test(groups = "slow", description = "https://github.com/killbill/killbill/issues/783")
+ public void testIntegrationWithRecurringFreePlan() throws Exception {
+ final DateTime initialCreationDate = new DateTime(2017, 1, 1, 0, 0, 0, 0, testTimeZone);
+ // set clock to the initial start date
+ clock.setTime(initialCreationDate);
- final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Shotgun", BillingPeriod.ANNUAL, PriceListSet.DEFAULT_PRICELIST_NAME, null);
+ final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(1));
- final LocalDate futureDate = new LocalDate(2017, 5, 1);
+ final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Blowdart", BillingPeriod.MONTHLY, "notrial", null);
- // No CREATE event as this is set in the future
- final Entitlement createdEntitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, futureDate, futureDate, false, ImmutableList.<PluginProperty>of(), callContext);
- assertEquals(createdEntitlement.getState(), Entitlement.EntitlementState.PENDING);
- assertEquals(createdEntitlement.getEffectiveStartDate().compareTo(futureDate), 0);
- assertEquals(createdEntitlement.getEffectiveEndDate(), null);
+ // Price override of $0
+ final List<PlanPhasePriceOverride> overrides = new ArrayList<PlanPhasePriceOverride>();
+ overrides.add(new DefaultPlanPhasePriceOverride("blowdart-monthly-notrial-evergreen", account.getCurrency(), null, BigDecimal.ZERO, ImmutableList.<UsagePriceOverride>of()));
+ busHandler.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
+ final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, "bundleExternalKey", overrides, null, null, false, true, ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
- // Generate a dryRun invoice on the billing startDate
- final DryRunArguments dryRunArguments1 = new TestDryRunArguments(DryRunType.TARGET_DATE);
- final Invoice dryRunInvoice1 = invoiceUserApi.triggerInvoiceGeneration(createdEntitlement.getAccountId(), futureDate, dryRunArguments1, callContext);
- assertEquals(dryRunInvoice1.getInvoiceItems().size(), 1);
- assertEquals(dryRunInvoice1.getInvoiceItems().get(0).getInvoiceItemType(), InvoiceItemType.FIXED);
- assertEquals(dryRunInvoice1.getInvoiceItems().get(0).getAmount().compareTo(BigDecimal.ZERO), 0);
- assertEquals(dryRunInvoice1.getInvoiceItems().get(0).getStartDate(), futureDate);
- assertEquals(dryRunInvoice1.getInvoiceItems().get(0).getPlanName(), "shotgun-annual");
-
-
- // Generate a dryRun invoice with a plan change
- final DryRunArguments dryRunArguments = new TestDryRunArguments(DryRunType.SUBSCRIPTION_ACTION, "Pistol", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null,
- SubscriptionEventType.CHANGE, createdEntitlement.getId(), createdEntitlement.getBundleId(), futureDate, BillingActionPolicy.IMMEDIATE);
-
- // First one day prior subscription starts
- try {
- invoiceUserApi.triggerInvoiceGeneration(createdEntitlement.getAccountId(), futureDate.minusDays(1), dryRunArguments, callContext);
- fail("Should fail to trigger dryRun invoice prior subscription starts");
- } catch (final InvoiceApiException e) {
- assertEquals(e.getCode(),ErrorCode.INVOICE_NOTHING_TO_DO.getCode());
- }
-
- // Second, on the startDate
- final Invoice dryRunInvoice2 = invoiceUserApi.triggerInvoiceGeneration(createdEntitlement.getAccountId(), futureDate, dryRunArguments, callContext);
- assertEquals(dryRunInvoice2.getInvoiceItems().size(), 1);
- assertEquals(dryRunInvoice2.getInvoiceItems().get(0).getInvoiceItemType(), InvoiceItemType.FIXED);
- assertEquals(dryRunInvoice2.getInvoiceItems().get(0).getAmount().compareTo(BigDecimal.ZERO), 0);
- assertEquals(dryRunInvoice2.getInvoiceItems().get(0).getStartDate(), futureDate);
- assertEquals(dryRunInvoice2.getInvoiceItems().get(0).getPlanName(), "pistol-monthly");
-
-
- // Check BCD is not yet set
- final Account refreshedAccount1 = accountUserApi.getAccountById(account.getId(), callContext);
- assertEquals(refreshedAccount1.getBillCycleDayLocal(), new Integer(0));
-
+ invoiceChecker.checkInvoice(account.getId(), 1, callContext,
+ new ExpectedInvoiceItemCheck(new LocalDate(2017, 1, 1), new LocalDate(2017, 2, 1), InvoiceItemType.RECURRING, BigDecimal.ZERO));
+ // 2017-02-01
busHandler.pushExpectedEvents(NextEvent.INVOICE);
- final Invoice realInvoice = invoiceUserApi.triggerInvoiceGeneration(createdEntitlement.getAccountId(), futureDate, null, callContext);
+ clock.addMonths(1);
assertListenerStatus();
- assertEquals(realInvoice.getInvoiceItems().size(), 1);
- assertEquals(realInvoice.getInvoiceItems().get(0).getInvoiceItemType(), InvoiceItemType.FIXED);
- assertEquals(realInvoice.getInvoiceItems().get(0).getAmount().compareTo(BigDecimal.ZERO), 0);
- assertEquals(realInvoice.getInvoiceItems().get(0).getStartDate(), futureDate);
- assertEquals(realInvoice.getInvoiceItems().get(0).getPlanName(), "shotgun-annual");
-
- // Check BCD is now set
- final Account refreshedAccount2 = accountUserApi.getAccountById(account.getId(), callContext);
- assertEquals(refreshedAccount2.getBillCycleDayLocal(), new Integer(31));
+ invoiceChecker.checkInvoice(account.getId(), 2, callContext,
+ new ExpectedInvoiceItemCheck(new LocalDate(2017, 2, 1), new LocalDate(2017, 3, 1), InvoiceItemType.RECURRING, BigDecimal.ZERO));
-
- // Move clock past startDate to check nothing happens
- busHandler.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK, NextEvent.NULL_INVOICE);
- clock.addDays(31);
+ // Do the change mid-month so the repair triggers the bug in https://github.com/killbill/killbill/issues/783
+ entitlement.changePlanWithDate(spec, ImmutableList.<PlanPhasePriceOverride>of(), new LocalDate("2017-02-15"), ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
- // Move clock after PHASE event
- busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.INVOICE_PAYMENT, NextEvent.PAYMENT);
- clock.addMonths(12);
+ // 2017-02-15
+ busHandler.pushExpectedEvents(NextEvent.CHANGE, NextEvent.INVOICE, NextEvent.INVOICE_PAYMENT, NextEvent.PAYMENT);
+ clock.addDays(15);
assertListenerStatus();
- }
-
-
- @Test(groups = "slow")
- public void testDryRunWithPendingCancelledSubscription() throws Exception {
-
- final LocalDate initialDate = new LocalDate(2017, 4, 1);
- clock.setDay(initialDate);
- // Create account with non BCD to force junction BCD logic to activate
- final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(null));
+ // Note: no repair
+ invoiceChecker.checkInvoice(account.getId(), 2, callContext,
+ new ExpectedInvoiceItemCheck(new LocalDate(2017, 2, 1), new LocalDate(2017, 3, 1), InvoiceItemType.RECURRING, BigDecimal.ZERO));
- final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("pistol-monthly-notrial", null);
+ invoiceChecker.checkInvoice(account.getId(), 3, callContext,
+ new ExpectedInvoiceItemCheck(new LocalDate(2017, 2, 1), new LocalDate(2017, 2, 15), InvoiceItemType.RECURRING, BigDecimal.ZERO),
+ new ExpectedInvoiceItemCheck(new LocalDate(2017, 2, 15), new LocalDate(2017, 3, 1), InvoiceItemType.RECURRING, new BigDecimal("14.98")));
- final LocalDate futureDate = new LocalDate(2017, 5, 1);
-
- // No CREATE event as this is set in the future
- final Entitlement createdEntitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, futureDate, futureDate, false, ImmutableList.<PluginProperty>of(), callContext);
- assertEquals(createdEntitlement.getState(), Entitlement.EntitlementState.PENDING);
- assertEquals(createdEntitlement.getEffectiveStartDate().compareTo(futureDate), 0);
- assertEquals(createdEntitlement.getEffectiveEndDate(), null);
- assertListenerStatus();
-
- // Generate an invoice using a future targetDate
+ // 2017-03-01
busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.INVOICE_PAYMENT, NextEvent.PAYMENT);
- final Invoice firstInvoice = invoiceUserApi.triggerInvoiceGeneration(createdEntitlement.getAccountId(), futureDate, null, callContext);
+ clock.addDays(15);
assertListenerStatus();
- assertEquals(firstInvoice.getInvoiceItems().size(), 1);
- assertEquals(firstInvoice.getInvoiceItems().get(0).getInvoiceItemType(), InvoiceItemType.RECURRING);
- assertEquals(firstInvoice.getInvoiceItems().get(0).getAmount().compareTo(new BigDecimal("19.95")), 0);
- assertEquals(firstInvoice.getInvoiceItems().get(0).getStartDate(), futureDate);
- assertEquals(firstInvoice.getInvoiceItems().get(0).getPlanName(), "pistol-monthly-notrial");
-
-
- // Cancel subscription on its pending startDate
- createdEntitlement.cancelEntitlementWithDate(futureDate, true, ImmutableList.<PluginProperty>of(), callContext);
+ invoiceChecker.checkInvoice(account.getId(), 4, callContext,
+ new ExpectedInvoiceItemCheck(new LocalDate(2017, 3, 1), new LocalDate(2017, 4, 1), InvoiceItemType.RECURRING, new BigDecimal("29.95")));
- // Move to startDate/cancel Date
- busHandler.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK, NextEvent.CANCEL, NextEvent.BLOCK, NextEvent.NULL_INVOICE, NextEvent.INVOICE);
+ // 2017-04-01
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.INVOICE_PAYMENT, NextEvent.PAYMENT);
clock.addMonths(1);
assertListenerStatus();
- final List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
- assertEquals(invoices.size(), 2);
-
- final List<ExpectedInvoiceItemCheck> toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
- new ExpectedInvoiceItemCheck(new LocalDate(2017, 5, 1), new LocalDate(2017, 6, 1), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-19.95")),
- new ExpectedInvoiceItemCheck(new LocalDate(2017, 5, 1), new LocalDate(2017, 5, 1), InvoiceItemType.CBA_ADJ, new BigDecimal("19.95")));
- invoiceChecker.checkInvoice(invoices.get(1).getId(), callContext, toBeChecked);
-
-
-
-
+ invoiceChecker.checkInvoice(account.getId(), 5, callContext,
+ new ExpectedInvoiceItemCheck(new LocalDate(2017, 4, 1), new LocalDate(2017, 5, 1), InvoiceItemType.RECURRING, new BigDecimal("29.95")));
}
-
}
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationInvoiceWithRepairLogic.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationInvoiceWithRepairLogic.java
index 18f2da2..2ed5d58 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationInvoiceWithRepairLogic.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationInvoiceWithRepairLogic.java
@@ -41,7 +41,6 @@ import org.killbill.billing.catalog.api.ProductCategory;
import org.killbill.billing.entitlement.api.DefaultEntitlement;
import org.killbill.billing.entitlement.api.Entitlement.EntitlementActionPolicy;
import org.killbill.billing.invoice.InvoiceDispatcher.FutureAccountNotifications;
-import org.killbill.billing.invoice.InvoiceDispatcher.FutureAccountNotifications.SubscriptionNotification;
import org.killbill.billing.invoice.api.Invoice;
import org.killbill.billing.invoice.api.InvoiceItemType;
import org.killbill.billing.invoice.api.InvoiceStatus;
@@ -51,7 +50,6 @@ import org.killbill.billing.invoice.dao.InvoiceModelDao;
import org.killbill.billing.payment.api.Payment;
import org.killbill.billing.payment.api.PluginProperty;
import org.killbill.billing.payment.api.TransactionStatus;
-import org.testng.annotations.AfterMethod;
import org.testng.annotations.Test;
import com.google.common.collect.ImmutableList;
@@ -64,12 +62,6 @@ public class TestIntegrationInvoiceWithRepairLogic extends TestIntegrationBase {
@Inject
protected InvoiceDao invoiceDao;
-
- @AfterMethod(groups = "slow")
- public void afterMethod() throws Exception {
- super.afterMethod();
- }
-
@Test(groups = "slow")
public void testSimplePartialRepairWithItemAdjustment() throws Exception {
// We take april as it has 30 days (easier to play with BCD)
@@ -728,7 +720,7 @@ public class TestIntegrationInvoiceWithRepairLogic extends TestIntegrationBase {
newItems.add(recurring3);
newItems.add(repair3);
shellInvoice.addInvoiceItems(newItems);
- invoiceDao.createInvoice(shellInvoice, new FutureAccountNotifications(new HashMap<UUID, List<SubscriptionNotification>>()), internalCallContext);
+ invoiceDao.createInvoice(shellInvoice, new FutureAccountNotifications(), internalCallContext);
// Move ahead one month, verify nothing from previous data was generated
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationParentInvoice.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationParentInvoice.java
index 945dece..2f139e2 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationParentInvoice.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationParentInvoice.java
@@ -18,10 +18,17 @@
package org.killbill.billing.beatrix.integration;
import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Iterator;
import java.util.List;
+import java.util.UUID;
+
+import javax.inject.Inject;
import org.joda.time.DateTime;
import org.joda.time.LocalDate;
+import org.killbill.billing.ErrorCode;
+import org.killbill.billing.ObjectType;
import org.killbill.billing.account.api.Account;
import org.killbill.billing.account.api.DefaultAccount;
import org.killbill.billing.account.dao.AccountModelDao;
@@ -29,16 +36,25 @@ import org.killbill.billing.api.TestApiListener.NextEvent;
import org.killbill.billing.catalog.api.BillingActionPolicy;
import org.killbill.billing.catalog.api.BillingPeriod;
import org.killbill.billing.catalog.api.Currency;
-import org.killbill.billing.catalog.api.PlanSpecifier;
+import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
import org.killbill.billing.catalog.api.ProductCategory;
import org.killbill.billing.entitlement.api.DefaultEntitlement;
+import org.killbill.billing.invoice.api.DefaultInvoiceService;
import org.killbill.billing.invoice.api.Invoice;
import org.killbill.billing.invoice.api.InvoiceApiException;
import org.killbill.billing.invoice.api.InvoiceItem;
import org.killbill.billing.invoice.api.InvoiceItemType;
import org.killbill.billing.invoice.api.InvoiceStatus;
+import org.killbill.billing.invoice.notification.ParentInvoiceCommitmentNotifier;
import org.killbill.billing.payment.api.Payment;
+import org.killbill.billing.payment.api.PaymentApiException;
import org.killbill.billing.payment.api.PluginProperty;
+import org.killbill.billing.payment.invoice.InvoicePaymentControlPluginApi;
+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 org.testng.Assert;
import org.testng.annotations.Test;
import com.google.common.collect.ImmutableList;
@@ -50,6 +66,9 @@ import static org.testng.Assert.assertTrue;
public class TestIntegrationParentInvoice extends TestIntegrationBase {
+ @Inject
+ private NotificationQueueService notificationQueueService;
+
@Test(groups = "slow")
public void testParentInvoiceGeneration() throws Exception {
@@ -77,6 +96,15 @@ public class TestIntegrationParentInvoice extends TestIntegrationBase {
assertTrue(parentInvoice.isParentInvoice());
assertEquals(parentInvoice.getBalance().compareTo(BigDecimal.ZERO), 0);
+ // Verify the notification exists and the efective time matches the default configuration '23:59:59'
+ final NotificationQueue notificationQueue = notificationQueueService.getNotificationQueue(DefaultInvoiceService.INVOICE_SERVICE_NAME, ParentInvoiceCommitmentNotifier.PARENT_INVOICE_COMMITMENT_NOTIFIER_QUEUE);
+ final Iterable<NotificationEventWithMetadata<NotificationEvent>> events = notificationQueue.getFutureNotificationForSearchKey2(new DateTime(2050, 5, 15, 0, 0, 0, 0, testTimeZone), internalCallContext.getTenantRecordId());
+ //
+ final Iterator<NotificationEventWithMetadata<NotificationEvent>> metadataEventIterator = events.iterator();
+ assertTrue(metadataEventIterator.hasNext());
+ final NotificationEventWithMetadata<NotificationEvent> notificationEvent = metadataEventIterator.next();
+ assertTrue(notificationEvent.getEffectiveDate().compareTo(new DateTime(2015, 5, 15, 23, 59, 59, 0, testTimeZone)) == 0);
+
// Moving a day the NotificationQ calls the commitInvoice. No payment is expected
busHandler.pushExpectedEvents(NextEvent.INVOICE);
clock.addDays(1);
@@ -179,7 +207,7 @@ public class TestIntegrationParentInvoice extends TestIntegrationBase {
// upgrade plan
busHandler.pushExpectedEvents(NextEvent.CHANGE, NextEvent.INVOICE);
- baseEntitlementChild.changePlanWithDate(new PlanSpecifier("Shotgun", BillingPeriod.MONTHLY, baseEntitlementChild.getLastActivePriceList().getName()), null, null,null, callContext);
+ baseEntitlementChild.changePlanWithDate(new PlanPhaseSpecifier("Shotgun", BillingPeriod.MONTHLY, baseEntitlementChild.getLastActivePriceList().getName()), null, null, null, callContext);
assertListenerStatus();
// check parent invoice. Expected to have the same invoice item with the amount updated
@@ -910,7 +938,7 @@ public class TestIntegrationParentInvoice extends TestIntegrationBase {
clock.setTime(date);
assertListenerStatus();
- createBaseEntitlementAndCheckForCompletion(childAccount.getId(), "bundleKey1", "Shotgun", ProductCategory.BASE, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
+ createBaseEntitlementAndCheckForCompletion(childAccount.getId(), "bundleKey2", "Shotgun", ProductCategory.BASE, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
// Move through time and verify new parent Invoice.
busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.INVOICE);
@@ -1127,6 +1155,22 @@ public class TestIntegrationParentInvoice extends TestIntegrationBase {
assertEquals(invoiceUserApi.getAccountBalance(parentAccount.getId(), callContext).compareTo(new BigDecimal("249.95")), 0);
assertEquals(invoiceUserApi.getAccountBalance(childAccount.getId(), callContext).compareTo(new BigDecimal("249.95")), 0);
+
+ // Verify Invoice apis getParentAccountId/getParentInvoiceId
+ assertEquals(childInvoices.get(1).getParentAccountId(), parentAccount.getId());
+ assertEquals(childInvoices.get(1).getParentInvoiceId(), parentInvoice.getId());
+
+ try {
+ final List<PluginProperty> properties = new ArrayList<PluginProperty>();
+ final PluginProperty prop1 = new PluginProperty(InvoicePaymentControlPluginApi.PROP_IPCD_INVOICE_ID, childInvoices.get(1).getId().toString(), false);
+ properties.add(prop1);
+ paymentApi.createPurchaseWithPaymentControl(childAccount, childAccount.getPaymentMethodId(), null, childInvoices.get(1).getBalance(), childInvoices.get(1).getCurrency(), null, UUID.randomUUID().toString(),
+ UUID.randomUUID().toString(), properties, PAYMENT_OPTIONS, callContext);
+ Assert.fail("Payment should fail, invoice belongs to parent");
+ } catch (final PaymentApiException e) {
+ assertEquals(ErrorCode.PAYMENT_PLUGIN_API_ABORTED.getCode(), e.getCode());
+ }
+
final int nbDaysBeforeRetry = paymentConfig.getPaymentFailureRetryDays(internalCallContext).get(0);
// Move time for retry to happen
@@ -1274,4 +1318,191 @@ public class TestIntegrationParentInvoice extends TestIntegrationBase {
assertEquals(childInvoices.size(), 3);
assertEquals(childInvoices.get(2).getBalance().compareTo(BigDecimal.ZERO), 0);
}
+
+ @Test(groups = "slow")
+ public void testWithAutoInvoicingDraft() throws Exception {
+
+ final int billingDay = 14;
+ final DateTime initialCreationDate = new DateTime(2015, 5, 15, 0, 0, 0, 0, testTimeZone);
+ // set clock to the initial start date
+ clock.setTime(initialCreationDate);
+
+ final Account parentAccount = createAccountWithNonOsgiPaymentMethod(getAccountData(billingDay));
+ final Account childAccount = createAccountWithNonOsgiPaymentMethod(getChildAccountData(billingDay, parentAccount.getId(), true));
+
+ createBaseEntitlementAndCheckForCompletion(childAccount.getId(), "bundleKey1", "Shotgun", ProductCategory.BASE, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
+
+ // First Parent invoice over TRIAL period
+ List<Invoice> parentInvoices = invoiceUserApi.getInvoicesByAccount(parentAccount.getId(), false, callContext);
+ assertEquals(parentInvoices.size(), 1);
+
+ Invoice parentInvoice = parentInvoices.get(0);
+ assertEquals(parentInvoice.getNumberOfItems(), 1);
+ assertEquals(parentInvoice.getStatus(), InvoiceStatus.DRAFT);
+ assertTrue(parentInvoice.isParentInvoice());
+ assertEquals(parentInvoice.getBalance().compareTo(BigDecimal.ZERO), 0);
+
+ // Moving a day the NotificationQ calls the commitInvoice. No payment is expected
+ busHandler.pushExpectedEvents(NextEvent.INVOICE);
+ clock.addDays(1);
+ assertListenerStatus();
+
+ // reload parent invoice
+ parentInvoice = invoiceUserApi.getInvoice(parentInvoice.getId(), callContext);
+ assertEquals(parentInvoice.getStatus(), InvoiceStatus.COMMITTED);
+
+ add_AUTO_INVOICING_DRAFT_Tag(childAccount.getId(), ObjectType.ACCOUNT);
+
+ busHandler.pushExpectedEvents(NextEvent.PHASE);
+ clock.addDays(29);
+ assertListenerStatus();
+
+ // Check we don't see yet any new invoice for the parent.
+ parentInvoices = invoiceUserApi.getInvoicesByAccount(parentAccount.getId(), false, callContext);
+ assertEquals(parentInvoices.size(), 1);
+
+ // Check we see the new invoice for the child but as DRAFT
+ List<Invoice> childInvoices = invoiceUserApi.getInvoicesByAccount(childAccount.getId(), false, callContext);
+ assertEquals(childInvoices.size(), 2);
+ assertEquals(childInvoices.get(1).getStatus(), InvoiceStatus.DRAFT);
+
+ // Move clock ahead a few days and commit child invoice
+ busHandler.pushExpectedEvents(NextEvent.INVOICE);
+ clock.addDays(5);
+ invoiceUserApi.commitInvoice(childInvoices.get(1).getId(), callContext);
+ assertListenerStatus();
+
+ parentInvoices = invoiceUserApi.getInvoicesByAccount(parentAccount.getId(), false, callContext);
+ assertEquals(parentInvoices.size(), 2);
+ parentInvoice = parentInvoices.get(1);
+ assertEquals(parentInvoice.getNumberOfItems(), 1);
+ assertEquals(parentInvoice.getStatus(), InvoiceStatus.DRAFT);
+ assertTrue(parentInvoice.isParentInvoice());
+ // balance is 0 because parent invoice status is DRAFT
+ assertEquals(parentInvoice.getBalance().compareTo(BigDecimal.ZERO), 0);
+ // total 279.95
+ assertEquals(parentInvoice.getInvoiceItems().get(0).getInvoiceItemType(), InvoiceItemType.PARENT_SUMMARY);
+ assertEquals(parentInvoice.getInvoiceItems().get(0).getAmount().compareTo(BigDecimal.valueOf(249.95)), 0);
+
+ // Check Child Balance. It should be > 0 here because Parent invoice is unpaid yet.
+ childInvoices = invoiceUserApi.getInvoicesByAccount(childAccount.getId(), false, callContext);
+ assertEquals(childInvoices.size(), 2);
+ // child balance is 0 because parent invoice status is DRAFT at this point
+ assertEquals(childInvoices.get(1).getBalance().compareTo(BigDecimal.ZERO), 0);
+
+ // Moving a day the NotificationQ calls the commitInvoice. Payment is expected.
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+ clock.addDays(1);
+ assertListenerStatus();
+
+ parentInvoice = invoiceUserApi.getInvoice(parentInvoice.getId(), callContext);
+ assertEquals(parentInvoice.getStatus(), InvoiceStatus.COMMITTED);
+
+ // Check Child Balance. It should be = 0 because parent invoice had already paid.
+ childInvoices = invoiceUserApi.getInvoicesByAccount(childAccount.getId(), false, callContext);
+ assertEquals(childInvoices.size(), 2);
+ assertTrue(parentInvoice.getBalance().compareTo(BigDecimal.ZERO) == 0);
+ assertTrue(childInvoices.get(1).getBalance().compareTo(BigDecimal.ZERO) == 0);
+
+ // load children invoice items
+ final List<InvoiceItem> childrenInvoiceItems = invoiceUserApi.getInvoiceItemsByParentInvoice(parentInvoice.getId(), callContext);
+ assertEquals(childrenInvoiceItems.size(), 1);
+ assertEquals(childrenInvoiceItems.get(0).getAccountId(), childAccount.getId());
+ assertEquals(childrenInvoiceItems.get(0).getAmount().compareTo(BigDecimal.valueOf(249.95)), 0);
+
+ // loading children items from non parent account should return empty list
+ assertEquals(invoiceUserApi.getInvoiceItemsByParentInvoice(childInvoices.get(1).getId(), callContext).size(), 0);
+ }
+
+
+
+ @Test(groups = "slow")
+ public void testWithEarlyCommitParentInvoice() throws Exception {
+
+ final int billingDay = 14;
+ final DateTime initialCreationDate = new DateTime(2015, 5, 15, 0, 0, 0, 0, testTimeZone);
+
+ // set clock to the initial start date
+ clock.setTime(initialCreationDate);
+
+ log.info("Beginning test with BCD of " + billingDay);
+ final Account parentAccount = createAccountWithNonOsgiPaymentMethod(getAccountData(billingDay));
+ final Account childAccount = createAccountWithNonOsgiPaymentMethod(getChildAccountData(billingDay, parentAccount.getId(), true));
+
+ DefaultEntitlement baseEntitlementChild = createBaseEntitlementAndCheckForCompletion(childAccount.getId(), "bundleKey1", "Pistol", ProductCategory.BASE, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
+
+ // Moving a day the NotificationQ calls the commitInvoice. No payment is expected.
+ busHandler.pushExpectedEvents(NextEvent.INVOICE);
+ clock.addDays(1);
+ assertListenerStatus();
+
+ // Move through time and verify new parent Invoice. No payments are expected yet.
+ busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE);
+ clock.addDays(29);
+ assertListenerStatus();
+
+ List<Invoice> childInvoices = invoiceUserApi.getInvoicesByAccount(childAccount.getId(), false, callContext);
+ assertEquals(childInvoices.size(), 2);
+
+ // check parent Invoice with child plan amount
+ List<Invoice> parentInvoices = invoiceUserApi.getInvoicesByAccount(parentAccount.getId(), false, callContext);
+ assertEquals(parentInvoices.size(), 2);
+
+ Invoice parentInvoice = parentInvoices.get(1);
+ assertEquals(parentInvoice.getNumberOfItems(), 1);
+ assertEquals(parentInvoice.getStatus(), InvoiceStatus.DRAFT);
+ assertTrue(parentInvoice.isParentInvoice());
+ // balance is 0 because parent invoice status is DRAFT
+ assertEquals(parentInvoice.getBalance().compareTo(BigDecimal.ZERO), 0);
+ assertEquals(parentInvoice.getInvoiceItems().get(0).getAmount().compareTo(BigDecimal.valueOf(29.95)), 0);
+
+
+ // Move clock 3 hours ahead
+ clock.addDeltaFromReality(3 * 3600 * 1000);
+
+ // Commit parent invoice prior end of the day
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+ invoiceUserApi.commitInvoice(parentInvoice.getId(), callContext);
+ assertListenerStatus();
+
+
+ // Create an ADD_ON for the child
+ DefaultEntitlement aoEntitlementChild = addAOEntitlementAndCheckForCompletion(baseEntitlementChild.getBundleId(), "Refurbish-Maintenance", ProductCategory.ADD_ON, BillingPeriod.MONTHLY,
+ NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
+
+
+ childInvoices = invoiceUserApi.getInvoicesByAccount(childAccount.getId(), false, callContext);
+ assertEquals(childInvoices.size(), 3);
+
+ parentInvoices = invoiceUserApi.getInvoicesByAccount(parentAccount.getId(), false, callContext);
+ assertEquals(parentInvoices.size(), 3);
+
+
+ parentInvoice = parentInvoices.get(2);
+ assertEquals(parentInvoice.getNumberOfItems(), 1);
+ assertEquals(parentInvoice.getStatus(), InvoiceStatus.DRAFT);
+ assertTrue(parentInvoice.isParentInvoice());
+ // balance is 0 because parent invoice status is DRAFT
+ assertEquals(parentInvoice.getBalance().compareTo(BigDecimal.ZERO), 0);
+ assertEquals(parentInvoice.getInvoiceItems().get(0).getAmount().compareTo(BigDecimal.valueOf(799.90)), 0);
+
+
+ // Moving a day the NotificationQ calls the commitInvoice.
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+ clock.addDays(1);
+ assertListenerStatus();
+
+ parentInvoices = invoiceUserApi.getInvoicesByAccount(parentAccount.getId(), false, callContext);
+ assertEquals(parentInvoices.size(), 3);
+
+ parentInvoice = parentInvoices.get(2);
+ assertEquals(parentInvoice.getStatus(), InvoiceStatus.COMMITTED);
+ assertTrue(parentInvoice.isParentInvoice());
+ assertEquals(parentInvoice.getChargedAmount().compareTo(BigDecimal.valueOf(799.90)), 0);
+ assertEquals(parentInvoice.getCreditedAmount().compareTo(BigDecimal.ZERO), 0);
+
+
+ }
+
+
}
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithAutoInvoiceDraft.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithAutoInvoiceDraft.java
new file mode 100644
index 0000000..3956312
--- /dev/null
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithAutoInvoiceDraft.java
@@ -0,0 +1,274 @@
+/*
+ * 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;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.joda.time.LocalDate;
+import org.killbill.billing.ObjectType;
+import org.killbill.billing.account.api.Account;
+import org.killbill.billing.api.TestApiListener.NextEvent;
+import org.killbill.billing.beatrix.util.InvoiceChecker.ExpectedInvoiceItemCheck;
+import org.killbill.billing.catalog.api.BillingPeriod;
+import org.killbill.billing.catalog.api.Currency;
+import org.killbill.billing.catalog.api.ProductCategory;
+import org.killbill.billing.entitlement.api.DefaultEntitlement;
+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.InvoiceStatus;
+import org.killbill.billing.invoice.api.InvoiceUserApi;
+import org.killbill.billing.invoice.model.ExternalChargeInvoiceItem;
+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.tag.ControlTagType;
+import org.killbill.billing.util.tag.Tag;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.google.inject.Inject;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+
+public class TestIntegrationWithAutoInvoiceDraft extends TestIntegrationBase {
+
+ @Inject
+ private InvoiceUserApi invoiceApi;
+
+ @Inject
+ private TagUserApi tagApi;
+
+ private Account account;
+ private String productName;
+ private BillingPeriod term;
+
+ @Override
+ @BeforeMethod(groups = "slow")
+ public void beforeMethod() throws Exception {
+ super.beforeMethod();
+ account = createAccountWithNonOsgiPaymentMethod(getAccountData(25));
+ assertNotNull(account);
+ productName = "Shotgun";
+ term = BillingPeriod.MONTHLY;
+ }
+
+
+ @Test(groups = "slow")
+ public void testAutoInvoicingDraftBasic() throws Exception {
+ clock.setTime(new DateTime(2017, 6, 16, 18, 24, 42, 0));
+ add_AUTO_INVOICING_DRAFT_Tag(account.getId(), ObjectType.ACCOUNT);
+
+ final DefaultEntitlement bpEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "externalKey", productName, ProductCategory.BASE, term, NextEvent.CREATE, NextEvent.BLOCK);
+ assertNotNull(bpEntitlement);
+
+ List<Invoice> invoices = invoiceApi.getInvoicesByAccount(account.getId(), false, callContext);
+ assertEquals(invoices.size(), 1);
+ final Invoice trialInvoice = invoices.get(0);
+ assertEquals(trialInvoice.getStatus(), InvoiceStatus.DRAFT);
+
+ busHandler.pushExpectedEvent(NextEvent.INVOICE);
+ invoiceApi.commitInvoice(trialInvoice.getId(), callContext);
+ assertListenerStatus();
+
+ // Move out of TRIAL
+ busHandler.pushExpectedEvents(NextEvent.PHASE);
+ clock.addDays(30);
+ assertListenerStatus();
+
+ invoices = invoiceApi.getInvoicesByAccount(account.getId(), false, callContext);
+ assertEquals(invoices.size(), 2);
+
+ final Invoice firstNonTrialInvoice = invoices.get(1);
+ assertEquals(firstNonTrialInvoice.getStatus(), InvoiceStatus.DRAFT);
+
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.INVOICE_PAYMENT, NextEvent.PAYMENT);
+ invoiceApi.commitInvoice(firstNonTrialInvoice.getId(), callContext);
+ assertListenerStatus();
+
+
+ remove_AUTO_INVOICING_DRAFT_Tag(account.getId(), ObjectType.ACCOUNT);
+
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+ clock.addMonths(1);
+ assertListenerStatus();
+
+ invoices = invoiceApi.getInvoicesByAccount(account.getId(), false, callContext);
+ assertEquals(invoices.size(), 3);
+
+
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+ clock.addMonths(1);
+ assertListenerStatus();
+
+ invoices = invoiceApi.getInvoicesByAccount(account.getId(), false, callContext);
+ assertEquals(invoices.size(), 4);
+ }
+
+
+ @Test(groups = "slow")
+ public void testWithExistingDraftInvoice() throws Exception {
+ clock.setTime(new DateTime(2017, 6, 16, 18, 24, 42, 0));
+ add_AUTO_INVOICING_DRAFT_Tag(account.getId(), ObjectType.ACCOUNT);
+
+ final DefaultEntitlement bpEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "externalKey", productName, ProductCategory.BASE, term, NextEvent.CREATE, NextEvent.BLOCK);
+ assertNotNull(bpEntitlement);
+
+ List<Invoice> invoices = invoiceApi.getInvoicesByAccount(account.getId(), false, callContext);
+ assertEquals(invoices.size(), 1);
+ final Invoice trialInvoice = invoices.get(0);
+ assertEquals(trialInvoice.getStatus(), InvoiceStatus.DRAFT);
+
+ busHandler.pushExpectedEvent(NextEvent.INVOICE);
+ invoiceApi.commitInvoice(trialInvoice.getId(), callContext);
+ assertListenerStatus();
+
+ // Move out of TRIAL
+ busHandler.pushExpectedEvents(NextEvent.PHASE);
+ clock.addDays(30);
+ assertListenerStatus();
+
+ invoices = invoiceApi.getInvoicesByAccount(account.getId(), false, callContext);
+ assertEquals(invoices.size(), 2);
+
+ // Check firstNonTrialInvoice is still in DRAFT
+ final Invoice firstNonTrialInvoice = invoices.get(1);
+ assertEquals(firstNonTrialInvoice.getStatus(), InvoiceStatus.DRAFT);
+ assertEquals(firstNonTrialInvoice.getInvoiceDate(), new LocalDate(2017, 07, 16));
+
+
+ final List<ExpectedInvoiceItemCheck> toBeChecked = new ArrayList<ExpectedInvoiceItemCheck>();
+ toBeChecked.add(new ExpectedInvoiceItemCheck(new LocalDate(2017, 7, 16), new LocalDate(2017, 7, 25), InvoiceItemType.RECURRING, new BigDecimal("74.99")));
+ invoiceChecker.checkInvoice(invoices.get(1).getId(), callContext, toBeChecked);
+ toBeChecked.clear();
+
+ // Check account balance is still reflected as Zero
+ final BigDecimal accountBalance = invoiceApi.getAccountBalance(account.getId(), callContext);
+ assertEquals(accountBalance.compareTo(BigDecimal.ZERO), 0);
+
+ remove_AUTO_INVOICING_DRAFT_Tag(account.getId(), ObjectType.ACCOUNT);
+
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+ clock.addMonths(1);
+ assertListenerStatus();
+
+ invoices = invoiceApi.getInvoicesByAccount(account.getId(), false, callContext);
+ assertEquals(invoices.size(), 3);
+
+ // Check prev invoice is still in DRAFT
+ assertEquals(invoices.get(1).getStatus(), InvoiceStatus.DRAFT);
+
+ // Check most recent invoice is COMMITTED
+ assertEquals(invoices.get(2).getStatus(), InvoiceStatus.COMMITTED);
+
+ // Verify most recent invoice *only* contains the items for the period 2017-07-25 -> 2017-08-25 (and not 2017-07-16 -> 2017-07-25 from the DRAFT invoice)
+ toBeChecked.add(new ExpectedInvoiceItemCheck(new LocalDate(2017, 7, 25), new LocalDate(2017, 8, 25), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
+ invoiceChecker.checkInvoice(invoices.get(2).getId(), callContext, toBeChecked);
+ toBeChecked.clear();
+
+ // Finally commit second invoice
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.INVOICE_PAYMENT, NextEvent.PAYMENT);
+ invoiceApi.commitInvoice(firstNonTrialInvoice.getId(), callContext);
+ assertListenerStatus();
+
+ final BigDecimal accountBalance2 = invoiceApi.getAccountBalance(account.getId(), callContext);
+ assertEquals(accountBalance2.compareTo(BigDecimal.ZERO), 0);
+
+ }
+
+
+ @Test(groups = "slow")
+ public void testAutoInvoicingReuseDraftBasic() throws Exception {
+ clock.setTime(new DateTime(2017, 6, 16, 18, 24, 42, 0));
+
+ // Set both AUTO_INVOICING_DRAFT and AUTO_INVOICING_REUSE_DRAFT
+ add_AUTO_INVOICING_DRAFT_Tag(account.getId(), ObjectType.ACCOUNT);
+ add_AUTO_INVOICING_REUSE_DRAFT_Tag(account.getId(), ObjectType.ACCOUNT);
+
+ // Create initial DRAFt invoice that will be reused by the system
+ final LocalDate startDate = clock.getUTCToday();
+ final LocalDate endDate = startDate.plusDays(5);
+ final InvoiceItem externalCharge = new ExternalChargeInvoiceItem(null, account.getId(), null, "Initial external charge", startDate, endDate, BigDecimal.TEN, Currency.USD);
+ invoiceUserApi.insertExternalCharges(account.getId(), clock.getUTCToday(), ImmutableList.<InvoiceItem>of(externalCharge), false, callContext).get(0);
+
+ List<Invoice> invoices;
+ invoices = invoiceApi.getInvoicesByAccount(account.getId(), false, callContext);
+ assertEquals(invoices.size(), 1);
+ assertEquals(invoices.get(0).getInvoiceItems().size(), 1);
+ assertEquals(invoices.get(0).getStatus(), InvoiceStatus.DRAFT);
+
+ final UUID invoiceId = invoices.get(0).getId();
+
+ final DefaultEntitlement bpEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "externalKey", productName, ProductCategory.BASE, term, NextEvent.CREATE, NextEvent.BLOCK);
+ assertNotNull(bpEntitlement);
+
+ // Verify we see the new item on our existing DRAFT invoice
+ invoices = invoiceApi.getInvoicesByAccount(account.getId(), false, callContext);
+ assertEquals(invoices.size(), 1);
+ assertEquals(invoices.get(0).getId(), invoiceId);
+ assertEquals(invoices.get(0).getInvoiceItems().size(), 2);
+ assertEquals(invoices.get(0).getStatus(), InvoiceStatus.DRAFT);
+
+
+ // Move out of TRIAL
+ busHandler.pushExpectedEvents(NextEvent.PHASE);
+ clock.addDays(30);
+ assertListenerStatus();
+
+ // Verify again we see the new item on our existing DRAFT invoice
+ invoices = invoiceApi.getInvoicesByAccount(account.getId(), false, callContext);
+ assertEquals(invoices.size(), 1);
+ assertEquals(invoices.get(0).getId(), invoiceId);
+ assertEquals(invoices.get(0).getInvoiceItems().size(), 3);
+ assertEquals(invoices.get(0).getStatus(), InvoiceStatus.DRAFT);
+
+ // Remove AUTO_INVOICING_DRAFT, so next invoicing should commit DRAFt invoice
+ remove_AUTO_INVOICING_DRAFT_Tag(account.getId(), ObjectType.ACCOUNT);
+
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+ clock.addMonths(1);
+ assertListenerStatus();
+
+ // Verify again we see the new item and this time invoice is in COMMITTED
+ invoices = invoiceApi.getInvoicesByAccount(account.getId(), false, callContext);
+ assertEquals(invoices.size(), 1);
+ assertEquals(invoices.get(0).getId(), invoiceId);
+ assertEquals(invoices.get(0).getInvoiceItems().size(), 4);
+ assertEquals(invoices.get(0).getStatus(), InvoiceStatus.COMMITTED);
+
+
+ // Verify we see a new invoice
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+ clock.addMonths(1);
+ assertListenerStatus();
+
+ invoices = invoiceApi.getInvoicesByAccount(account.getId(), false, callContext);
+ assertEquals(invoices.size(), 2);
+ assertEquals(invoices.get(1).getInvoiceItems().size(), 1);
+ assertEquals(invoices.get(1).getStatus(), InvoiceStatus.COMMITTED);
+ }
+
+
+}
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithAutoInvoiceOffTag.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithAutoInvoiceOffTag.java
index 729e277..3cd5c9b 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithAutoInvoiceOffTag.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithAutoInvoiceOffTag.java
@@ -23,9 +23,6 @@ import java.util.List;
import java.util.UUID;
import org.joda.time.DateTime;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-
import org.killbill.billing.ObjectType;
import org.killbill.billing.account.api.Account;
import org.killbill.billing.api.TestApiListener.NextEvent;
@@ -33,12 +30,15 @@ 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.invoice.api.Invoice;
+import org.killbill.billing.invoice.api.InvoiceStatus;
import org.killbill.billing.invoice.api.InvoiceUserApi;
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.tag.ControlTagType;
import org.killbill.billing.util.tag.Tag;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
import com.google.inject.Inject;
@@ -92,9 +92,7 @@ public class TestIntegrationWithAutoInvoiceOffTag extends TestIntegrationBase {
invoices = invoiceApi.getInvoicesByAccount(account.getId(), false, callContext);
assertEquals(invoices.size(), 0);
- busHandler.pushExpectedEvents(NextEvent.TAG, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
- remove_AUTO_INVOICING_OFF_Tag(account.getId(), ObjectType.ACCOUNT);
- assertListenerStatus();
+ remove_AUTO_INVOICING_OFF_Tag(account.getId(), ObjectType.ACCOUNT, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
invoices = invoiceApi.getInvoicesByAccount(account.getId(), false, callContext);
assertEquals(invoices.size(), 1);
@@ -145,15 +143,4 @@ public class TestIntegrationWithAutoInvoiceOffTag extends TestIntegrationBase {
assertEquals(invoices.size(), 3); // Only one additional invoice generated
}
- private void add_AUTO_INVOICING_OFF_Tag(final UUID id, final ObjectType type) throws TagDefinitionApiException, TagApiException {
- busHandler.pushExpectedEvent(NextEvent.TAG);
- tagApi.addTag(id, type, ControlTagType.AUTO_INVOICING_OFF.getId(), callContext);
- assertListenerStatus();
- final List<Tag> tags = tagApi.getTagsForObject(id, type, false, callContext);
- assertEquals(tags.size(), 1);
- }
-
- private void remove_AUTO_INVOICING_OFF_Tag(final UUID id, final ObjectType type) throws TagDefinitionApiException, TagApiException {
- tagApi.removeTag(id, type, ControlTagType.AUTO_INVOICING_OFF.getId(), callContext);
- }
}
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 a57824b..b128d8a 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
@@ -40,7 +40,6 @@ import org.killbill.billing.catalog.api.CatalogUserApi;
import org.killbill.billing.catalog.api.Plan;
import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
-import org.killbill.billing.catalog.api.PlanSpecifier;
import org.killbill.billing.catalog.api.PriceListSet;
import org.killbill.billing.catalog.api.ProductCategory;
import org.killbill.billing.catalog.api.SimplePlanDescriptor;
@@ -116,7 +115,7 @@ public class TestIntegrationWithCatalogUpdate extends TestIntegrationBase {
// Change Plan to the newly added Plan and verify correct default rules behavior (IMMEDIATE change)
busHandler.pushExpectedEvents(NextEvent.CHANGE, NextEvent.INVOICE, NextEvent.INVOICE_PAYMENT, NextEvent.PAYMENT);
- baseEntitlement.changePlan(new PlanSpecifier("SuperFoo", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME), null, ImmutableList.<PluginProperty>of(), testCallContext);
+ baseEntitlement.changePlan(new PlanPhaseSpecifier("SuperFoo", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME), null, ImmutableList.<PluginProperty>of(), testCallContext);
assertListenerStatus();
invoiceChecker.checkInvoice(account.getId(), 2, testCallContext,
@@ -182,7 +181,7 @@ public class TestIntegrationWithCatalogUpdate extends TestIntegrationBase {
try {
final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Zoe", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
- entitlementApi.createBaseEntitlement(account.getId(), spec, UUID.randomUUID().toString(), null, null, null, false, ImmutableList.<PluginProperty>of(), testCallContext);
+ entitlementApi.createBaseEntitlement(account.getId(), spec, UUID.randomUUID().toString(), null, null, null, false, true, ImmutableList.<PluginProperty>of(), testCallContext);
fail("Creating entitlement should fail");
} catch (final EntitlementApiException e) {
assertEquals(e.getCode(), ErrorCode.CAT_MULTIPLE_MATCHING_PLANS_FOR_PRICELIST.getCode());
@@ -248,7 +247,7 @@ public class TestIntegrationWithCatalogUpdate extends TestIntegrationBase {
final List<ExpectedInvoiceItemCheck> expectedInvoices = new ArrayList<ExpectedInvoiceItemCheck>();
expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2016, 6, 1), new LocalDate(2016, 7, 1), InvoiceItemType.RECURRING, BigDecimal.TEN));
- invoiceChecker.checkInvoiceNoAudits(invoices.get(0), callContext, expectedInvoices);
+ invoiceChecker.checkInvoiceNoAudits(invoices.get(0), expectedInvoices);
int invoiceSize = 2;
LocalDate startDate = new LocalDate(2016, 7, 1);
@@ -265,7 +264,7 @@ public class TestIntegrationWithCatalogUpdate extends TestIntegrationBase {
assertEquals(invoices.size(), invoiceSize);
expectedInvoices.add(new ExpectedInvoiceItemCheck(startDate, endDate, InvoiceItemType.RECURRING, BigDecimal.TEN));
- invoiceChecker.checkInvoiceNoAudits(invoices.get(invoices.size() - 1), callContext, expectedInvoices);
+ invoiceChecker.checkInvoiceNoAudits(invoices.get(invoices.size() - 1), expectedInvoices);
startDate = endDate;
invoiceSize++;
@@ -284,7 +283,7 @@ public class TestIntegrationWithCatalogUpdate extends TestIntegrationBase {
final PlanPhaseSpecifier specZero = new PlanPhaseSpecifier("zeroDesc-monthly", null);
busHandler.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
- final Entitlement baseEntitlement = entitlementApi.createBaseEntitlement(account.getId(), specZero, UUID.randomUUID().toString(), ImmutableList.<PlanPhasePriceOverride>of(), null, null, false, ImmutableList.<PluginProperty>of(), testCallContext);
+ final Entitlement baseEntitlement = entitlementApi.createBaseEntitlement(account.getId(), specZero, UUID.randomUUID().toString(), ImmutableList.<PlanPhasePriceOverride>of(), null, null, false, true, ImmutableList.<PluginProperty>of(), testCallContext);
assertListenerStatus();
Subscription refreshedBaseEntitlement = subscriptionApi.getSubscriptionForEntitlementId(baseEntitlement.getId(), testCallContext);
@@ -313,7 +312,7 @@ public class TestIntegrationWithCatalogUpdate extends TestIntegrationBase {
final PlanPhaseSpecifier specNonZero = new PlanPhaseSpecifier("superfoo-monthly", null);
busHandler.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE, NextEvent.INVOICE_PAYMENT, NextEvent.PAYMENT);
- final Entitlement baseEntitlement2 = entitlementApi.createBaseEntitlement(account.getId(), specNonZero, UUID.randomUUID().toString(), ImmutableList.<PlanPhasePriceOverride>of(), null, null, false, ImmutableList.<PluginProperty>of(), testCallContext);
+ final Entitlement baseEntitlement2 = entitlementApi.createBaseEntitlement(account.getId(), specNonZero, UUID.randomUUID().toString(), ImmutableList.<PlanPhasePriceOverride>of(), null, null, false, true, ImmutableList.<PluginProperty>of(), testCallContext);
assertListenerStatus();
busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.INVOICE_PAYMENT, NextEvent.PAYMENT);
@@ -374,7 +373,7 @@ public class TestIntegrationWithCatalogUpdate extends TestIntegrationBase {
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);
+ final Entitlement baseEntitlement = entitlementApi.createBaseEntitlement(account.getId(), planPhaseSpec, UUID.randomUUID().toString(), ImmutableList.<PlanPhasePriceOverride>of(), null, null, false, true, ImmutableList.<PluginProperty>of(), testCallContext);
assertListenerStatus();
Subscription refreshedBaseEntitlement = subscriptionApi.getSubscriptionForEntitlementId(baseEntitlement.getId(), testCallContext);
@@ -408,7 +407,7 @@ public class TestIntegrationWithCatalogUpdate extends TestIntegrationBase {
} else {
busHandler.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
}
- final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, UUID.randomUUID().toString(), overrides, null, null, false, ImmutableList.<PluginProperty>of(), testCallContext);
+ final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, UUID.randomUUID().toString(), overrides, null, null, false, true, ImmutableList.<PluginProperty>of(), testCallContext);
assertListenerStatus();
return entitlement;
}
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestInvoicePayment.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestInvoicePayment.java
index 6bce758..8bd1845 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestInvoicePayment.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestInvoicePayment.java
@@ -453,9 +453,14 @@ public class TestInvoicePayment extends TestIntegrationBase {
clock.setDay(new LocalDate(2012, 4, 1));
busHandler.pushExpectedEvents(NextEvent.INVOICE);
- final InvoiceItem externalCharge = new ExternalChargeInvoiceItem(null, account.getId(), null, "Initial external charge", clock.getUTCToday(), BigDecimal.TEN, Currency.USD);
+ final LocalDate startDate = clock.getUTCToday();
+ final LocalDate endDate = startDate.plusDays(5);
+ final InvoiceItem externalCharge = new ExternalChargeInvoiceItem(null, account.getId(), null, "Initial external charge", startDate, endDate, BigDecimal.TEN, Currency.USD);
final InvoiceItem item1 = invoiceUserApi.insertExternalCharges(account.getId(), clock.getUTCToday(), ImmutableList.<InvoiceItem>of(externalCharge), true, callContext).get(0);
assertListenerStatus();
+ // Verify service period for external charge -- seee #151
+ assertEquals(item1.getStartDate().compareTo(startDate), 0);
+ assertEquals(item1.getEndDate().compareTo(endDate), 0);
// Trigger first partial payment ($4) on first invoice
final Invoice invoice = invoiceUserApi.getInvoice(item1.getInvoiceId(), callContext);
@@ -1064,11 +1069,11 @@ public class TestInvoicePayment extends TestIntegrationBase {
final PluginProperty prop1 = new PluginProperty(InvoicePaymentControlPluginApi.PROP_IPCD_INVOICE_ID, updateInvoice2.getId().toString(), false);
properties.add(prop1);
try {
- paymentApi.createPurchaseWithPaymentControl(account, account.getPaymentMethodId(), null, updateInvoice2.getBalance(), updateInvoice2.getCurrency(), UUID.randomUUID().toString(),
+ paymentApi.createPurchaseWithPaymentControl(account, account.getPaymentMethodId(), null, updateInvoice2.getBalance(), updateInvoice2.getCurrency(), null, UUID.randomUUID().toString(),
UUID.randomUUID().toString(), properties, PAYMENT_OPTIONS, callContext);
Assert.fail("The payment should not succeed (and yet it will repair the broken state....)");
} catch (final PaymentApiException expected) {
- Assert.assertEquals(expected.getCode(), ErrorCode.PAYMENT_PLUGIN_EXCEPTION.getCode());
+ Assert.assertEquals(expected.getCode(), ErrorCode.PAYMENT_PLUGIN_API_ABORTED.getCode());
}
assertListenerStatus();
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestInvoiceSystemDisabling.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestInvoiceSystemDisabling.java
index cc57eae..200aadf 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestInvoiceSystemDisabling.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestInvoiceSystemDisabling.java
@@ -80,7 +80,7 @@ public class TestInvoiceSystemDisabling extends TestIntegrationBase {
assertListenerStatus();
final ImmutableList<ExpectedInvoiceItemCheck> expected = ImmutableList.<ExpectedInvoiceItemCheck>of(new ExpectedInvoiceItemCheck(new LocalDate(2012, 4, 1), null, InvoiceItemType.FIXED, BigDecimal.ZERO),
new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2012, 6, 1), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
- invoiceChecker.checkInvoiceNoAudits(invoice, callContext, expected);
+ invoiceChecker.checkInvoiceNoAudits(invoice, expected);
// Still parked
Assert.assertTrue(parkedAccountsManager.isParked(internalCallContext));
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestMigrationSubscriptions.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestMigrationSubscriptions.java
index 16eadea..0357f98 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestMigrationSubscriptions.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestMigrationSubscriptions.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2014-2016 Groupon, Inc
- * Copyright 2014-2016 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
@@ -22,27 +22,39 @@ import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
import org.joda.time.LocalDate;
+import org.killbill.billing.ObjectType;
import org.killbill.billing.account.api.Account;
import org.killbill.billing.account.api.AccountData;
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.Currency;
import org.killbill.billing.catalog.api.PhaseType;
+import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
import org.killbill.billing.catalog.api.PriceListSet;
import org.killbill.billing.entitlement.api.BaseEntitlementWithAddOnsSpecifier;
+import org.killbill.billing.entitlement.api.BlockingState;
+import org.killbill.billing.entitlement.api.BlockingStateType;
import org.killbill.billing.entitlement.api.DefaultEntitlementSpecifier;
import org.killbill.billing.entitlement.api.Entitlement;
import org.killbill.billing.entitlement.api.Entitlement.EntitlementState;
import org.killbill.billing.entitlement.api.EntitlementSpecifier;
import org.killbill.billing.invoice.api.InvoiceItemType;
+import org.killbill.billing.junction.DefaultBlockingState;
+import org.killbill.billing.mock.MockAccountBuilder;
import org.killbill.billing.payment.api.PluginProperty;
+import org.killbill.billing.util.tag.ControlTagType;
import org.testng.Assert;
import org.testng.annotations.Test;
import com.google.common.collect.ImmutableList;
+import static org.testng.Assert.assertNotNull;
+
//
// These scenarios emulate commons migrations problems (they go on verifying proper entitlement startDate, and proper billing startDate along with invoices, ..)
//
@@ -74,7 +86,7 @@ public class TestMigrationSubscriptions extends TestIntegrationBase {
// Entitlement wil be created in PENDING state
final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Shotgun", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
- final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, "bundleKey", null, entitlementMigrationDate, billingMigrationDate, false, ImmutableList.<PluginProperty>of(), callContext);
+ final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, "bundleKey", null, entitlementMigrationDate, billingMigrationDate, false, true, ImmutableList.<PluginProperty>of(), callContext);
Assert.assertEquals(entitlement.getState(), EntitlementState.PENDING);
// Move clock to entitlementMigrationDate (migration cutOverDate), and expect the associated event
@@ -130,7 +142,7 @@ public class TestMigrationSubscriptions extends TestIntegrationBase {
// Entitlement wil be created in ACTIVE state because entitlementMigrationDate was set in the past
busHandler.pushExpectedEvents(NextEvent.BLOCK);
final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Shotgun", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.EVERGREEN);
- final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, "bundleKey", null, entitlementMigrationDate, billingMigrationDate, false, ImmutableList.<PluginProperty>of(), callContext);
+ final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, "bundleKey", null, entitlementMigrationDate, billingMigrationDate, false, true, ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
Assert.assertEquals(entitlement.getState(), EntitlementState.ACTIVE);
@@ -175,7 +187,7 @@ public class TestMigrationSubscriptions extends TestIntegrationBase {
// Entitlement wil be created in ACTIVE state because entitlementMigrationDate was set in the past
busHandler.pushExpectedEvents(NextEvent.BLOCK);
final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Shotgun", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.EVERGREEN);
- final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, "bundleKey", null, entitlementMigrationDate, billingMigrationDate, false, ImmutableList.<PluginProperty>of(), callContext);
+ final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, "bundleKey", null, entitlementMigrationDate, billingMigrationDate, false, true, ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
Assert.assertEquals(entitlement.getState(), EntitlementState.ACTIVE);
@@ -244,6 +256,7 @@ public class TestMigrationSubscriptions extends TestIntegrationBase {
final List<Entitlement> baseEntitlements = entitlementApi.createBaseEntitlementsWithAddOns(
account.getId(),
baseEntitlementWithAddOnsSpecifierList,
+ true,
ImmutableList.<PluginProperty>of(),
callContext);
assertListenerStatus();
@@ -296,6 +309,7 @@ public class TestMigrationSubscriptions extends TestIntegrationBase {
final List<Entitlement> baseEntitlements = entitlementApi.createBaseEntitlementsWithAddOns(
account.getId(),
baseEntitlementWithAddOnsSpecifierList,
+ true,
ImmutableList.<PluginProperty>of(),
callContext);
assertListenerStatus();
@@ -321,6 +335,158 @@ public class TestMigrationSubscriptions extends TestIntegrationBase {
assertListenerStatus();
}
+ // Not exactly migration tests, but verify correct behavior when using BlockingState (see https://github.com/killbill/killbill/issues/744)
+
+ @Test(groups = "slow")
+ public void testBlockingStatesV1() throws Exception {
+ final DateTime initialDate = new DateTime(2017, 3, 1, 0, 1, 35, 0, DateTimeZone.UTC);
+ clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis());
+
+ final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(0));
+ assertNotNull(account);
+
+ busHandler.pushExpectedEvents(NextEvent.BLOCK);
+ final BlockingState blockingState1 = new DefaultBlockingState(account.getId(), BlockingStateType.ACCOUNT, "state1", "Service", false, false, true, null);
+ subscriptionApi.addBlockingState(blockingState1, null, ImmutableList.<PluginProperty>of(), callContext);
+ assertListenerStatus();
+
+ clock.addDays(1);
+
+ final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("pistol-monthly-notrial", null);
+ busHandler.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK);
+ entitlementApi.createBaseEntitlement(account.getId(), spec, "bundleExternalKey", ImmutableList.<PlanPhasePriceOverride>of(), null, null, false, true, ImmutableList.<PluginProperty>of(), callContext);
+ assertListenerStatus();
+
+ clock.addMonths(1);
+
+ busHandler.pushExpectedEvents(NextEvent.BLOCK, NextEvent.INVOICE, NextEvent.INVOICE_PAYMENT, NextEvent.PAYMENT);
+ final BlockingState blockingState2 = new DefaultBlockingState(account.getId(), BlockingStateType.ACCOUNT, "state2", "Service", false, false, false, null);
+ subscriptionApi.addBlockingState(blockingState2, null, ImmutableList.<PluginProperty>of(), callContext);
+ assertListenerStatus();
+
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.INVOICE_PAYMENT, NextEvent.PAYMENT);
+ clock.addMonths(1);
+ assertListenerStatus();
+ }
+
+ @Test(groups = "slow")
+ public void testBlockingStatesV2() throws Exception {
+ final DateTime initialDate = new DateTime(2017, 3, 1, 0, 1, 35, 0, DateTimeZone.UTC);
+ clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis());
+
+ final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(0));
+ assertNotNull(account);
+
+ final BlockingState blockingState1 = new DefaultBlockingState(account.getId(), BlockingStateType.ACCOUNT, "state1", "Service", false, false, true, null);
+ final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("pistol-monthly-notrial", null);
+
+ // Unlike the previous scenario, we create the subscription and set the blocking state at the same time
+ busHandler.pushExpectedEvents(NextEvent.BLOCK, NextEvent.CREATE, NextEvent.BLOCK);
+ subscriptionApi.addBlockingState(blockingState1, null, ImmutableList.<PluginProperty>of(), callContext);
+ entitlementApi.createBaseEntitlement(account.getId(), spec, "bundleExternalKey", ImmutableList.<PlanPhasePriceOverride>of(), null, null, false, true, ImmutableList.<PluginProperty>of(), callContext);
+ assertListenerStatus();
+
+ clock.addMonths(1);
+
+ busHandler.pushExpectedEvents(NextEvent.BLOCK, NextEvent.INVOICE, NextEvent.INVOICE_PAYMENT, NextEvent.PAYMENT);
+ final BlockingState blockingState2 = new DefaultBlockingState(account.getId(), BlockingStateType.ACCOUNT, "state2", "Service", false, false, false, null);
+ subscriptionApi.addBlockingState(blockingState2, null, ImmutableList.<PluginProperty>of(), callContext);
+ assertListenerStatus();
+
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.INVOICE_PAYMENT, NextEvent.PAYMENT);
+ clock.addMonths(1);
+ assertListenerStatus();
+ }
+
+ @Test(groups = "slow")
+ public void testBlockingStatesV3() throws Exception {
+ final DateTimeZone timeZone = DateTimeZone.forID("America/Los_Angeles");
+
+ // 2017-03-12 00:01:35 (change to DST happens at 2am on that day)
+ final DateTime initialDate = new DateTime(2017, 3, 12, 0, 1, 35, 0, timeZone);
+ clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis());
+
+ // Account in PDT
+ final AccountData accountData = new MockAccountBuilder().currency(Currency.USD)
+ .timeZone(timeZone)
+ .build();
+
+ final Account account = createAccountWithNonOsgiPaymentMethod(accountData);
+ assertNotNull(account);
+
+ busHandler.pushExpectedEvent(NextEvent.TAG);
+ tagUserApi.addTag(account.getId(), ObjectType.ACCOUNT, ControlTagType.AUTO_INVOICING_OFF.getId(), callContext);
+ assertListenerStatus();
+
+ final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("pistol-monthly-notrial", null);
+ busHandler.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK);
+ entitlementApi.createBaseEntitlement(account.getId(), spec, "bundleExternalKey", ImmutableList.<PlanPhasePriceOverride>of(), null, null, false, true, ImmutableList.<PluginProperty>of(), callContext);
+ assertListenerStatus();
+
+ // Add less than a day between the CREATE and the BLOCK, to verify invoicing behavior
+ clock.setTime(initialDate.plusHours(23).plusMinutes(30));
+
+ busHandler.pushExpectedEvents(NextEvent.BLOCK);
+ final BlockingState blockingState1 = new DefaultBlockingState(account.getId(), BlockingStateType.ACCOUNT, "state1", "Service", false, false, true, null);
+ subscriptionApi.addBlockingState(blockingState1, null, ImmutableList.<PluginProperty>of(), callContext);
+ assertListenerStatus();
+
+ busHandler.pushExpectedEvents(NextEvent.TAG, NextEvent.NULL_INVOICE);
+ tagUserApi.removeTag(account.getId(), ObjectType.ACCOUNT, ControlTagType.AUTO_INVOICING_OFF.getId(), callContext);
+ assertListenerStatus();
+
+ clock.addMonths(1);
+
+ busHandler.pushExpectedEvents(NextEvent.BLOCK, NextEvent.INVOICE, NextEvent.INVOICE_PAYMENT, NextEvent.PAYMENT);
+ final BlockingState blockingState2 = new DefaultBlockingState(account.getId(), BlockingStateType.ACCOUNT, "state2", "Service", false, false, false, null);
+ subscriptionApi.addBlockingState(blockingState2, null, ImmutableList.<PluginProperty>of(), callContext);
+ assertListenerStatus();
+
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.INVOICE_PAYMENT, NextEvent.PAYMENT);
+ clock.addMonths(1);
+ assertListenerStatus();
+ }
+
+ @Test(groups = "slow")
+ public void testBlockingStatesV4() throws Exception {
+ final DateTime initialDate = new DateTime(2017, 3, 1, 0, 1, 35, 0, DateTimeZone.UTC);
+ clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis());
+
+ final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(0));
+ assertNotNull(account);
+
+ busHandler.pushExpectedEvents(NextEvent.BLOCK);
+ final BlockingState blockingState1 = new DefaultBlockingState(account.getId(), BlockingStateType.ACCOUNT, "state1", "Service", false, false, true, null);
+ subscriptionApi.addBlockingState(blockingState1, null, ImmutableList.<PluginProperty>of(), callContext);
+ assertListenerStatus();
+
+ clock.addDays(1);
+
+ final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("pistol-monthly-notrial", null);
+ busHandler.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK);
+ final Entitlement baseEntitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, "bundleExternalKey", ImmutableList.<PlanPhasePriceOverride>of(), null, null, false, true, ImmutableList.<PluginProperty>of(), callContext);
+ assertListenerStatus();
+
+ clock.addDays(1);
+
+ // Add an add-on while bundle is already blocked
+ final PlanPhaseSpecifier spec2 = new PlanPhaseSpecifier("cleaning-monthly", null);
+ busHandler.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK);
+ entitlementApi.addEntitlement(baseEntitlement.getBundleId(), spec2, ImmutableList.<PlanPhasePriceOverride>of(), null, null, false, ImmutableList.<PluginProperty>of(), callContext);
+ assertListenerStatus();
+
+ clock.addMonths(1);
+
+ busHandler.pushExpectedEvents(NextEvent.BLOCK, NextEvent.INVOICE, NextEvent.INVOICE_PAYMENT, NextEvent.PAYMENT);
+ final BlockingState blockingState2 = new DefaultBlockingState(account.getId(), BlockingStateType.ACCOUNT, "state2", "Service", false, false, false, null);
+ subscriptionApi.addBlockingState(blockingState2, null, ImmutableList.<PluginProperty>of(), callContext);
+ assertListenerStatus();
+
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.INVOICE_PAYMENT, NextEvent.PAYMENT);
+ clock.addMonths(1);
+ assertListenerStatus();
+ }
+
private BaseEntitlementWithAddOnsSpecifier buildBaseEntitlementWithAddOnsSpecifier(final LocalDate entitlementMigrationDate, final LocalDate billingMigrationDate, final String externalKey, final List<EntitlementSpecifier> specifierList) {
return new BaseEntitlementWithAddOnsSpecifier() {
@Override
@@ -349,5 +515,4 @@ public class TestMigrationSubscriptions extends TestIntegrationBase {
}
};
}
-
}
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 86912e4..a9b8c5a 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
@@ -57,7 +57,9 @@ import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
+import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
import static org.testng.Assert.fail;
public class TestPaymentRefund extends TestIntegrationBase {
@@ -83,6 +85,15 @@ public class TestPaymentRefund extends TestIntegrationBase {
// Although we don't adjust the invoice, the invoicing system sends an event because invoice balance changes and overdue system-- in particular-- needs to know about it.
refundPaymentAndCheckForCompletion(account, payment, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
refundChecker.checkRefund(payment.getId(), callContext, new ExpectedRefundCheck(payment.getId(), false, new BigDecimal("233.82"), Currency.USD, initialCreationDate.toLocalDate()));
+
+
+ final Invoice invoiceRefreshed = invoiceUserApi.getInvoice(invoice.getId(), callContext);
+ assertTrue(invoiceRefreshed.getBalance().compareTo(new BigDecimal("233.82")) == 0);
+
+ final BigDecimal accountBalance = invoiceUserApi.getAccountBalance(account.getId(), callContext);
+ assertTrue(accountBalance.compareTo(new BigDecimal("233.82")) == 0);
+
+
}
@Test(groups = "slow")
@@ -106,7 +117,7 @@ public class TestPaymentRefund extends TestIntegrationBase {
final PluginProperty prop1 = new PluginProperty(InvoicePaymentControlPluginApi.PROP_IPCD_REFUND_WITH_ADJUSTMENTS, "true", false);
properties.add(prop1);
try {
- paymentApi.createRefundWithPaymentControl(account, payment.getId(), payment.getPurchasedAmount(), payment.getCurrency(), UUID.randomUUID().toString(),
+ paymentApi.createRefundWithPaymentControl(account, payment.getId(), payment.getPurchasedAmount(), payment.getCurrency(), null, UUID.randomUUID().toString(),
properties, PAYMENT_OPTIONS, callContext);
fail("Refund with invoice adjustment should now throw an Exception");
} catch (final PaymentApiException e) {
@@ -126,7 +137,7 @@ public class TestPaymentRefund extends TestIntegrationBase {
// 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(),
+ paymentApi.createRefund(account, payment.getId(), payment.getPurchasedAmount(), payment.getCurrency(), null,
UUID.randomUUID().toString(), PLUGIN_PROPERTIES, callContext);
assertListenerStatus();
}
@@ -152,9 +163,16 @@ public class TestPaymentRefund extends TestIntegrationBase {
// No end date for the trial item (fixed price of zero), and CTD should be today (i.e. when the trial started)
invoiceChecker.checkChargedThroughDate(bpEntitlement.getId(), clock.getUTCToday(), callContext);
- setDateAndCheckForCompletion(new DateTime(2012, 3, 2, 23, 59, 59, 0, testTimeZone), NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+ setDateAndCheckForCompletion(new DateTime(2012, 3, 2, 23, 59, 0, 0, testTimeZone), NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
invoice = invoiceChecker.checkInvoice(account.getId(), ++invoiceItemCount, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 3, 2),
new LocalDate(2012, 3, 31), InvoiceItemType.RECURRING, new BigDecimal("233.82")));
+
+ assertTrue(invoice.getChargedAmount().compareTo(new BigDecimal("233.82")) == 0);
+ assertTrue(invoice.getBalance().compareTo(BigDecimal.ZERO) == 0);
+
+ final BigDecimal accountBalance = invoiceUserApi.getAccountBalance(account.getId(), callContext);
+ assertTrue(accountBalance.compareTo(BigDecimal.ZERO) == 0);
+
payment = paymentChecker.checkPayment(account.getId(), 1, callContext, new ExpectedPaymentCheck(new LocalDate(2012, 3, 2), new BigDecimal("233.82"), TransactionStatus.SUCCESS, invoice.getId(), Currency.USD));
// Filter and extract UUId from all Recuring invoices
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestPaymentWithControl.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestPaymentWithControl.java
index f5b74e6..6b56100 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestPaymentWithControl.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestPaymentWithControl.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2014-2016 Groupon, Inc
- * Copyright 2014-2016 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
@@ -39,6 +39,7 @@ import org.killbill.billing.control.plugin.api.PriorPaymentControlResult;
import org.killbill.billing.osgi.api.OSGIServiceDescriptor;
import org.killbill.billing.osgi.api.OSGIServiceRegistration;
import org.killbill.billing.payment.api.Payment;
+import org.killbill.billing.payment.api.PaymentMethodPlugin;
import org.killbill.billing.payment.api.PaymentOptions;
import org.killbill.billing.payment.api.PluginProperty;
import org.killbill.billing.payment.api.TransactionType;
@@ -108,13 +109,61 @@ public class TestPaymentWithControl extends TestIntegrationBase {
}
@Test(groups = "slow")
+ public void testAuthSuccessWithPaymentControlNullPaymentMethodId() throws Exception {
+ final AccountData accountData = getAccountData(1);
+ final Account account = accountUserApi.createAccount(accountData, callContext);
+
+ // Add non-default payment method
+ final PaymentMethodPlugin info = createPaymentMethodPlugin();
+ final UUID paymentMethodId = paymentApi.addPaymentMethod(account, UUID.randomUUID().toString(), BeatrixIntegrationModule.NON_OSGI_PLUGIN_NAME, false, info, PLUGIN_PROPERTIES, callContext);
+
+ testPaymentControlWithControl.setAdjustedPaymentMethodId(paymentMethodId);
+
+ busHandler.pushExpectedEvents(NextEvent.PAYMENT);
+ final Payment payment = paymentApi.createAuthorizationWithPaymentControl(account, null, null, BigDecimal.ONE, account.getCurrency(), null, null, null,
+ properties, paymentOptions, callContext);
+ assertListenerStatus();
+
+ final Payment paymentWithAttempts = paymentApi.getPayment(payment.getId(), false, true, ImmutableList.<PluginProperty>of(), callContext);
+ Assert.assertEquals(paymentWithAttempts.getPaymentMethodId(), paymentMethodId);
+ Assert.assertEquals(paymentWithAttempts.getPaymentAttempts().size(), 1);
+ Assert.assertEquals(paymentWithAttempts.getPaymentAttempts().get(0).getPaymentMethodId(), paymentMethodId);
+ Assert.assertEquals(paymentWithAttempts.getPaymentAttempts().get(0).getStateName(), "SUCCESS");
+ }
+
+ @Test(groups = "slow")
+ public void testAuthFailureWithPaymentControlNullPaymentMethodId() throws Exception {
+ final AccountData accountData = getAccountData(1);
+ final Account account = accountUserApi.createAccount(accountData, callContext);
+
+ // Add non-default payment method
+ final PaymentMethodPlugin info = createPaymentMethodPlugin();
+ final UUID paymentMethodId = paymentApi.addPaymentMethod(account, UUID.randomUUID().toString(), BeatrixIntegrationModule.NON_OSGI_PLUGIN_NAME, false, info, PLUGIN_PROPERTIES, callContext);
+
+ testPaymentControlWithControl.setAdjustedPaymentMethodId(paymentMethodId);
+
+ paymentPlugin.makeNextPaymentFailWithError();
+
+ busHandler.pushExpectedEvents(NextEvent.PAYMENT_ERROR);
+ final Payment payment = paymentApi.createAuthorizationWithPaymentControl(account, null, null, BigDecimal.ONE, account.getCurrency(), null, null, null,
+ properties, paymentOptions, callContext);
+ assertListenerStatus();
+
+ final Payment paymentWithAttempts = paymentApi.getPayment(payment.getId(), false, true, ImmutableList.<PluginProperty>of(), callContext);
+ Assert.assertEquals(paymentWithAttempts.getPaymentMethodId(), paymentMethodId);
+ Assert.assertEquals(paymentWithAttempts.getPaymentAttempts().size(), 1);
+ Assert.assertEquals(paymentWithAttempts.getPaymentAttempts().get(0).getPaymentMethodId(), paymentMethodId);
+ Assert.assertEquals(paymentWithAttempts.getPaymentAttempts().get(0).getStateName(), "ABORTED");
+ }
+
+ @Test(groups = "slow")
public void testAuthCaptureWithPaymentControl() throws Exception {
final AccountData accountData = getAccountData(1);
final Account account = createAccountWithNonOsgiPaymentMethod(accountData);
busHandler.pushExpectedEvents(NextEvent.PAYMENT);
- final Payment payment = paymentApi.createAuthorizationWithPaymentControl(account, account.getPaymentMethodId(), null, BigDecimal.ONE, account.getCurrency(), null, null,
+ final Payment payment = paymentApi.createAuthorizationWithPaymentControl(account, account.getPaymentMethodId(), null, BigDecimal.ONE, account.getCurrency(), null, null, null,
properties, paymentOptions, callContext);
assertListenerStatus();
@@ -127,7 +176,7 @@ public class TestPaymentWithControl extends TestIntegrationBase {
Assert.assertEquals(paymentWithAttempts.getTransactions().get(0).getId().toString(), paymentWithAttempts.getPaymentAttempts().get(0).getTransactionExternalKey());
busHandler.pushExpectedEvents(NextEvent.PAYMENT);
- paymentApi.createCaptureWithPaymentControl(account, payment.getId(), BigDecimal.ONE, account.getCurrency(), null, properties, paymentOptions, callContext);
+ paymentApi.createCaptureWithPaymentControl(account, payment.getId(), BigDecimal.ONE, account.getCurrency(), null, null, properties, paymentOptions, callContext);
assertListenerStatus();
paymentWithAttempts = paymentApi.getPayment(payment.getId(), false, true, ImmutableList.<PluginProperty>of(), callContext);
@@ -149,7 +198,7 @@ public class TestPaymentWithControl extends TestIntegrationBase {
final String paymentTransactionExternalKey = "something-that-is-not-a-uuid-2";
busHandler.pushExpectedEvents(NextEvent.PAYMENT);
- final Payment payment = paymentApi.createAuthorizationWithPaymentControl(account, account.getPaymentMethodId(), null, BigDecimal.ONE, account.getCurrency(), paymentExternalKey, paymentTransactionExternalKey,
+ final Payment payment = paymentApi.createAuthorizationWithPaymentControl(account, account.getPaymentMethodId(), null, BigDecimal.ONE, account.getCurrency(), null, paymentExternalKey, paymentTransactionExternalKey,
properties, paymentOptions, callContext);
assertListenerStatus();
@@ -166,7 +215,7 @@ public class TestPaymentWithControl extends TestIntegrationBase {
final String paymentTransactionExternalKey2 = "something-that-is-not-a-uuid-3";
busHandler.pushExpectedEvents(NextEvent.PAYMENT);
- paymentApi.createVoidWithPaymentControl(account, payment.getId(), paymentTransactionExternalKey2, properties, paymentOptions, callContext);
+ paymentApi.createVoidWithPaymentControl(account, payment.getId(), null, paymentTransactionExternalKey2, properties, paymentOptions, callContext);
assertListenerStatus();
paymentWithAttempts = paymentApi.getPayment(payment.getId(), false, true, ImmutableList.<PluginProperty>of(), callContext);
@@ -184,6 +233,8 @@ public class TestPaymentWithControl extends TestIntegrationBase {
private final Map<String, Integer> calls;
+ private UUID adjustedPaymentMethodId = null;
+
public TestPaymentControlPluginApi() {
calls = new HashMap<String, Integer>();
}
@@ -194,6 +245,11 @@ public class TestPaymentWithControl extends TestIntegrationBase {
public void reset() {
calls.clear();
+ adjustedPaymentMethodId = null;
+ }
+
+ public void setAdjustedPaymentMethodId(final UUID adjustedPaymentMethodId) {
+ this.adjustedPaymentMethodId = adjustedPaymentMethodId;
}
@Override
@@ -213,8 +269,14 @@ public class TestPaymentWithControl extends TestIntegrationBase {
}
@Override
public UUID getAdjustedPaymentMethodId() {
+ return adjustedPaymentMethodId;
+ }
+
+ @Override
+ public String getAdjustedPluginName() {
return null;
}
+
@Override
public Iterable<PluginProperty> getAdjustedPluginProperties() {
return null;
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 606d7dc..e3d7799 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
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014 Groupon, Inc
- * Copyright 2014 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
@@ -24,13 +24,10 @@ import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicInteger;
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;
@@ -46,11 +43,6 @@ 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;
@@ -58,75 +50,37 @@ 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;
-import static org.awaitility.Awaitility.await;
import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.awaitility.Awaitility.await;
import static org.testng.Assert.assertNotNull;
public class TestPublicBus extends TestIntegrationBase {
- private PublicListener publicListener;
+ private static final ObjectMapper mapper = new ObjectMapper();
+ private PublicListener publicListener;
private AtomicInteger externalBusCount;
- private final ObjectMapper mapper = new ObjectMapper();
-
@Override
protected KillbillConfigSource getConfigSource() {
- ImmutableMap additionalProperties = new ImmutableMap.Builder()
- .put("org.killbill.billing.util.broadcast.rate", "500ms")
- .build();
+ final ImmutableMap<String, String> additionalProperties = new ImmutableMap.Builder<String, String>().put("org.killbill.billing.util.broadcast.rate", "500ms")
+ .build();
return getConfigSource("/beatrix.properties", additionalProperties);
}
-
- public class PublicListener {
-
- @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();
-
- }
- }
-
@Override
@BeforeMethod(groups = "slow")
public void beforeMethod() throws Exception {
-
/*
We copy the initialization instead of invoking the super method so we can add the registration
of the publicBus event;
TODO modify sequence to allow optional registration of publicListener
*/
- try {
- DBTestingHelper.get().getInstance().cleanupAllTables();
- } catch (final Exception ignored) {
- }
+ cleanupAllTables();
log.debug("RESET TEST FRAMEWORK");
@@ -149,9 +103,6 @@ public class TestPublicBus extends TestIntegrationBase {
paymentPlugin.clear();
this.externalBusCount = new AtomicInteger(0);
-
- // Make sure we start with a clean state
- assertListenerStatus();
}
@AfterMethod(groups = "slow")
@@ -162,7 +113,6 @@ public class TestPublicBus extends TestIntegrationBase {
@Test(groups = "slow")
public void testSimple() throws Exception {
-
final DateTime initialDate = new DateTime(2012, 2, 1, 0, 3, 42, 0, testTimeZone);
final int billingDay = 2;
@@ -189,7 +139,6 @@ public class TestPublicBus extends TestIntegrationBase {
addDaysAndCheckForCompletion(31, NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
-
await().atMost(10, SECONDS).until(new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
@@ -197,12 +146,10 @@ public class TestPublicBus extends TestIntegrationBase {
return externalBusCount.get() == 10;
}
});
-
}
@Test(groups = "slow")
public void testTenantKVChange() throws Exception {
-
final TenantData tenantData = new DefaultTenant(null, clock.getUTCNow(), clock.getUTCNow(), "MY_TENANT", "key", "s3Cr3T");
final CallContext contextWithNoTenant = new DefaultCallContext(null, null, "loulou", CallOrigin.EXTERNAL, UserType.ADMIN, "no reason", "hum", UUID.randomUUID(), clock);
final Tenant tenant = tenantUserApi.createTenant(tenantData, contextWithNoTenant);
@@ -219,4 +166,34 @@ public class TestPublicBus extends TestIntegrationBase {
}
});
}
+
+ public class PublicListener {
+
+ @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 (final JsonParseException e) {
+ Assert.fail("Could not deserialize metada section", e);
+ } catch (final JsonMappingException e) {
+ Assert.fail("Could not deserialize metada section", e);
+ } catch (final IOException e) {
+ Assert.fail("Could not deserialize metada section", e);
+ }
+ }
+
+ externalBusCount.incrementAndGet();
+
+ }
+ }
}
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestSubscription.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestSubscription.java
index 880c3a5..ff77fb8 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestSubscription.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestSubscription.java
@@ -30,7 +30,6 @@ import org.killbill.billing.api.TestApiListener.NextEvent;
import org.killbill.billing.beatrix.util.InvoiceChecker.ExpectedInvoiceItemCheck;
import org.killbill.billing.catalog.api.BillingActionPolicy;
import org.killbill.billing.catalog.api.BillingPeriod;
-import org.killbill.billing.catalog.api.PlanPhase;
import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
import org.killbill.billing.catalog.api.PriceListSet;
import org.killbill.billing.catalog.api.ProductCategory;
@@ -105,7 +104,7 @@ public class TestSubscription extends TestIntegrationBase {
TestDryRunArguments dryRun = new TestDryRunArguments(DryRunType.SUBSCRIPTION_ACTION, productName, ProductCategory.BASE, BillingPeriod.MONTHLY, null, null,
SubscriptionEventType.CHANGE, bpEntitlement.getId(), bpEntitlement.getBundleId(), null, BillingActionPolicy.IMMEDIATE);
Invoice dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), clock.getUTCToday(), dryRun, callContext);
- invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, callContext, toBeChecked);
+ invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, toBeChecked);
changeEntitlementAndCheckForCompletion(bpEntitlement, productName, BillingPeriod.MONTHLY, BillingActionPolicy.IMMEDIATE, NextEvent.CHANGE, NextEvent.INVOICE);
@@ -236,7 +235,7 @@ public class TestSubscription extends TestIntegrationBase {
NextEvent.INVOICE,
NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT
);
- final List<Entitlement> allEntitlements = entitlementApi.createBaseEntitlementsWithAddOns(account.getId(), entitlementWithAddOnsSpecifierList, ImmutableList.<PluginProperty>of(), callContext);
+ final List<Entitlement> allEntitlements = entitlementApi.createBaseEntitlementsWithAddOns(account.getId(), entitlementWithAddOnsSpecifierList, true, ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
checkNoMoreInvoiceToGenerate(account);
@@ -320,7 +319,7 @@ public class TestSubscription extends TestIntegrationBase {
NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT
);
- final List<Entitlement> entitlements = entitlementApi.createBaseEntitlementsWithAddOns(account.getId(), entitlementWithAddOnsSpecifierList, ImmutableList.<PluginProperty>of(), callContext);
+ final List<Entitlement> entitlements = entitlementApi.createBaseEntitlementsWithAddOns(account.getId(), entitlementWithAddOnsSpecifierList, true, ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
Assert.assertEquals(entitlements.size(), 5);
@@ -355,7 +354,7 @@ public class TestSubscription extends TestIntegrationBase {
final BaseEntitlementWithAddOnsSpecifier cartSpecifierA = new DefaultBaseEntitlementWithAddOnsSpecifier(null, externalKeyA, specifierListA, null, null, false);
entitlementWithAddOnsSpecifierList.add(cartSpecifierA);
- entitlementApi.createBaseEntitlementsWithAddOns(account.getId(), entitlementWithAddOnsSpecifierList, ImmutableList.<PluginProperty>of(), callContext);
+ entitlementApi.createBaseEntitlementsWithAddOns(account.getId(), entitlementWithAddOnsSpecifierList, true, ImmutableList.<PluginProperty>of(), callContext);
}
@Test(groups = "slow")
@@ -442,7 +441,7 @@ public class TestSubscription extends TestIntegrationBase {
final LocalDate futureDate = new LocalDate(2015, 10, 1);
// No CREATE event as this is set in the future
- final Entitlement createdEntitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, futureDate, futureDate, false, ImmutableList.<PluginProperty>of(), callContext);
+ final Entitlement createdEntitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, futureDate, futureDate, false, true, ImmutableList.<PluginProperty>of(), callContext);
assertEquals(createdEntitlement.getEffectiveStartDate().compareTo(futureDate), 0);
assertEquals(createdEntitlement.getEffectiveEndDate(), null);
assertListenerStatus();
@@ -474,7 +473,7 @@ public class TestSubscription extends TestIntegrationBase {
final LocalDate futureDate = new LocalDate(2015, 10, 1);
// No CREATE event as this is set in the future
- final Entitlement createdEntitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, futureDate, futureDate, false, ImmutableList.<PluginProperty>of(), callContext);
+ final Entitlement createdEntitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, futureDate, futureDate, false, true, ImmutableList.<PluginProperty>of(), callContext);
assertEquals(createdEntitlement.getState(), EntitlementState.PENDING);
assertEquals(createdEntitlement.getEffectiveStartDate().compareTo(futureDate), 0);
assertEquals(createdEntitlement.getEffectiveEndDate(), null);
@@ -518,7 +517,7 @@ public class TestSubscription extends TestIntegrationBase {
final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Shotgun", BillingPeriod.ANNUAL, PriceListSet.DEFAULT_PRICELIST_NAME, null);
busHandler.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
- final Entitlement createdEntitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, initialDate, initialDate, false, ImmutableList.<PluginProperty>of(), callContext);
+ final Entitlement createdEntitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, initialDate, initialDate, false, true, ImmutableList.<PluginProperty>of(), callContext);
assertEquals(createdEntitlement.getEffectiveStartDate().compareTo(initialDate), 0);
assertEquals(createdEntitlement.getEffectiveEndDate(), null);
assertListenerStatus();
@@ -547,7 +546,7 @@ public class TestSubscription extends TestIntegrationBase {
final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Shotgun", BillingPeriod.ANNUAL, PriceListSet.DEFAULT_PRICELIST_NAME, null);
busHandler.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
- final Entitlement createdEntitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, initialDate, initialDate, false, ImmutableList.<PluginProperty>of(), callContext);
+ final Entitlement createdEntitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, initialDate, initialDate, false, true, ImmutableList.<PluginProperty>of(), callContext);
assertEquals(createdEntitlement.getEffectiveStartDate().compareTo(initialDate), 0);
assertEquals(createdEntitlement.getEffectiveEndDate(), null);
assertListenerStatus();
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestTagApi.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestTagApi.java
index 0654926..11dbba0 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestTagApi.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestTagApi.java
@@ -35,6 +35,9 @@ import org.killbill.billing.util.tag.ControlTagType;
import org.killbill.billing.util.tag.Tag;
import org.killbill.billing.util.tag.TagDefinition;
+import com.google.common.collect.ImmutableSet;
+
+import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
public class TestTagApi extends TestIntegrationBase {
@@ -95,9 +98,14 @@ public class TestTagApi extends TestIntegrationBase {
// Create a new tag definition
//
busHandler.pushExpectedEvents(NextEvent.TAG_DEFINITION);
- final TagDefinition tagDefinition = tagUserApi.createTagDefinition("foo", "foo desc", callContext);
+ final TagDefinition tagDefinition = tagUserApi.createTagDefinition("foo", "foo desc", ImmutableSet.<ObjectType>of(ObjectType.ACCOUNT, ObjectType.INVOICE), callContext);
assertListenerStatus();
+ final TagDefinition tagDefinition2 = tagUserApi.getTagDefinition(tagDefinition.getId(), callContext);
+ assertEquals(tagDefinition2.getApplicableObjectTypes().size(), 2);
+ assertEquals(tagDefinition2.getApplicableObjectTypes().get(0), ObjectType.ACCOUNT);
+ assertEquals(tagDefinition2.getApplicableObjectTypes().get(1), ObjectType.INVOICE);
+
//
// Add 2 Tags on the invoice (1 invoice tag and 1 user tag)
//
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithBCDUpdate.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithBCDUpdate.java
index 8ba0800..49583e8 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithBCDUpdate.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithBCDUpdate.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2014-2016 Groupon, Inc
- * Copyright 2014-2016 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
@@ -28,9 +28,14 @@ import org.joda.time.LocalDate;
import org.killbill.billing.account.api.Account;
import org.killbill.billing.api.TestApiListener.NextEvent;
import org.killbill.billing.beatrix.util.InvoiceChecker.ExpectedInvoiceItemCheck;
+import org.killbill.billing.catalog.DefaultPlanPhasePriceOverride;
import org.killbill.billing.catalog.api.BillingActionPolicy;
import org.killbill.billing.catalog.api.BillingPeriod;
+import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
+import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
+import org.killbill.billing.catalog.api.PlanSpecifier;
import org.killbill.billing.catalog.api.ProductCategory;
+import org.killbill.billing.catalog.api.UsagePriceOverride;
import org.killbill.billing.entitlement.api.BlockingState;
import org.killbill.billing.entitlement.api.BlockingStateType;
import org.killbill.billing.entitlement.api.DefaultEntitlement;
@@ -538,4 +543,197 @@ public class TestWithBCDUpdate extends TestIntegrationBase {
invoiceChecker.checkInvoice(invoices.get(3).getId(), callContext, expectedInvoices);
expectedInvoices.clear();
}
+
+ @Test(groups = "slow")
+ public void testBCDChangeFromFreePlanToPayingPlanNoTrial() throws Exception {
+ final PlanPhaseSpecifier specNoTrial = new PlanPhaseSpecifier("Blowdart", BillingPeriod.MONTHLY, "notrial", null);
+ testBCDChangeFromFreePlanToPayingPlan(specNoTrial);
+ }
+
+ @Test(groups = "slow")
+ public void testBCDChangeFromFreePlanToPayingPlanWithTrial() throws Exception {
+ // Change to the paying plan (alignment is START_OF_SUBSCRIPTION, but because we are already in an EVERGREEN phase, we will re-align with the EVERGREEN phase of the new 3-phases paying plan)
+ final PlanPhaseSpecifier specWithTrial = new PlanPhaseSpecifier("Blowdart", BillingPeriod.MONTHLY, "DEFAULT", null);
+ testBCDChangeFromFreePlanToPayingPlan(specWithTrial);
+ }
+
+ @Test(groups = "slow")
+ public void testBCDChangeFromFreePlanToPayingPlanWithTrialAndCHANGE_OF_PLANPolicy30DaysMonth() throws Exception {
+ final DateTime initialDate = new DateTime(2016, 4, 1, 0, 13, 42, 0, testTimeZone);
+ clock.setTime(initialDate);
+
+ final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(0));
+ assertNotNull(account);
+
+ final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Blowdart", BillingPeriod.MONTHLY, "notrial", null);
+
+ // Price override of $0
+ final List<PlanPhasePriceOverride> overrides = new ArrayList<PlanPhasePriceOverride>();
+ overrides.add(new DefaultPlanPhasePriceOverride("blowdart-monthly-notrial-evergreen", account.getCurrency(), null, BigDecimal.ZERO, ImmutableList.<UsagePriceOverride>of()));
+ busHandler.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
+ // BP creation : Will set Account BCD to the first (DateOfFirstRecurringNonZeroCharge is the subscription start date in this case)
+ final Entitlement baseEntitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, "bundleExternalKey", overrides, null, null, false, true, ImmutableList.<PluginProperty>of(), callContext);
+ assertListenerStatus();
+
+ invoiceChecker.checkInvoice(account.getId(), 1, callContext,
+ new ExpectedInvoiceItemCheck(new LocalDate(2016, 4, 1), new LocalDate(2016, 5, 1), InvoiceItemType.RECURRING, BigDecimal.ZERO));
+
+ // 2016-4-15
+ clock.addDays(14);
+
+ // Set next BCD to be the 15
+ busHandler.pushExpectedEvents(NextEvent.BCD_CHANGE, NextEvent.INVOICE);
+ subscriptionBaseInternalApi.updateBCD(baseEntitlement.getId(), 15, null, internalCallContext);
+ assertListenerStatus();
+
+ // Re-alignment invoice
+ invoiceChecker.checkInvoice(account.getId(), 2, callContext,
+ new ExpectedInvoiceItemCheck(new LocalDate(2016, 4, 1), new LocalDate(2016, 4, 15), InvoiceItemType.RECURRING, BigDecimal.ZERO),
+ new ExpectedInvoiceItemCheck(new LocalDate(2016, 4, 15), new LocalDate(2016, 5, 15), InvoiceItemType.RECURRING, BigDecimal.ZERO));
+
+ // Change to the paying plan (alignment is CHANGE_OF_PLAN: we end up in TRIAL)
+ final PlanPhaseSpecifier specWithTrial = new PlanPhaseSpecifier("Blowdart", BillingPeriod.MONTHLY, "trial", null);
+ busHandler.pushExpectedEvents(NextEvent.CHANGE, NextEvent.INVOICE);
+ baseEntitlement.changePlanOverrideBillingPolicy(specWithTrial, ImmutableList.<PlanPhasePriceOverride>of(), clock.getUTCToday(), BillingActionPolicy.IMMEDIATE, ImmutableList.<PluginProperty>of(), callContext);
+ assertListenerStatus();
+
+ // Trial invoice
+ invoiceChecker.checkInvoice(account.getId(), 3, callContext,
+ new ExpectedInvoiceItemCheck(new LocalDate(2016, 4, 15), null, InvoiceItemType.FIXED, BigDecimal.ZERO));
+
+ // Verify next month (extra null invoice because of the original notification set on the 1st)
+ busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.NULL_INVOICE, NextEvent.NULL_INVOICE, NextEvent.INVOICE, NextEvent.INVOICE_PAYMENT, NextEvent.PAYMENT);
+ clock.addMonths(1);
+ assertListenerStatus();
+
+ // First paying invoice
+ invoiceChecker.checkInvoice(account.getId(), 4, callContext,
+ new ExpectedInvoiceItemCheck(new LocalDate(2016, 5, 15), new LocalDate(2016, 6, 15), InvoiceItemType.RECURRING, new BigDecimal("29.95")));
+
+ // Verify next month
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.INVOICE_PAYMENT, NextEvent.PAYMENT);
+ clock.addMonths(1);
+ assertListenerStatus();
+
+ invoiceChecker.checkInvoice(account.getId(), 5, callContext,
+ new ExpectedInvoiceItemCheck(new LocalDate(2016, 6, 15), new LocalDate(2016, 7, 15), InvoiceItemType.RECURRING, new BigDecimal("29.95")));
+ }
+
+ @Test(groups = "slow")
+ public void testBCDChangeFromFreePlanToPayingPlanWithTrialAndCHANGE_OF_PLANPolicy31DaysMonth() throws Exception {
+ final DateTime initialDate = new DateTime(2016, 5, 1, 0, 13, 42, 0, testTimeZone);
+ clock.setTime(initialDate);
+
+ final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(0));
+ assertNotNull(account);
+
+ final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Blowdart", BillingPeriod.MONTHLY, "notrial", null);
+
+ // Price override of $0
+ final List<PlanPhasePriceOverride> overrides = new ArrayList<PlanPhasePriceOverride>();
+ overrides.add(new DefaultPlanPhasePriceOverride("blowdart-monthly-notrial-evergreen", account.getCurrency(), null, BigDecimal.ZERO, ImmutableList.<UsagePriceOverride>of()));
+ busHandler.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
+ // BP creation : Will set Account BCD to the first (DateOfFirstRecurringNonZeroCharge is the subscription start date in this case)
+ final Entitlement baseEntitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, "bundleExternalKey", overrides, null, null, false, true, ImmutableList.<PluginProperty>of(), callContext);
+ assertListenerStatus();
+
+ invoiceChecker.checkInvoice(account.getId(), 1, callContext,
+ new ExpectedInvoiceItemCheck(new LocalDate(2016, 5, 1), new LocalDate(2016, 6, 1), InvoiceItemType.RECURRING, BigDecimal.ZERO));
+
+ // 2016-5-15
+ clock.addDays(14);
+
+ // Set next BCD to be the 14
+ subscriptionBaseInternalApi.updateBCD(baseEntitlement.getId(), 14, null, internalCallContext);
+ // No bus event, no invoice expected
+ assertListenerStatus();
+
+ // Change to the paying plan (alignment is CHANGE_OF_PLAN: we end up in TRIAL)
+ // Extra NULL_INVOICE event because invoice computes a future notification effective right away
+ final PlanPhaseSpecifier specWithTrial = new PlanPhaseSpecifier("Blowdart", BillingPeriod.MONTHLY, "trial", null);
+ busHandler.pushExpectedEvents(NextEvent.CHANGE, NextEvent.NULL_INVOICE, NextEvent.INVOICE);
+ baseEntitlement.changePlanOverrideBillingPolicy(specWithTrial, ImmutableList.<PlanPhasePriceOverride>of(), clock.getUTCToday(), BillingActionPolicy.IMMEDIATE, ImmutableList.<PluginProperty>of(), callContext);
+ assertListenerStatus();
+
+ // Trial invoice (with re-alignment invoice item from free plan)
+ invoiceChecker.checkInvoice(account.getId(), 2, callContext,
+ new ExpectedInvoiceItemCheck(new LocalDate(2016, 5, 1), new LocalDate(2016, 5, 15), InvoiceItemType.RECURRING, BigDecimal.ZERO),
+ new ExpectedInvoiceItemCheck(new LocalDate(2016, 5, 15), null, InvoiceItemType.FIXED, BigDecimal.ZERO));
+
+ // Verify next month (extra null invoice because of the original notification set on the 1st)
+ busHandler.pushExpectedEvents(NextEvent.BCD_CHANGE, NextEvent.PHASE, NextEvent.NULL_INVOICE, NextEvent.NULL_INVOICE, NextEvent.INVOICE, NextEvent.INVOICE_PAYMENT, NextEvent.PAYMENT);
+ clock.addMonths(1);
+ assertListenerStatus();
+
+ // First paying invoice
+ invoiceChecker.checkInvoice(account.getId(), 3, callContext,
+ new ExpectedInvoiceItemCheck(new LocalDate(2016, 6, 14), new LocalDate(2016, 7, 14), InvoiceItemType.RECURRING, new BigDecimal("29.95")));
+
+ // Verify next month
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.INVOICE_PAYMENT, NextEvent.PAYMENT);
+ clock.addMonths(1);
+ assertListenerStatus();
+
+ invoiceChecker.checkInvoice(account.getId(), 4, callContext,
+ new ExpectedInvoiceItemCheck(new LocalDate(2016, 7, 14), new LocalDate(2016, 8, 14), InvoiceItemType.RECURRING, new BigDecimal("29.95")));
+ }
+
+ private void testBCDChangeFromFreePlanToPayingPlan(final PlanPhaseSpecifier toSpec) throws Exception {
+ final DateTime initialDate = new DateTime(2016, 4, 1, 0, 13, 42, 0, testTimeZone);
+ clock.setTime(initialDate);
+
+ final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(0));
+ assertNotNull(account);
+
+ final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Blowdart", BillingPeriod.MONTHLY, "notrial", null);
+
+ // Price override of $0
+ final List<PlanPhasePriceOverride> overrides = new ArrayList<PlanPhasePriceOverride>();
+ overrides.add(new DefaultPlanPhasePriceOverride("blowdart-monthly-notrial-evergreen", account.getCurrency(), null, BigDecimal.ZERO, ImmutableList.<UsagePriceOverride>of()));
+ busHandler.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
+ // BP creation : Will set Account BCD to the first (DateOfFirstRecurringNonZeroCharge is the subscription start date in this case)
+ final Entitlement baseEntitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, "bundleExternalKey", overrides, null, null, false, true, ImmutableList.<PluginProperty>of(), callContext);
+ assertListenerStatus();
+
+ invoiceChecker.checkInvoice(account.getId(), 1, callContext,
+ new ExpectedInvoiceItemCheck(new LocalDate(2016, 4, 1), new LocalDate(2016, 5, 1), InvoiceItemType.RECURRING, BigDecimal.ZERO));
+
+ // 2016-4-15
+ clock.addDays(14);
+
+ // Set next BCD to be the 15
+ busHandler.pushExpectedEvents(NextEvent.BCD_CHANGE, NextEvent.INVOICE);
+ subscriptionBaseInternalApi.updateBCD(baseEntitlement.getId(), 15, null, internalCallContext);
+ assertListenerStatus();
+
+ // Re-alignment invoice
+ invoiceChecker.checkInvoice(account.getId(), 2, callContext,
+ new ExpectedInvoiceItemCheck(new LocalDate(2016, 4, 1), new LocalDate(2016, 4, 15), InvoiceItemType.RECURRING, BigDecimal.ZERO),
+ new ExpectedInvoiceItemCheck(new LocalDate(2016, 4, 15), new LocalDate(2016, 5, 15), InvoiceItemType.RECURRING, BigDecimal.ZERO));
+
+ // Change to the paying plan
+ busHandler.pushExpectedEvents(NextEvent.CHANGE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+ baseEntitlement.changePlanOverrideBillingPolicy(toSpec, ImmutableList.<PlanPhasePriceOverride>of(), clock.getUTCToday(), BillingActionPolicy.IMMEDIATE, ImmutableList.<PluginProperty>of(), callContext);
+ assertListenerStatus();
+
+ // First paying invoice
+ invoiceChecker.checkInvoice(account.getId(), 3, callContext,
+ new ExpectedInvoiceItemCheck(new LocalDate(2016, 4, 15), new LocalDate(2016, 5, 15), InvoiceItemType.RECURRING, new BigDecimal("29.95")));
+
+ // Verify next month (null invoice because of the original notification set on the 1st)
+ busHandler.pushExpectedEvents(NextEvent.NULL_INVOICE, NextEvent.INVOICE, NextEvent.INVOICE_PAYMENT, NextEvent.PAYMENT);
+ clock.addMonths(1);
+ assertListenerStatus();
+
+ invoiceChecker.checkInvoice(account.getId(), 4, callContext,
+ new ExpectedInvoiceItemCheck(new LocalDate(2016, 5, 15), new LocalDate(2016, 6, 15), InvoiceItemType.RECURRING, new BigDecimal("29.95")));
+
+ // Verify next month
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.INVOICE_PAYMENT, NextEvent.PAYMENT);
+ clock.addMonths(1);
+ assertListenerStatus();
+
+ invoiceChecker.checkInvoice(account.getId(), 5, callContext,
+ new ExpectedInvoiceItemCheck(new LocalDate(2016, 6, 15), new LocalDate(2016, 7, 15), InvoiceItemType.RECURRING, new BigDecimal("29.95")));
+ }
}
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithCatalogPlugin.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithCatalogPlugin.java
index a67a191..134819d 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithCatalogPlugin.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithCatalogPlugin.java
@@ -207,7 +207,7 @@ public class TestWithCatalogPlugin extends TestIntegrationBase {
public VersionedPluginCatalog getVersionedPluginCatalog(final Iterable<PluginProperty> properties, final TenantContext tenantContext) {
nbVersionedPluginCatalogApiCalls++;
Assert.assertNotNull(versionedCatalog, "test did not initialize plugin catalog");
- return new TestModelVersionedPluginCatalog(versionedCatalog.getCatalogName(), versionedCatalog.getRecurringBillingMode(), toStandalonePluginCatalogs(versionedCatalog.getVersions()));
+ return new TestModelVersionedPluginCatalog(versionedCatalog.getCatalogName(), toStandalonePluginCatalogs(versionedCatalog.getVersions()));
}
public void addCatalogVersion(final String catalogResource) throws Exception {
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithFakeKPMPlugin.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithFakeKPMPlugin.java
index b239488..60e3176 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithFakeKPMPlugin.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithFakeKPMPlugin.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
@@ -21,7 +21,6 @@ import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
-import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
@@ -38,12 +37,10 @@ import org.killbill.billing.notification.plugin.api.ExtBusEventType;
import org.killbill.billing.osgi.BundleRegistry;
import org.killbill.billing.osgi.BundleWithConfig;
import org.killbill.billing.osgi.FileInstall;
-import org.killbill.billing.osgi.PureOSGIBundleFinder;
import org.killbill.billing.osgi.api.PluginInfo;
import org.killbill.billing.osgi.api.PluginStateChange;
import org.killbill.billing.osgi.api.PluginsInfoApi;
import org.killbill.billing.osgi.api.config.PluginConfig;
-import org.killbill.billing.osgi.api.config.PluginConfigServiceApi;
import org.killbill.billing.osgi.api.config.PluginLanguage;
import org.killbill.billing.osgi.api.config.PluginType;
import org.killbill.billing.osgi.config.OSGIConfig;
@@ -51,7 +48,6 @@ import org.killbill.billing.osgi.pluginconf.PluginConfigException;
import org.killbill.billing.osgi.pluginconf.PluginFinder;
import org.killbill.billing.platform.api.KillbillConfigSource;
import org.killbill.billing.util.jackson.ObjectMapper;
-import org.killbill.billing.util.nodes.KillbillNodesApi;
import org.killbill.billing.util.nodes.NodeCommand;
import org.killbill.billing.util.nodes.NodeCommandMetadata;
import org.killbill.billing.util.nodes.NodeCommandProperty;
@@ -61,8 +57,10 @@ import org.killbill.billing.util.nodes.PluginNodeCommandMetadata;
import org.killbill.billing.util.nodes.SystemNodeCommandType;
import org.mockito.Mockito;
import org.osgi.framework.Bundle;
+import org.osgi.framework.launch.Framework;
import org.testng.Assert;
import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import com.fasterxml.jackson.databind.SerializationFeature;
@@ -79,8 +77,10 @@ import com.google.inject.Module;
import com.google.inject.Stage;
import com.google.inject.util.Modules;
-import static org.awaitility.Awaitility.await;
import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.awaitility.Awaitility.await;
+import static org.mockito.Mockito.RETURNS_MOCKS;
+import static org.mockito.Mockito.withSettings;
public class TestWithFakeKPMPlugin extends TestIntegrationBase {
@@ -226,13 +226,16 @@ public class TestWithFakeKPMPlugin extends TestIntegrationBase {
@Inject
public FakeBundleRegistry() {
- super(null);
+ super(Mockito.mock(FileInstall.class, withSettings().defaultAnswer(RETURNS_MOCKS)));
bundles = new ArrayList<BundleWithMetadata>();
}
- public void installNewBundle(final String pluginName, @Nullable final String version, final PluginLanguage pluginLanguage) {
+ @Override
+ public void installBundles(final Framework framework) {
+ super.installBundles(framework);
+
final Bundle bundle = Mockito.mock(Bundle.class);
- Mockito.when(bundle.getSymbolicName()).thenReturn(pluginName);
+ Mockito.when(bundle.getSymbolicName()).thenReturn(NEW_PLUGIN_NAME);
final BundleWithConfig config = new BundleWithConfig(bundle, new PluginConfig() {
@Override
@@ -247,7 +250,7 @@ public class TestWithFakeKPMPlugin extends TestIntegrationBase {
@Override
public String getPluginName() {
- return pluginName;
+ return NEW_PLUGIN_NAME;
}
@Override
@@ -257,7 +260,7 @@ public class TestWithFakeKPMPlugin extends TestIntegrationBase {
@Override
public String getVersion() {
- return version;
+ return NEW_PLUGIN_VERSION;
}
@Override
@@ -272,7 +275,7 @@ public class TestWithFakeKPMPlugin extends TestIntegrationBase {
@Override
public PluginLanguage getPluginLanguage() {
- return pluginLanguage;
+ return PluginLanguage.JAVA;
}
@Override
@@ -296,10 +299,6 @@ public class TestWithFakeKPMPlugin extends TestIntegrationBase {
}
}).orNull();
}
-
- public Collection<BundleWithMetadata> getBundles() {
- return bundles;
- }
}
public static class OverrideModuleForOSGI implements Module {
@@ -317,16 +316,12 @@ public class TestWithFakeKPMPlugin extends TestIntegrationBase {
g.injectMembers(this);
}
- @BeforeClass(groups = "slow")
+ @BeforeMethod(groups = "slow")
public void beforeMethod() throws Exception {
-
- try {
- DBTestingHelper.get().getInstance().cleanupAllTables();
- } catch (final Exception ignored) {
- }
-
log.debug("RESET TEST FRAMEWORK");
+ cleanupAllTables();
+
clock.resetDeltaFromReality();
busHandler.reset();
@@ -335,9 +330,6 @@ public class TestWithFakeKPMPlugin extends TestIntegrationBase {
externalBus.register(new FakeKPMPlugin());
lifecycle.fireStartupSequencePostEventRegistration();
-
- // Make sure we start with a clean state
- assertListenerStatus();
}
@Test(groups = "slow")
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithInvoicePlugin.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithInvoicePlugin.java
new file mode 100644
index 0000000..03dcc30
--- /dev/null
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithInvoicePlugin.java
@@ -0,0 +1,266 @@
+/*
+ * 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;
+
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.TimeUnit;
+
+import javax.inject.Inject;
+
+import org.awaitility.Awaitility;
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.joda.time.LocalDate;
+import org.killbill.billing.account.api.Account;
+import org.killbill.billing.account.api.AccountData;
+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.invoice.api.DefaultInvoiceService;
+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.model.TaxInvoiceItem;
+import org.killbill.billing.invoice.notification.DefaultNextBillingDateNotifier;
+import org.killbill.billing.invoice.plugin.api.InvoicePluginApi;
+import org.killbill.billing.invoice.plugin.api.InvoicePluginApiRetryException;
+import org.killbill.billing.osgi.api.OSGIServiceDescriptor;
+import org.killbill.billing.osgi.api.OSGIServiceRegistration;
+import org.killbill.billing.payment.api.PluginProperty;
+import org.killbill.billing.util.callcontext.CallContext;
+import org.killbill.notificationq.api.NotificationEventWithMetadata;
+import org.killbill.notificationq.api.NotificationQueue;
+import org.killbill.notificationq.api.NotificationQueueService;
+import org.killbill.notificationq.api.NotificationQueueService.NoSuchNotificationQueue;
+import org.killbill.queue.retry.RetryNotificationEvent;
+import org.killbill.queue.retry.RetryableService;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+
+import static org.testng.Assert.assertEquals;
+
+public class TestWithInvoicePlugin extends TestIntegrationBase {
+
+ @Inject
+ private OSGIServiceRegistration<InvoicePluginApi> pluginRegistry;
+
+ @Inject
+ private NotificationQueueService notificationQueueService;
+
+ private TestInvoicePluginApi testInvoicePluginApi;
+
+ @BeforeClass(groups = "slow")
+ public void beforeClass() throws Exception {
+ super.beforeClass();
+
+ this.testInvoicePluginApi = new TestInvoicePluginApi();
+ pluginRegistry.registerService(new OSGIServiceDescriptor() {
+ @Override
+ public String getPluginSymbolicName() {
+ return "TestInvoicePluginApi";
+ }
+
+ @Override
+ public String getPluginName() {
+ return "TestInvoicePluginApi";
+ }
+
+ @Override
+ public String getRegistrationName() {
+ return "TestInvoicePluginApi";
+ }
+ }, testInvoicePluginApi);
+ }
+
+ @Test(groups = "slow")
+ public void testWithRetries() throws Exception {
+ // We take april as it has 30 days (easier to play with BCD)
+ // Set clock to the initial start date - we implicitly assume here that the account timezone is UTC
+ clock.setDay(new LocalDate(2012, 4, 1));
+
+ final AccountData accountData = getAccountData(1);
+ final Account account = createAccountWithNonOsgiPaymentMethod(accountData);
+ accountChecker.checkAccount(account.getId(), accountData, callContext);
+
+ // Make invoice plugin fail
+ testInvoicePluginApi.shouldThrowException = true;
+
+ // Create original subscription (Trial PHASE)
+ final DefaultEntitlement bpSubscription = createBaseEntitlementAndCheckForCompletion(account.getId(), "bundleKey", "Pistol", ProductCategory.BASE, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.BLOCK);
+ subscriptionChecker.checkSubscriptionCreated(bpSubscription.getId(), internalCallContext);
+ // Invoice failed to generate
+ assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext).size(), 0);
+
+ // Verify bus event has moved to the retry service (can't easily check the timestamp unfortunately)
+ // No future notification at this point (FIXED item, the PHASE event is the trigger for the next one)
+ checkRetryBusEvents(1, 0);
+
+ // Add 5'
+ clock.addDeltaFromReality(5 * 60 * 1000);
+ checkRetryBusEvents(2, 0);
+
+ // Fix invoice plugin
+ testInvoicePluginApi.shouldThrowException = false;
+
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.INVOICE_PAYMENT, NextEvent.PAYMENT);
+ clock.addDeltaFromReality(10 * 60 * 1000);
+ assertListenerStatus();
+ // No notification in the main queue at this point (the PHASE event is the trigger for the next one)
+ checkNotificationsNoRetry(0);
+
+ assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext).size(), 1);
+ invoiceChecker.checkInvoice(account.getId(),
+ 1,
+ callContext,
+ new ExpectedInvoiceItemCheck(new LocalDate(2012, 4, 1), null, InvoiceItemType.FIXED, BigDecimal.ZERO),
+ new ExpectedInvoiceItemCheck(new LocalDate(2012, 4, 1), null, InvoiceItemType.TAX, new BigDecimal("1.0")));
+
+ busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.INVOICE_PAYMENT, NextEvent.PAYMENT);
+ clock.setDay(new LocalDate("2012-05-01"));
+ assertListenerStatus();
+ checkNotificationsNoRetry(1);
+
+ assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext).size(), 2);
+ invoiceChecker.checkInvoice(account.getId(),
+ 2,
+ callContext,
+ new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2012, 6, 1), InvoiceItemType.RECURRING, new BigDecimal("29.95")),
+ new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), null, InvoiceItemType.TAX, new BigDecimal("1.0")));
+
+ // Make invoice plugin fail again
+ testInvoicePluginApi.shouldThrowException = true;
+
+ clock.addMonths(1);
+ assertListenerStatus();
+
+ // Invoice failed to generate
+ assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext).size(), 2);
+
+ // Verify notification has moved to the retry service
+ checkRetryNotifications("2012-06-01T00:05:00", 1);
+
+ // Add 5'
+ clock.addDeltaFromReality(5 * 60 * 1000);
+ // Verify there are no notification duplicates
+ checkRetryNotifications("2012-06-01T00:15:00", 1);
+
+ // Fix invoice plugin
+ testInvoicePluginApi.shouldThrowException = false;
+
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.INVOICE_PAYMENT, NextEvent.PAYMENT);
+ clock.addDeltaFromReality(10 * 60 * 1000);
+ assertListenerStatus();
+ checkNotificationsNoRetry(1);
+
+ // Invoice was generated
+ assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext).size(), 3);
+ invoiceChecker.checkInvoice(account.getId(),
+ 3,
+ callContext,
+ new ExpectedInvoiceItemCheck(new LocalDate(2012, 6, 1), new LocalDate(2012, 7, 1), InvoiceItemType.RECURRING, new BigDecimal("29.95")),
+ new ExpectedInvoiceItemCheck(new LocalDate(2012, 6, 1), null, InvoiceItemType.TAX, new BigDecimal("1.0")));
+
+ // Make invoice plugin fail again
+ testInvoicePluginApi.shouldThrowException = true;
+
+ clock.setTime(new DateTime("2012-07-01T00:00:00"));
+ assertListenerStatus();
+
+ // Invoice failed to generate
+ assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext).size(), 3);
+
+ // Verify notification has moved to the retry service
+ checkRetryNotifications("2012-07-01T00:05:00", 1);
+
+ testInvoicePluginApi.shouldThrowException = false;
+
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+ clock.addDeltaFromReality(5 * 60 * 1000);
+ assertListenerStatus();
+ checkNotificationsNoRetry(1);
+
+ assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext).size(), 4);
+ }
+
+ private void checkRetryBusEvents(final int retryNb, final int expectedFutureInvoiceNotifications) throws NoSuchNotificationQueue {
+ // Verify notification(s) moved to the retry queue
+ Awaitility.await().atMost(5, TimeUnit.SECONDS).until(new Callable<Boolean>() {
+ @Override
+ public Boolean call() throws Exception {
+ final List<NotificationEventWithMetadata> futureInvoiceRetryableBusEvents = getFutureInvoiceRetryableBusEvents();
+ return futureInvoiceRetryableBusEvents.size() == 1 && ((RetryNotificationEvent) futureInvoiceRetryableBusEvents.get(0).getEvent()).getRetryNb() == retryNb;
+ }
+ });
+ assertEquals(getFutureInvoiceNotifications().size(), expectedFutureInvoiceNotifications);
+ }
+
+ private void checkRetryNotifications(final String retryDateTime, final int expectedFutureInvoiceNotifications) throws NoSuchNotificationQueue {
+ // Verify notification(s) moved to the retry queue
+ Awaitility.await().atMost(5, TimeUnit.SECONDS).until(new Callable<Boolean>() {
+ @Override
+ public Boolean call() throws Exception {
+ final List<NotificationEventWithMetadata> futureInvoiceRetryableNotifications = getFutureInvoiceRetryableNotifications();
+ return futureInvoiceRetryableNotifications.size() == 1 && futureInvoiceRetryableNotifications.get(0).getEffectiveDate().compareTo(new DateTime(retryDateTime, DateTimeZone.UTC)) == 0;
+ }
+ });
+ assertEquals(getFutureInvoiceNotifications().size(), expectedFutureInvoiceNotifications);
+ }
+
+ private void checkNotificationsNoRetry(final int main) throws NoSuchNotificationQueue {
+ assertEquals(getFutureInvoiceRetryableNotifications().size(), 0);
+ assertEquals(getFutureInvoiceNotifications().size(), main);
+ }
+
+ private List<NotificationEventWithMetadata> getFutureInvoiceNotifications() throws NoSuchNotificationQueue {
+ final NotificationQueue notificationQueue = notificationQueueService.getNotificationQueue(DefaultInvoiceService.INVOICE_SERVICE_NAME, DefaultNextBillingDateNotifier.NEXT_BILLING_DATE_NOTIFIER_QUEUE);
+ return ImmutableList.<NotificationEventWithMetadata>copyOf(notificationQueue.getFutureNotificationForSearchKeys(internalCallContext.getAccountRecordId(), internalCallContext.getTenantRecordId()));
+ }
+
+ private List<NotificationEventWithMetadata> getFutureInvoiceRetryableNotifications() throws NoSuchNotificationQueue {
+ final NotificationQueue notificationQueue = notificationQueueService.getNotificationQueue(RetryableService.RETRYABLE_SERVICE_NAME, DefaultNextBillingDateNotifier.NEXT_BILLING_DATE_NOTIFIER_QUEUE);
+ return ImmutableList.<NotificationEventWithMetadata>copyOf(notificationQueue.getFutureNotificationForSearchKeys(internalCallContext.getAccountRecordId(), internalCallContext.getTenantRecordId()));
+ }
+
+ private List<NotificationEventWithMetadata> getFutureInvoiceRetryableBusEvents() throws NoSuchNotificationQueue {
+ final NotificationQueue notificationQueue = notificationQueueService.getNotificationQueue(RetryableService.RETRYABLE_SERVICE_NAME, "invoice-listener");
+ return ImmutableList.<NotificationEventWithMetadata>copyOf(notificationQueue.getFutureNotificationForSearchKeys(internalCallContext.getAccountRecordId(), internalCallContext.getTenantRecordId()));
+ }
+
+ public class TestInvoicePluginApi implements InvoicePluginApi {
+
+ boolean shouldThrowException = false;
+
+ @Override
+ public List<InvoiceItem> getAdditionalInvoiceItems(final Invoice invoice, final boolean isDryRun, final Iterable<PluginProperty> pluginProperties, final CallContext callContext) {
+ if (shouldThrowException) {
+ throw new InvoicePluginApiRetryException();
+ }
+ return ImmutableList.<InvoiceItem>of(createTaxInvoiceItem(invoice));
+ }
+
+ private InvoiceItem createTaxInvoiceItem(final Invoice invoice) {
+ return new TaxInvoiceItem(invoice.getId(), invoice.getAccountId(), null, "Tax Item", clock.getUTCNow().toLocalDate(), BigDecimal.ONE, invoice.getCurrency());
+ }
+ }
+}
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithPriceOverride.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithPriceOverride.java
index bc7a72f..0091248 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithPriceOverride.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithPriceOverride.java
@@ -26,20 +26,17 @@ import org.killbill.billing.account.api.Account;
import org.killbill.billing.account.api.AccountData;
import org.killbill.billing.api.TestApiListener.NextEvent;
import org.killbill.billing.beatrix.util.InvoiceChecker.ExpectedInvoiceItemCheck;
-import org.killbill.billing.beatrix.util.PaymentChecker.ExpectedPaymentCheck;
import org.killbill.billing.catalog.DefaultPlanPhasePriceOverride;
import org.killbill.billing.catalog.api.BillingActionPolicy;
import org.killbill.billing.catalog.api.BillingPeriod;
-import org.killbill.billing.catalog.api.Currency;
import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
+import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
import org.killbill.billing.catalog.api.PlanSpecifier;
import org.killbill.billing.catalog.api.PriceListSet;
import org.killbill.billing.catalog.api.ProductCategory;
import org.killbill.billing.entitlement.api.DefaultEntitlement;
-import org.killbill.billing.invoice.api.Invoice;
import org.killbill.billing.invoice.api.InvoiceItemType;
import org.killbill.billing.payment.api.PluginProperty;
-import org.killbill.billing.payment.api.TransactionStatus;
import org.testng.annotations.Test;
import com.google.common.collect.ImmutableList;
@@ -121,7 +118,7 @@ public class TestWithPriceOverride extends TestIntegrationBase {
overrides.add(new DefaultPlanPhasePriceOverride("shotgun-monthly-evergreen", account.getCurrency(), null, new BigDecimal("279.95"), null));
busHandler.pushExpectedEvents(NextEvent.CHANGE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
- bpSubscription.changePlanOverrideBillingPolicy(new PlanSpecifier("Shotgun", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME), overrides, null, BillingActionPolicy.IMMEDIATE, ImmutableList.<PluginProperty>of(), callContext);
+ bpSubscription.changePlanOverrideBillingPolicy(new PlanPhaseSpecifier("Shotgun", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME), overrides, null, BillingActionPolicy.IMMEDIATE, ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
invoiceChecker.checkInvoice(account.getId(), 3, callContext,
@@ -147,7 +144,7 @@ public class TestWithPriceOverride extends TestIntegrationBase {
final Account account = createAccountWithNonOsgiPaymentMethod(accountData);
accountChecker.checkAccount(account.getId(), accountData, callContext);
- final DefaultEntitlement bpSubscription = createBaseEntitlementAndCheckForCompletion(account.getId(), "bundleKey", "Shotgun", ProductCategory.BASE, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
+ final DefaultEntitlement bpSubscription = createBaseEntitlementAndCheckForCompletion(account.getId(), "bundleKey", "Shotgun", ProductCategory.BASE, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
// Check bundle after BP got created otherwise we get an error from auditApi.
subscriptionChecker.checkSubscriptionCreated(bpSubscription.getId(), internalCallContext);
invoiceChecker.checkInvoice(account.getId(), 1, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 4, 1), null, InvoiceItemType.FIXED, new BigDecimal("0")));
@@ -168,7 +165,7 @@ public class TestWithPriceOverride extends TestIntegrationBase {
overrides.add(new DefaultPlanPhasePriceOverride("telescopic-scope-monthly-evergreen", account.getCurrency(), null, new BigDecimal("1200.00"), null));
busHandler.pushExpectedEvents(NextEvent.CHANGE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
- aoEntitlement.changePlanOverrideBillingPolicy(new PlanSpecifier("Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME), overrides, null, BillingActionPolicy.IMMEDIATE, ImmutableList.<PluginProperty>of(), callContext);
+ aoEntitlement.changePlanOverrideBillingPolicy(new PlanPhaseSpecifier("Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME), overrides, null, BillingActionPolicy.IMMEDIATE, ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
}
}
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithTaxItems.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithTaxItems.java
index 6d1b9df..1ef97cb 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithTaxItems.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithTaxItems.java
@@ -18,10 +18,11 @@
package org.killbill.billing.beatrix.integration;
import java.math.BigDecimal;
+import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
-import java.util.concurrent.atomic.AtomicBoolean;
+import javax.annotation.Nullable;
import javax.inject.Inject;
import org.joda.time.LocalDate;
@@ -38,6 +39,7 @@ import org.killbill.billing.invoice.api.DryRunType;
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.model.ExternalChargeInvoiceItem;
import org.killbill.billing.invoice.model.TaxInvoiceItem;
import org.killbill.billing.invoice.plugin.api.InvoicePluginApi;
import org.killbill.billing.osgi.api.OSGIServiceDescriptor;
@@ -49,11 +51,15 @@ import org.killbill.billing.util.callcontext.CallContext;
import org.killbill.billing.util.tag.ControlTagType;
import org.killbill.billing.util.tag.Tag;
import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
+import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
public class TestWithTaxItems extends TestIntegrationBase {
@@ -85,6 +91,12 @@ public class TestWithTaxItems extends TestIntegrationBase {
}, testInvoicePluginApi);
}
+ @BeforeMethod(groups = "slow")
+ public void beforeMethod() throws Exception {
+ super.beforeMethod();
+ testInvoicePluginApi.reset();
+ }
+
private void add_AUTO_INVOICING_OFF_Tag(final UUID id) throws TagDefinitionApiException, TagApiException {
busHandler.pushExpectedEvent(NextEvent.TAG);
tagUserApi.addTag(id, ObjectType.ACCOUNT, ControlTagType.AUTO_INVOICING_OFF.getId(), callContext);
@@ -133,7 +145,8 @@ public class TestWithTaxItems extends TestIntegrationBase {
assertListenerStatus();
// Make sure TestInvoicePluginApi will return an additional TAX item
- testInvoicePluginApi.addTaxItem();
+ final UUID pluginInvoiceItemId = UUID.randomUUID();
+ testInvoicePluginApi.addTaxItem(new TaxInvoiceItem(pluginInvoiceItemId, null, account.getId(), null, "Tax Item", new LocalDate(2012, 5, 1), BigDecimal.ONE, account.getCurrency()));
// Remove AUTO_INVOICING_OFF => Invoice + Payment
remove_AUTO_INVOICING_OFF_Tag(account.getId(), NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
@@ -142,6 +155,19 @@ public class TestWithTaxItems extends TestIntegrationBase {
new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2012, 6, 1), InvoiceItemType.RECURRING, new BigDecimal("2.95")),
new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), null, InvoiceItemType.TAX, new BigDecimal("1.0")));
+ final List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ assertEquals(invoices.size(), 2);
+ final List<InvoiceItem> invoiceItems = invoices.get(1).getInvoiceItems();
+ final InvoiceItem taxItem = Iterables.tryFind(invoiceItems, new Predicate<InvoiceItem>() {
+ @Override
+ public boolean apply(final InvoiceItem input) {
+ return input.getInvoiceItemType() == InvoiceItemType.TAX;
+ }
+ }).orNull();
+ assertNotNull(taxItem);
+ // verify the ID is the one passed by the plugin #818
+ assertEquals(taxItem.getId(), pluginInvoiceItemId);
+
// Add AUTO_INVOICING_OFF and change to a higher plan on the same day that already include the 'Cleaning' ADD_ON, so it gets cancelled
add_AUTO_INVOICING_OFF_Tag(account.getId());
busHandler.pushExpectedEvents(NextEvent.CHANGE, NextEvent.CANCEL, NextEvent.BLOCK);
@@ -149,7 +175,7 @@ public class TestWithTaxItems extends TestIntegrationBase {
assertListenerStatus();
// Make sure TestInvoicePluginApi will return an additional TAX item
- testInvoicePluginApi.addTaxItem();
+ testInvoicePluginApi.addTaxItem(new TaxInvoiceItem(UUID.randomUUID(), null, account.getId(), null, "Tax Item", new LocalDate(2012, 5, 1), BigDecimal.ONE, account.getCurrency()));
// Remove AUTO_INVOICING_OFF => Invoice + Payment
remove_AUTO_INVOICING_OFF_Tag(account.getId(), NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
@@ -171,12 +197,11 @@ public class TestWithTaxItems extends TestIntegrationBase {
assertListenerStatus();
// Make sure TestInvoicePluginApi will return an additional TAX item
- testInvoicePluginApi.addTaxItem();
+ testInvoicePluginApi.addTaxItem(new TaxInvoiceItem(UUID.randomUUID(), null, account.getId(), null, "Tax Item", new LocalDate(2012, 5, 1), BigDecimal.ONE, account.getCurrency()));
// Remove AUTO_INVOICING_OFF => Invoice + Payment
remove_AUTO_INVOICING_OFF_Tag(account.getId(), NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
-
invoiceChecker.checkInvoice(account.getId(), 3, callContext,
new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2012, 6, 1), InvoiceItemType.RECURRING, new BigDecimal("249.95")),
new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2012, 6, 1), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-29.95")),
@@ -214,17 +239,17 @@ public class TestWithTaxItems extends TestIntegrationBase {
new ExpectedInvoiceItemCheck(new LocalDate(2012, 4, 1), new LocalDate(2012, 4, 1), InvoiceItemType.CREDIT_ADJ, new BigDecimal("-100")));
// Make sure TestInvoicePluginApi will return an additional TAX item
- testInvoicePluginApi.addTaxItem();
+ testInvoicePluginApi.addTaxItem(new TaxInvoiceItem(UUID.randomUUID(), null, account.getId(), null, "Tax Item", new LocalDate(2012, 4, 1), BigDecimal.ONE, account.getCurrency()));
// Verify dry-run scenario
final Invoice dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), new LocalDate(2012, 5, 1), new TestDryRunArguments(DryRunType.TARGET_DATE), callContext);
- invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, callContext,
+ invoiceChecker.checkInvoiceNoAudits(dryRunInvoice,
ImmutableList.<ExpectedInvoiceItemCheck>of(new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2012, 6, 1), InvoiceItemType.RECURRING, new BigDecimal("29.95")),
new ExpectedInvoiceItemCheck(new LocalDate(2012, 4, 1), null, InvoiceItemType.TAX, new BigDecimal("1.0")),
new ExpectedInvoiceItemCheck(new LocalDate(2012, 4, 1), new LocalDate(2012, 4, 1), InvoiceItemType.CBA_ADJ, new BigDecimal("-30.95"))));
// Make sure TestInvoicePluginApi will return an additional TAX item
- testInvoicePluginApi.addTaxItem();
+ testInvoicePluginApi.addTaxItem(new TaxInvoiceItem(UUID.randomUUID(), null, account.getId(), null, "Tax Item", new LocalDate(2012, 5, 1), BigDecimal.ONE, account.getCurrency()));
// Move to Evergreen PHASE to verify non-dry-run scenario
busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE);
@@ -237,25 +262,77 @@ public class TestWithTaxItems extends TestIntegrationBase {
new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2012, 5, 1), InvoiceItemType.CBA_ADJ, new BigDecimal("-30.95")));
}
+ @Test(groups = "slow")
+ public void testUpdateTaxItems() throws Exception {
+
+ clock.setDay(new LocalDate(2012, 4, 1));
+
+ final AccountData accountData = getAccountData(1);
+ final Account account = createAccountWithNonOsgiPaymentMethod(accountData);
+
+ // Create original subscription (Trial PHASE) -> $0 invoice.
+ final DefaultEntitlement bpSubscription = createBaseEntitlementAndCheckForCompletion(account.getId(), "bundleKey", "Pistol", ProductCategory.BASE, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
+ invoiceChecker.checkInvoice(account.getId(), 1, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 4, 1), null, InvoiceItemType.FIXED, new BigDecimal("0")));
+ subscriptionChecker.checkSubscriptionCreated(bpSubscription.getId(), internalCallContext);
+
+ // Add tags to keep DRAFT invoices and reuse them
+ add_AUTO_INVOICING_DRAFT_Tag(account.getId(), ObjectType.ACCOUNT);
+ add_AUTO_INVOICING_REUSE_DRAFT_Tag(account.getId(), ObjectType.ACCOUNT);
+
+ // Make sure TestInvoicePluginApi will return an additional TAX item
+ final UUID invoiceTaxItemId = UUID.randomUUID();
+ testInvoicePluginApi.addTaxItem(new TaxInvoiceItem(invoiceTaxItemId, null, account.getId(), null, "Tax Item", new LocalDate(2012, 4, 1), BigDecimal.ONE, account.getCurrency()));
+
+ // Insert external charge autoCommit = false => Invoice will be in DRAFT
+ invoiceUserApi.insertExternalCharges(account.getId(), clock.getUTCNow().toLocalDate(), ImmutableList.<InvoiceItem>of(new ExternalChargeInvoiceItem(null, account.getId(), null, "foo", new LocalDate(2012, 4, 1), null, new BigDecimal("33.80"), account.getCurrency())), false, callContext);
+
+ // Make sure TestInvoicePluginApi **update** the original TAX item
+ testInvoicePluginApi.addTaxItem(new TaxInvoiceItem(invoiceTaxItemId, null, account.getId(), null, "Tax Item", new LocalDate(2012, 4, 1), new BigDecimal("12.45"), account.getCurrency()));
+
+ // Move to Evergreen PHASE, but invoice remains in DRAFT mode
+ busHandler.pushExpectedEvents(NextEvent.PHASE /*, NextEvent.INVOICE */);
+ clock.addDays(30);
+ assertListenerStatus();
+
+ final List<Invoice> accountInvoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ assertEquals(accountInvoices.size(), 2);
+
+ // Commit invoice
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.INVOICE_PAYMENT, NextEvent.PAYMENT);
+ invoiceUserApi.commitInvoice(accountInvoices.get(1).getId(), callContext);
+ assertListenerStatus();
+
+ invoiceChecker.checkInvoice(account.getId(), 2, callContext,
+ new ExpectedInvoiceItemCheck(new LocalDate(2012, 4, 1), null, InvoiceItemType.EXTERNAL_CHARGE, new BigDecimal("33.80")),
+ new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2012, 6, 1), InvoiceItemType.RECURRING, new BigDecimal("29.95")),
+ new ExpectedInvoiceItemCheck(new LocalDate(2012, 4, 1), null, InvoiceItemType.TAX, new BigDecimal("12.45")));
+
+ }
+
public class TestInvoicePluginApi implements InvoicePluginApi {
- AtomicBoolean addTaxItem;
+ private final List<TaxInvoiceItem> taxItems;
public TestInvoicePluginApi() {
- this.addTaxItem = new AtomicBoolean();
+ taxItems = new ArrayList<TaxInvoiceItem>();
}
@Override
public List<InvoiceItem> getAdditionalInvoiceItems(final Invoice invoice, final boolean isDryRun, final Iterable<PluginProperty> pluginProperties, final CallContext callContext) {
- return addTaxItem.compareAndSet(true, false) ? ImmutableList.<InvoiceItem>of(createTaxInvoiceItem(invoice)) : ImmutableList.<InvoiceItem>of();
+ final List<InvoiceItem> result = new ArrayList<InvoiceItem>();
+ for (TaxInvoiceItem item : taxItems) {
+ result.add(new TaxInvoiceItem(item.getId(), invoice.getId(), invoice.getAccountId(), item.getBundleId(), "Tax Item", item.getStartDate(), item.getAmount(), invoice.getCurrency()));
+ }
+ taxItems.clear();
+ return result;
}
- private InvoiceItem createTaxInvoiceItem(final Invoice invoice) {
- return new TaxInvoiceItem(invoice.getId(), invoice.getAccountId(), null, "Tax Item", clock.getUTCNow().toLocalDate(), BigDecimal.ONE, invoice.getCurrency());
+ public void reset() {
+ taxItems.clear();
}
- public void addTaxItem() {
- this.addTaxItem.set(true);
+ public void addTaxItem(final TaxInvoiceItem item) {
+ taxItems.add(item);
}
}
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithTimeZones.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithTimeZones.java
index 2c1cc04..50237ba 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithTimeZones.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithTimeZones.java
@@ -70,6 +70,7 @@ public class TestWithTimeZones extends TestIntegrationBase {
.billingCycleDayLocal(1)
.currency(Currency.USD)
.paymentMethodId(UUID.randomUUID())
+ .referenceTime(clock.getUTCNow())
.timeZone(tz)
.build();
final Account account = createAccountWithNonOsgiPaymentMethod(accountData);
@@ -81,7 +82,7 @@ public class TestWithTimeZones extends TestIntegrationBase {
SubscriptionEventType.START_BILLING, null, null, null, null);
final Invoice dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), clock.getUTCToday(), dryRun, callContext);
expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 9, 1), null, InvoiceItemType.FIXED, new BigDecimal("0")));
- invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, callContext, expectedInvoices);
+ invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, expectedInvoices);
final DefaultEntitlement bpSubscription = createBaseEntitlementAndCheckForCompletion(account.getId(), "bundleKey", "Shotgun", ProductCategory.BASE, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
// Check bundle after BP got created otherwise we get an error from auditApi.
@@ -129,6 +130,7 @@ public class TestWithTimeZones extends TestIntegrationBase {
.billingCycleDayLocal(1)
.currency(Currency.USD)
.paymentMethodId(UUID.randomUUID())
+ .referenceTime(clock.getUTCNow())
.timeZone(tz)
.build();
final Account account = createAccountWithNonOsgiPaymentMethod(accountData);
@@ -136,7 +138,7 @@ public class TestWithTimeZones extends TestIntegrationBase {
busHandler.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Blowdart", BillingPeriod.MONTHLY, "notrial", null);
- Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, "Something", ImmutableList.<PlanPhasePriceOverride>of(), null, null, false, ImmutableList.<PluginProperty>of(), callContext);
+ Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, "Something", ImmutableList.<PlanPhasePriceOverride>of(), null, null, false, true, ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
// Cancel the next month specifying just a LocalDate
@@ -174,6 +176,7 @@ public class TestWithTimeZones extends TestIntegrationBase {
.billingCycleDayLocal(1)
.currency(Currency.USD)
.paymentMethodId(UUID.randomUUID())
+ .referenceTime(clock.getUTCNow())
.timeZone(tz)
.build();
final Account account = createAccountWithNonOsgiPaymentMethod(accountData);
@@ -181,7 +184,7 @@ public class TestWithTimeZones extends TestIntegrationBase {
busHandler.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Blowdart", BillingPeriod.MONTHLY, "notrial", null);
- Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, "Something", ImmutableList.<PlanPhasePriceOverride>of(), null, null, false, ImmutableList.<PluginProperty>of(), callContext);
+ Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, "Something", ImmutableList.<PlanPhasePriceOverride>of(), null, null, false, true, ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
// Cancel the next month specifying just a LocalDate
@@ -207,6 +210,7 @@ public class TestWithTimeZones extends TestIntegrationBase {
clock.setTime(new DateTime(2015, 3, 7, 2, 0, 0, tz));
final AccountData accountData = new MockAccountBuilder().currency(Currency.USD)
+ .referenceTime(clock.getUTCNow())
.timeZone(tz)
.build();
final Account account = createAccountWithNonOsgiPaymentMethod(accountData);
@@ -234,7 +238,7 @@ public class TestWithTimeZones extends TestIntegrationBase {
busHandler.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Blowdart", BillingPeriod.MONTHLY, "notrial", null);
// Pass a date of today, to trigger TimeAwareContext#toUTCDateTime
- final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, "Something", ImmutableList.<PlanPhasePriceOverride>of(), clock.getUTCToday(), clock.getUTCToday(), false, ImmutableList.<PluginProperty>of(), callContext);
+ final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, "Something", ImmutableList.<PlanPhasePriceOverride>of(), clock.getUTCToday(), clock.getUTCToday(), false, true, ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
Assert.assertEquals(entitlement.getEffectiveStartDate().compareTo(new LocalDate("2015-03-08")), 0);
@@ -269,6 +273,7 @@ public class TestWithTimeZones extends TestIntegrationBase {
clock.setTime(new DateTime(2017, 3, 1, 23, 30, 0, tz));
final AccountData accountData = new MockAccountBuilder().currency(Currency.USD)
+ .referenceTime(clock.getUTCNow())
.timeZone(tz)
.build();
@@ -306,6 +311,7 @@ public class TestWithTimeZones extends TestIntegrationBase {
clock.setTime(new DateTime(2016, 11, 5, 23, 30, 0, tz));
final AccountData accountData = new MockAccountBuilder().currency(Currency.USD)
+ .referenceTime(clock.getUTCNow())
.timeZone(tz)
.build();
@@ -350,6 +356,7 @@ public class TestWithTimeZones extends TestIntegrationBase {
final AccountData accountData = new MockAccountBuilder().currency(Currency.USD)
.timeZone(tz)
+ .referenceTime(clock.getUTCNow())
.build();
// Create account with non BCD to force junction BCD logic to activate
@@ -357,7 +364,7 @@ public class TestWithTimeZones extends TestIntegrationBase {
final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("pistol-monthly-notrial",null);
busHandler.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE, NextEvent.INVOICE_PAYMENT, NextEvent.PAYMENT);
- entitlementApi.createBaseEntitlement(account.getId(), spec, "bundleExternalKey", ImmutableList.<PlanPhasePriceOverride>of(), null, null, false, ImmutableList.<PluginProperty>of(), callContext);
+ entitlementApi.createBaseEntitlement(account.getId(), spec, "bundleExternalKey", ImmutableList.<PlanPhasePriceOverride>of(), null, null, false, true, ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
@@ -386,6 +393,7 @@ public class TestWithTimeZones extends TestIntegrationBase {
final AccountData accountData = new MockAccountBuilder().currency(Currency.USD)
.timeZone(tz)
+ .referenceTime(clock.getUTCNow())
.build();
// Create account with non BCD to force junction BCD logic to activate
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/usage/TestConsumableInArrear.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/usage/TestConsumableInArrear.java
index 9aa8eb1..b67a99a 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/usage/TestConsumableInArrear.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/usage/TestConsumableInArrear.java
@@ -54,11 +54,6 @@ import com.google.common.collect.ImmutableList;
public class TestConsumableInArrear extends TestIntegrationBase {
- @BeforeMethod(groups = "slow")
- public void beforeMethod() throws Exception {
- super.beforeMethod();
- }
-
@Test(groups = "slow")
public void testWithNoUsageInPeriodAndOldUsage() throws Exception {
// We take april as it has 30 days (easier to play with BCD)
@@ -194,6 +189,7 @@ public class TestConsumableInArrear extends TestIntegrationBase {
.billingCycleDayLocal(1)
.currency(Currency.USD)
.paymentMethodId(UUID.randomUUID())
+ .referenceTime(clock.getUTCNow())
.timeZone(tz)
.build();
final Account account = createAccountWithNonOsgiPaymentMethod(accountData);
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/util/AuditChecker.java b/beatrix/src/test/java/org/killbill/billing/beatrix/util/AuditChecker.java
index fbc182f..db44b58 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/util/AuditChecker.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/util/AuditChecker.java
@@ -152,12 +152,12 @@ public class AuditChecker {
public void checkInvoiceCreated(final Invoice invoice, final CallContext context) {
final List<AuditLog> invoiceLogs = getAuditLogForInvoice(invoice, context);
- Assert.assertEquals(invoiceLogs.size(), 1);
+ //Assert.assertEquals(invoiceLogs.size(), 1);
checkAuditLog(ChangeType.INSERT, context, invoiceLogs.get(0), invoice.getId(), InvoiceSqlDao.class, false, false);
for (InvoiceItem cur : invoice.getInvoiceItems()) {
final List<AuditLog> auditLogs = getAuditLogForInvoiceItem(cur, context);
- Assert.assertEquals(auditLogs.size(), 1);
+ Assert.assertTrue(auditLogs.size() >= 1);
checkAuditLog(ChangeType.INSERT, context, auditLogs.get(0), cur.getId(), InvoiceItemSqlDao.class, false, false);
}
}
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/util/InvoiceChecker.java b/beatrix/src/test/java/org/killbill/billing/beatrix/util/InvoiceChecker.java
index 8060745..304a0d4 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/util/InvoiceChecker.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/util/InvoiceChecker.java
@@ -79,7 +79,7 @@ public class InvoiceChecker {
checkInvoice(invoice, context, expected);
}
- public void checkInvoiceNoAudits(final Invoice invoice, final CallContext context, final List<ExpectedInvoiceItemCheck> expected) throws InvoiceApiException {
+ public void checkInvoiceNoAudits(final Invoice invoice, final List<ExpectedInvoiceItemCheck> expected) throws InvoiceApiException {
final List<InvoiceItem> actual = invoice.getInvoiceItems();
Assert.assertEquals(actual.size(), expected.size(), String.format("Expected items: %s, actual items: %s", expected, actual));
for (final ExpectedInvoiceItemCheck cur : expected) {
@@ -146,7 +146,7 @@ public class InvoiceChecker {
}
public void checkInvoice(final Invoice invoice, final CallContext context, final List<ExpectedInvoiceItemCheck> expected) throws InvoiceApiException {
- checkInvoiceNoAudits(invoice, context, expected);
+ checkInvoiceNoAudits(invoice, expected);
auditChecker.checkInvoiceCreated(invoice, context);
}
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/util/PaymentChecker.java b/beatrix/src/test/java/org/killbill/billing/beatrix/util/PaymentChecker.java
index 03440c5..9925dde 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/util/PaymentChecker.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/util/PaymentChecker.java
@@ -93,7 +93,7 @@ public class PaymentChecker {
final PaymentTransaction transaction = getPurchaseTransaction(payment);
Assert.assertTrue(transaction.getAmount().compareTo(expected.getAmount()) == 0, "Actual amount " + transaction.getAmount() + ", expected amount " + expected.getAmount());
Assert.assertEquals(transaction.getTransactionStatus(), expected.getStatus());
- Assert.assertEquals(transaction.getEffectiveDate().toLocalDate().compareTo(expected.getPaymentDate()), 0);
+ Assert.assertEquals(transaction.getEffectiveDate().toLocalDate().compareTo(expected.getPaymentDate()), 0, "Actual date " + transaction.getEffectiveDate() + ", expected date " + expected.getPaymentDate());
auditChecker.checkPaymentCreated(payment, context);
}
catalog/pom.xml 6(+1 -5)
diff --git a/catalog/pom.xml b/catalog/pom.xml
index 532cb51..499e1f6 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.19.0-SNAPSHOT</version>
+ <version>0.19.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-catalog</artifactId>
@@ -76,10 +76,6 @@
<scope>test</scope>
</dependency>
<dependency>
- <groupId>org.jdbi</groupId>
- <artifactId>jdbi</artifactId>
- </dependency>
- <dependency>
<groupId>org.kill-bill.billing</groupId>
<artifactId>killbill-api</artifactId>
</dependency>
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 048f41c..0373e89 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
@@ -22,6 +22,7 @@ import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
+import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.xml.bind.JAXBException;
import javax.xml.transform.TransformerException;
@@ -44,9 +45,11 @@ import org.killbill.billing.catalog.caching.CatalogCache;
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.cache.Cachable.CacheType;
import org.killbill.billing.util.callcontext.CallContext;
import org.killbill.billing.util.callcontext.InternalCallContextFactory;
import org.killbill.billing.util.callcontext.TenantContext;
+import org.killbill.clock.Clock;
import org.killbill.xmlloader.ValidationErrors;
import org.killbill.xmlloader.ValidationException;
import org.killbill.xmlloader.XMLLoader;
@@ -54,7 +57,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.SAXException;
-public class DefaultCatalogUserApi implements CatalogUserApi {
+public class DefaultCatalogUserApi implements CatalogUserApi {
private final Logger logger = LoggerFactory.getLogger(DefaultCatalogUserApi.class);
@@ -62,28 +65,41 @@ public class DefaultCatalogUserApi implements CatalogUserApi {
private final InternalCallContextFactory internalCallContextFactory;
private final TenantUserApi tenantApi;
private final CatalogCache catalogCache;
+ private final Clock clock;
@Inject
public DefaultCatalogUserApi(final CatalogService catalogService,
final TenantUserApi tenantApi,
final CatalogCache catalogCache,
+ final Clock clock,
final InternalCallContextFactory internalCallContextFactory) {
this.catalogService = catalogService;
this.tenantApi = tenantApi;
this.catalogCache = catalogCache;
+ this.clock = clock;
this.internalCallContextFactory = internalCallContextFactory;
}
@Override
public Catalog getCatalog(final String catalogName, final TenantContext tenantContext) throws CatalogApiException {
- final InternalTenantContext internalTenantContext = internalCallContextFactory.createInternalTenantContextWithoutAccountRecordId(tenantContext);
+ final InternalTenantContext internalTenantContext;
+ if (tenantContext.getAccountId() != null) {
+ internalTenantContext = internalCallContextFactory.createInternalTenantContext(tenantContext.getAccountId(), tenantContext);
+ } else {
+ internalTenantContext = createInternalTenantContext(tenantContext);
+ }
return catalogService.getFullCatalog(true, true, internalTenantContext);
}
@Override
public StaticCatalog getCurrentCatalog(final String catalogName, final TenantContext tenantContext) throws CatalogApiException {
- final InternalTenantContext internalTenantContext = createInternalTenantContext(tenantContext);
+ final InternalTenantContext internalTenantContext;
+ if (tenantContext.getAccountId() != null) {
+ internalTenantContext = internalCallContextFactory.createInternalTenantContext(tenantContext.getAccountId(), tenantContext);
+ } else {
+ internalTenantContext = createInternalTenantContext(tenantContext);
+ }
return catalogService.getCurrentCatalog(true, true, internalTenantContext);
}
@@ -103,10 +119,10 @@ public class DefaultCatalogUserApi implements CatalogUserApi {
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())) {
+ 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(), versionedCatalog.getCatalogName()),
- new URI("dummy"), StandaloneCatalog.class, "");
+ 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());
@@ -118,7 +134,7 @@ public class DefaultCatalogUserApi implements CatalogUserApi {
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, "");
+ 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());
@@ -152,14 +168,14 @@ public class DefaultCatalogUserApi implements CatalogUserApi {
@Override
- public void createDefaultEmptyCatalog(final DateTime effectiveDate, final CallContext callContext) throws CatalogApiException {
+ public void createDefaultEmptyCatalog(@Nullable final DateTime effectiveDate, final CallContext callContext) throws CatalogApiException {
try {
final InternalTenantContext internalTenantContext = internalCallContextFactory.createInternalTenantContextWithoutAccountRecordId(callContext);
final StandaloneCatalog currentCatalog = getCurrentStandaloneCatalogForTenant(internalTenantContext);
final CatalogUpdater catalogUpdater = (currentCatalog != null) ?
new CatalogUpdater(currentCatalog) :
- new CatalogUpdater(BillingMode.IN_ADVANCE, effectiveDate, null);
+ new CatalogUpdater(getSafeFirstCatalogEffectiveDate(effectiveDate), null);
catalogCache.clearCatalog(internalTenantContext);
tenantApi.updateTenantKeyValue(TenantKey.CATALOG.toString(), catalogUpdater.getCatalogXML(), callContext);
@@ -169,15 +185,14 @@ public class DefaultCatalogUserApi implements CatalogUserApi {
}
@Override
- public void addSimplePlan(final SimplePlanDescriptor descriptor, final DateTime effectiveDate, final CallContext callContext) throws CatalogApiException {
+ public void addSimplePlan(final SimplePlanDescriptor descriptor, @Nullable final DateTime effectiveDate, final CallContext callContext) throws CatalogApiException {
try {
final InternalTenantContext internalTenantContext = internalCallContextFactory.createInternalTenantContextWithoutAccountRecordId(callContext);
final StandaloneCatalog currentCatalog = getCurrentStandaloneCatalogForTenant(internalTenantContext);
final CatalogUpdater catalogUpdater = (currentCatalog != null) ?
new CatalogUpdater(currentCatalog) :
- new CatalogUpdater(BillingMode.IN_ADVANCE, effectiveDate, descriptor.getCurrency());
-
+ new CatalogUpdater(getSafeFirstCatalogEffectiveDate(effectiveDate), descriptor.getCurrency());
catalogUpdater.addSimplePlanDescriptor(descriptor);
catalogCache.clearCatalog(internalTenantContext);
@@ -187,6 +202,24 @@ public class DefaultCatalogUserApi implements CatalogUserApi {
}
}
+ @Override
+ public void deleteCatalog(final CallContext callContext) throws CatalogApiException {
+
+ final InternalTenantContext internalTenantContext = internalCallContextFactory.createInternalTenantContextWithoutAccountRecordId(callContext);
+ try {
+ tenantApi.deleteTenantKey(TenantKey.CATALOG.toString(), callContext);
+ catalogCache.clearCatalog(internalTenantContext);
+ createDefaultEmptyCatalog(clock.getUTCNow(), callContext);
+ } catch (final TenantApiException e) {
+ throw new CatalogApiException(e);
+ }
+ }
+ private DateTime getSafeFirstCatalogEffectiveDate(@Nullable final DateTime input) {
+ // The effectiveDate for the initial version does not matter too much
+ // Because of #760, we want to make that client passing a approximate date (e.g today.toDateTimeAtStartOfDay()) will find the version
+ final DateTime catalogEffectiveDate = clock.getUTCNow().minusDays(1);
+ return (input == null || input.isAfter(catalogEffectiveDate)) ? catalogEffectiveDate : input;
+ }
private StandaloneCatalog getCurrentStandaloneCatalogForTenant(final InternalTenantContext internalTenantContext) throws CatalogApiException {
final VersionedCatalog versionedCatalog = (VersionedCatalog) catalogService.getCurrentCatalog(false, false, internalTenantContext);
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/CatalogUpdater.java b/catalog/src/main/java/org/killbill/billing/catalog/CatalogUpdater.java
index 154949e..6f53186 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/CatalogUpdater.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/CatalogUpdater.java
@@ -68,15 +68,17 @@ public class CatalogUpdater {
public CatalogUpdater(final StandaloneCatalog standaloneCatalog) {
this.catalog = new DefaultMutableStaticCatalog(standaloneCatalog);
+ this.catalog.setRecurringBillingMode(BillingMode.IN_ADVANCE);
+
}
- public CatalogUpdater(final BillingMode billingMode, final DateTime effectiveDate, final Currency... currencies) {
+ public CatalogUpdater(final DateTime effectiveDate, final Currency... currencies) {
final DefaultPriceList defaultPriceList = new DefaultPriceList().setName(PriceListSet.DEFAULT_PRICELIST_NAME);
final StandaloneCatalog tmp = new StandaloneCatalog()
.setCatalogName(DEFAULT_CATALOG_NAME)
.setEffectiveDate(effectiveDate.toDate())
- .setRecurringBillingMode(billingMode)
+ .setRecurringBillingMode(BillingMode.IN_ADVANCE)
.setProducts(ImmutableList.<Product>of())
.setPlans(ImmutableList.<Plan>of())
.setPriceLists(new DefaultPriceListSet(defaultPriceList, new DefaultPriceList[0]))
@@ -132,6 +134,7 @@ public class CatalogUpdater {
plan.setName(desc.getPlanId());
plan.setPriceListName(PriceListSet.DEFAULT_PRICELIST_NAME);
plan.setProduct(product);
+ plan.setRecurringBillingMode(catalog.getRecurringBillingMode());
if (desc.getTrialLength() > 0 && desc.getTrialTimeUnit() != TimeUnit.UNLIMITED) {
final DefaultPlanPhase trialPhase = new DefaultPlanPhase();
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/dao/CatalogOverrideBlockDefinitionSqlDao.java b/catalog/src/main/java/org/killbill/billing/catalog/dao/CatalogOverrideBlockDefinitionSqlDao.java
index a09b696..0d730ab 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/dao/CatalogOverrideBlockDefinitionSqlDao.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/dao/CatalogOverrideBlockDefinitionSqlDao.java
@@ -22,15 +22,15 @@ import java.util.List;
import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.callcontext.InternalTenantContext;
-import org.killbill.billing.util.entity.dao.EntitySqlDaoStringTemplate;
import org.killbill.commons.jdbi.binder.SmartBindBean;
+import org.killbill.commons.jdbi.template.KillBillSqlDaoStringTemplate;
import org.skife.jdbi.v2.sqlobject.Bind;
import org.skife.jdbi.v2.sqlobject.SqlQuery;
import org.skife.jdbi.v2.sqlobject.SqlUpdate;
import org.skife.jdbi.v2.sqlobject.mixins.CloseMe;
import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
-@EntitySqlDaoStringTemplate
+@KillBillSqlDaoStringTemplate
public interface CatalogOverrideBlockDefinitionSqlDao extends Transactional<CatalogOverrideBlockDefinitionSqlDao>, CloseMe {
@SqlUpdate
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/dao/CatalogOverridePhaseDefinitionSqlDao.java b/catalog/src/main/java/org/killbill/billing/catalog/dao/CatalogOverridePhaseDefinitionSqlDao.java
index 19164aa..ea1c2d1 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/dao/CatalogOverridePhaseDefinitionSqlDao.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/dao/CatalogOverridePhaseDefinitionSqlDao.java
@@ -22,7 +22,7 @@ import java.util.List;
import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.callcontext.InternalTenantContext;
-import org.killbill.billing.util.entity.dao.EntitySqlDaoStringTemplate;
+import org.killbill.commons.jdbi.template.KillBillSqlDaoStringTemplate;
import org.killbill.commons.jdbi.binder.SmartBindBean;
import org.skife.jdbi.v2.sqlobject.Bind;
import org.skife.jdbi.v2.sqlobject.SqlQuery;
@@ -30,7 +30,7 @@ import org.skife.jdbi.v2.sqlobject.SqlUpdate;
import org.skife.jdbi.v2.sqlobject.mixins.CloseMe;
import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
-@EntitySqlDaoStringTemplate
+@KillBillSqlDaoStringTemplate
public interface CatalogOverridePhaseDefinitionSqlDao extends Transactional<CatalogOverridePhaseDefinitionSqlDao>, CloseMe {
@SqlUpdate
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/dao/CatalogOverridePhaseUsageSqlDao.java b/catalog/src/main/java/org/killbill/billing/catalog/dao/CatalogOverridePhaseUsageSqlDao.java
index 5c63d82..004f1a7 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/dao/CatalogOverridePhaseUsageSqlDao.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/dao/CatalogOverridePhaseUsageSqlDao.java
@@ -22,15 +22,15 @@ import java.util.List;
import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.callcontext.InternalTenantContext;
-import org.killbill.billing.util.entity.dao.EntitySqlDaoStringTemplate;
import org.killbill.commons.jdbi.binder.SmartBindBean;
+import org.killbill.commons.jdbi.template.KillBillSqlDaoStringTemplate;
import org.skife.jdbi.v2.sqlobject.Bind;
import org.skife.jdbi.v2.sqlobject.SqlQuery;
import org.skife.jdbi.v2.sqlobject.SqlUpdate;
import org.skife.jdbi.v2.sqlobject.mixins.CloseMe;
import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
-@EntitySqlDaoStringTemplate
+@KillBillSqlDaoStringTemplate
public interface CatalogOverridePhaseUsageSqlDao extends Transactional<CatalogOverridePhaseUsageSqlDao>, CloseMe {
@SqlUpdate
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/dao/CatalogOverridePlanDefinitionSqlDao.java b/catalog/src/main/java/org/killbill/billing/catalog/dao/CatalogOverridePlanDefinitionSqlDao.java
index 8570039..47ac60c 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/dao/CatalogOverridePlanDefinitionSqlDao.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/dao/CatalogOverridePlanDefinitionSqlDao.java
@@ -19,7 +19,7 @@ package org.killbill.billing.catalog.dao;
import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.callcontext.InternalTenantContext;
-import org.killbill.billing.util.entity.dao.EntitySqlDaoStringTemplate;
+import org.killbill.commons.jdbi.template.KillBillSqlDaoStringTemplate;
import org.killbill.commons.jdbi.binder.SmartBindBean;
import org.skife.jdbi.v2.sqlobject.Bind;
import org.skife.jdbi.v2.sqlobject.SqlQuery;
@@ -27,7 +27,7 @@ import org.skife.jdbi.v2.sqlobject.SqlUpdate;
import org.skife.jdbi.v2.sqlobject.mixins.CloseMe;
import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
-@EntitySqlDaoStringTemplate
+@KillBillSqlDaoStringTemplate
public interface CatalogOverridePlanDefinitionSqlDao extends Transactional<CatalogOverridePlanDefinitionSqlDao>, CloseMe {
@SqlUpdate
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/dao/CatalogOverridePlanPhaseSqlDao.java b/catalog/src/main/java/org/killbill/billing/catalog/dao/CatalogOverridePlanPhaseSqlDao.java
index 3993ddf..ae7d936 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/dao/CatalogOverridePlanPhaseSqlDao.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/dao/CatalogOverridePlanPhaseSqlDao.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
@@ -21,15 +21,16 @@ import java.util.Collection;
import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.callcontext.InternalTenantContext;
-import org.killbill.billing.util.entity.dao.EntitySqlDaoStringTemplate;
import org.killbill.commons.jdbi.binder.SmartBindBean;
+import org.killbill.commons.jdbi.template.KillBillSqlDaoStringTemplate;
import org.skife.jdbi.v2.sqlobject.Bind;
import org.skife.jdbi.v2.sqlobject.SqlQuery;
import org.skife.jdbi.v2.sqlobject.SqlUpdate;
import org.skife.jdbi.v2.sqlobject.mixins.CloseMe;
import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
+import org.skife.jdbi.v2.unstable.BindIn;
-@EntitySqlDaoStringTemplate
+@KillBillSqlDaoStringTemplate
public interface CatalogOverridePlanPhaseSqlDao extends Transactional<CatalogOverridePlanPhaseSqlDao>, CloseMe {
@SqlUpdate
@@ -41,7 +42,7 @@ public interface CatalogOverridePlanPhaseSqlDao extends Transactional<CatalogOve
@SmartBindBean final InternalTenantContext context);
@SqlQuery
- public Long getTargetPlanDefinition(@PlanPhaseKeysCollectionBinder final Collection<String> concatPhaseNumAndPhaseDefRecordId,
+ public Long getTargetPlanDefinition(@BindIn("keys") final Collection<String> concatPhaseNumAndPhaseDefRecordId,
@Bind("targetCount") final Integer targetCount,
@SmartBindBean final InternalTenantContext context);
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/dao/CatalogOverrideTierBlockSqlDao.java b/catalog/src/main/java/org/killbill/billing/catalog/dao/CatalogOverrideTierBlockSqlDao.java
index 998cb87..f5cbc87 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/dao/CatalogOverrideTierBlockSqlDao.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/dao/CatalogOverrideTierBlockSqlDao.java
@@ -21,15 +21,15 @@ import java.util.Collection;
import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.callcontext.InternalTenantContext;
-import org.killbill.billing.util.entity.dao.EntitySqlDaoStringTemplate;
import org.killbill.commons.jdbi.binder.SmartBindBean;
+import org.killbill.commons.jdbi.template.KillBillSqlDaoStringTemplate;
import org.skife.jdbi.v2.sqlobject.Bind;
import org.skife.jdbi.v2.sqlobject.SqlQuery;
import org.skife.jdbi.v2.sqlobject.SqlUpdate;
import org.skife.jdbi.v2.sqlobject.mixins.CloseMe;
import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
-@EntitySqlDaoStringTemplate
+@KillBillSqlDaoStringTemplate
public interface CatalogOverrideTierBlockSqlDao extends Transactional<CatalogOverrideTierBlockSqlDao>, CloseMe {
@SqlUpdate
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/dao/CatalogOverrideTierDefinitionSqlDao.java b/catalog/src/main/java/org/killbill/billing/catalog/dao/CatalogOverrideTierDefinitionSqlDao.java
index eecf646..4a9f628 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/dao/CatalogOverrideTierDefinitionSqlDao.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/dao/CatalogOverrideTierDefinitionSqlDao.java
@@ -21,15 +21,15 @@ import java.util.List;
import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.callcontext.InternalTenantContext;
-import org.killbill.billing.util.entity.dao.EntitySqlDaoStringTemplate;
import org.killbill.commons.jdbi.binder.SmartBindBean;
+import org.killbill.commons.jdbi.template.KillBillSqlDaoStringTemplate;
import org.skife.jdbi.v2.sqlobject.Bind;
import org.skife.jdbi.v2.sqlobject.SqlQuery;
import org.skife.jdbi.v2.sqlobject.SqlUpdate;
import org.skife.jdbi.v2.sqlobject.mixins.CloseMe;
import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
-@EntitySqlDaoStringTemplate
+@KillBillSqlDaoStringTemplate
public interface CatalogOverrideTierDefinitionSqlDao extends Transactional<CatalogOverrideTierDefinitionSqlDao>, CloseMe {
@SqlUpdate
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/dao/CatalogOverrideUsageDefinitionSqlDao.java b/catalog/src/main/java/org/killbill/billing/catalog/dao/CatalogOverrideUsageDefinitionSqlDao.java
index 24ad294..4c1c5a0 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/dao/CatalogOverrideUsageDefinitionSqlDao.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/dao/CatalogOverrideUsageDefinitionSqlDao.java
@@ -21,15 +21,15 @@ import java.util.List;
import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.callcontext.InternalTenantContext;
-import org.killbill.billing.util.entity.dao.EntitySqlDaoStringTemplate;
import org.killbill.commons.jdbi.binder.SmartBindBean;
+import org.killbill.commons.jdbi.template.KillBillSqlDaoStringTemplate;
import org.skife.jdbi.v2.sqlobject.Bind;
import org.skife.jdbi.v2.sqlobject.SqlQuery;
import org.skife.jdbi.v2.sqlobject.SqlUpdate;
import org.skife.jdbi.v2.sqlobject.mixins.CloseMe;
import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
-@EntitySqlDaoStringTemplate
+@KillBillSqlDaoStringTemplate
public interface CatalogOverrideUsageDefinitionSqlDao extends Transactional<CatalogOverrideUsageDefinitionSqlDao>, CloseMe {
@SqlUpdate
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/dao/CatalogOverrideUsageTierSqlDao.java b/catalog/src/main/java/org/killbill/billing/catalog/dao/CatalogOverrideUsageTierSqlDao.java
index 7665047..84f29d2 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/dao/CatalogOverrideUsageTierSqlDao.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/dao/CatalogOverrideUsageTierSqlDao.java
@@ -22,15 +22,15 @@ import java.util.List;
import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.callcontext.InternalTenantContext;
-import org.killbill.billing.util.entity.dao.EntitySqlDaoStringTemplate;
import org.killbill.commons.jdbi.binder.SmartBindBean;
+import org.killbill.commons.jdbi.template.KillBillSqlDaoStringTemplate;
import org.skife.jdbi.v2.sqlobject.Bind;
import org.skife.jdbi.v2.sqlobject.SqlQuery;
import org.skife.jdbi.v2.sqlobject.SqlUpdate;
import org.skife.jdbi.v2.sqlobject.mixins.CloseMe;
import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
-@EntitySqlDaoStringTemplate
+@KillBillSqlDaoStringTemplate
public interface CatalogOverrideUsageTierSqlDao extends Transactional<CatalogOverrideUsageTierSqlDao>, CloseMe {
@SqlUpdate
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultMutableStaticCatalog.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultMutableStaticCatalog.java
index 8e83016..c6d9a6c 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultMutableStaticCatalog.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultMutableStaticCatalog.java
@@ -42,12 +42,12 @@ public class DefaultMutableStaticCatalog extends StandaloneCatalog implements Mu
public DefaultMutableStaticCatalog(final StandaloneCatalog input) {
this.setCatalogName(input.getCatalogName())
+ .setRecurringBillingMode(input.getRecurringBillingMode())
.setEffectiveDate(input.getEffectiveDate())
.setSupportedCurrencies(input.getCurrentSupportedCurrencies())
.setUnits(input.getCurrentUnits())
.setProducts(input.getCurrentProducts())
.setPlans(input.getCurrentPlans())
- .setRecurringBillingMode(input.getRecurringBillingMode())
.setPlanRules(input.getPlanRules())
.setPriceLists(input.getPriceLists());
initialize(this, null);
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 cf7c9a3..a61fa3c 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultPlan.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultPlan.java
@@ -40,6 +40,7 @@ import javax.xml.bind.annotation.XmlIDREF;
import org.joda.time.DateTime;
import org.killbill.billing.ErrorCode;
+import org.killbill.billing.catalog.api.BillingMode;
import org.killbill.billing.catalog.api.BillingPeriod;
import org.killbill.billing.catalog.api.CatalogApiException;
import org.killbill.billing.catalog.api.PhaseType;
@@ -76,6 +77,10 @@ public class DefaultPlan extends ValidatingConfig<StandaloneCatalog> implements
@XmlIDREF
private DefaultProduct product;
+ // In order to support older versions where recurringBillingMode is defined at the StandaloneCatalog level, we don't require that field.
+ @XmlElement(required = false)
+ private BillingMode recurringBillingMode;
+
@XmlElementWrapper(name = "initialPhases", required = false)
@XmlElement(name = "phase", required = false)
private DefaultPlanPhase[] initialPhases;
@@ -108,6 +113,7 @@ public class DefaultPlan extends ValidatingConfig<StandaloneCatalog> implements
}
this.finalPhase = new DefaultPlanPhase(this, in.getFinalPhase(), overrides[overrides.length - 1]);
this.priceListName = in.getPriceListName();
+ this.recurringBillingMode = in.getRecurringBillingMode();
}
@Override
@@ -115,6 +121,10 @@ public class DefaultPlan extends ValidatingConfig<StandaloneCatalog> implements
return effectiveDateForExistingSubscriptions;
}
+ @Override
+ public BillingMode getRecurringBillingMode() {
+ return recurringBillingMode;
+ }
@Override
public DefaultPlanPhase[] getInitialPhases() {
@@ -204,6 +214,10 @@ public class DefaultPlan extends ValidatingConfig<StandaloneCatalog> implements
p.setPlan(this);
p.initialize(catalog, sourceURI);
}
+ if (recurringBillingMode == null) {
+ this.recurringBillingMode = catalog.getRecurringBillingMode();
+ }
+
this.priceListName = this.priceListName != null ? this.priceListName : findPriceListForPlan(catalog);
}
@@ -217,13 +231,17 @@ public class DefaultPlan extends ValidatingConfig<StandaloneCatalog> implements
catalog.getCatalogURI(), DefaultPlan.class, ""));
}
+ if (recurringBillingMode == null) {
+ errors.add(new ValidationError(String.format("Invalid reccuring billingMode for plan '%s'", name), catalog.getCatalogURI(), DefaultPlan.class, ""));
+ }
+
if (product == null) {
errors.add(new ValidationError(String.format("Invalid product for plan '%s'", name), catalog.getCatalogURI(), DefaultPlan.class, ""));
}
for (final DefaultPlanPhase cur : initialPhases) {
cur.validate(catalog, errors);
- if (cur.getPhaseType() == PhaseType.EVERGREEN || cur.getPhaseType() == PhaseType.FIXEDTERM) {
+ if (cur.getPhaseType() == PhaseType.EVERGREEN) {
errors.add(new ValidationError(String.format("Initial Phase %s of plan %s cannot be of type %s",
cur.getName(), name, cur.getPhaseType()),
catalog.getCatalogURI(), DefaultPlan.class, ""));
@@ -284,6 +302,11 @@ public class DefaultPlan extends ValidatingConfig<StandaloneCatalog> implements
return this;
}
+ public DefaultPlan setRecurringBillingMode(final BillingMode billingMode) {
+ this.recurringBillingMode = billingMode;
+ return this;
+ }
+
@Override
public DateTime dateOfFirstRecurringNonZeroCharge(final DateTime subscriptionStartDate, final PhaseType initialPhaseType) {
DateTime result = subscriptionStartDate;
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultUnit.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultUnit.java
index 9d3f148..b3ad882 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultUnit.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultUnit.java
@@ -29,11 +29,14 @@ import org.killbill.xmlloader.ValidationErrors;
@XmlAccessorType(XmlAccessType.NONE)
public class DefaultUnit extends ValidatingConfig<StandaloneCatalog> implements Unit {
-
+
@XmlAttribute(required = true)
@XmlID
private String name;
+ @XmlAttribute(required = false)
+ private String prettyName;
+
/* (non-Javadoc)
* @see org.killbill.billing.catalog.Unit#getName()
*/
@@ -44,7 +47,7 @@ public class DefaultUnit extends ValidatingConfig<StandaloneCatalog> implements
@Override
public String getPrettyName() {
- return name;
+ return prettyName;
}
@Override
@@ -57,14 +60,21 @@ public class DefaultUnit extends ValidatingConfig<StandaloneCatalog> implements
public void initialize(final StandaloneCatalog catalog, final URI sourceURI) {
super.initialize(catalog, sourceURI);
CatalogSafetyInitializer.initializeNonRequiredNullFieldsWithDefaultValue(this);
+ if (prettyName == null) {
+ this.prettyName = name;
+ }
}
-
public DefaultUnit setName(final String name) {
this.name = name;
return this;
}
+ public DefaultUnit setPrettyName(final String prettyName) {
+ this.prettyName = prettyName;
+ return this;
+ }
+
@Override
public boolean equals(final Object o) {
if (this == o) {
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/plugin/StandaloneCatalogMapper.java b/catalog/src/main/java/org/killbill/billing/catalog/plugin/StandaloneCatalogMapper.java
index 7e5dc48..c8bf3d4 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/plugin/StandaloneCatalogMapper.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/plugin/StandaloneCatalogMapper.java
@@ -44,7 +44,6 @@ import org.killbill.billing.catalog.DefaultTieredBlock;
import org.killbill.billing.catalog.DefaultUnit;
import org.killbill.billing.catalog.DefaultUsage;
import org.killbill.billing.catalog.StandaloneCatalog;
-import org.killbill.billing.catalog.api.BillingMode;
import org.killbill.billing.catalog.api.Block;
import org.killbill.billing.catalog.api.CurrencyValueNull;
import org.killbill.billing.catalog.api.Duration;
@@ -91,16 +90,13 @@ import com.google.common.collect.Iterables;
public class StandaloneCatalogMapper {
private final String catalogName;
- private final BillingMode recurringBillingMode;
-
private Map<String, Product> tmpDefaultProducts;
private Map<String, Plan> tmpDefaultPlans;
private DefaultPriceListSet tmpDefaultPriceListSet;
private Map<String, DefaultPriceList> tmpDefaultPriceListMap;
- public StandaloneCatalogMapper(final String catalogName, final BillingMode recurringBillingMode) {
+ public StandaloneCatalogMapper(final String catalogName) {
this.catalogName = catalogName;
- this.recurringBillingMode = recurringBillingMode;
this.tmpDefaultProducts = null;
this.tmpDefaultPlans = null;
this.tmpDefaultPriceListMap = new HashMap<String, DefaultPriceList>();
@@ -114,7 +110,6 @@ public class StandaloneCatalogMapper {
result.setProducts(toDefaultProducts(pluginCatalog.getProducts()));
result.setPlans(toDefaultPlans(pluginCatalog.getPlans()));
result.setPriceLists(toDefaultPriceListSet(pluginCatalog.getDefaultPriceList(), pluginCatalog.getChildrenPriceList()));
- result.setRecurringBillingMode(recurringBillingMode);
result.setSupportedCurrencies(toArray(pluginCatalog.getCurrencies()));
result.setUnits(toDefaultUnits(pluginCatalog.getUnits()));
result.setPlanRules(toDefaultPlanRules(pluginCatalog.getPlanRules()));
@@ -358,6 +353,7 @@ public class StandaloneCatalogMapper {
private DefaultUnit toDefaultUnit(final Unit input) {
final DefaultUnit result = new DefaultUnit();
result.setName(input.getName());
+ result.setPrettyName(input.getPrettyName());
return result;
}
@@ -402,6 +398,7 @@ public class StandaloneCatalogMapper {
final DefaultPlan result = new DefaultPlan();
result.setName(input.getName());
result.setPrettyName(input.getPrettyName());
+ result.setRecurringBillingMode(input.getRecurringBillingMode());
result.setEffectiveDateForExistingSubscriptions(input.getEffectiveDateForExistingSubscriptions());
result.setFinalPhase(toDefaultPlanPhase(input.getFinalPhase()));
result.setInitialPhases(toDefaultPlanPhases(ImmutableList.copyOf(input.getInitialPhases())));
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/plugin/VersionedCatalogMapper.java b/catalog/src/main/java/org/killbill/billing/catalog/plugin/VersionedCatalogMapper.java
index 1ee8470..eb170ce 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/plugin/VersionedCatalogMapper.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/plugin/VersionedCatalogMapper.java
@@ -59,7 +59,7 @@ public class VersionedCatalogMapper {
}
private StandaloneCatalogWithPriceOverride toStandaloneCatalogWithPriceOverride(final VersionedPluginCatalog pluginCatalog, final StandalonePluginCatalog input, final InternalTenantContext internalTenantContext) {
- final StandaloneCatalogMapper mapper = new StandaloneCatalogMapper(pluginCatalog.getCatalogName(), pluginCatalog.getRecurringBillingMode());
+ final StandaloneCatalogMapper mapper = new StandaloneCatalogMapper(pluginCatalog.getCatalogName());
final StandaloneCatalog catalog = mapper.toStandaloneCatalog(input, null);
final StandaloneCatalogWithPriceOverride result = new StandaloneCatalogWithPriceOverride(catalog, priceOverride, internalTenantContext.getTenantRecordId(), internalCallContextFactory);
return result;
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/StandaloneCatalog.java b/catalog/src/main/java/org/killbill/billing/catalog/StandaloneCatalog.java
index 96869e4..db734df 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/StandaloneCatalog.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/StandaloneCatalog.java
@@ -65,7 +65,7 @@ public class StandaloneCatalog extends ValidatingConfig<StandaloneCatalog> imple
@XmlElement(required = true)
private String catalogName;
- @XmlElement(required = true)
+ @XmlElement(required = false)
private BillingMode recurringBillingMode;
@XmlElementWrapper(name = "currencies", required = true)
@@ -114,11 +114,6 @@ public class StandaloneCatalog extends ValidatingConfig<StandaloneCatalog> imple
return effectiveDate;
}
- @Override
- public BillingMode getRecurringBillingMode() {
- return recurringBillingMode;
- }
-
/* (non-Javadoc)
* @see org.killbill.billing.catalog.ICatalog#getProducts()
*/
@@ -316,6 +311,9 @@ public class StandaloneCatalog extends ValidatingConfig<StandaloneCatalog> imple
}
}
+ public BillingMode getRecurringBillingMode() {
+ return recurringBillingMode;
+ }
//////////////////////////////////////////////////////////////////////////////
//
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 d7a2d9b..f78480d 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/StandaloneCatalogWithPriceOverride.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/StandaloneCatalogWithPriceOverride.java
@@ -49,7 +49,6 @@ public class StandaloneCatalogWithPriceOverride extends StandaloneCatalog implem
// Initialize from input catalog
setCatalogName(catalog.getCatalogName());
setEffectiveDate(catalog.getEffectiveDate());
- setRecurringBillingMode(catalog.getRecurringBillingMode());
setProducts(catalog.getCurrentProducts());
setPlans(catalog.getCurrentPlans());
setPriceLists(catalog.getPriceLists());
@@ -75,8 +74,7 @@ public class StandaloneCatalogWithPriceOverride extends StandaloneCatalog implem
final Plan defaultPlan = super.createOrFindCurrentPlan(spec, null);
if (overrides == null ||
overrides.getOverrides() == null ||
- overrides.getOverrides().isEmpty() ||
- isOverrideUsedForPlanAlignmentTargetPhaseType(overrides)) {
+ overrides.getOverrides().isEmpty()) {
return defaultPlan;
}
@@ -84,21 +82,6 @@ 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);
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/VersionedCatalog.java b/catalog/src/main/java/org/killbill/billing/catalog/VersionedCatalog.java
index e52d6a8..73dfb28 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/VersionedCatalog.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/VersionedCatalog.java
@@ -85,9 +85,6 @@ public class VersionedCatalog extends ValidatingConfig<VersionedCatalog> impleme
@XmlElement(required = true)
private String catalogName;
- @XmlElement(required = true)
- private BillingMode recurringBillingMode;
-
// Required for JAXB deserialization
public VersionedCatalog() {
this.clock = null;
@@ -122,6 +119,13 @@ public class VersionedCatalog extends ValidatingConfig<VersionedCatalog> impleme
return i;
}
}
+ // If the only version we have are after the input date, we return the first version
+ // This is not strictly correct from an api point of view, but there is no real good use case
+ // where the system would ask for the catalog for a date prior any catalog was uploaded and
+ // yet time manipulation could end of inn that state -- see https://github.com/killbill/killbill/issues/760
+ if (versions.size() > 0) {
+ return 0;
+ }
throw new CatalogApiException(ErrorCode.CAT_NO_CATALOG_FOR_GIVEN_DATE, date.toString());
}
@@ -174,8 +178,11 @@ public class VersionedCatalog extends ValidatingConfig<VersionedCatalog> impleme
}
}
+
+ final boolean initialVersion = (i == 0);
final DateTime catalogEffectiveDate = CatalogDateHelper.toUTCDateTime(c.getEffectiveDate());
- if (!subscriptionStartDate.isBefore(catalogEffectiveDate)) { // Its a new subscription this plan always applies
+ if (initialVersion || // Prevent issue with time granularity -- see #760
+ !subscriptionStartDate.isBefore(catalogEffectiveDate)) { // It's a new subscription this plan always applies
return new CatalogPlanEntry(c, plan);
} else { //Its an existing subscription
if (plan.getEffectiveDateForExistingSubscriptions() != null) { //if it is null any change to this does not apply to existing subscriptions
@@ -236,9 +243,6 @@ public class VersionedCatalog extends ValidatingConfig<VersionedCatalog> impleme
if (catalogName == null && e.getCatalogName() != null) {
catalogName = e.getCatalogName();
}
- if (recurringBillingMode == null) {
- recurringBillingMode = e.getRecurringBillingMode();
- }
versions.add(e);
Collections.sort(versions, new Comparator<StandaloneCatalog>() {
@Override
@@ -274,6 +278,10 @@ public class VersionedCatalog extends ValidatingConfig<VersionedCatalog> impleme
return versionForDate(requestedDate).getCurrentSupportedCurrencies();
}
+ public Unit[] getUnits(final DateTime requestedDate) throws CatalogApiException {
+ return versionForDate(requestedDate).getCurrentUnits();
+ }
+
@Override
public Collection<Plan> getPlans(final DateTime requestedDate) throws CatalogApiException {
return versionForDate(requestedDate).getCurrentPlans();
@@ -433,10 +441,6 @@ public class VersionedCatalog extends ValidatingConfig<VersionedCatalog> impleme
errors.add(new ValidationError(String.format("Catalog name '%s' is not consistent across versions ", c.getCatalogName()),
c.getCatalogURI(), VersionedCatalog.class, ""));
}
- if (!c.getRecurringBillingMode().equals(recurringBillingMode)) {
- errors.add(new ValidationError(String.format("Catalog recurringBillingMode '%s' is not consistent across versions ", c.getCatalogName()),
- c.getCatalogURI(), VersionedCatalog.class, ""));
- }
errors.addAll(c.validate(c, errors));
}
return errors;
@@ -456,11 +460,6 @@ public class VersionedCatalog extends ValidatingConfig<VersionedCatalog> impleme
}
@Override
- public BillingMode getRecurringBillingMode() {
- return recurringBillingMode;
- }
-
- @Override
public Currency[] getCurrentSupportedCurrencies() throws CatalogApiException {
return versionForDate(clock.getUTCNow()).getCurrentSupportedCurrencies();
}
diff --git a/catalog/src/main/resources/org/killbill/billing/catalog/dao/CatalogOverridePhaseDefinitionSqlDao.sql.stg b/catalog/src/main/resources/org/killbill/billing/catalog/dao/CatalogOverridePhaseDefinitionSqlDao.sql.stg
index e51ae47..7e987c9 100644
--- a/catalog/src/main/resources/org/killbill/billing/catalog/dao/CatalogOverridePhaseDefinitionSqlDao.sql.stg
+++ b/catalog/src/main/resources/org/killbill/billing/catalog/dao/CatalogOverridePhaseDefinitionSqlDao.sql.stg
@@ -1,5 +1,3 @@
-group CatalogOverridePhaseDefinitionSqlDao;
-
tableName() ::= "catalog_override_phase_definition"
@@ -39,7 +37,7 @@ allTableValues() ::= <<
create() ::= <<
insert into <tableName()> (
-<tableFields()>
+<tableFields("")>
)
values (
<tableValues()>
@@ -48,7 +46,7 @@ values (
>>
getByRecordId() ::= <<
-select <allTableFields()>
+select <allTableFields("")>
from <tableName()>
where record_id = :recordId
and tenant_record_id = :tenantRecordId
@@ -56,7 +54,7 @@ and tenant_record_id = :tenantRecordId
>>
getByAttributes() ::= <<
-select <allTableFields()>
+select <allTableFields("")>
from <tableName()>
where parent_phase_name = :parentPhaseName
and currency = :currency
diff --git a/catalog/src/main/resources/org/killbill/billing/catalog/dao/CatalogOverridePlanDefinitionSqlDao.sql.stg b/catalog/src/main/resources/org/killbill/billing/catalog/dao/CatalogOverridePlanDefinitionSqlDao.sql.stg
index 3024bc0..b9ae158 100644
--- a/catalog/src/main/resources/org/killbill/billing/catalog/dao/CatalogOverridePlanDefinitionSqlDao.sql.stg
+++ b/catalog/src/main/resources/org/killbill/billing/catalog/dao/CatalogOverridePlanDefinitionSqlDao.sql.stg
@@ -1,5 +1,3 @@
-group CatalogOverridePlanDefinitionSqlDao;
-
tableName() ::= "catalog_override_plan_definition"
tableFields(prefix) ::= <<
@@ -34,7 +32,7 @@ allTableValues() ::= <<
create() ::= <<
insert into <tableName()> (
-<tableFields()>
+<tableFields("")>
)
values (
<tableValues()>
@@ -43,7 +41,7 @@ values (
>>
getByRecordId() ::= <<
-select <allTableFields()>
+select <allTableFields("")>
from <tableName()>
where record_id = :recordId
and tenant_record_id = :tenantRecordId
diff --git a/catalog/src/main/resources/org/killbill/billing/catalog/dao/CatalogOverridePlanPhaseSqlDao.sql.stg b/catalog/src/main/resources/org/killbill/billing/catalog/dao/CatalogOverridePlanPhaseSqlDao.sql.stg
index be35a86..ba61278 100644
--- a/catalog/src/main/resources/org/killbill/billing/catalog/dao/CatalogOverridePlanPhaseSqlDao.sql.stg
+++ b/catalog/src/main/resources/org/killbill/billing/catalog/dao/CatalogOverridePlanPhaseSqlDao.sql.stg
@@ -1,6 +1,3 @@
-group CatalogOverridePlanPhaseSqlDao;
-
-
tableName() ::= "catalog_override_plan_phase"
@@ -36,7 +33,7 @@ allTableValues() ::= <<
create() ::= <<
insert into <tableName()> (
-<tableFields()>
+<tableFields("")>
)
values (
<tableValues()>
@@ -45,7 +42,7 @@ values (
>>
getByRecordId() ::= <<
-select <allTableFields()>
+select <allTableFields("")>
from
<tableName()>
where record_id = :recordId
@@ -62,7 +59,7 @@ from (select
from
<tableName()>
where
- concat_ws(',', phase_number, phase_def_record_id) in (<keys: {key | :key_<i0>}; separator="," >)
+ concat_ws(',', phase_number, phase_def_record_id) in (<keys>)
and tenant_record_id = :tenantRecordId
group by target_plan_def_record_id) tmp
where
diff --git a/catalog/src/main/resources/org/killbill/billing/catalog/ddl.sql b/catalog/src/main/resources/org/killbill/billing/catalog/ddl.sql
index 0bbd08d..dcae41e 100644
--- a/catalog/src/main/resources/org/killbill/billing/catalog/ddl.sql
+++ b/catalog/src/main/resources/org/killbill/billing/catalog/ddl.sql
@@ -32,7 +32,7 @@ CREATE INDEX catalog_override_phase_definition_idx ON catalog_override_phase_def
DROP TABLE IF EXISTS catalog_override_plan_phase;
CREATE TABLE catalog_override_plan_phase (
record_id serial unique,
- phase_number smallint /*! unsigned */ NOT NULL,
+ phase_number int /*! unsigned */ NOT NULL,
phase_def_record_id bigint /*! unsigned */ not null,
target_plan_def_record_id bigint /*! unsigned */ not null,
created_date datetime NOT NULL,
@@ -80,8 +80,8 @@ create table catalog_override_block_definition
(
record_id serial unique,
parent_unit_name varchar(255) NOT NULL,
-size double NOT NULL,
-max double NULL,
+size decimal(15,9) NOT NULL,
+max decimal(15,9) NULL,
currency varchar(3) NOT NULL,
price decimal(15,9) NOT NULL,
effective_date datetime NOT NULL,
@@ -97,7 +97,7 @@ DROP TABLE IF EXISTS catalog_override_phase_usage;
create table catalog_override_phase_usage
(
record_id serial unique,
-usage_number smallint(5) unsigned,
+usage_number int /*! unsigned */,
usage_def_record_id bigint /*! unsigned */ not null,
target_phase_def_record_id bigint /*! unsigned */ not null,
created_date datetime NOT NULL,
@@ -111,7 +111,7 @@ DROP TABLE IF EXISTS catalog_override_usage_tier;
create table catalog_override_usage_tier
(
record_id serial unique,
-tier_number smallint(5) unsigned,
+tier_number int /*! unsigned */,
tier_def_record_id bigint /*! unsigned */ not null,
target_usage_def_record_id bigint /*! unsigned */ not null,
created_date datetime NOT NULL,
@@ -126,7 +126,7 @@ DROP TABLE IF EXISTS catalog_override_tier_block;
create table catalog_override_tier_block
(
record_id serial unique,
-block_number smallint(5) unsigned,
+block_number int /*! unsigned */,
block_def_record_id bigint /*! unsigned */ not null,
target_tier_def_record_id bigint /*! unsigned */ not null,
created_date datetime NOT NULL,
diff --git a/catalog/src/main/resources/org/killbill/billing/catalog/migration/V20161220000000__unit_price_override.sql b/catalog/src/main/resources/org/killbill/billing/catalog/migration/V20161220000000__unit_price_override.sql
index 85f5589..0abe200 100644
--- a/catalog/src/main/resources/org/killbill/billing/catalog/migration/V20161220000000__unit_price_override.sql
+++ b/catalog/src/main/resources/org/killbill/billing/catalog/migration/V20161220000000__unit_price_override.sql
@@ -15,6 +15,7 @@ PRIMARY KEY(record_id)
);
CREATE INDEX catalog_override_usage_definition_idx ON catalog_override_usage_definition(tenant_record_id, parent_usage_name, currency);
+
DROP TABLE IF EXISTS catalog_override_tier_definition;
create table catalog_override_tier_definition
(
@@ -35,8 +36,8 @@ create table catalog_override_block_definition
(
record_id serial unique,
parent_unit_name varchar(255) NOT NULL,
-size double NOT NULL,
-max double NULL,
+size decimal(15,9) NOT NULL,
+max decimal(15,9) NULL,
currency varchar(3) NOT NULL,
price decimal(15,9) NOT NULL,
effective_date datetime NOT NULL,
@@ -52,7 +53,7 @@ DROP TABLE IF EXISTS catalog_override_phase_usage;
create table catalog_override_phase_usage
(
record_id serial unique,
-usage_number smallint(5) unsigned,
+usage_number int /*! unsigned */,
usage_def_record_id bigint /*! unsigned */ not null,
target_phase_def_record_id bigint /*! unsigned */ not null,
created_date datetime NOT NULL,
@@ -66,7 +67,7 @@ DROP TABLE IF EXISTS catalog_override_usage_tier;
create table catalog_override_usage_tier
(
record_id serial unique,
-tier_number smallint(5) unsigned,
+tier_number int /*! unsigned */,
tier_def_record_id bigint /*! unsigned */ not null,
target_usage_def_record_id bigint /*! unsigned */ not null,
created_date datetime NOT NULL,
@@ -81,7 +82,7 @@ DROP TABLE IF EXISTS catalog_override_tier_block;
create table catalog_override_tier_block
(
record_id serial unique,
-block_number smallint(5) unsigned,
+block_number int /*! unsigned */,
block_def_record_id bigint /*! unsigned */ not null,
target_tier_def_record_id bigint /*! unsigned */ not null,
created_date datetime NOT NULL,
@@ -89,6 +90,4 @@ created_by varchar(50) NOT NULL,
tenant_record_id bigint /*! unsigned */ NOT NULL default 0,
PRIMARY KEY(record_id)
);
-CREATE INDEX catalog_override_tier_block_idx ON catalog_override_tier_block(tenant_record_id, block_number, block_def_record_id);
-
-
+CREATE INDEX catalog_override_tier_block_idx ON catalog_override_tier_block(tenant_record_id, block_number, block_def_record_id);
\ No newline at end of file
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/io/TestXMLWriter.java b/catalog/src/test/java/org/killbill/billing/catalog/io/TestXMLWriter.java
index b431e9a..7a3c905 100644
--- a/catalog/src/test/java/org/killbill/billing/catalog/io/TestXMLWriter.java
+++ b/catalog/src/test/java/org/killbill/billing/catalog/io/TestXMLWriter.java
@@ -36,6 +36,7 @@ import org.killbill.billing.catalog.DefaultRecurring;
import org.killbill.billing.catalog.StandaloneCatalog;
import org.killbill.billing.catalog.StandaloneCatalogWithPriceOverride;
import org.killbill.billing.catalog.VersionedCatalog;
+import org.killbill.billing.catalog.api.BillingMode;
import org.killbill.billing.catalog.api.BillingPeriod;
import org.killbill.billing.catalog.api.Currency;
import org.killbill.billing.catalog.api.MutableStaticCatalog;
@@ -111,6 +112,7 @@ public class TestXMLWriter extends CatalogTestSuiteNoDB {
newPlan.setProduct(newProduct);
newPlan.setInitialPhases(new DefaultPlanPhase[]{trialPhase});
newPlan.setFinalPhase(evergreenPhase);
+ newPlan.setRecurringBillingMode(BillingMode.IN_ADVANCE);
// TODO Ordering breaks
mutableCatalog.addPlan(newPlan);
newPlan.initialize((StandaloneCatalog) mutableCatalog, new URI("dummy"));
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/MockPlan.java b/catalog/src/test/java/org/killbill/billing/catalog/MockPlan.java
index 54a95ba..9fde075 100644
--- a/catalog/src/test/java/org/killbill/billing/catalog/MockPlan.java
+++ b/catalog/src/test/java/org/killbill/billing/catalog/MockPlan.java
@@ -18,6 +18,7 @@ package org.killbill.billing.catalog;
import java.util.Collection;
+import org.killbill.billing.catalog.api.BillingMode;
import org.killbill.billing.catalog.api.Plan;
import com.google.common.collect.ImmutableList;
@@ -94,6 +95,7 @@ public class MockPlan extends DefaultPlan {
setFinalPhase(finalPhase);
setInitialPhases(planPhases);
setPlansAllowedInBundle(plansAllowedInBundle);
+ setRecurringBillingMode(BillingMode.IN_ADVANCE);
finalPhase.setPlan(this);
for (final DefaultPlanPhase pp : planPhases) {
@@ -113,7 +115,7 @@ public class MockPlan extends DefaultPlan {
setName("Test");
setProduct(MockProduct.createBicycle());
setFinalPhase(mockPlanPhase);
-
+ setRecurringBillingMode(BillingMode.IN_ADVANCE);
mockPlanPhase.setPlan(this);
}
@@ -122,6 +124,7 @@ public class MockPlan extends DefaultPlan {
setProduct(new MockProduct());
setFinalPhase(new MockPlanPhase(this));
setInitialPhases(null);
+ setRecurringBillingMode(BillingMode.IN_ADVANCE);
setPlansAllowedInBundle(1);
}
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/plugin/TestCatalogPluginMapping.java b/catalog/src/test/java/org/killbill/billing/catalog/plugin/TestCatalogPluginMapping.java
index ff48895..ed37579 100644
--- a/catalog/src/test/java/org/killbill/billing/catalog/plugin/TestCatalogPluginMapping.java
+++ b/catalog/src/test/java/org/killbill/billing/catalog/plugin/TestCatalogPluginMapping.java
@@ -46,9 +46,10 @@ public class TestCatalogPluginMapping extends CatalogTestSuiteNoDB {
final StandaloneCatalog inputCatalog = XMLLoader.getObjectFromString(Resources.getResource("SpyCarAdvanced.xml").toExternalForm(), StandaloneCatalog.class);
final StandalonePluginCatalog pluginCatalog = buildStandalonePluginCatalog(inputCatalog);
- final StandaloneCatalogMapper mapper = new StandaloneCatalogMapper(inputCatalog.getCatalogName(), inputCatalog.getRecurringBillingMode());
+ final StandaloneCatalogMapper mapper = new StandaloneCatalogMapper(inputCatalog.getCatalogName());
final StandaloneCatalog output = mapper.toStandaloneCatalog(pluginCatalog, inputCatalog.getCatalogURI());
+ output.setRecurringBillingMode(inputCatalog.getRecurringBillingMode());
Assert.assertEquals(output, inputCatalog);
}
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/plugin/TestModelVersionedPluginCatalog.java b/catalog/src/test/java/org/killbill/billing/catalog/plugin/TestModelVersionedPluginCatalog.java
index 18de96f..660e234 100644
--- a/catalog/src/test/java/org/killbill/billing/catalog/plugin/TestModelVersionedPluginCatalog.java
+++ b/catalog/src/test/java/org/killbill/billing/catalog/plugin/TestModelVersionedPluginCatalog.java
@@ -24,15 +24,11 @@ import org.killbill.billing.catalog.plugin.api.VersionedPluginCatalog;
public class TestModelVersionedPluginCatalog implements VersionedPluginCatalog {
private final String catalogName;
- private final BillingMode billingMode;
-
private final Iterable<StandalonePluginCatalog> standalonePluginCatalogs;
public TestModelVersionedPluginCatalog(final String catalogName,
- final BillingMode billingMode,
final Iterable<StandalonePluginCatalog> standalonePluginCatalogs) {
this.catalogName = catalogName;
- this.billingMode = billingMode;
this.standalonePluginCatalogs = standalonePluginCatalogs;
}
@@ -41,10 +37,6 @@ public class TestModelVersionedPluginCatalog implements VersionedPluginCatalog {
return catalogName;
}
- @Override
- public BillingMode getRecurringBillingMode() {
- return billingMode;
- }
@Override
public Iterable<StandalonePluginCatalog> getStandalonePluginCatalogs() {
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/TestCatalogUpdater.java b/catalog/src/test/java/org/killbill/billing/catalog/TestCatalogUpdater.java
index f0707ed..585e2b0 100644
--- a/catalog/src/test/java/org/killbill/billing/catalog/TestCatalogUpdater.java
+++ b/catalog/src/test/java/org/killbill/billing/catalog/TestCatalogUpdater.java
@@ -39,6 +39,7 @@ import org.killbill.billing.catalog.api.TimeUnit;
import org.killbill.billing.catalog.api.user.DefaultSimplePlanDescriptor;
import org.killbill.xmlloader.XMLLoader;
import org.killbill.xmlloader.XMLWriter;
+import org.testng.Assert;
import org.testng.annotations.Test;
import com.google.common.collect.ImmutableList;
@@ -55,7 +56,7 @@ public class TestCatalogUpdater extends CatalogTestSuiteNoDB {
final DateTime now = clock.getUTCNow();
- final CatalogUpdater catalogUpdater = new CatalogUpdater(BillingMode.IN_ADVANCE, now, null);
+ final CatalogUpdater catalogUpdater = new CatalogUpdater(now, null);
final String catalogXML = catalogUpdater.getCatalogXML();
final StandaloneCatalog catalog = XMLLoader.getObjectFromStream(new URI("dummy"), new ByteArrayInputStream(catalogXML.getBytes(Charset.forName("UTF-8"))), StandaloneCatalog.class);
assertEquals(catalog.getCurrentPlans().size(), 0);
@@ -67,7 +68,7 @@ public class TestCatalogUpdater extends CatalogTestSuiteNoDB {
final DateTime now = clock.getUTCNow();
final SimplePlanDescriptor desc = new DefaultSimplePlanDescriptor("foo-monthly", "Foo", ProductCategory.BASE, Currency.EUR, BigDecimal.TEN, BillingPeriod.MONTHLY, 0, TimeUnit.UNLIMITED, ImmutableList.<String>of());
- final CatalogUpdater catalogUpdater = new CatalogUpdater(BillingMode.IN_ADVANCE, now, desc.getCurrency());
+ final CatalogUpdater catalogUpdater = new CatalogUpdater(now, desc.getCurrency());
catalogUpdater.addSimplePlanDescriptor(desc);
@@ -108,7 +109,7 @@ public class TestCatalogUpdater extends CatalogTestSuiteNoDB {
final DateTime now = clock.getUTCNow();
final SimplePlanDescriptor desc = new DefaultSimplePlanDescriptor("foo-monthly", "Foo", ProductCategory.BASE, Currency.EUR, BigDecimal.TEN, BillingPeriod.MONTHLY, 14, TimeUnit.DAYS, ImmutableList.<String>of());
- final CatalogUpdater catalogUpdater = new CatalogUpdater(BillingMode.IN_ADVANCE, now, desc.getCurrency());
+ final CatalogUpdater catalogUpdater = new CatalogUpdater(now, desc.getCurrency());
catalogUpdater.addSimplePlanDescriptor(desc);
@@ -258,6 +259,59 @@ public class TestCatalogUpdater extends CatalogTestSuiteNoDB {
@Test(groups = "fast")
+ public void testPlanWithNonFinalFixedTermPhase() throws Exception {
+
+
+ final StandaloneCatalog catalog = XMLLoader.getObjectFromString(Resources.getResource("SpyCarBasic.xml").toExternalForm(), StandaloneCatalog.class);
+
+ final MutableStaticCatalog mutableCatalog = new DefaultMutableStaticCatalog(catalog);
+
+ final DefaultProduct newProduct = new DefaultProduct();
+ newProduct.setName("Something");
+ newProduct.setCatagory(ProductCategory.BASE);
+ newProduct.initialize((StandaloneCatalog) mutableCatalog, null);
+ mutableCatalog.addProduct(newProduct);
+
+
+ final DefaultPlanPhase trialPhase = new DefaultPlanPhase();
+ trialPhase.setPhaseType(PhaseType.TRIAL);
+ trialPhase.setDuration(new DefaultDuration().setUnit(TimeUnit.DAYS).setNumber(14));
+ trialPhase.setFixed(new DefaultFixed().setFixedPrice(new DefaultInternationalPrice().setPrices(new DefaultPrice[]{new DefaultPrice().setCurrency(Currency.USD).setValue(BigDecimal.ZERO)})));
+
+ // Add a Plan with a FIXEDTERM phase
+ final DefaultPlanPhase fixedTermPhase = new DefaultPlanPhase();
+ fixedTermPhase.setPhaseType(PhaseType.FIXEDTERM);
+ fixedTermPhase.setDuration(new DefaultDuration().setUnit(TimeUnit.MONTHS).setNumber(3));
+ fixedTermPhase.setRecurring(new DefaultRecurring().setBillingPeriod(BillingPeriod.MONTHLY).setRecurringPrice(new DefaultInternationalPrice().setPrices(new DefaultPrice[]{new DefaultPrice().setCurrency(Currency.USD).setValue(BigDecimal.TEN)})));
+
+
+ final DefaultPlanPhase evergreenPhase = new DefaultPlanPhase();
+ evergreenPhase.setPhaseType(PhaseType.EVERGREEN);
+ evergreenPhase.setDuration(new DefaultDuration().setUnit(TimeUnit.MONTHS).setNumber(1));
+ evergreenPhase.setRecurring(new DefaultRecurring().setBillingPeriod(BillingPeriod.MONTHLY).setRecurringPrice(new DefaultInternationalPrice().setPrices(new DefaultPrice[]{new DefaultPrice().setCurrency(Currency.USD).setValue(BigDecimal.TEN)})));
+
+
+ final DefaultPlan newPlan = new DefaultPlan();
+ newPlan.setName("something-with-fixed-term");
+ newPlan.setPriceListName(DefaultPriceListSet.DEFAULT_PRICELIST_NAME);
+ newPlan.setProduct(newProduct);
+ newPlan.setInitialPhases(new DefaultPlanPhase[] {trialPhase, fixedTermPhase});
+ newPlan.setFinalPhase(fixedTermPhase);
+ mutableCatalog.addPlan(newPlan);
+ newPlan.initialize((StandaloneCatalog) mutableCatalog, new URI("dummy"));
+
+ final String newCatalogStr = XMLWriter.writeXML((StandaloneCatalog) mutableCatalog, StandaloneCatalog.class);
+ final StandaloneCatalog newCatalog = XMLLoader.getObjectFromStream(new URI("dummy"), new ByteArrayInputStream(newCatalogStr.getBytes(Charset.forName("UTF-8"))), StandaloneCatalog.class);
+
+ final DefaultPlan targetPlan = newCatalog.findCurrentPlan("something-with-fixed-term");
+ Assert.assertEquals(targetPlan.getInitialPhases().length, 2);
+ Assert.assertEquals(targetPlan.getInitialPhases()[1].getPhaseType(), PhaseType.FIXEDTERM);
+
+ }
+
+
+
+ @Test(groups = "fast")
public void testVerifyXML() throws Exception {
final StandaloneCatalog originalCatalog = XMLLoader.getObjectFromString(Resources.getResource("SpyCarBasic.xml").toExternalForm(), StandaloneCatalog.class);
@@ -341,6 +395,7 @@ public class TestCatalogUpdater extends CatalogTestSuiteNoDB {
" <plans>\n" +
" <plan name=\"dynamic-annual\" prettyName=\"dynamic-annual\">\n" +
" <product>Dynamic</product>\n" +
+ " <recurringBillingMode>IN_ADVANCE</recurringBillingMode>\n" +
" <initialPhases>\n" +
" <phase type=\"TRIAL\">\n" +
" <duration>\n" +
@@ -378,6 +433,7 @@ public class TestCatalogUpdater extends CatalogTestSuiteNoDB {
" </plan>\n" +
" <plan name=\"sports-monthly\" prettyName=\"sports-monthly\">\n" +
" <product>Sports</product>\n" +
+ " <recurringBillingMode>IN_ADVANCE</recurringBillingMode>\n" +
" <initialPhases>\n" +
" <phase type=\"TRIAL\">\n" +
" <duration>\n" +
@@ -414,6 +470,7 @@ public class TestCatalogUpdater extends CatalogTestSuiteNoDB {
" </plan>\n" +
" <plan name=\"standard-monthly\" prettyName=\"standard-monthly\">\n" +
" <product>Standard</product>\n" +
+ " <recurringBillingMode>IN_ADVANCE</recurringBillingMode>\n" +
" <initialPhases>\n" +
" <phase type=\"TRIAL\">\n" +
" <duration>\n" +
@@ -450,6 +507,7 @@ public class TestCatalogUpdater extends CatalogTestSuiteNoDB {
" </plan>\n" +
" <plan name=\"super-monthly\" prettyName=\"super-monthly\">\n" +
" <product>Super</product>\n" +
+ " <recurringBillingMode>IN_ADVANCE</recurringBillingMode>\n" +
" <initialPhases>\n" +
" <phase type=\"TRIAL\">\n" +
" <duration>\n" +
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/TestVersionedCatalog.java b/catalog/src/test/java/org/killbill/billing/catalog/TestVersionedCatalog.java
index c4bd0b9..b70e54f 100644
--- a/catalog/src/test/java/org/killbill/billing/catalog/TestVersionedCatalog.java
+++ b/catalog/src/test/java/org/killbill/billing/catalog/TestVersionedCatalog.java
@@ -18,24 +18,16 @@
package org.killbill.billing.catalog;
-import java.io.IOException;
import java.math.BigDecimal;
-import java.net.URISyntaxException;
-
-import javax.xml.bind.JAXBException;
-import javax.xml.transform.TransformerException;
import org.joda.time.DateTime;
import org.killbill.billing.ErrorCode;
import org.killbill.billing.catalog.api.CatalogApiException;
import org.killbill.billing.catalog.api.Currency;
-import org.killbill.billing.catalog.api.InvalidConfigException;
import org.killbill.billing.catalog.api.Plan;
-import org.killbill.billing.platform.api.KillbillService.ServiceException;
import org.testng.Assert;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
-import org.xml.sax.SAXException;
public class TestVersionedCatalog extends CatalogTestSuiteNoDB {
@@ -56,15 +48,9 @@ public class TestVersionedCatalog extends CatalogTestSuiteNoDB {
final DateTime dt214 = new DateTime("2011-02-14T00:01:00+00:00");
final DateTime dt3 = new DateTime("2011-03-03T00:01:00+00:00");
- // New subscription
- try {
- vc.findPlan("pistol-monthly", dt0, dt0);
- Assert.fail("Exception should have been thrown there are no plans for this date");
- } catch (CatalogApiException e) {
- // Expected behaviour
- log.error("Expected exception", e);
+ // We find it although the date provided is too early because we default to first catalog version
+ final Plan newSubPlan0 = vc.findPlan("pistol-monthly", dt0, dt0);
- }
final Plan newSubPlan1 = vc.findPlan("pistol-monthly", dt1, dt1);
final Plan newSubPlan2 = vc.findPlan("pistol-monthly", dt2, dt2);
final Plan newSubPlan214 = vc.findPlan("pistol-monthly", dt214, dt214);
@@ -88,13 +74,35 @@ public class TestVersionedCatalog extends CatalogTestSuiteNoDB {
}
@Test(groups = "fast")
- public void testErrorOnDateTooEarly() {
+ public void testErrorOnDateTooEarly() throws CatalogApiException {
final DateTime dt0 = new DateTime("1977-01-01T00:00:00+00:00");
+
+ // We find it although the date provided is too early because we default to first catalog version
+ vc.findPlan("shotgun-monthly", dt0);
+
try {
- vc.findPlan("foo", dt0);
+ // We **don't find it** because date is too early and not part of first catalog version
+ vc.findPlan("shotgun-quarterly", dt0);
Assert.fail("Date is too early an exception should have been thrown");
} catch (CatalogApiException e) {
- Assert.assertEquals(e.getCode(), ErrorCode.CAT_NO_CATALOG_FOR_GIVEN_DATE.getCode());
+ Assert.assertEquals(e.getCode(), ErrorCode.CAT_NO_SUCH_PLAN.getCode());
+ }
+ }
+
+
+ @Test(groups = "fast")
+ public void testWithDeletedPlan() throws CatalogApiException {
+
+ // We find it because this is version 2 whose effectiveDate is "2011-02-02T00:00:00+00:00"
+ vc.findPlan("shotgun-quarterly", new DateTime("2011-02-02T00:01:00+00:00"));
+
+ try {
+ // We **don't find it** because date provided matches version 3 where plan was removed
+ vc.findPlan("shotgun-quarterly", new DateTime("2011-03-03T00:01:00+00:00"));
+ Assert.fail("Plan has been removed");
+ } catch (CatalogApiException e) {
+ Assert.assertEquals(e.getCode(), ErrorCode.CAT_NO_SUCH_PLAN.getCode());
}
+
}
}
catalog/src/test/resources/catalogTest.xml 48(+47 -1)
diff --git a/catalog/src/test/resources/catalogTest.xml b/catalog/src/test/resources/catalogTest.xml
index d15d55f..f8d9f93 100644
--- a/catalog/src/test/resources/catalogTest.xml
+++ b/catalog/src/test/resources/catalogTest.xml
@@ -143,6 +143,11 @@
<alignment>CHANGE_OF_PRICELIST</alignment>
</changeAlignmentCase>
<changeAlignmentCase>
+ <fromPriceList>notrial</fromPriceList>
+ <toPriceList>trial</toPriceList>
+ <alignment>CHANGE_OF_PLAN</alignment>
+ </changeAlignmentCase>
+ <changeAlignmentCase>
<alignment>START_OF_SUBSCRIPTION</alignment>
</changeAlignmentCase>
</changeAlignment>
@@ -297,6 +302,43 @@
</recurring>
</finalPhase>
</plan>
+ <plan name="blowdart-monthly-trial">
+ <product>Blowdart</product>
+ <initialPhases>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <fixed>
+ <fixedPrice>
+ </fixedPrice>
+ </fixed>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <recurring>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price>
+ <currency>USD</currency>
+ <value>29.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>29.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>29.95</value>
+ </price>
+ </recurringPrice>
+ </recurring>
+ </finalPhase>
+ </plan>
<plan name="pistol-weekly">
<product>Pistol</product>
<initialPhases>
@@ -1333,7 +1375,11 @@
<plans>
<plan>blowdart-monthly-notrial</plan>
<plan>pistol-monthly-notrial</plan>
-
+ </plans>
+ </childPriceList>
+ <childPriceList name="trial">
+ <plans>
+ <plan>blowdart-monthly-trial</plan>
</plans>
</childPriceList>
</priceLists>
diff --git a/catalog/src/test/resources/versionedCatalog/WeaponsHireSmall-2.xml b/catalog/src/test/resources/versionedCatalog/WeaponsHireSmall-2.xml
index df1c09d..3fee3ea 100644
--- a/catalog/src/test/resources/versionedCatalog/WeaponsHireSmall-2.xml
+++ b/catalog/src/test/resources/versionedCatalog/WeaponsHireSmall-2.xml
@@ -183,6 +183,44 @@
</recurring>
</finalPhase>
</plan>
+ <plan name="shotgun-quarterly">
+ <product>Shotgun</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>
+ <number>-1</number>
+ </duration>
+ <recurring>
+ <billingPeriod>QUARTERLY</billingPeriod>
+ <recurringPrice>
+ <price>
+ <currency>USD</currency>
+ <value>249.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>149.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>169.95</value>
+ </price>
+ </recurringPrice>
+ </recurring>
+ </finalPhase>
+ </plan>
<plan name="shotgun-annual">
<product>Shotgun</product>
<initialPhases>
@@ -285,6 +323,7 @@
<plans>
<plan>pistol-monthly</plan>
<plan>shotgun-monthly</plan>
+ <plan>shotgun-quarterly</plan>
<plan>shotgun-annual</plan>
<plan>laser-scope-monthly</plan>
<plan>extra-ammo-monthly</plan>
currency/pom.xml 2(+1 -1)
diff --git a/currency/pom.xml b/currency/pom.xml
index d7cf768..6430f67 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.19.0-SNAPSHOT</version>
+ <version>0.19.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-currency</artifactId>
entitlement/pom.xml 8(+2 -6)
diff --git a/entitlement/pom.xml b/entitlement/pom.xml
index 4373590..fd73092 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.19.0-SNAPSHOT</version>
+ <version>0.19.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-entitlement</artifactId>
@@ -75,7 +75,7 @@
</dependency>
<dependency>
<groupId>org.antlr</groupId>
- <artifactId>stringtemplate</artifactId>
+ <artifactId>ST4</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
@@ -88,10 +88,6 @@
<scope>test</scope>
</dependency>
<dependency>
- <groupId>org.jdbi</groupId>
- <artifactId>jdbi</artifactId>
- </dependency>
- <dependency>
<groupId>org.kill-bill.billing</groupId>
<artifactId>killbill-account</artifactId>
</dependency>
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 0c1c5b7..6648e03 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
@@ -1,6 +1,6 @@
/*
- * Copyright 2014-2016 Groupon, Inc
- * Copyright 2014-2016 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
@@ -19,6 +19,7 @@ package org.killbill.billing.entitlement.api;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
@@ -42,6 +43,7 @@ import org.killbill.billing.entitlement.block.BlockingChecker.BlockingAggregator
import org.killbill.billing.entitlement.block.DefaultBlockingChecker.DefaultBlockingAggregator;
import org.killbill.billing.junction.DefaultBlockingState;
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
@@ -53,13 +55,13 @@ import com.google.common.collect.Sets;
// Given an event stream (across one or multiple entitlements), insert the blocking events at the right place
public class BlockingStateOrdering extends EntitlementOrderingBase {
- private static final BlockingStateOrdering INSTANCE = new BlockingStateOrdering();
+ @VisibleForTesting
+ static final BlockingStateOrdering INSTANCE = new BlockingStateOrdering();
private BlockingStateOrdering() {}
public static void insertSorted(final Iterable<Entitlement> entitlements, final InternalTenantContext internalTenantContext, final LinkedList<SubscriptionEvent> inputAndOutputResult) {
INSTANCE.computeEvents(entitlements, internalTenantContext, inputAndOutputResult);
-
}
private void computeEvents(final Iterable<Entitlement> entitlements, final InternalTenantContext internalTenantContext, final LinkedList<SubscriptionEvent> inputAndOutputResult) {
@@ -71,6 +73,14 @@ public class BlockingStateOrdering extends EntitlementOrderingBase {
blockingStates.addAll(((DefaultEntitlement) entitlement).getEventsStream().getBlockingStates());
}
+ computeEvents(new LinkedList<UUID>(allEntitlementUUIDs), blockingStates, internalTenantContext, inputAndOutputResult);
+ }
+
+ @VisibleForTesting
+ void computeEvents(final LinkedList<UUID> allEntitlementUUIDs, final Collection<BlockingState> blockingStates, final InternalTenantContext internalTenantContext, final LinkedList<SubscriptionEvent> inputAndOutputResult) {
+ // Make sure the ordering is stable
+ Collections.sort(allEntitlementUUIDs);
+
final SupportForOlderVersionThan_0_17_X backwardCompatibleContext = new SupportForOlderVersionThan_0_17_X(inputAndOutputResult, blockingStates);
// Trust the incoming ordering here: blocking states were sorted using ProxyBlockingStateDao#sortedCopy
@@ -107,12 +117,7 @@ public class BlockingStateOrdering extends EntitlementOrderingBase {
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;
- }
+ shouldContinue = compareBlockingStateWithNextSubscriptionEvent(currentBlockingState, cur) > 0;
break;
case 1:
shouldContinue = true;
@@ -169,9 +174,75 @@ public class BlockingStateOrdering extends EntitlementOrderingBase {
outputNewEvents.add(toSubscriptionEvent(prevNext[0], prevNext[1], targetEntitlementId, currentBlockingState, t, internalTenantContext));
}
}
+
return index;
}
+ private int compareBlockingStateWithNextSubscriptionEvent(final BlockingState blockingState, final SubscriptionEvent next) {
+ final String serviceName = blockingState.getService();
+
+ // For consistency, make sure entitlement-service and billing-service events always happen in a
+ // deterministic order (e.g. after other services for STOP events and before for START events)
+ if ((DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME.equals(serviceName) ||
+ BILLING_SERVICE_NAME.equals(serviceName) ||
+ ENT_BILLING_SERVICE_NAME.equals(serviceName)) &&
+ !(DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME.equals(next.getServiceName()) ||
+ BILLING_SERVICE_NAME.equals(next.getServiceName()) ||
+ ENT_BILLING_SERVICE_NAME.equals(next.getServiceName()))) {
+ // first is an entitlement-service or billing-service event, but not second
+ if (blockingState.isBlockBilling() || blockingState.isBlockEntitlement()) {
+ // PAUSE_ and STOP_ events go last
+ return 1;
+ } else {
+ return -1;
+ }
+ } else if ((DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME.equals(next.getServiceName()) ||
+ BILLING_SERVICE_NAME.equals(next.getServiceName()) ||
+ ENT_BILLING_SERVICE_NAME.equals(next.getServiceName())) &&
+ !(DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME.equals(serviceName) ||
+ BILLING_SERVICE_NAME.equals(serviceName) ||
+ ENT_BILLING_SERVICE_NAME.equals(serviceName))) {
+ // second is an entitlement-service or billing-service event, but not first
+ if (next.getSubscriptionEventType().equals(SubscriptionEventType.START_ENTITLEMENT) ||
+ next.getSubscriptionEventType().equals(SubscriptionEventType.START_BILLING) ||
+ next.getSubscriptionEventType().equals(SubscriptionEventType.RESUME_ENTITLEMENT) ||
+ next.getSubscriptionEventType().equals(SubscriptionEventType.RESUME_BILLING) ||
+ next.getSubscriptionEventType().equals(SubscriptionEventType.PHASE) ||
+ next.getSubscriptionEventType().equals(SubscriptionEventType.CHANGE)) {
+ return 1;
+ } else if (next.getSubscriptionEventType().equals(SubscriptionEventType.PAUSE_ENTITLEMENT) ||
+ next.getSubscriptionEventType().equals(SubscriptionEventType.PAUSE_BILLING) ||
+ next.getSubscriptionEventType().equals(SubscriptionEventType.STOP_ENTITLEMENT) ||
+ next.getSubscriptionEventType().equals(SubscriptionEventType.STOP_BILLING)) {
+ return -1;
+ } else {
+ // Default behavior
+ return 1;
+ }
+ } else if (isStartEntitlement(blockingState)) {
+ // START_ENTITLEMENT is always first
+ return -1;
+ } else if (next.getSubscriptionEventType().equals(SubscriptionEventType.START_ENTITLEMENT)) {
+ // START_ENTITLEMENT is always first
+ return 1;
+ } else if (next.getSubscriptionEventType().equals(SubscriptionEventType.STOP_BILLING)) {
+ // STOP_BILLING is always last
+ return -1;
+ } else if (next.getSubscriptionEventType().equals(SubscriptionEventType.START_BILLING)) {
+ // START_BILLING is first after START_ENTITLEMENT
+ return 1;
+ } else if (isStopEntitlement(blockingState)) {
+ // STOP_ENTITLEMENT is last after STOP_BILLING
+ return 1;
+ } else if (next.getSubscriptionEventType().equals(SubscriptionEventType.STOP_ENTITLEMENT)) {
+ // STOP_ENTITLEMENT is last after STOP_BILLING
+ return -1;
+ } else {
+ // Trust the current ordering
+ return 1;
+ }
+ }
+
// Extract prev and next events in the stream events for that particular target subscription from the insertionEvent
private SubscriptionEvent[] findPrevNext(final List<SubscriptionEvent> events, final UUID targetEntitlementId, final SubscriptionEvent insertionEvent) {
// Find prev/next event for the same entitlement
@@ -390,11 +461,11 @@ public class BlockingStateOrdering extends EntitlementOrderingBase {
bs.getEffectiveDate());
final List<SubscriptionEventType> result = new ArrayList<SubscriptionEventType>(4);
- if (fixedBlockingState.getStateName().equals(DefaultEntitlementApi.ENT_STATE_START)) {
+ if (isStartEntitlement(fixedBlockingState)) {
isEntitlementStarted = true;
result.add(SubscriptionEventType.START_ENTITLEMENT);
return result;
- } else if (fixedBlockingState.getStateName().equals(DefaultEntitlementApi.ENT_STATE_CANCELLED)) {
+ } else if (isStopEntitlement(fixedBlockingState)) {
isEntitlementStopped = true;
result.add(SubscriptionEventType.STOP_ENTITLEMENT);
return result;
@@ -450,6 +521,16 @@ public class BlockingStateOrdering extends EntitlementOrderingBase {
}
}
+ private static boolean isStartEntitlement(final BlockingState blockingState) {
+ return DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME.equals(blockingState.getService()) &&
+ DefaultEntitlementApi.ENT_STATE_START.equals(blockingState.getStateName());
+ }
+
+ private static boolean isStopEntitlement(final BlockingState blockingState) {
+ return DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME.equals(blockingState.getService()) &&
+ DefaultEntitlementApi.ENT_STATE_CANCELLED.equals(blockingState.getStateName());
+ }
+
//
// The logic to add the missing START_ENTITLEMENT for older subscriptions is contained in this class. When we want/need to drop backward compatibility we can
// simply drop this class and where it is called.
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 ad4fcc5..5491cae 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
@@ -37,6 +37,7 @@ 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.PlanPhaseSpecifier;
import org.killbill.billing.catalog.api.PlanSpecifier;
import org.killbill.billing.catalog.api.PriceList;
import org.killbill.billing.catalog.api.Product;
@@ -51,6 +52,7 @@ import org.killbill.billing.entitlement.engine.core.EntitlementNotificationKey;
import org.killbill.billing.entitlement.engine.core.EntitlementNotificationKeyAction;
import org.killbill.billing.entitlement.engine.core.EntitlementUtils;
import org.killbill.billing.entitlement.engine.core.EventsStreamBuilder;
+import org.killbill.billing.entitlement.logging.EntitlementLoggingHelper;
import org.killbill.billing.entitlement.plugin.api.EntitlementContext;
import org.killbill.billing.entitlement.plugin.api.OperationType;
import org.killbill.billing.entity.EntityBase;
@@ -79,6 +81,7 @@ import com.google.common.collect.ImmutableList;
import static org.killbill.billing.entitlement.logging.EntitlementLoggingHelper.logCancelEntitlement;
import static org.killbill.billing.entitlement.logging.EntitlementLoggingHelper.logChangePlan;
import static org.killbill.billing.entitlement.logging.EntitlementLoggingHelper.logUncancelEntitlement;
+import static org.killbill.billing.entitlement.logging.EntitlementLoggingHelper.logUndoChangePlan;
import static org.killbill.billing.entitlement.logging.EntitlementLoggingHelper.logUpdateBCD;
public class DefaultEntitlement extends EntityBase implements Entitlement {
@@ -343,7 +346,8 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
final InternalCallContext contextWithValidAccountRecordId = internalCallContextFactory.createInternalCallContext(getAccountId(), callContext);
- final DateTime billingEffectiveCancelDate = dateHelper.fromLocalDateAndReferenceTimeWithMinimum(billingEffectiveDate, getEventsStream().getSubscriptionBase().getStartDate(), contextWithValidAccountRecordId);
+ final DateTime now = clock.getUTCNow();
+ final DateTime billingEffectiveCancelDate = dateHelper.fromLocalDateAndReferenceTimeWithMinimum(billingEffectiveDate, getEventsStream().getSubscriptionBase().getStartDate(), now, contextWithValidAccountRecordId);
try {
if (overrideBillingEffectiveDate) {
getSubscriptionBase().cancelWithDate(billingEffectiveCancelDate, callContext);
@@ -354,7 +358,7 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
throw new EntitlementApiException(e);
}
- final DateTime entitlementEffectiveCancelDate = dateHelper.fromLocalDateAndReferenceTimeWithMinimum(entitlementEffectiveDate, getEventsStream().getEntitlementEffectiveStartDateTime(), contextWithValidAccountRecordId);
+ final DateTime entitlementEffectiveCancelDate = dateHelper.fromLocalDateAndReferenceTimeWithMinimum(entitlementEffectiveDate, getEventsStream().getEntitlementEffectiveStartDateTime(), now, contextWithValidAccountRecordId);
final BlockingState newBlockingState = new DefaultBlockingState(getId(), BlockingStateType.SUBSCRIPTION, DefaultEntitlementApi.ENT_STATE_CANCELLED, EntitlementService.ENTITLEMENT_SERVICE_NAME, true, true, false, entitlementEffectiveCancelDate);
final Collection<NotificationEvent> notificationEvents = new ArrayList<NotificationEvent>();
final Collection<BlockingState> addOnsBlockingStates = computeAddOnBlockingStates(entitlementEffectiveCancelDate, notificationEvents, callContext, contextWithValidAccountRecordId);
@@ -391,7 +395,7 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
false);
final List<BaseEntitlementWithAddOnsSpecifier> baseEntitlementWithAddOnsSpecifierList = new ArrayList<BaseEntitlementWithAddOnsSpecifier>();
baseEntitlementWithAddOnsSpecifierList.add(baseEntitlementWithAddOnsSpecifier);
- final EntitlementContext pluginContext = new DefaultEntitlementContext(OperationType.UNCANCEL_SUBSCRIPTION,
+ final EntitlementContext pluginContext = new DefaultEntitlementContext(OperationType.UNDO_PENDING_SUBSCRIPTION_OPERATION,
getAccountId(),
null,
baseEntitlementWithAddOnsSpecifierList,
@@ -498,7 +502,8 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
throw new EntitlementApiException(e);
}
- final DateTime effectiveCancelDate = dateHelper.fromLocalDateAndReferenceTimeWithMinimum(entitlementEffectiveDate, getEventsStream().getEntitlementEffectiveStartDateTime(), contextWithValidAccountRecordId);
+ final DateTime now = clock.getUTCNow();
+ final DateTime effectiveCancelDate = dateHelper.fromLocalDateAndReferenceTimeWithMinimum(entitlementEffectiveDate, getEventsStream().getEntitlementEffectiveStartDateTime(), now, contextWithValidAccountRecordId);
final BlockingState newBlockingState = new DefaultBlockingState(getId(), BlockingStateType.SUBSCRIPTION, DefaultEntitlementApi.ENT_STATE_CANCELLED, EntitlementService.ENTITLEMENT_SERVICE_NAME, true, true, false, effectiveCancelDate);
final Collection<NotificationEvent> notificationEvents = new ArrayList<NotificationEvent>();
final Collection<BlockingState> addOnsBlockingStates = computeAddOnBlockingStates(effectiveCancelDate, notificationEvents, callContext, contextWithValidAccountRecordId);
@@ -538,7 +543,7 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
@Override
- public Entitlement changePlan(final PlanSpecifier spec, final List<PlanPhasePriceOverride> overrides, final Iterable<PluginProperty> properties, final CallContext callContext) throws EntitlementApiException {
+ public Entitlement changePlan(final PlanPhaseSpecifier spec, final List<PlanPhasePriceOverride> overrides, final Iterable<PluginProperty> properties, final CallContext callContext) throws EntitlementApiException {
logChangePlan(log, this, spec, overrides, null, null);
@@ -609,7 +614,52 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
}
@Override
- public Entitlement changePlanWithDate(final PlanSpecifier spec, final List<PlanPhasePriceOverride> overrides, @Nullable final LocalDate effectiveDate, final Iterable<PluginProperty> properties, final CallContext callContext) throws EntitlementApiException {
+ public void undoChangePlan(final Iterable<PluginProperty> properties, final CallContext callContext) throws EntitlementApiException {
+
+ logUndoChangePlan(log, this);
+
+ checkForPermissions(Permission.ENTITLEMENT_CAN_CHANGE_PLAN, callContext);
+
+ // Get the latest state from disk
+ refresh(callContext);
+
+ final BaseEntitlementWithAddOnsSpecifier baseEntitlementWithAddOnsSpecifier = new DefaultBaseEntitlementWithAddOnsSpecifier(
+ getBundleId(),
+ getExternalKey(),
+ null,
+ null,
+ null,
+ false);
+ final List<BaseEntitlementWithAddOnsSpecifier> baseEntitlementWithAddOnsSpecifierList = new ArrayList<BaseEntitlementWithAddOnsSpecifier>();
+ baseEntitlementWithAddOnsSpecifierList.add(baseEntitlementWithAddOnsSpecifier);
+ final EntitlementContext pluginContext = new DefaultEntitlementContext(OperationType.UNDO_PENDING_SUBSCRIPTION_OPERATION,
+ getAccountId(),
+ null,
+ baseEntitlementWithAddOnsSpecifierList,
+ null,
+ properties,
+ callContext);
+
+ final WithEntitlementPlugin<Void> undoChangePlanEntitlementWithPlugin = new WithEntitlementPlugin<Void>() {
+
+ @Override
+ public Void doCall(final EntitlementApi entitlementApi, final EntitlementContext updatedPluginContext) throws EntitlementApiException {
+
+ try {
+ getSubscriptionBase().undoChangePlan(callContext);
+ } catch (final SubscriptionBaseApiException e) {
+ throw new EntitlementApiException(e);
+ }
+ return null;
+ }
+ };
+
+ pluginExecution.executeWithPlugin(undoChangePlanEntitlementWithPlugin, pluginContext);
+
+ }
+
+ @Override
+ public Entitlement changePlanWithDate(final PlanPhaseSpecifier spec, final List<PlanPhasePriceOverride> overrides, @Nullable final LocalDate effectiveDate, final Iterable<PluginProperty> properties, final CallContext callContext) throws EntitlementApiException {
logChangePlan(log, this, spec, overrides, effectiveDate, null);
@@ -646,7 +696,8 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
final InternalCallContext context = internalCallContextFactory.createInternalCallContext(getAccountId(), callContext);
- final DateTime effectiveChangeDate = effectiveDate != null ? dateHelper.fromLocalDateAndReferenceTime(effectiveDate, context) : null;
+ final DateTime now = clock.getUTCNow();
+ final DateTime effectiveChangeDate = effectiveDate != null ? dateHelper.fromLocalDateAndReferenceTime(effectiveDate, now, context) : null;
final DateTime resultingEffectiveDate;
try {
@@ -685,7 +736,7 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
}
@Override
- public Entitlement changePlanOverrideBillingPolicy(final PlanSpecifier spec, final List<PlanPhasePriceOverride> overrides, final LocalDate unused, final BillingActionPolicy actionPolicy, final Iterable<PluginProperty> properties, final CallContext callContext) throws EntitlementApiException {
+ public Entitlement changePlanOverrideBillingPolicy(final PlanPhaseSpecifier spec, final List<PlanPhasePriceOverride> overrides, final LocalDate unused, final BillingActionPolicy actionPolicy, final Iterable<PluginProperty> properties, final CallContext callContext) throws EntitlementApiException {
logChangePlan(log, this, spec, overrides, null, actionPolicy);
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 82e0c05..d36367d 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
@@ -44,6 +44,7 @@ import org.killbill.billing.entitlement.EventsStream;
import org.killbill.billing.entitlement.api.EntitlementPluginExecution.WithEntitlementPlugin;
import org.killbill.billing.entitlement.api.svcs.DefaultEntitlementApiBase;
import org.killbill.billing.entitlement.block.BlockingChecker;
+import org.killbill.billing.entitlement.block.BlockingChecker.BlockingAggregator;
import org.killbill.billing.entitlement.dao.BlockingStateDao;
import org.killbill.billing.entitlement.engine.core.EntitlementUtils;
import org.killbill.billing.entitlement.engine.core.EventsStreamBuilder;
@@ -118,12 +119,12 @@ public class DefaultEntitlementApi extends DefaultEntitlementApiBase implements
this.entitlementUtils = entitlementUtils;
this.pluginExecution = pluginExecution;
this.securityApi = securityApi;
- this.dateHelper = new EntitlementDateHelper(clock);
+ this.dateHelper = new EntitlementDateHelper();
}
@Override
public Entitlement createBaseEntitlement(final UUID accountId, final PlanPhaseSpecifier planPhaseSpecifier, final String externalKey, final List<PlanPhasePriceOverride> overrides,
- @Nullable final LocalDate entitlementEffectiveDate, @Nullable final LocalDate billingEffectiveDate, final boolean isMigrated,
+ @Nullable final LocalDate entitlementEffectiveDate, @Nullable final LocalDate billingEffectiveDate, final boolean isMigrated, final boolean renameCancelledBundleIfExist,
final Iterable<PluginProperty> properties, final CallContext callContext) throws EntitlementApiException {
logCreateEntitlement(log, null, planPhaseSpecifier, overrides, entitlementEffectiveDate, billingEffectiveDate);
@@ -156,15 +157,19 @@ public class DefaultEntitlementApi extends DefaultEntitlementApiBase implements
final InternalCallContext contextWithValidAccountRecordId = internalCallContextFactory.createInternalCallContext(accountId, callContext);
try {
- final SubscriptionBaseBundle bundle = subscriptionBaseInternalApi.createBundleForAccount(accountId, externalKey, contextWithValidAccountRecordId);
+ final DateTime now = clock.getUTCNow();
+
+ final DateTime entitlementRequestedDate = dateHelper.fromLocalDateAndReferenceTime(baseEntitlementWithAddOnsSpecifier.getEntitlementEffectiveDate(), now, contextWithValidAccountRecordId);
+ checkForAccountBlockingChange(accountId, entitlementRequestedDate, contextWithValidAccountRecordId);
+
+ final SubscriptionBaseBundle bundle = subscriptionBaseInternalApi.createBundleForAccount(accountId, externalKey, renameCancelledBundleIfExist, contextWithValidAccountRecordId);
final BaseEntitlementWithAddOnsSpecifier baseEntitlementWithAddOnsSpecifier = getFirstBaseEntitlementWithAddOnsSpecifier(updatedPluginContext.getBaseEntitlementWithAddOnsSpecifiers());
final EntitlementSpecifier specifier = getFirstEntitlementSpecifier(baseEntitlementWithAddOnsSpecifier);
- final DateTime billingRequestedDate = dateHelper.fromLocalDateAndReferenceTime(baseEntitlementWithAddOnsSpecifier.getBillingEffectiveDate(), contextWithValidAccountRecordId);
+ final DateTime billingRequestedDate = dateHelper.fromLocalDateAndReferenceTime(baseEntitlementWithAddOnsSpecifier.getBillingEffectiveDate(), now, contextWithValidAccountRecordId);
final SubscriptionBase subscription = subscriptionBaseInternalApi.createSubscription(bundle.getId(), specifier.getPlanPhaseSpecifier(), specifier.getOverrides(), billingRequestedDate, isMigrated, contextWithValidAccountRecordId);
- final DateTime entitlementRequestedDate = dateHelper.fromLocalDateAndReferenceTime(baseEntitlementWithAddOnsSpecifier.getEntitlementEffectiveDate(), contextWithValidAccountRecordId);
final BlockingState newBlockingState = new DefaultBlockingState(subscription.getId(), BlockingStateType.SUBSCRIPTION, DefaultEntitlementApi.ENT_STATE_START, EntitlementService.ENTITLEMENT_SERVICE_NAME, false, false, false, entitlementRequestedDate);
entitlementUtils.setBlockingStatesAndPostBlockingTransitionEvent(ImmutableList.<BlockingState>of(newBlockingState), subscription.getBundleId(), contextWithValidAccountRecordId);
@@ -201,7 +206,7 @@ public class DefaultEntitlementApi extends DefaultEntitlementApiBase implements
}
@Override
- public List<Entitlement> createBaseEntitlementsWithAddOns(final UUID accountId, final Iterable<BaseEntitlementWithAddOnsSpecifier> baseEntitlementWithAddOnsSpecifiers, final Iterable<PluginProperty> properties, final CallContext callContext) throws EntitlementApiException {
+ public List<Entitlement> createBaseEntitlementsWithAddOns(final UUID accountId, final Iterable<BaseEntitlementWithAddOnsSpecifier> baseEntitlementWithAddOnsSpecifiers, final boolean renameCancelledBundleIfExist, final Iterable<PluginProperty> properties, final CallContext callContext) throws EntitlementApiException {
logCreateEntitlementsWithAOs(log, baseEntitlementWithAddOnsSpecifiers);
@@ -220,9 +225,25 @@ public class DefaultEntitlementApi extends DefaultEntitlementApiBase implements
try {
- final List<SubscriptionBaseWithAddOns> subscriptionsWithAddOns = subscriptionBaseInternalApi.createBaseSubscriptionsWithAddOns(accountId, baseEntitlementWithAddOnsSpecifiers, contextWithValidAccountRecordId);
+ final DateTime now = clock.getUTCNow();
+
+ // Verify if operation is allowed by looking for is_block_change on Account
+ final Iterator<BaseEntitlementWithAddOnsSpecifier> it1 = baseEntitlementWithAddOnsSpecifiers.iterator();
+ DateTime upTo = null;
+ while (it1.hasNext()) {
+ final BaseEntitlementWithAddOnsSpecifier baseEntitlementWithAddOnsSpecifier = it1.next();
+ final DateTime cur = dateHelper.fromLocalDateAndReferenceTime(baseEntitlementWithAddOnsSpecifier.getEntitlementEffectiveDate(), now, contextWithValidAccountRecordId);
+ if (cur != null) {
+ upTo = upTo == null || upTo.compareTo(cur) < 0 ? cur : upTo;
+ }
+ }
+ // Note that to fully check for block_change we should also look for BlockingState at the BUNDLE/SUBSCRIPTION level in case some of the input contain a BP that already exists.
+ checkForAccountBlockingChange(accountId, upTo, contextWithValidAccountRecordId);
+
+ final List<SubscriptionBaseWithAddOns> subscriptionsWithAddOns = subscriptionBaseInternalApi.createBaseSubscriptionsWithAddOns(accountId, baseEntitlementWithAddOnsSpecifiers, renameCancelledBundleIfExist, contextWithValidAccountRecordId);
final Map<BlockingState, UUID> blockingStateMap = new HashMap<BlockingState, UUID>();
int i = 0;
+
for (final Iterator<BaseEntitlementWithAddOnsSpecifier> it = baseEntitlementWithAddOnsSpecifiers.iterator(); it.hasNext(); i++) {
final BaseEntitlementWithAddOnsSpecifier baseEntitlementWithAddOnsSpecifier = it.next();
for (final SubscriptionBase subscriptionBase : subscriptionsWithAddOns.get(i).getSubscriptionBaseList()) {
@@ -230,7 +251,7 @@ public class DefaultEntitlementApi extends DefaultEntitlementApiBase implements
DefaultEntitlementApi.ENT_STATE_START,
EntitlementService.ENTITLEMENT_SERVICE_NAME,
false, false, false,
- dateHelper.fromLocalDateAndReferenceTime(baseEntitlementWithAddOnsSpecifier.getEntitlementEffectiveDate(), contextWithValidAccountRecordId));
+ dateHelper.fromLocalDateAndReferenceTime(baseEntitlementWithAddOnsSpecifier.getEntitlementEffectiveDate(), now, contextWithValidAccountRecordId));
blockingStateMap.put(blockingState, subscriptionsWithAddOns.get(i).getBundleId());
}
}
@@ -288,6 +309,11 @@ public class DefaultEntitlementApi extends DefaultEntitlementApiBase implements
final WithEntitlementPlugin<Entitlement> addEntitlementWithPlugin = new WithEntitlementPlugin<Entitlement>() {
@Override
public Entitlement doCall(final EntitlementApi entitlementApi, final EntitlementContext updatedPluginContext) throws EntitlementApiException {
+
+ final DateTime now = clock.getUTCNow();
+ final InternalCallContext context = internalCallContextFactory.createInternalCallContext(bundleId, ObjectType.BUNDLE, callContext);
+ final DateTime entitlementRequestedDate = dateHelper.fromLocalDateAndReferenceTime(baseEntitlementWithAddOnsSpecifier.getEntitlementEffectiveDate(), now, context);
+
final EventsStream eventsStreamForBaseSubscription = eventsStreamBuilder.buildForBaseSubscription(bundleId, callContext);
if (eventsStreamForBaseSubscription.isEntitlementCancelled() ||
@@ -298,7 +324,7 @@ public class DefaultEntitlementApi extends DefaultEntitlementApiBase implements
}
// Check the base entitlement state is not blocked
- if (eventsStreamForBaseSubscription.isBlockChange()) {
+ if (eventsStreamForBaseSubscription.isBlockChange(entitlementRequestedDate)) {
throw new EntitlementApiException(new BlockingApiException(ErrorCode.BLOCK_BLOCKED_ACTION, BlockingChecker.ACTION_CHANGE, BlockingChecker.TYPE_SUBSCRIPTION, eventsStreamForBaseSubscription.getEntitlementId().toString()));
}
@@ -306,11 +332,9 @@ public class DefaultEntitlementApi extends DefaultEntitlementApiBase implements
final BaseEntitlementWithAddOnsSpecifier baseEntitlementWithAddOnsSpecifier = getFirstBaseEntitlementWithAddOnsSpecifier(updatedPluginContext.getBaseEntitlementWithAddOnsSpecifiers());
final EntitlementSpecifier specifier = getFirstEntitlementSpecifier(baseEntitlementWithAddOnsSpecifier);
- final InternalCallContext context = internalCallContextFactory.createInternalCallContext(eventsStreamForBaseSubscription.getAccountId(), callContext);
- final DateTime billingRequestedDate = dateHelper.fromLocalDateAndReferenceTime(baseEntitlementWithAddOnsSpecifier.getBillingEffectiveDate(), context);
+ final DateTime billingRequestedDate = dateHelper.fromLocalDateAndReferenceTime(baseEntitlementWithAddOnsSpecifier.getBillingEffectiveDate(), now, context);
final SubscriptionBase subscription = subscriptionBaseInternalApi.createSubscription(bundleId, specifier.getPlanPhaseSpecifier(), specifier.getOverrides(), billingRequestedDate, isMigrated, context);
- final DateTime entitlementRequestedDate = dateHelper.fromLocalDateAndReferenceTime(baseEntitlementWithAddOnsSpecifier.getEntitlementEffectiveDate(), context);
final BlockingState newBlockingState = new DefaultBlockingState(subscription.getId(), BlockingStateType.SUBSCRIPTION, DefaultEntitlementApi.ENT_STATE_START, EntitlementService.ENTITLEMENT_SERVICE_NAME, false, false, false, entitlementRequestedDate);
entitlementUtils.setBlockingStatesAndPostBlockingTransitionEvent(ImmutableList.<BlockingState>of(newBlockingState), subscription.getBundleId(), context);
@@ -334,7 +358,8 @@ public class DefaultEntitlementApi extends DefaultEntitlementApiBase implements
final SubscriptionBase baseSubscription = subscriptionBaseInternalApi.getBaseSubscription(bundleId, internalContext);
final InternalTenantContext contextWithValidAccountRecordId = internalCallContextFactory.createInternalTenantContext(bundle.getAccountId(), context);
- final DateTime requestedDate = dateHelper.fromLocalDateAndReferenceTime(effectiveDate, contextWithValidAccountRecordId);
+ final DateTime now = clock.getUTCNow();
+ final DateTime requestedDate = dateHelper.fromLocalDateAndReferenceTime(effectiveDate, now, contextWithValidAccountRecordId);
return subscriptionBaseInternalApi.getDryRunChangePlanStatus(baseSubscription.getId(), targetProductName, requestedDate, contextWithValidAccountRecordId);
} catch (final SubscriptionBaseApiException e) {
throw new EntitlementApiException(e);
@@ -412,7 +437,6 @@ public class DefaultEntitlementApi extends DefaultEntitlementApiBase implements
final InternalCallContext contextWithValidAccountRecordId = internalCallContextFactory.createInternalCallContext(bundleId, ObjectType.BUNDLE, context);
super.resume(bundleId, localEffectiveDate, properties, contextWithValidAccountRecordId);
-
}
@Override
@@ -473,7 +497,8 @@ public class DefaultEntitlementApi extends DefaultEntitlementApiBase implements
final BaseEntitlementWithAddOnsSpecifier baseEntitlementWithAddOnsSpecifier = getFirstBaseEntitlementWithAddOnsSpecifier(updatedPluginContext.getBaseEntitlementWithAddOnsSpecifiers());
- final DateTime requestedDate = dateHelper.fromLocalDateAndReferenceTime(baseEntitlementWithAddOnsSpecifier.getBillingEffectiveDate(), contextWithSourceAccountRecordId);
+ final DateTime now = clock.getUTCNow();
+ final DateTime requestedDate = dateHelper.fromLocalDateAndReferenceTime(baseEntitlementWithAddOnsSpecifier.getBillingEffectiveDate(), now, contextWithSourceAccountRecordId);
final SubscriptionBaseBundle newBundle = subscriptionBaseTransferApi.transferBundle(sourceAccountId, destAccountId, externalKey, requestedDate, true, cancelImm, context);
@@ -491,7 +516,7 @@ public class DefaultEntitlementApi extends DefaultEntitlementApiBase implements
final InternalCallContext contextWithDestAccountRecordId = internalCallContextFactory.createInternalCallContext(destAccountId, context);
blockingStates.clear();
- final DateTime entitlementRequestedDate = dateHelper.fromLocalDateAndReferenceTime(baseEntitlementWithAddOnsSpecifier.getEntitlementEffectiveDate(), contextWithDestAccountRecordId);
+ final DateTime entitlementRequestedDate = dateHelper.fromLocalDateAndReferenceTime(baseEntitlementWithAddOnsSpecifier.getEntitlementEffectiveDate(), now, contextWithDestAccountRecordId);
for (final SubscriptionBase subscriptionBase : subscriptionBaseInternalApi.getSubscriptionsForBundle(newBundle.getId(), null, contextWithDestAccountRecordId)) {
final BlockingState newBlockingState = new DefaultBlockingState(subscriptionBase.getId(), BlockingStateType.SUBSCRIPTION, DefaultEntitlementApi.ENT_STATE_START, EntitlementService.ENTITLEMENT_SERVICE_NAME, false, false, false, entitlementRequestedDate);
blockingStates.put(newBlockingState, subscriptionBase.getBundleId());
@@ -509,4 +534,16 @@ public class DefaultEntitlementApi extends DefaultEntitlementApiBase implements
};
return pluginExecution.executeWithPlugin(transferWithPlugin, pluginContext);
}
+
+ private void checkForAccountBlockingChange(final UUID accountId, @Nullable final DateTime upTo, final InternalCallContext context) throws EntitlementApiException {
+
+ try {
+ final BlockingAggregator blockingAggregator = checker.getBlockedStatus(accountId, BlockingStateType.ACCOUNT, upTo, context);
+ if (blockingAggregator.isBlockChange()) {
+ throw new EntitlementApiException(new BlockingApiException(ErrorCode.BLOCK_BLOCKED_ACTION, BlockingChecker.ACTION_CHANGE, BlockingChecker.TYPE_ACCOUNT, accountId.toString()));
+ }
+ } catch (final BlockingApiException e) {
+ throw new EntitlementApiException(e);
+ }
+ }
}
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlementContext.java b/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlementContext.java
index eb2a68d..03811c9 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlementContext.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlementContext.java
@@ -65,7 +65,7 @@ public class DefaultEntitlementContext implements EntitlementContext {
private static <T> Iterable<T> merge(final Iterable<T> prevValues, final Iterable<T> newValues) {
// Be lenient if a plugin returns an empty list (default behavior for Ruby plugins): at this point,
// we know the isAborted flag hasn't been set, so let's assume the user actually wants to use the previous list
- if (newValues != null && !Iterables.<BaseEntitlementWithAddOnsSpecifier>isEmpty(newValues)) {
+ if (newValues != null && !Iterables.isEmpty(newValues)) {
return newValues;
} else {
return prevValues;
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultSubscriptionApi.java b/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultSubscriptionApi.java
index 9035ffe..a9416a7 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultSubscriptionApi.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultSubscriptionApi.java
@@ -187,7 +187,9 @@ public class DefaultSubscriptionApi implements SubscriptionApi {
if (activeSubscriptionIdForKey == null) {
throw new SubscriptionApiException(new SubscriptionBaseApiException(ErrorCode.SUB_GET_INVALID_BUNDLE_KEY, externalKey));
}
- final SubscriptionBase subscriptionBase = subscriptionBaseInternalApi.getSubscriptionFromId(activeSubscriptionIdForKey, internalContext);
+
+ final InternalTenantContext internalContextWithAccountRecordId = internalCallContextFactory.createInternalTenantContext(activeSubscriptionIdForKey, ObjectType.SUBSCRIPTION, context);
+ final SubscriptionBase subscriptionBase = subscriptionBaseInternalApi.getSubscriptionFromId(activeSubscriptionIdForKey, internalContextWithAccountRecordId);
return getSubscriptionBundle(subscriptionBase.getBundleId(), context);
} catch (final SubscriptionBaseApiException e) {
throw new SubscriptionApiException(e);
@@ -266,7 +268,7 @@ public class DefaultSubscriptionApi implements SubscriptionApi {
logUpdateExternalKey(log, bundleId, newExternalKey);
- final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContextWithoutAccountRecordId(callContext);
+ final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(bundleId, ObjectType.BUNDLE, callContext);
final SubscriptionBaseBundle bundle;
final ImmutableAccountData account;
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/api/EntitlementDateHelper.java b/entitlement/src/main/java/org/killbill/billing/entitlement/api/EntitlementDateHelper.java
index 2f52230..ed30017 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/api/EntitlementDateHelper.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/api/EntitlementDateHelper.java
@@ -27,18 +27,15 @@ import org.killbill.clock.Clock;
public class EntitlementDateHelper {
- private final Clock clock;
-
- public EntitlementDateHelper(final Clock clock) {
- this.clock = clock;
+ public EntitlementDateHelper() {
}
- public DateTime fromLocalDateAndReferenceTime(@Nullable final LocalDate requestedDate, final InternalTenantContext callContext) throws EntitlementApiException {
- return requestedDate == null ? clock.getUTCNow() : callContext.toUTCDateTime(requestedDate);
+ public DateTime fromLocalDateAndReferenceTime(@Nullable final LocalDate requestedDate, final DateTime now, final InternalTenantContext callContext) throws EntitlementApiException {
+ return requestedDate == null ? now : callContext.toUTCDateTime(requestedDate);
}
- public DateTime fromLocalDateAndReferenceTimeWithMinimum(@Nullable final LocalDate requestedDate, final DateTime min, final InternalTenantContext callContext) throws EntitlementApiException {
- final DateTime candidate = fromLocalDateAndReferenceTime(requestedDate, callContext);
+ public DateTime fromLocalDateAndReferenceTimeWithMinimum(@Nullable final LocalDate requestedDate, final DateTime min, final DateTime now, final InternalTenantContext callContext) throws EntitlementApiException {
+ final DateTime candidate = fromLocalDateAndReferenceTime(requestedDate, now, callContext);
return candidate.compareTo(min) < 0 ? min : candidate;
}
}
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/api/SubscriptionEventOrdering.java b/entitlement/src/main/java/org/killbill/billing/entitlement/api/SubscriptionEventOrdering.java
index 9283363..96b3d26 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/api/SubscriptionEventOrdering.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/api/SubscriptionEventOrdering.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2014-2016 Groupon, Inc
- * Copyright 2014-2016 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
@@ -17,7 +17,6 @@
package org.killbill.billing.entitlement.api;
-import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
@@ -25,7 +24,6 @@ import java.util.List;
import java.util.Map;
import org.killbill.billing.callcontext.InternalTenantContext;
-import org.killbill.billing.entitlement.DefaultEntitlementService;
import org.killbill.billing.subscription.api.SubscriptionBase;
import org.killbill.billing.subscription.api.SubscriptionBaseTransitionType;
import org.killbill.billing.subscription.api.user.SubscriptionBaseTransition;
@@ -42,8 +40,7 @@ import com.google.common.collect.ImmutableList;
//
public class SubscriptionEventOrdering extends EntitlementOrderingBase {
- @VisibleForTesting
- static final SubscriptionEventOrdering INSTANCE = new SubscriptionEventOrdering();
+ private static final SubscriptionEventOrdering INSTANCE = new SubscriptionEventOrdering();
private SubscriptionEventOrdering() {}
@@ -63,7 +60,6 @@ public class SubscriptionEventOrdering extends EntitlementOrderingBase {
BlockingStateOrdering.insertSorted(entitlements, internalTenantContext, result);
// Final cleanups
- reOrderSubscriptionEventsOnSameDateByType(result);
removeOverlappingSubscriptionEvents(result);
return result;
@@ -146,7 +142,8 @@ public class SubscriptionEventOrdering extends EntitlementOrderingBase {
result.add(index, event);
}
- private SubscriptionEvent toSubscriptionEvent(final SubscriptionBaseTransition in, final SubscriptionEventType eventType, final InternalTenantContext internalTenantContext) {
+ @VisibleForTesting
+ static SubscriptionEvent toSubscriptionEvent(final SubscriptionBaseTransition in, final SubscriptionEventType eventType, final InternalTenantContext internalTenantContext) {
return new DefaultSubscriptionEvent(in.getId(),
in.getSubscriptionId(),
in.getEffectiveTransitionTime(),
@@ -169,47 +166,6 @@ public class SubscriptionEventOrdering extends EntitlementOrderingBase {
internalTenantContext);
}
- //
- // All events have been inserted and should be at the right place, except that we want to ensure that events for a given subscription,
- // and for a given time are ordered by SubscriptionEventType.
- //
- // All this seems a little over complicated, and one wonders why we don't just shove all events and call Collections.sort on the list prior
- // to return:
- // - One explanation is that we don't know the events in advance and each time the new events to be inserted are computed from the current state
- // of the stream, which requires ordering all along
- // - A careful reader will notice that the algorithm is N^2, -- so that we care so much considering we have very events -- but in addition to that
- // the recursive path will be used very infrequently and when it is used, this will be probably just reorder with the prev event and that's it.
- //
- @VisibleForTesting
- protected void reOrderSubscriptionEventsOnSameDateByType(final List<SubscriptionEvent> events) {
- final int size = events.size();
- for (int i = 0; i < size; i++) {
- final SubscriptionEvent cur = events.get(i);
- final SubscriptionEvent next = (i < (size - 1)) ? events.get(i + 1) : null;
-
- final boolean shouldSwap = (next != null && shouldSwap(cur, next, true));
- final boolean shouldReverseSort = (next == null || shouldSwap);
-
- int currentIndex = i;
- if (shouldSwap) {
- Collections.swap(events, i, i + 1);
- }
- if (shouldReverseSort) {
- while (currentIndex >= 1) {
- final SubscriptionEvent revCur = events.get(currentIndex);
- final SubscriptionEvent other = events.get(currentIndex - 1);
- if (shouldSwap(revCur, other, false)) {
- Collections.swap(events, currentIndex, currentIndex - 1);
- }
- if (revCur.getEffectiveDate().compareTo(other.getEffectiveDate()) != 0) {
- break;
- }
- currentIndex--;
- }
- }
- }
- }
-
// Make sure the argument supports the remove operation - hence expect a LinkedList, not a List
private void removeOverlappingSubscriptionEvents(final LinkedList<SubscriptionEvent> events) {
final Iterator<SubscriptionEvent> iterator = events.iterator();
@@ -228,92 +184,4 @@ public class SubscriptionEventOrdering extends EntitlementOrderingBase {
}
}
}
-
- private boolean shouldSwap(final SubscriptionEvent cur, final SubscriptionEvent other, final boolean isAscending) {
- // For a given date, order by subscriptionId, and within subscription by event type
- final int idComp = cur.getEntitlementId().compareTo(other.getEntitlementId());
- final Integer comparison = compareSubscriptionEventsForSameEffectiveDateAndEntitlementId(cur, other);
- return (cur.getEffectiveDate().compareTo(other.getEffectiveDate()) == 0 &&
- ((isAscending &&
- ((idComp > 0) ||
- (idComp == 0 && comparison != null && comparison > 0))) ||
- (!isAscending &&
- ((idComp < 0) ||
- (idComp == 0 && comparison != null && comparison < 0)))));
- }
-
- private Integer compareSubscriptionEventsForSameEffectiveDateAndEntitlementId(final SubscriptionEvent first, final SubscriptionEvent second) {
- // For consistency, make sure entitlement-service and billing-service events always happen in a
- // deterministic order (e.g. after other services for STOP events and before for START events)
- if ((DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME.equals(first.getServiceName()) ||
- BILLING_SERVICE_NAME.equals(first.getServiceName())) &&
- !(DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME.equals(second.getServiceName()) ||
- BILLING_SERVICE_NAME.equals(second.getServiceName()))) {
- // first is an entitlement-service or billing-service event, but not second
- if (first.getSubscriptionEventType().equals(SubscriptionEventType.START_ENTITLEMENT) ||
- first.getSubscriptionEventType().equals(SubscriptionEventType.START_BILLING) ||
- first.getSubscriptionEventType().equals(SubscriptionEventType.RESUME_ENTITLEMENT) ||
- first.getSubscriptionEventType().equals(SubscriptionEventType.RESUME_BILLING) ||
- first.getSubscriptionEventType().equals(SubscriptionEventType.PHASE) ||
- first.getSubscriptionEventType().equals(SubscriptionEventType.CHANGE)) {
- return -1;
- } else if (first.getSubscriptionEventType().equals(SubscriptionEventType.PAUSE_ENTITLEMENT) ||
- first.getSubscriptionEventType().equals(SubscriptionEventType.PAUSE_BILLING) ||
- first.getSubscriptionEventType().equals(SubscriptionEventType.STOP_ENTITLEMENT) ||
- first.getSubscriptionEventType().equals(SubscriptionEventType.STOP_BILLING)) {
- return 1;
- } else {
- // Default behavior
- return -1;
- }
- } else if ((DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME.equals(second.getServiceName()) ||
- BILLING_SERVICE_NAME.equals(second.getServiceName())) &&
- !(DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME.equals(first.getServiceName()) ||
- BILLING_SERVICE_NAME.equals(first.getServiceName()))) {
- // second is an entitlement-service or billing-service event, but not first
- if (second.getSubscriptionEventType().equals(SubscriptionEventType.START_ENTITLEMENT) ||
- second.getSubscriptionEventType().equals(SubscriptionEventType.START_BILLING) ||
- second.getSubscriptionEventType().equals(SubscriptionEventType.RESUME_ENTITLEMENT) ||
- second.getSubscriptionEventType().equals(SubscriptionEventType.RESUME_BILLING) ||
- second.getSubscriptionEventType().equals(SubscriptionEventType.PHASE) ||
- second.getSubscriptionEventType().equals(SubscriptionEventType.CHANGE)) {
- return 1;
- } else if (second.getSubscriptionEventType().equals(SubscriptionEventType.PAUSE_ENTITLEMENT) ||
- second.getSubscriptionEventType().equals(SubscriptionEventType.PAUSE_BILLING) ||
- second.getSubscriptionEventType().equals(SubscriptionEventType.STOP_ENTITLEMENT) ||
- second.getSubscriptionEventType().equals(SubscriptionEventType.STOP_BILLING)) {
- return -1;
- } else {
- // Default behavior
- return 1;
- }
- } else if (first.getSubscriptionEventType().equals(SubscriptionEventType.START_ENTITLEMENT)) {
- // START_ENTITLEMENT is always first
- return -1;
- } else if (second.getSubscriptionEventType().equals(SubscriptionEventType.START_ENTITLEMENT)) {
- // START_ENTITLEMENT is always first
- return 1;
- } else if (first.getSubscriptionEventType().equals(SubscriptionEventType.STOP_BILLING)) {
- // STOP_BILLING is always last
- return 1;
- } else if (second.getSubscriptionEventType().equals(SubscriptionEventType.STOP_BILLING)) {
- // STOP_BILLING is always last
- return -1;
- } else if (first.getSubscriptionEventType().equals(SubscriptionEventType.START_BILLING)) {
- // START_BILLING is first after START_ENTITLEMENT
- return -1;
- } else if (second.getSubscriptionEventType().equals(SubscriptionEventType.START_BILLING)) {
- // START_BILLING is first after START_ENTITLEMENT
- return 1;
- } else if (first.getSubscriptionEventType().equals(SubscriptionEventType.STOP_ENTITLEMENT)) {
- // STOP_ENTITLEMENT is last after STOP_BILLING
- return 1;
- } else if (second.getSubscriptionEventType().equals(SubscriptionEventType.STOP_ENTITLEMENT)) {
- // STOP_ENTITLEMENT is last after STOP_BILLING
- return -1;
- } else {
- // Trust the current ordering
- return null;
- }
- }
}
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/api/svcs/DefaultEntitlementApiBase.java b/entitlement/src/main/java/org/killbill/billing/entitlement/api/svcs/DefaultEntitlementApiBase.java
index f1c8e57..169c38a 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/api/svcs/DefaultEntitlementApiBase.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/api/svcs/DefaultEntitlementApiBase.java
@@ -110,7 +110,7 @@ public class DefaultEntitlementApiBase {
this.eventsStreamBuilder = eventsStreamBuilder;
this.entitlementUtils = entitlementUtils;
this.securityApi = securityApi;
- this.dateHelper = new EntitlementDateHelper(clock);
+ this.dateHelper = new EntitlementDateHelper();
}
public AccountEntitlements getAllEntitlementsForAccount(final InternalTenantContext tenantContext) throws EntitlementApiException {
@@ -209,7 +209,8 @@ public class DefaultEntitlementApiBase {
private UUID blockUnblockBundle(final UUID bundleId, final String stateName, final String serviceName, @Nullable final LocalDate localEffectiveDate, boolean blockBilling, boolean blockEntitlement, boolean blockChange, @Nullable final SubscriptionBase inputBaseSubscription, final InternalCallContext internalCallContext)
throws EntitlementApiException {
- final DateTime effectiveDate = dateHelper.fromLocalDateAndReferenceTime(localEffectiveDate, internalCallContext);
+ final DateTime now = clock.getUTCNow();
+ final DateTime effectiveDate = dateHelper.fromLocalDateAndReferenceTime(localEffectiveDate, now, internalCallContext);
final BlockingState state = new DefaultBlockingState(bundleId, BlockingStateType.SUBSCRIPTION_BUNDLE, stateName, serviceName, blockChange, blockEntitlement, blockBilling, effectiveDate);
entitlementUtils.setBlockingStatesAndPostBlockingTransitionEvent(ImmutableList.<BlockingState>of(state), bundleId, internalCallContext);
return state.getId();
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 de22aaf..7e91ad9 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
@@ -234,7 +234,8 @@ 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);
+ final DateTime now = clock.getUTCNow();
+ DateTime effectiveDate = dateHelper.fromLocalDateAndReferenceTime(updatedPluginContext.getBaseEntitlementWithAddOnsSpecifiers().iterator().next().getEntitlementEffectiveDate(), now, internalCallContext);
//
// If the entitlementDate provided is ahead we default to the effective subscriptionBase cancellationDate to avoid weird timing issues.
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/dao/BlockingStateSqlDao.java b/entitlement/src/main/java/org/killbill/billing/entitlement/dao/BlockingStateSqlDao.java
index 95024c3..344a645 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/dao/BlockingStateSqlDao.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/dao/BlockingStateSqlDao.java
@@ -28,33 +28,33 @@ import org.killbill.billing.entitlement.api.BlockingState;
import org.killbill.billing.util.audit.ChangeType;
import org.killbill.billing.util.entity.dao.Audited;
import org.killbill.billing.util.entity.dao.EntitySqlDao;
-import org.killbill.billing.util.entity.dao.EntitySqlDaoStringTemplate;
+import org.killbill.commons.jdbi.template.KillBillSqlDaoStringTemplate;
import org.skife.jdbi.v2.sqlobject.Bind;
-import org.skife.jdbi.v2.sqlobject.BindBean;
+import org.killbill.commons.jdbi.binder.SmartBindBean;
import org.skife.jdbi.v2.sqlobject.SqlQuery;
import org.skife.jdbi.v2.sqlobject.SqlUpdate;
-@EntitySqlDaoStringTemplate
+@KillBillSqlDaoStringTemplate
public interface BlockingStateSqlDao extends EntitySqlDao<BlockingStateModelDao, BlockingState> {
@SqlQuery
public abstract BlockingStateModelDao getBlockingStateForService(@Bind("blockableId") UUID blockableId,
@Bind("service") String serviceName,
@Bind("effectiveDate") Date effectiveDate,
- @BindBean final InternalTenantContext context);
+ @SmartBindBean final InternalTenantContext context);
@SqlQuery
public abstract List<BlockingStateModelDao> getBlockingState(@Bind("blockableId") UUID blockableId,
@Bind("effectiveDate") Date effectiveDate,
- @BindBean final InternalTenantContext context);
+ @SmartBindBean final InternalTenantContext context);
@SqlQuery
public abstract List<BlockingStateModelDao> getBlockingHistoryForService(@Bind("blockableId") UUID blockableId,
@Bind("service") String serviceName,
- @BindBean final InternalTenantContext context);
+ @SmartBindBean final InternalTenantContext context);
@SqlUpdate
@Audited(ChangeType.UPDATE)
public void unactiveEvent(@Bind("id") String id,
- @BindBean final InternalCallContext context);
+ @SmartBindBean final InternalCallContext context);
}
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/DefaultEventsStream.java b/entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/DefaultEventsStream.java
index 27d9591..7c84dac 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/DefaultEventsStream.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/DefaultEventsStream.java
@@ -48,6 +48,7 @@ import org.killbill.billing.subscription.api.user.SubscriptionBaseBundle;
import org.killbill.billing.subscription.api.user.SubscriptionBaseTransition;
import com.google.common.base.Function;
+import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableList;
@@ -72,7 +73,7 @@ public class DefaultEventsStream implements EventsStream {
private final LocalDate utcToday;
private final int defaultBillCycleDayLocal;
- private BlockingAggregator blockingAggregator;
+ private BlockingAggregator currentStateBlockingAggregator;
private List<BlockingState> subscriptionEntitlementStates;
private LocalDate entitlementEffectiveStartDate;
private DateTime entitlementEffectiveStartDateTime;
@@ -90,6 +91,7 @@ public class DefaultEventsStream implements EventsStream {
final List<SubscriptionBase> allSubscriptionsForBundle,
final int defaultBillCycleDayLocal,
final InternalTenantContext contextWithValidAccountRecordId, final DateTime utcNow) {
+ sanityChecks(account, bundle, baseSubscription, subscription);
this.account = account;
this.bundle = bundle;
this.blockingStates = blockingStates;
@@ -105,6 +107,20 @@ public class DefaultEventsStream implements EventsStream {
setup();
}
+ private void sanityChecks(@Nullable final ImmutableAccountData account,
+ @Nullable final SubscriptionBaseBundle bundle,
+ @Nullable final SubscriptionBase baseSubscription,
+ @Nullable final SubscriptionBase subscription) {
+ for (final Object object : new Object[]{account, bundle, baseSubscription, subscription}) {
+ Preconditions.checkNotNull(object,
+ "accountId='%s', bundleId='%s', baseSubscriptionId='%s', subscriptionId='%s'",
+ account != null ? account.getId() : null,
+ bundle != null ? bundle.getId() : null,
+ baseSubscription != null ? baseSubscription.getId() : null,
+ subscription != null ? subscription.getId() : null);
+ }
+ }
+
@Override
public UUID getAccountId() {
return account.getId();
@@ -167,7 +183,7 @@ public class DefaultEventsStream implements EventsStream {
@Override
public boolean isBlockChange() {
- return blockingAggregator.isBlockChange();
+ return currentStateBlockingAggregator.isBlockChange();
}
public boolean isEntitlementFutureCancelled() {
@@ -199,6 +215,13 @@ public class DefaultEventsStream implements EventsStream {
}
@Override
+ public boolean isBlockChange(final DateTime effectiveDate) {
+ Preconditions.checkState(effectiveDate != null);
+ final BlockingAggregator aggregator = getBlockingAggregator(effectiveDate);
+ return aggregator.isBlockChange();
+ }
+
+ @Override
public int getDefaultBillCycleDayLocal() {
return defaultBillCycleDayLocal;
}
@@ -385,24 +408,32 @@ public class DefaultEventsStream implements EventsStream {
private void setup() {
computeEntitlementBlockingStates();
- computeBlockingAggregator();
+ computeCurrentBlockingAggregator();
computeEntitlementStartEvent();
computeEntitlementCancelEvent();
computeStateForEntitlement();
}
- private void computeBlockingAggregator() {
+ private void computeCurrentBlockingAggregator() {
+ currentStateBlockingAggregator = getBlockingAggregator(null);
+ }
+
+ private BlockingAggregator getBlockingAggregator(final DateTime upTo) {
- final List<BlockingState> currentSubscriptionBlockingStatesForServices = filterCurrentBlockableStatePerService(BlockingStateType.SUBSCRIPTION, subscription.getId());
- final List<BlockingState> currentBundleBlockingStatesForServices = filterCurrentBlockableStatePerService(BlockingStateType.SUBSCRIPTION_BUNDLE, subscription.getBundleId());
- final List<BlockingState> currentAccountBlockingStatesForServices = filterCurrentBlockableStatePerService(BlockingStateType.ACCOUNT, account.getId());
- blockingAggregator = blockingChecker.getBlockedStatus(currentAccountBlockingStatesForServices,
- currentBundleBlockingStatesForServices,
- currentSubscriptionBlockingStatesForServices,
- internalTenantContext);
+ final List<BlockingState> currentSubscriptionBlockingStatesForServices = filterCurrentBlockableStatePerService(BlockingStateType.SUBSCRIPTION, subscription.getId(), upTo);
+ final List<BlockingState> currentBundleBlockingStatesForServices = filterCurrentBlockableStatePerService(BlockingStateType.SUBSCRIPTION_BUNDLE, subscription.getBundleId(), upTo);
+ final List<BlockingState> currentAccountBlockingStatesForServices = filterCurrentBlockableStatePerService(BlockingStateType.ACCOUNT, account.getId(), upTo);
+ return blockingChecker.getBlockedStatus(currentAccountBlockingStatesForServices,
+ currentBundleBlockingStatesForServices,
+ currentSubscriptionBlockingStatesForServices, internalTenantContext);
}
- private List<BlockingState> filterCurrentBlockableStatePerService(final BlockingStateType type, final UUID blockableId) {
+
+
+ private List<BlockingState> filterCurrentBlockableStatePerService(final BlockingStateType type, final UUID blockableId, @Nullable final DateTime upTo) {
+
+ final DateTime resolvedUpTo = upTo != null ? upTo : utcNow;
+
final Map<String, BlockingState> currentBlockingStatePerService = new HashMap<String, BlockingState>();
for (final BlockingState blockingState : blockingStates) {
if (!blockingState.getBlockedId().equals(blockableId)) {
@@ -411,7 +442,7 @@ public class DefaultEventsStream implements EventsStream {
if (blockingState.getType() != type) {
continue;
}
- if (blockingState.getEffectiveDate().isAfter(utcNow)) {
+ if (blockingState.getEffectiveDate().isAfter(resolvedUpTo)) {
continue;
}
@@ -462,7 +493,7 @@ public class DefaultEventsStream implements EventsStream {
entitlementState = EntitlementState.PENDING;
} else {
// Gather states across all services and check if one of them is set to 'blockEntitlement'
- entitlementState = (blockingAggregator != null && blockingAggregator.isBlockEntitlement() ? EntitlementState.BLOCKED : EntitlementState.ACTIVE);
+ entitlementState = (currentStateBlockingAggregator != null && currentStateBlockingAggregator.isBlockEntitlement() ? EntitlementState.BLOCKED : EntitlementState.ACTIVE);
}
}
}
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/logging/EntitlementLoggingHelper.java b/entitlement/src/main/java/org/killbill/billing/entitlement/logging/EntitlementLoggingHelper.java
index 0793d6f..a0b158c 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/logging/EntitlementLoggingHelper.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/logging/EntitlementLoggingHelper.java
@@ -214,6 +214,16 @@ public abstract class EntitlementLoggingHelper {
}
}
+ public static void logUndoChangePlan(final Logger log, final Entitlement entitlement) {
+ if (log.isInfoEnabled()) {
+ final StringBuilder logLine = new StringBuilder("Undo Entitlement Change Plan: ")
+ .append(" id = '")
+ .append(entitlement.getId())
+ .append("'");
+ log.info(logLine.toString());
+ }
+ }
+
public static void logChangePlan(final Logger log, final Entitlement entitlement, final PlanSpecifier spec,
final List<PlanPhasePriceOverride> overrides, final LocalDate entitlementEffectiveDate, final BillingActionPolicy actionPolicy) {
if (log.isInfoEnabled()) {
diff --git a/entitlement/src/main/resources/org/killbill/billing/entitlement/dao/BlockingStateSqlDao.sql.stg b/entitlement/src/main/resources/org/killbill/billing/entitlement/dao/BlockingStateSqlDao.sql.stg
index feeee40..9d78819 100644
--- a/entitlement/src/main/resources/org/killbill/billing/entitlement/dao/BlockingStateSqlDao.sql.stg
+++ b/entitlement/src/main/resources/org/killbill/billing/entitlement/dao/BlockingStateSqlDao.sql.stg
@@ -1,5 +1,4 @@
-group BlockingStateSqlDao: EntitySqlDao;
-
+import "org/killbill/billing/util/entity/dao/EntitySqlDao.sql.stg"
tableName() ::= "blocking_states"
@@ -45,14 +44,14 @@ tableValues() ::= <<
getBlockingStateForService() ::= <<
select
-<allTableFields()>
+<allTableFields("")>
from
<tableName()>
where blockable_id = :blockableId
and service = :service
and effective_date \<= :effectiveDate
and is_active
-<AND_CHECK_TENANT()>
+<AND_CHECK_TENANT("")>
-- We want the current state, hence the order desc and limit 1
order by effective_date desc, record_id desc
limit 1
@@ -71,7 +70,7 @@ getBlockingState() ::= <<
where blockable_id = :blockableId
and effective_date \<= :effectiveDate
and is_active
- <AND_CHECK_TENANT()>
+ <AND_CHECK_TENANT("")>
group by service
) tmp
on t.record_id = tmp.record_id
@@ -81,14 +80,14 @@ getBlockingState() ::= <<
getBlockingHistoryForService() ::= <<
select
-<allTableFields()>
+<allTableFields("")>
from
<tableName()>
where blockable_id = :blockableId
and service = :service
and is_active
-<AND_CHECK_TENANT()>
-<defaultOrderBy()>
+<AND_CHECK_TENANT("")>
+<defaultOrderBy("")>
;
>>
@@ -97,6 +96,6 @@ update
<tableName()>
set is_active = false
where id = :id
-<AND_CHECK_TENANT()>
+<AND_CHECK_TENANT("")>
;
>>
diff --git a/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestBlockingStateOrdering.java b/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestBlockingStateOrdering.java
new file mode 100644
index 0000000..02a7581
--- /dev/null
+++ b/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestBlockingStateOrdering.java
@@ -0,0 +1,378 @@
+/*
+ * 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.entitlement.api;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.killbill.billing.entitlement.DefaultEntitlementService;
+import org.killbill.billing.entitlement.EntitlementTestSuiteNoDB;
+import org.killbill.billing.junction.DefaultBlockingState;
+import org.killbill.billing.subscription.api.user.SubscriptionBaseTransition;
+import org.mockito.Mockito;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+// invocationCount > 1 to verify flakiness
+public class TestBlockingStateOrdering extends EntitlementTestSuiteNoDB {
+
+ private long globalOrdering = 0;
+
+ @Test(groups = "fast", invocationCount = 10)
+ public void testIgnore_ENTITLEMENT_SERVICE_NAME_WithNoFlag() throws Exception {
+ final DateTime now = clock.getUTCNow();
+ final UUID subscriptionId1 = UUID.randomUUID();
+
+ final Collection<BlockingState> blockingStates = new LinkedList<BlockingState>();
+ blockingStates.add(createBlockingState(subscriptionId1, BlockingStateType.SUBSCRIPTION, DefaultEntitlementApi.ENT_STATE_START, DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME, false, false, now));
+ blockingStates.add(createBlockingState(subscriptionId1, BlockingStateType.SUBSCRIPTION, "stuff", DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME, false, false, now.plusDays(1)));
+
+ final LinkedList<SubscriptionEvent> allEvents = new LinkedList<SubscriptionEvent>();
+ allEvents.add(createEvent(subscriptionId1, SubscriptionEventType.START_BILLING, now));
+ allEvents.add(createEvent(subscriptionId1, SubscriptionEventType.PHASE, now.plusDays(30)));
+
+ computeEvents(allEvents, blockingStates);
+
+ Assert.assertEquals(allEvents.size(), 3);
+ Assert.assertEquals(allEvents.get(0).getSubscriptionEventType(), SubscriptionEventType.START_ENTITLEMENT);
+ Assert.assertEquals(allEvents.get(1).getSubscriptionEventType(), SubscriptionEventType.START_BILLING);
+ Assert.assertEquals(allEvents.get(2).getSubscriptionEventType(), SubscriptionEventType.PHASE);
+ }
+
+ @Test(groups = "fast", invocationCount = 10)
+ public void test_ENT_STATE_IsNotInterpreted() throws Exception {
+ final DateTime now = clock.getUTCNow();
+ final UUID subscriptionId1 = UUID.randomUUID();
+
+ final Collection<BlockingState> blockingStates = new LinkedList<BlockingState>();
+ blockingStates.add(createBlockingState(subscriptionId1, BlockingStateType.SUBSCRIPTION, DefaultEntitlementApi.ENT_STATE_START, DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME, false, false, now));
+ blockingStates.add(createBlockingState(subscriptionId1, BlockingStateType.SUBSCRIPTION, DefaultEntitlementApi.ENT_STATE_START, "svc1", false, false, now.plusDays(1)));
+ blockingStates.add(createBlockingState(subscriptionId1, BlockingStateType.SUBSCRIPTION, DefaultEntitlementApi.ENT_STATE_CANCELLED, "svc1", false, false, now.plusDays(2)));
+
+ final LinkedList<SubscriptionEvent> allEvents = new LinkedList<SubscriptionEvent>();
+ allEvents.add(createEvent(subscriptionId1, SubscriptionEventType.START_BILLING, now));
+ allEvents.add(createEvent(subscriptionId1, SubscriptionEventType.PHASE, now.plusDays(30)));
+
+ computeEvents(allEvents, blockingStates);
+
+ Assert.assertEquals(allEvents.size(), 5);
+ Assert.assertEquals(allEvents.get(0).getSubscriptionEventType(), SubscriptionEventType.START_ENTITLEMENT);
+ Assert.assertEquals(allEvents.get(1).getSubscriptionEventType(), SubscriptionEventType.START_BILLING);
+ Assert.assertEquals(allEvents.get(2).getSubscriptionEventType(), SubscriptionEventType.SERVICE_STATE_CHANGE);
+ Assert.assertEquals(allEvents.get(3).getSubscriptionEventType(), SubscriptionEventType.SERVICE_STATE_CHANGE);
+ Assert.assertEquals(allEvents.get(4).getSubscriptionEventType(), SubscriptionEventType.PHASE);
+ }
+
+ @Test(groups = "fast", invocationCount = 10)
+ public void testPauseAtStart() throws Exception {
+ final DateTime now = clock.getUTCNow();
+ final UUID subscriptionId1 = UUID.randomUUID();
+
+ final Collection<BlockingState> blockingStates = new LinkedList<BlockingState>();
+ blockingStates.add(createBlockingState(subscriptionId1, BlockingStateType.SUBSCRIPTION, DefaultEntitlementApi.ENT_STATE_START, DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME, false, false, now));
+ blockingStates.add(createBlockingState(subscriptionId1, BlockingStateType.SUBSCRIPTION, "stuff", "svc1", true, true, now));
+
+ final LinkedList<SubscriptionEvent> allEvents = new LinkedList<SubscriptionEvent>();
+ allEvents.add(createEvent(subscriptionId1, SubscriptionEventType.START_BILLING, now));
+ allEvents.add(createEvent(subscriptionId1, SubscriptionEventType.PHASE, now.plusDays(30)));
+
+ computeEvents(allEvents, blockingStates);
+
+ Assert.assertEquals(allEvents.size(), 5);
+ Assert.assertEquals(allEvents.get(0).getSubscriptionEventType(), SubscriptionEventType.START_ENTITLEMENT);
+ Assert.assertEquals(allEvents.get(1).getSubscriptionEventType(), SubscriptionEventType.START_BILLING);
+ Assert.assertEquals(allEvents.get(2).getSubscriptionEventType(), SubscriptionEventType.PAUSE_ENTITLEMENT);
+ Assert.assertEquals(allEvents.get(3).getSubscriptionEventType(), SubscriptionEventType.PAUSE_BILLING);
+ Assert.assertEquals(allEvents.get(4).getSubscriptionEventType(), SubscriptionEventType.PHASE);
+ }
+
+ @Test(groups = "fast", invocationCount = 10)
+ public void testPausePostPhase_0_17_X() throws Exception {
+ final DateTime now = clock.getUTCNow();
+ final UUID subscriptionId1 = UUID.randomUUID();
+
+ final Collection<BlockingState> blockingStates = new LinkedList<BlockingState>();
+ blockingStates.add(createBlockingState(subscriptionId1, BlockingStateType.SUBSCRIPTION, "stuff", "svc1", true, true, now.plusDays(40)));
+
+ final LinkedList<SubscriptionEvent> allEvents = new LinkedList<SubscriptionEvent>();
+ allEvents.add(createEvent(subscriptionId1, SubscriptionEventType.START_BILLING, now));
+ allEvents.add(createEvent(subscriptionId1, SubscriptionEventType.PHASE, now.plusDays(30)));
+
+ computeEvents(allEvents, blockingStates);
+
+ Assert.assertEquals(allEvents.size(), 5);
+ Assert.assertEquals(allEvents.get(0).getSubscriptionEventType(), SubscriptionEventType.START_ENTITLEMENT);
+ Assert.assertEquals(allEvents.get(1).getSubscriptionEventType(), SubscriptionEventType.START_BILLING);
+ Assert.assertEquals(allEvents.get(2).getSubscriptionEventType(), SubscriptionEventType.PHASE);
+ Assert.assertEquals(allEvents.get(3).getSubscriptionEventType(), SubscriptionEventType.PAUSE_ENTITLEMENT);
+ Assert.assertEquals(allEvents.get(4).getSubscriptionEventType(), SubscriptionEventType.PAUSE_BILLING);
+ }
+
+ @Test(groups = "fast", invocationCount = 10)
+ public void testPausePostPhase() throws Exception {
+ final DateTime now = clock.getUTCNow();
+ final UUID subscriptionId1 = UUID.randomUUID();
+
+ final Collection<BlockingState> blockingStates = new LinkedList<BlockingState>();
+ blockingStates.add(createBlockingState(subscriptionId1, BlockingStateType.SUBSCRIPTION, DefaultEntitlementApi.ENT_STATE_START, DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME, false, false, now));
+ blockingStates.add(createBlockingState(subscriptionId1, BlockingStateType.SUBSCRIPTION, "stuff", "svc1", true, true, now.plusDays(40)));
+
+ final LinkedList<SubscriptionEvent> allEvents = new LinkedList<SubscriptionEvent>();
+ allEvents.add(createEvent(subscriptionId1, SubscriptionEventType.START_BILLING, now));
+ allEvents.add(createEvent(subscriptionId1, SubscriptionEventType.PHASE, now.plusDays(30)));
+
+ computeEvents(allEvents, blockingStates);
+
+ Assert.assertEquals(allEvents.size(), 5);
+ Assert.assertEquals(allEvents.get(0).getSubscriptionEventType(), SubscriptionEventType.START_ENTITLEMENT);
+ Assert.assertEquals(allEvents.get(1).getSubscriptionEventType(), SubscriptionEventType.START_BILLING);
+ Assert.assertEquals(allEvents.get(2).getSubscriptionEventType(), SubscriptionEventType.PHASE);
+ Assert.assertEquals(allEvents.get(3).getSubscriptionEventType(), SubscriptionEventType.PAUSE_ENTITLEMENT);
+ Assert.assertEquals(allEvents.get(4).getSubscriptionEventType(), SubscriptionEventType.PAUSE_BILLING);
+ }
+
+ @Test(groups = "fast", invocationCount = 10)
+ public void testPauseAtPhase() throws Exception {
+ final DateTime now = clock.getUTCNow();
+ final UUID subscriptionId1 = UUID.randomUUID();
+
+ final Collection<BlockingState> blockingStates = new LinkedList<BlockingState>();
+ blockingStates.add(createBlockingState(subscriptionId1, BlockingStateType.SUBSCRIPTION, DefaultEntitlementApi.ENT_STATE_START, DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME, false, false, now));
+ blockingStates.add(createBlockingState(subscriptionId1, BlockingStateType.SUBSCRIPTION, "stuff", "svc1", true, true, now.plusDays(30)));
+
+ final LinkedList<SubscriptionEvent> allEvents = new LinkedList<SubscriptionEvent>();
+ allEvents.add(createEvent(subscriptionId1, SubscriptionEventType.START_BILLING, now));
+ allEvents.add(createEvent(subscriptionId1, SubscriptionEventType.PHASE, now.plusDays(30)));
+
+ computeEvents(allEvents, blockingStates);
+
+ Assert.assertEquals(allEvents.size(), 5);
+ Assert.assertEquals(allEvents.get(0).getSubscriptionEventType(), SubscriptionEventType.START_ENTITLEMENT);
+ Assert.assertEquals(allEvents.get(1).getSubscriptionEventType(), SubscriptionEventType.START_BILLING);
+ Assert.assertEquals(allEvents.get(2).getSubscriptionEventType(), SubscriptionEventType.PHASE);
+ Assert.assertEquals(allEvents.get(3).getSubscriptionEventType(), SubscriptionEventType.PAUSE_ENTITLEMENT);
+ Assert.assertEquals(allEvents.get(4).getSubscriptionEventType(), SubscriptionEventType.PAUSE_BILLING);
+ }
+
+ @Test(groups = "fast", invocationCount = 10)
+ public void testPauseResumeAtPhase() throws Exception {
+ final DateTime now = clock.getUTCNow();
+ final UUID subscriptionId1 = UUID.randomUUID();
+
+ final Collection<BlockingState> blockingStates = new LinkedList<BlockingState>();
+ blockingStates.add(createBlockingState(subscriptionId1, BlockingStateType.SUBSCRIPTION, DefaultEntitlementApi.ENT_STATE_START, DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME, false, false, now));
+ blockingStates.add(createBlockingState(subscriptionId1, BlockingStateType.SUBSCRIPTION, "stuff", "svc1", true, true, now.plusDays(30)));
+ blockingStates.add(createBlockingState(subscriptionId1, BlockingStateType.SUBSCRIPTION, "stuff", "svc1", false, false, now.plusDays(30)));
+
+ final LinkedList<SubscriptionEvent> allEvents = new LinkedList<SubscriptionEvent>();
+ allEvents.add(createEvent(subscriptionId1, SubscriptionEventType.START_BILLING, now));
+ allEvents.add(createEvent(subscriptionId1, SubscriptionEventType.PHASE, now.plusDays(30)));
+
+ computeEvents(allEvents, blockingStates);
+
+ Assert.assertEquals(allEvents.size(), 7);
+ Assert.assertEquals(allEvents.get(0).getSubscriptionEventType(), SubscriptionEventType.START_ENTITLEMENT);
+ Assert.assertEquals(allEvents.get(1).getSubscriptionEventType(), SubscriptionEventType.START_BILLING);
+ Assert.assertEquals(allEvents.get(2).getSubscriptionEventType(), SubscriptionEventType.PHASE);
+ Assert.assertEquals(allEvents.get(3).getSubscriptionEventType(), SubscriptionEventType.PAUSE_ENTITLEMENT);
+ Assert.assertEquals(allEvents.get(4).getSubscriptionEventType(), SubscriptionEventType.PAUSE_BILLING);
+ Assert.assertEquals(allEvents.get(5).getSubscriptionEventType(), SubscriptionEventType.RESUME_ENTITLEMENT);
+ Assert.assertEquals(allEvents.get(6).getSubscriptionEventType(), SubscriptionEventType.RESUME_BILLING);
+ }
+
+ @Test(groups = "fast", invocationCount = 10)
+ public void testPauseAccountAtPhase() throws Exception {
+ final DateTime now = clock.getUTCNow();
+ final UUID subscriptionId1 = UUID.randomUUID();
+
+ final Collection<BlockingState> blockingStates = new LinkedList<BlockingState>();
+ blockingStates.add(createBlockingState(subscriptionId1, BlockingStateType.SUBSCRIPTION, DefaultEntitlementApi.ENT_STATE_START, DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME, false, false, now));
+ blockingStates.add(createBlockingState(UUID.randomUUID(), BlockingStateType.ACCOUNT, "stuff", "svc1", true, true, now.plusDays(30)));
+
+ final LinkedList<SubscriptionEvent> allEvents = new LinkedList<SubscriptionEvent>();
+ allEvents.add(createEvent(subscriptionId1, SubscriptionEventType.START_BILLING, now));
+ allEvents.add(createEvent(subscriptionId1, SubscriptionEventType.PHASE, now.plusDays(30)));
+
+ computeEvents(allEvents, blockingStates);
+
+ Assert.assertEquals(allEvents.size(), 5);
+ Assert.assertEquals(allEvents.get(0).getSubscriptionEventType(), SubscriptionEventType.START_ENTITLEMENT);
+ Assert.assertEquals(allEvents.get(1).getSubscriptionEventType(), SubscriptionEventType.START_BILLING);
+ Assert.assertEquals(allEvents.get(2).getSubscriptionEventType(), SubscriptionEventType.PHASE);
+ Assert.assertEquals(allEvents.get(3).getSubscriptionEventType(), SubscriptionEventType.PAUSE_ENTITLEMENT);
+ Assert.assertEquals(allEvents.get(4).getSubscriptionEventType(), SubscriptionEventType.PAUSE_BILLING);
+ }
+
+ @Test(groups = "fast", invocationCount = 10)
+ public void testDifferentTypesOfBlockingSameService() throws Exception {
+ final DateTime now = clock.getUTCNow();
+ final UUID subscriptionId1 = UUID.randomUUID();
+
+ final Collection<BlockingState> blockingStates = new LinkedList<BlockingState>();
+ blockingStates.add(createBlockingState(subscriptionId1, BlockingStateType.SUBSCRIPTION, DefaultEntitlementApi.ENT_STATE_START, DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME, false, false, now));
+ blockingStates.add(createBlockingState(UUID.randomUUID(), BlockingStateType.ACCOUNT, "stuff", "svc1", false, true, now.plusDays(10)));
+ // Same service
+ blockingStates.add(createBlockingState(subscriptionId1, BlockingStateType.SUBSCRIPTION, "stuff", "svc1", true, false, now.plusDays(15)));
+ blockingStates.add(createBlockingState(subscriptionId1, BlockingStateType.SUBSCRIPTION, "stuff", "svc1", false, false, now.plusDays(20)));
+ blockingStates.add(createBlockingState(subscriptionId1, BlockingStateType.ACCOUNT, "stuff", "svc1", false, false, now.plusDays(30)));
+
+ final LinkedList<SubscriptionEvent> allEvents = new LinkedList<SubscriptionEvent>();
+ allEvents.add(createEvent(subscriptionId1, SubscriptionEventType.START_BILLING, now));
+ allEvents.add(createEvent(subscriptionId1, SubscriptionEventType.PHASE, now.plusDays(30)));
+
+ computeEvents(allEvents, blockingStates);
+
+ Assert.assertEquals(allEvents.size(), 8);
+ Assert.assertEquals(allEvents.get(0).getSubscriptionEventType(), SubscriptionEventType.START_ENTITLEMENT);
+ Assert.assertEquals(allEvents.get(1).getSubscriptionEventType(), SubscriptionEventType.START_BILLING);
+ Assert.assertEquals(allEvents.get(2).getSubscriptionEventType(), SubscriptionEventType.PAUSE_BILLING);
+ Assert.assertEquals(allEvents.get(3).getSubscriptionEventType(), SubscriptionEventType.RESUME_BILLING);
+ Assert.assertEquals(allEvents.get(4).getSubscriptionEventType(), SubscriptionEventType.PAUSE_ENTITLEMENT);
+ Assert.assertEquals(allEvents.get(5).getSubscriptionEventType(), SubscriptionEventType.RESUME_ENTITLEMENT);
+ Assert.assertEquals(allEvents.get(6).getSubscriptionEventType(), SubscriptionEventType.PHASE);
+ Assert.assertEquals(allEvents.get(7).getSubscriptionEventType(), SubscriptionEventType.SERVICE_STATE_CHANGE);
+ }
+
+ @Test(groups = "fast", invocationCount = 10)
+ public void testDifferentTypesOfBlockingDifferentServices() throws Exception {
+ final DateTime now = clock.getUTCNow();
+ final UUID subscriptionId1 = UUID.randomUUID();
+
+ final Collection<BlockingState> blockingStates = new LinkedList<BlockingState>();
+ blockingStates.add(createBlockingState(subscriptionId1, BlockingStateType.SUBSCRIPTION, DefaultEntitlementApi.ENT_STATE_START, DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME, false, false, now));
+ blockingStates.add(createBlockingState(UUID.randomUUID(), BlockingStateType.ACCOUNT, "stuff", "svc1", false, true, now.plusDays(10)));
+ // Different service
+ blockingStates.add(createBlockingState(subscriptionId1, BlockingStateType.SUBSCRIPTION, "stuff", "svc2", true, false, now.plusDays(15)));
+ blockingStates.add(createBlockingState(subscriptionId1, BlockingStateType.SUBSCRIPTION, "stuff", "svc2", false, false, now.plusDays(20)));
+ blockingStates.add(createBlockingState(subscriptionId1, BlockingStateType.ACCOUNT, "stuff", "svc1", false, false, now.plusDays(30)));
+
+ final LinkedList<SubscriptionEvent> allEvents = new LinkedList<SubscriptionEvent>();
+ allEvents.add(createEvent(subscriptionId1, SubscriptionEventType.START_BILLING, now));
+ allEvents.add(createEvent(subscriptionId1, SubscriptionEventType.PHASE, now.plusDays(30)));
+
+ computeEvents(allEvents, blockingStates);
+
+ Assert.assertEquals(allEvents.size(), 7);
+ Assert.assertEquals(allEvents.get(0).getSubscriptionEventType(), SubscriptionEventType.START_ENTITLEMENT);
+ Assert.assertEquals(allEvents.get(1).getSubscriptionEventType(), SubscriptionEventType.START_BILLING);
+ Assert.assertEquals(allEvents.get(2).getSubscriptionEventType(), SubscriptionEventType.PAUSE_BILLING);
+ Assert.assertEquals(allEvents.get(3).getSubscriptionEventType(), SubscriptionEventType.PAUSE_ENTITLEMENT);
+ Assert.assertEquals(allEvents.get(4).getSubscriptionEventType(), SubscriptionEventType.RESUME_ENTITLEMENT);
+ Assert.assertEquals(allEvents.get(5).getSubscriptionEventType(), SubscriptionEventType.PHASE);
+ Assert.assertEquals(allEvents.get(6).getSubscriptionEventType(), SubscriptionEventType.RESUME_BILLING);
+ }
+
+ @Test(groups = "fast", invocationCount = 10)
+ public void testPauseAccountAtPhaseAndPauseOtherSubscriptionFutureStartedV1() throws Exception {
+ final UUID subscriptionId1 = UUID.randomUUID();
+ UUID subscriptionId2 = UUID.randomUUID();
+ while (subscriptionId2.compareTo(subscriptionId1) <= 0) {
+ subscriptionId2 = UUID.randomUUID();
+ }
+ testPauseAccountAtPhaseAndPauseOtherSubscriptionFutureStarted(subscriptionId1, subscriptionId2);
+ }
+
+ @Test(groups = "fast", invocationCount = 10)
+ public void testPauseAccountAtPhaseAndPauseOtherSubscriptionFutureStartedV2() throws Exception {
+ final UUID subscriptionId1 = UUID.randomUUID();
+ UUID subscriptionId2 = UUID.randomUUID();
+ while (subscriptionId2.compareTo(subscriptionId1) >= 0) {
+ subscriptionId2 = UUID.randomUUID();
+ }
+ testPauseAccountAtPhaseAndPauseOtherSubscriptionFutureStarted(subscriptionId1, subscriptionId2);
+ }
+
+ private void testPauseAccountAtPhaseAndPauseOtherSubscriptionFutureStarted(final UUID subscriptionId1, final UUID subscriptionId2) throws Exception {
+ final DateTime now = clock.getUTCNow();
+
+ final Collection<BlockingState> blockingStates = new LinkedList<BlockingState>();
+ blockingStates.add(createBlockingState(subscriptionId1, BlockingStateType.SUBSCRIPTION, DefaultEntitlementApi.ENT_STATE_START, DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME, false, false, now));
+ blockingStates.add(createBlockingState(subscriptionId2, BlockingStateType.SUBSCRIPTION, DefaultEntitlementApi.ENT_STATE_START, DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME, false, false, now));
+ blockingStates.add(createBlockingState(UUID.randomUUID(), BlockingStateType.ACCOUNT, "stuff", "svc1", true, true, now.plusDays(30)));
+
+ final LinkedList<SubscriptionEvent> allEvents = new LinkedList<SubscriptionEvent>();
+ allEvents.add(createEvent(subscriptionId1, SubscriptionEventType.START_BILLING, now));
+ allEvents.add(createEvent(subscriptionId1, SubscriptionEventType.PHASE, now.plusDays(30)));
+ allEvents.add(createEvent(subscriptionId2, SubscriptionEventType.START_BILLING, now.plusDays(40)));
+
+ computeEvents(allEvents, blockingStates);
+
+ Assert.assertEquals(allEvents.size(), 8);
+ Assert.assertEquals(allEvents.get(0).getSubscriptionEventType(), SubscriptionEventType.START_ENTITLEMENT);
+ Assert.assertEquals(allEvents.get(1).getSubscriptionEventType(), SubscriptionEventType.START_ENTITLEMENT);
+ Assert.assertEquals(allEvents.get(2).getSubscriptionEventType(), SubscriptionEventType.START_BILLING);
+ Assert.assertEquals(allEvents.get(3).getSubscriptionEventType(), SubscriptionEventType.PHASE);
+ if (subscriptionId1.compareTo(subscriptionId2) >= 0) {
+ Assert.assertEquals(allEvents.get(4).getSubscriptionEventType(), SubscriptionEventType.PAUSE_ENTITLEMENT);
+ Assert.assertEquals(allEvents.get(5).getSubscriptionEventType(), SubscriptionEventType.PAUSE_ENTITLEMENT);
+ Assert.assertEquals(allEvents.get(6).getSubscriptionEventType(), SubscriptionEventType.PAUSE_BILLING);
+ } else {
+ Assert.assertEquals(allEvents.get(4).getSubscriptionEventType(), SubscriptionEventType.PAUSE_ENTITLEMENT);
+ Assert.assertEquals(allEvents.get(5).getSubscriptionEventType(), SubscriptionEventType.PAUSE_BILLING);
+ Assert.assertEquals(allEvents.get(6).getSubscriptionEventType(), SubscriptionEventType.PAUSE_ENTITLEMENT);
+ }
+ Assert.assertEquals(allEvents.get(7).getSubscriptionEventType(), SubscriptionEventType.START_BILLING);
+ }
+
+ private BlockingState createBlockingState(final UUID blockedId,
+ final BlockingStateType blockingStateType,
+ final String stateName,
+ final String service,
+ final boolean blockEntitlement,
+ final boolean blockBilling,
+ final DateTime effectiveDate) {
+ return new DefaultBlockingState(UUID.randomUUID(),
+ blockedId,
+ blockingStateType,
+ stateName,
+ service,
+ false,
+ blockEntitlement,
+ blockBilling,
+ effectiveDate,
+ effectiveDate,
+ effectiveDate,
+ globalOrdering++);
+ }
+
+ // Re-use SubscriptionEventOrdering method, as it's the input of BlockingStateOrdering
+ private SubscriptionEvent createEvent(final UUID subscriptionId, final SubscriptionEventType type, final DateTime effectiveDate) {
+ final SubscriptionBaseTransition subscriptionBaseTransition = Mockito.mock(SubscriptionBaseTransition.class);
+ Mockito.when(subscriptionBaseTransition.getId()).thenReturn(UUID.randomUUID());
+ Mockito.when(subscriptionBaseTransition.getSubscriptionId()).thenReturn(subscriptionId);
+ Mockito.when(subscriptionBaseTransition.getEffectiveTransitionTime()).thenReturn(effectiveDate);
+ return SubscriptionEventOrdering.toSubscriptionEvent(subscriptionBaseTransition, type, internalCallContext);
+ }
+
+ private void computeEvents(final LinkedList<SubscriptionEvent> allEvents, final Collection<BlockingState> blockingStates) {
+ final Collection<UUID> allEntitlementUUIDs = new HashSet<UUID>();
+ for (final SubscriptionEvent subscriptionEvent : allEvents) {
+ allEntitlementUUIDs.add(subscriptionEvent.getEntitlementId());
+ }
+ for (final BlockingState blockingState : blockingStates) {
+ if (blockingState.getType() == BlockingStateType.SUBSCRIPTION) {
+ allEntitlementUUIDs.add(blockingState.getBlockedId());
+ }
+ }
+
+ BlockingStateOrdering.INSTANCE.computeEvents(new LinkedList<UUID>(allEntitlementUUIDs), blockingStates, internalCallContext, allEvents);
+ }
+}
diff --git a/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultEntitlement.java b/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultEntitlement.java
index 6771c42..c5cc731 100644
--- a/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultEntitlement.java
+++ b/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultEntitlement.java
@@ -57,7 +57,7 @@ public class TestDefaultEntitlement extends EntitlementTestSuiteWithEmbeddedDB {
// Create entitlement and check each field
testListener.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK);
- final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, null, null, false, ImmutableList.<PluginProperty>of(), callContext);
+ final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, null, null, false, true, ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
assertEquals(entitlement.getState(), EntitlementState.ACTIVE);
assertEquals(entitlement.getSourceType(), EntitlementSourceType.NATIVE);
@@ -85,7 +85,7 @@ public class TestDefaultEntitlement extends EntitlementTestSuiteWithEmbeddedDB {
// Create entitlement and check each field
testListener.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK);
- final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, null, null, false, ImmutableList.<PluginProperty>of(), callContext);
+ final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, null, null, false, true, ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
assertEquals(entitlement.getState(), EntitlementState.ACTIVE);
@@ -118,7 +118,7 @@ public class TestDefaultEntitlement extends EntitlementTestSuiteWithEmbeddedDB {
// Create entitlement and check each field
testListener.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK);
- final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, null, null, false, ImmutableList.<PluginProperty>of(), callContext);
+ final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, null, null, false, true, ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
assertEquals(entitlement.getState(), EntitlementState.ACTIVE);
@@ -151,7 +151,7 @@ public class TestDefaultEntitlement extends EntitlementTestSuiteWithEmbeddedDB {
// Create entitlement and check each field
testListener.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK);
- final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, null, null, false, ImmutableList.<PluginProperty>of(), callContext);
+ final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, null, null, false, true, ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
testListener.pushExpectedEvents(NextEvent.CANCEL, NextEvent.BLOCK);
@@ -176,7 +176,7 @@ public class TestDefaultEntitlement extends EntitlementTestSuiteWithEmbeddedDB {
// Create entitlement and check each field
testListener.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK);
- final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, null, null, false, ImmutableList.<PluginProperty>of(), callContext);
+ final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, null, null, false, true, ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
final DateTime ctd = clock.getUTCNow().plusDays(30).plusMonths(1);
@@ -219,7 +219,7 @@ public class TestDefaultEntitlement extends EntitlementTestSuiteWithEmbeddedDB {
// Create entitlement and check each field
testListener.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK);
- final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, null, null, false, ImmutableList.<PluginProperty>of(), callContext);
+ final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, null, null, false, true, ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
final DateTime ctd = clock.getUTCNow().plusDays(30).plusMonths(1);
@@ -259,12 +259,12 @@ public class TestDefaultEntitlement extends EntitlementTestSuiteWithEmbeddedDB {
// Create entitlement and check each field
testListener.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK);
- final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, null, null, false, ImmutableList.<PluginProperty>of(), callContext);
+ final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, null, null, false, true, ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
// Immediate change during trial
testListener.pushExpectedEvent(NextEvent.CREATE);
- entitlement.changePlan(new PlanSpecifier("Assault-Rifle", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME), null, ImmutableList.<PluginProperty>of(), callContext);
+ entitlement.changePlan(new PlanPhaseSpecifier("Assault-Rifle", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME), null, ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
// Verify the change is immediate
@@ -295,7 +295,7 @@ public class TestDefaultEntitlement extends EntitlementTestSuiteWithEmbeddedDB {
final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Shotgun", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
// Create entitlement and check each field
- final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, startDate, startDate, false, ImmutableList.<PluginProperty>of(), callContext);
+ final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, startDate, startDate, false, true, ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
assertEquals(entitlement.getState(), EntitlementState.PENDING);
diff --git a/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultEntitlementApi.java b/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultEntitlementApi.java
index 718b61e..58285fb 100644
--- a/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultEntitlementApi.java
+++ b/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultEntitlementApi.java
@@ -63,7 +63,7 @@ public class TestDefaultEntitlementApi extends EntitlementTestSuiteWithEmbeddedD
// Keep the same object for the whole test, to make sure we refresh its state before r/w calls
testListener.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK);
- final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, null, null, false, ImmutableList.<PluginProperty>of(), callContext);
+ final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, null, null, false, true, ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
// Add ADD_ON
@@ -126,7 +126,7 @@ public class TestDefaultEntitlementApi extends EntitlementTestSuiteWithEmbeddedD
// Keep the same object for the whole test, to make sure we refresh its state before r/w calls
testListener.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK);
- final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, null, null, false, ImmutableList.<PluginProperty>of(), callContext);
+ final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, null, null, false, true, ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
testListener.pushExpectedEvent(NextEvent.PHASE);
@@ -159,7 +159,7 @@ public class TestDefaultEntitlementApi extends EntitlementTestSuiteWithEmbeddedD
// Create entitlement and check each field
testListener.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK);
- final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, null, null, false, ImmutableList.<PluginProperty>of(), callContext);
+ final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, null, null, false, true, ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
assertEquals(entitlement.getAccountId(), account.getId());
assertEquals(entitlement.getExternalKey(), account.getExternalKey());
@@ -251,7 +251,7 @@ public class TestDefaultEntitlementApi extends EntitlementTestSuiteWithEmbeddedD
// Create entitlement and check each field
testListener.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK);
- final Entitlement baseEntitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, null, null, false, ImmutableList.<PluginProperty>of(), callContext);
+ final Entitlement baseEntitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, null, null, false, true, ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
// Add ADD_ON
@@ -290,7 +290,7 @@ public class TestDefaultEntitlementApi extends EntitlementTestSuiteWithEmbeddedD
// Create entitlement and check each field
final LocalDate startDate = initialDate.plusDays(10);
- final Entitlement baseEntitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, startDate, startDate, false, ImmutableList.<PluginProperty>of(), callContext);
+ final Entitlement baseEntitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, startDate, startDate, false, true, ImmutableList.<PluginProperty>of(), callContext);
// Add ADD_ON immediately. Because BASE is PENDING should fail
final PlanPhaseSpecifier spec1 = new PlanPhaseSpecifier("Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
@@ -341,7 +341,7 @@ public class TestDefaultEntitlementApi extends EntitlementTestSuiteWithEmbeddedD
// Create entitlement and check each field
testListener.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK);
- final Entitlement baseEntitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, null, null, false, ImmutableList.<PluginProperty>of(), callContext);
+ final Entitlement baseEntitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, null, null, false, true, ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
clock.addDays(1);
@@ -355,7 +355,9 @@ public class TestDefaultEntitlementApi extends EntitlementTestSuiteWithEmbeddedD
clock.addDays(5);
testListener.pushExpectedEvents(NextEvent.BLOCK);
- entitlementApi.pause(baseEntitlement.getBundleId(), new LocalDate(clock.getUTCNow()), ImmutableList.<PluginProperty>of(), callContext);
+ final LocalDate blockingStateDate = new LocalDate(clock.getUTCNow());
+ entitlementApi.pause(baseEntitlement.getBundleId(), blockingStateDate, ImmutableList
+ .<PluginProperty>of(), callContext);
assertListenerStatus();
// Verify blocking state
@@ -374,7 +376,8 @@ public class TestDefaultEntitlementApi extends EntitlementTestSuiteWithEmbeddedD
// Try to add an ADD_ON, it should fail because BASE is blocked
try {
final PlanPhaseSpecifier spec3 = new PlanPhaseSpecifier("Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
- entitlementApi.addEntitlement(baseEntitlement.getBundleId(), spec3, null, effectiveDateSpec1, effectiveDateSpec1, false, ImmutableList.<PluginProperty>of(), callContext);
+ entitlementApi.addEntitlement(baseEntitlement.getBundleId(), spec3, null, blockingStateDate, effectiveDateSpec1, false, ImmutableList.<PluginProperty>of(), callContext);
+ fail("Should not be able to create ADD-ON because BP is paused");
} catch (EntitlementApiException e) {
assertEquals(e.getCode(), ErrorCode.BLOCK_BLOCKED_ACTION.getCode());
}
@@ -413,7 +416,7 @@ public class TestDefaultEntitlementApi extends EntitlementTestSuiteWithEmbeddedD
// Create entitlement
testListener.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK);
final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Shotgun", BillingPeriod.ANNUAL, PriceListSet.DEFAULT_PRICELIST_NAME, null);
- final Entitlement baseEntitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, null, null, false, ImmutableList.<PluginProperty>of(), callContext);
+ final Entitlement baseEntitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, null, null, false, true, ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
// Get the phase event out of the way
@@ -504,7 +507,7 @@ public class TestDefaultEntitlementApi extends EntitlementTestSuiteWithEmbeddedD
// Create entitlement (with migrated flag so we can check later that transferred subscription is in right status)
testListener.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK);
- final Entitlement baseEntitlement = entitlementApi.createBaseEntitlement(accountSrc.getId(), spec, accountSrc.getExternalKey(), null, null, null, true, ImmutableList.<PluginProperty>of(), callContext);
+ final Entitlement baseEntitlement = entitlementApi.createBaseEntitlement(accountSrc.getId(), spec, accountSrc.getExternalKey(), null, null, null, true, true, ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
assertEquals(baseEntitlement.getSourceType(), EntitlementSourceType.MIGRATED);
// Again to make sure this flag is correctly wrote/set
@@ -552,7 +555,7 @@ public class TestDefaultEntitlementApi extends EntitlementTestSuiteWithEmbeddedD
// Keep the same object for the whole test, to make sure we refresh its state before r/w calls
testListener.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK, NextEvent.PHASE);
- final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, initialDate, initialDate, false, ImmutableList.<PluginProperty>of(), callContext);
+ final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, initialDate, initialDate, false, true, ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
assertEquals(entitlement.getAccountId(), account.getId());
@@ -598,7 +601,7 @@ public class TestDefaultEntitlementApi extends EntitlementTestSuiteWithEmbeddedD
final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Shotgun", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
- Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, entitlementDate, billingDate, false, ImmutableList.<PluginProperty>of(), callContext);
+ Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, entitlementDate, billingDate, false, true, ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
assertEquals(entitlement.getState(), EntitlementState.PENDING);
@@ -656,7 +659,7 @@ public class TestDefaultEntitlementApi extends EntitlementTestSuiteWithEmbeddedD
final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Shotgun", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
testListener.pushExpectedEvents(NextEvent.CREATE);
- final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, entitlementDate, billingDate, false, ImmutableList.<PluginProperty>of(), callContext);
+ final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, entitlementDate, billingDate, false, true, ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
assertEquals(entitlement.getState(), EntitlementState.PENDING);
@@ -683,7 +686,7 @@ public class TestDefaultEntitlementApi extends EntitlementTestSuiteWithEmbeddedD
final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Shotgun", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
testListener.pushExpectedEvents(NextEvent.BLOCK);
- final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, entitlementDate, billingDate, false, ImmutableList.<PluginProperty>of(), callContext);
+ final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, entitlementDate, billingDate, false, true, ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
assertEquals(entitlement.getState(), EntitlementState.ACTIVE);
@@ -707,7 +710,7 @@ public class TestDefaultEntitlementApi extends EntitlementTestSuiteWithEmbeddedD
final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Shotgun", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
testListener.pushExpectedEvents(NextEvent.BLOCK, NextEvent.CREATE);
- final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, entitlementDate, billingDate, false, ImmutableList.<PluginProperty>of(), callContext);
+ final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, entitlementDate, billingDate, false, true, ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
assertEquals(entitlement.getState(), EntitlementState.ACTIVE);
@@ -727,7 +730,7 @@ public class TestDefaultEntitlementApi extends EntitlementTestSuiteWithEmbeddedD
final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Shotgun", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
testListener.pushExpectedEvents(NextEvent.BLOCK, NextEvent.CREATE);
- final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, entitlementDate, billingDate, false, ImmutableList.<PluginProperty>of(), callContext);
+ final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, entitlementDate, billingDate, false, true, ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
assertEquals(entitlement.getState(), EntitlementState.ACTIVE);
@@ -747,7 +750,7 @@ public class TestDefaultEntitlementApi extends EntitlementTestSuiteWithEmbeddedD
final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Shotgun", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
testListener.pushExpectedEvents(NextEvent.BLOCK, NextEvent.CREATE);
- final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, entitlementDate, billingDate, false, ImmutableList.<PluginProperty>of(), callContext);
+ final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, entitlementDate, billingDate, false, true, ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
assertEquals(entitlement.getState(), EntitlementState.ACTIVE);
@@ -765,7 +768,7 @@ public class TestDefaultEntitlementApi extends EntitlementTestSuiteWithEmbeddedD
final String bundleKey2 = "bundleKey2";
final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Shotgun", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
testListener.pushExpectedEvents(NextEvent.BLOCK, NextEvent.CREATE);
- entitlementApi.createBaseEntitlement(account.getId(), spec, bundleKey2, null, null, null, false, ImmutableList.<PluginProperty>of(), callContext);
+ entitlementApi.createBaseEntitlement(account.getId(), spec, bundleKey2, null, null, null, false, true, ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
@@ -787,7 +790,7 @@ public class TestDefaultEntitlementApi extends EntitlementTestSuiteWithEmbeddedD
// We expect 3 {BLOCK, CREATE} events for the 3 subscriptions created,.
testListener.pushExpectedEvents(NextEvent.BLOCK, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.CREATE);
- final List<Entitlement> entitlements = entitlementApi.createBaseEntitlementsWithAddOns(account.getId(), baseEntitlementWithAddOnsSpecifiers, ImmutableList.<PluginProperty>of(), callContext);
+ final List<Entitlement> entitlements = entitlementApi.createBaseEntitlementsWithAddOns(account.getId(), baseEntitlementWithAddOnsSpecifiers, true, ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
// Retun only the created subscriptions
@@ -818,7 +821,7 @@ public class TestDefaultEntitlementApi extends EntitlementTestSuiteWithEmbeddedD
final Iterable<BaseEntitlementWithAddOnsSpecifier> baseEntitlementWithAddOnsSpecifiers = ImmutableList.of(baseEntitlementWithAddOnsSpecifier2);
- entitlementApi.createBaseEntitlementsWithAddOns(account.getId(), baseEntitlementWithAddOnsSpecifiers, ImmutableList.<PluginProperty>of(), callContext);
+ entitlementApi.createBaseEntitlementsWithAddOns(account.getId(), baseEntitlementWithAddOnsSpecifiers, true, ImmutableList.<PluginProperty>of(), callContext);
}
@Test(groups = "slow")
@@ -838,7 +841,7 @@ public class TestDefaultEntitlementApi extends EntitlementTestSuiteWithEmbeddedD
testListener.pushExpectedEvents(NextEvent.BLOCK, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.CREATE);
- entitlementApi.createBaseEntitlementsWithAddOns(account.getId(), baseEntitlementWithAddOnsSpecifiers, ImmutableList.<PluginProperty>of(), callContext);
+ entitlementApi.createBaseEntitlementsWithAddOns(account.getId(), baseEntitlementWithAddOnsSpecifiers, true, ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
}
diff --git a/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultSubscriptionApi.java b/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultSubscriptionApi.java
index 18ac1dd..6fca4f6 100644
--- a/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultSubscriptionApi.java
+++ b/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultSubscriptionApi.java
@@ -61,14 +61,14 @@ public class TestDefaultSubscriptionApi extends EntitlementTestSuiteWithEmbedded
final Account account = createAccount(getAccountData(7));
final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Shotgun", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
testListener.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK);
- final Entitlement entitlement1 = entitlementApi.createBaseEntitlement(account.getId(), spec, UUID.randomUUID().toString(), null, null, null, false, ImmutableList.<PluginProperty>of(), callContext);
+ final Entitlement entitlement1 = entitlementApi.createBaseEntitlement(account.getId(), spec, UUID.randomUUID().toString(), null, null, null, false, true, ImmutableList.<PluginProperty>of(), callContext);
// Sleep 1 sec so created date are apart from each other and ordering in the bundle does not default on the UUID which is random.
try {
Thread.sleep(1000);
} catch (InterruptedException ignore) {
}
testListener.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK);
- final Entitlement entitlement2 = entitlementApi.createBaseEntitlement(account.getId(), spec, UUID.randomUUID().toString(), null, null, null, false, ImmutableList.<PluginProperty>of(), callContext);
+ final Entitlement entitlement2 = entitlementApi.createBaseEntitlement(account.getId(), spec, UUID.randomUUID().toString(), null, null, null, false, true, ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
testListener.pushExpectedEvents(NextEvent.BLOCK);
@@ -114,7 +114,7 @@ public class TestDefaultSubscriptionApi extends EntitlementTestSuiteWithEmbedded
// Create entitlement and check each field
testListener.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK);
- final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, externalKey, null, null, null, false, ImmutableList.<PluginProperty>of(), callContext);
+ final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, externalKey, null, null, null, false, true, ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
assertEquals(entitlement.getAccountId(), account.getId());
assertEquals(entitlement.getExternalKey(), externalKey);
@@ -148,7 +148,7 @@ public class TestDefaultSubscriptionApi extends EntitlementTestSuiteWithEmbedded
// Create entitlement and check each field
testListener.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK);
- final Entitlement entitlement2 = entitlementApi.createBaseEntitlement(account.getId(), spec2, externalKey, null, null, null, false, ImmutableList.<PluginProperty>of(), callContext);
+ final Entitlement entitlement2 = entitlementApi.createBaseEntitlement(account.getId(), spec2, externalKey, null, null, null, false, true, ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
assertEquals(entitlement2.getAccountId(), account.getId());
assertEquals(entitlement2.getExternalKey(), externalKey);
@@ -219,7 +219,7 @@ public class TestDefaultSubscriptionApi extends EntitlementTestSuiteWithEmbedded
// Create entitlement
testListener.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK);
final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Shotgun", BillingPeriod.ANNUAL, PriceListSet.DEFAULT_PRICELIST_NAME, null);
- final Entitlement baseEntitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, null, null, false, ImmutableList.<PluginProperty>of(), callContext);
+ final Entitlement baseEntitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, null, null, false, true, ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
// Get the phase event out of the way
@@ -277,7 +277,7 @@ public class TestDefaultSubscriptionApi extends EntitlementTestSuiteWithEmbedded
final LocalDate effectiveDate = initialDate.plusMonths(1);
// Create entitlement and check each field
- final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, externalKey, null, effectiveDate, effectiveDate, false, ImmutableList.<PluginProperty>of(), callContext);
+ final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, externalKey, null, effectiveDate, effectiveDate, false, true, ImmutableList.<PluginProperty>of(), callContext);
final Subscription subscription = subscriptionApi.getSubscriptionForEntitlementId(entitlement.getId(), callContext);
@@ -309,7 +309,7 @@ public class TestDefaultSubscriptionApi extends EntitlementTestSuiteWithEmbedded
final LocalDate futureDate = new LocalDate(2013, 9, 1);
// No CREATE event as this is set in the future
- final Entitlement createdEntitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, futureDate, futureDate, false, ImmutableList.<PluginProperty>of(), callContext);
+ final Entitlement createdEntitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, futureDate, futureDate, false, true, ImmutableList.<PluginProperty>of(), callContext);
assertEquals(createdEntitlement.getEffectiveStartDate().compareTo(futureDate), 0);
assertEquals(createdEntitlement.getEffectiveEndDate(), null);
@@ -337,7 +337,7 @@ public class TestDefaultSubscriptionApi extends EntitlementTestSuiteWithEmbedded
final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Shotgun", BillingPeriod.ANNUAL, PriceListSet.DEFAULT_PRICELIST_NAME, null);
testListener.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK);
- final Entitlement createdEntitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, initialDate, initialDate, false, ImmutableList.<PluginProperty>of(), callContext);
+ final Entitlement createdEntitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, initialDate, initialDate, false, true, ImmutableList.<PluginProperty>of(), callContext);
final Iterable<BlockingState> iterableForCreateState = subscriptionApi.getBlockingStates(account.getId(), ImmutableList.of(BlockingStateType.SUBSCRIPTION), null, OrderingType.ASCENDING, SubscriptionApi.ALL_EVENTS, callContext);
assertTrue(iterableForCreateState.iterator().hasNext());
@@ -412,7 +412,7 @@ public class TestDefaultSubscriptionApi extends EntitlementTestSuiteWithEmbedded
// Create entitlement and check each field
testListener.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK);
- final Entitlement baseEntitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, null, null, false, ImmutableList.<PluginProperty>of(), callContext);
+ final Entitlement baseEntitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, null, null, false, true, ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
clock.addDays(5);
@@ -486,7 +486,7 @@ public class TestDefaultSubscriptionApi extends EntitlementTestSuiteWithEmbedded
// Create entitlement and check each field
testListener.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK);
- final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, null, null, false, ImmutableList.<PluginProperty>of(), callContext);
+ final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, null, null, false, true, ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
clock.addDays(1);
@@ -498,7 +498,7 @@ public class TestDefaultSubscriptionApi extends EntitlementTestSuiteWithEmbedded
assertListenerStatus();
try {
- entitlement.changePlan(new PlanSpecifier("Assault-Rifle", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME), null, ImmutableList.<PluginProperty>of(), callContext);
+ entitlement.changePlan(new PlanPhaseSpecifier("Assault-Rifle", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME), null, ImmutableList.<PluginProperty>of(), callContext);
fail();
} catch (final EntitlementApiException e) {
assertEquals(e.getCode(), ErrorCode.BLOCK_BLOCKED_ACTION.getCode());
@@ -507,7 +507,7 @@ public class TestDefaultSubscriptionApi extends EntitlementTestSuiteWithEmbedded
}
try {
- entitlement.changePlanWithDate(new PlanSpecifier("Assault-Rifle", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME), null, clock.getUTCToday(), ImmutableList.<PluginProperty>of(), callContext);
+ entitlement.changePlanWithDate(new PlanPhaseSpecifier("Assault-Rifle", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME), null, clock.getUTCToday(), ImmutableList.<PluginProperty>of(), callContext);
fail();
} catch (final EntitlementApiException e) {
assertEquals(e.getCode(), ErrorCode.BLOCK_BLOCKED_ACTION.getCode());
@@ -526,7 +526,7 @@ public class TestDefaultSubscriptionApi extends EntitlementTestSuiteWithEmbedded
// Create entitlement
testListener.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK);
final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Shotgun", BillingPeriod.ANNUAL, PriceListSet.DEFAULT_PRICELIST_NAME, null);
- final Entitlement baseEntitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, null, null, false, ImmutableList.<PluginProperty>of(), callContext);
+ final Entitlement baseEntitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, null, null, false, true, ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
// 2013-08-10 : Stay in TRIAL to ensure IMMEDIATE billing policy is used
@@ -576,7 +576,7 @@ public class TestDefaultSubscriptionApi extends EntitlementTestSuiteWithEmbedded
final LocalDate effectiveDate = initialDate.plusMonths(1);
try {
- entitlementApi.createBaseEntitlement(account.getId(), spec, externalKey, null, effectiveDate, effectiveDate, false, ImmutableList.<PluginProperty>of(), callContext);
+ entitlementApi.createBaseEntitlement(account.getId(), spec, externalKey, null, effectiveDate, effectiveDate, false, true, ImmutableList.<PluginProperty>of(), callContext);
Assert.fail();
} catch (final EntitlementApiException e) {
assertEquals(e.getCode(), ErrorCode.EXTERNAL_KEY_LIMIT_EXCEEDED.getCode());
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 fc7fb25..b82d6e8 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
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2016 Groupon, Inc
- * Copyright 2014-2016 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
@@ -41,7 +41,6 @@ import org.killbill.billing.subscription.api.user.SubscriptionBaseTransitionData
import org.killbill.billing.subscription.events.SubscriptionBaseEvent.EventType;
import org.killbill.billing.subscription.events.user.ApiEventType;
import org.mockito.Mockito;
-import org.testng.Assert;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
@@ -62,209 +61,6 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
bundleExternalKey = bundleId.toString();
}
- public class TestSubscriptionBundleTimeline extends DefaultSubscriptionBundleTimeline {
-
- public TestSubscriptionBundleTimeline(final UUID accountId, final UUID bundleId, final String externalKey, final Iterable<Entitlement> entitlements) {
- super(accountId, bundleId, externalKey, entitlements, internalCallContext);
- }
-
- public SubscriptionEvent createEvent(final UUID subscriptionId, final SubscriptionEventType type, final DateTime effectiveDate) {
- return new DefaultSubscriptionEvent(UUID.randomUUID(),
- subscriptionId,
- effectiveDate,
- type,
- true,
- true,
- "foo",
- "bar",
- null,
- null,
- null,
- null,
- null,
- null,
- null,
- null,
- null,
- null,
- null,
- internalCallContext);
-
- }
- }
-
- @Test(groups = "fast")
- public void testReOrderSubscriptionEventsOnInvalidOrder1() {
- final TestSubscriptionBundleTimeline timeline = new TestSubscriptionBundleTimeline(null, null, null, new ArrayList<Entitlement>());
-
- final List<SubscriptionEvent> events = new ArrayList<SubscriptionEvent>();
- final UUID subscriptionId = UUID.randomUUID();
- final DateTime effectiveDate = clock.getUTCNow();
- events.add(timeline.createEvent(subscriptionId, SubscriptionEventType.START_BILLING, effectiveDate));
- events.add(timeline.createEvent(subscriptionId, SubscriptionEventType.START_ENTITLEMENT, effectiveDate));
- events.add(timeline.createEvent(subscriptionId, SubscriptionEventType.STOP_ENTITLEMENT, effectiveDate));
- events.add(timeline.createEvent(subscriptionId, SubscriptionEventType.STOP_BILLING, effectiveDate));
-
- SubscriptionEventOrdering.INSTANCE.reOrderSubscriptionEventsOnSameDateByType(events);
-
- Assert.assertEquals(events.get(0).getSubscriptionEventType(), SubscriptionEventType.START_ENTITLEMENT);
- Assert.assertEquals(events.get(1).getSubscriptionEventType(), SubscriptionEventType.START_BILLING);
- Assert.assertEquals(events.get(2).getSubscriptionEventType(), SubscriptionEventType.STOP_ENTITLEMENT);
- Assert.assertEquals(events.get(3).getSubscriptionEventType(), SubscriptionEventType.STOP_BILLING);
- }
-
- @Test(groups = "fast")
- public void testReOrderSubscriptionEventsOnInvalidOrder2() {
- final TestSubscriptionBundleTimeline timeline = new TestSubscriptionBundleTimeline(null, null, null, new ArrayList<Entitlement>());
-
- final List<SubscriptionEvent> events = new ArrayList<SubscriptionEvent>();
- final UUID subscriptionId = UUID.randomUUID();
- final DateTime effectiveDate = clock.getUTCNow();
- events.add(timeline.createEvent(subscriptionId, SubscriptionEventType.START_BILLING, effectiveDate));
- events.add(timeline.createEvent(subscriptionId, SubscriptionEventType.START_ENTITLEMENT, effectiveDate));
- events.add(timeline.createEvent(subscriptionId, SubscriptionEventType.STOP_BILLING, effectiveDate));
- events.add(timeline.createEvent(subscriptionId, SubscriptionEventType.STOP_ENTITLEMENT, effectiveDate));
-
- SubscriptionEventOrdering.INSTANCE.reOrderSubscriptionEventsOnSameDateByType(events);
-
- Assert.assertEquals(events.get(0).getSubscriptionEventType(), SubscriptionEventType.START_ENTITLEMENT);
- Assert.assertEquals(events.get(1).getSubscriptionEventType(), SubscriptionEventType.START_BILLING);
- Assert.assertEquals(events.get(2).getSubscriptionEventType(), SubscriptionEventType.STOP_ENTITLEMENT);
- Assert.assertEquals(events.get(3).getSubscriptionEventType(), SubscriptionEventType.STOP_BILLING);
- }
-
- @Test(groups = "fast")
- public void testReOrderSubscriptionEventsOnInvalidOrder3() {
- final TestSubscriptionBundleTimeline timeline = new TestSubscriptionBundleTimeline(null, null, null, new ArrayList<Entitlement>());
-
- final List<SubscriptionEvent> events = new ArrayList<SubscriptionEvent>();
- final UUID subscriptionId = UUID.randomUUID();
- final DateTime effectiveDate = clock.getUTCNow();
- events.add(timeline.createEvent(subscriptionId, SubscriptionEventType.START_BILLING, effectiveDate));
- events.add(timeline.createEvent(subscriptionId, SubscriptionEventType.STOP_ENTITLEMENT, effectiveDate));
- events.add(timeline.createEvent(subscriptionId, SubscriptionEventType.STOP_BILLING, effectiveDate));
- events.add(timeline.createEvent(subscriptionId, SubscriptionEventType.START_ENTITLEMENT, effectiveDate));
-
- SubscriptionEventOrdering.INSTANCE.reOrderSubscriptionEventsOnSameDateByType(events);
-
- Assert.assertEquals(events.get(0).getSubscriptionEventType(), SubscriptionEventType.START_ENTITLEMENT);
- Assert.assertEquals(events.get(1).getSubscriptionEventType(), SubscriptionEventType.START_BILLING);
- Assert.assertEquals(events.get(2).getSubscriptionEventType(), SubscriptionEventType.STOP_ENTITLEMENT);
- Assert.assertEquals(events.get(3).getSubscriptionEventType(), SubscriptionEventType.STOP_BILLING);
- }
-
- @Test(groups = "fast")
- public void testReOrderSubscriptionEventsOnInvalidOrderAndDifferentSubscriptionsSameDates1() {
- final TestSubscriptionBundleTimeline timeline = new TestSubscriptionBundleTimeline(null, null, null, new ArrayList<Entitlement>());
-
- final List<SubscriptionEvent> events = new ArrayList<SubscriptionEvent>();
- final UUID subscriptionId = UUID.fromString("60b64e0c-cefd-48c3-8de9-c731a9558165");
-
- final UUID otherSubscriptionId = UUID.fromString("35b3b340-31b2-46ea-b062-e9fc9fab3bc9");
- final DateTime effectiveDate = clock.getUTCNow();
-
- events.add(timeline.createEvent(subscriptionId, SubscriptionEventType.START_ENTITLEMENT, effectiveDate));
- events.add(timeline.createEvent(subscriptionId, SubscriptionEventType.START_BILLING, effectiveDate));
- events.add(timeline.createEvent(subscriptionId, SubscriptionEventType.STOP_BILLING, effectiveDate));
- events.add(timeline.createEvent(otherSubscriptionId, SubscriptionEventType.STOP_BILLING, effectiveDate));
- events.add(timeline.createEvent(subscriptionId, SubscriptionEventType.STOP_ENTITLEMENT, effectiveDate));
-
- SubscriptionEventOrdering.INSTANCE.reOrderSubscriptionEventsOnSameDateByType(events);
-
- Assert.assertEquals(events.get(0).getSubscriptionEventType(), SubscriptionEventType.STOP_BILLING);
- Assert.assertEquals(events.get(0).getEntitlementId(), otherSubscriptionId);
- Assert.assertEquals(events.get(1).getSubscriptionEventType(), SubscriptionEventType.START_ENTITLEMENT);
- Assert.assertEquals(events.get(2).getSubscriptionEventType(), SubscriptionEventType.START_BILLING);
- Assert.assertEquals(events.get(3).getSubscriptionEventType(), SubscriptionEventType.STOP_ENTITLEMENT);
- Assert.assertEquals(events.get(4).getSubscriptionEventType(), SubscriptionEventType.STOP_BILLING);
- Assert.assertEquals(events.get(4).getEntitlementId(), subscriptionId);
- }
-
- @Test(groups = "fast")
- public void testReOrderSubscriptionEventsOnInvalidOrderAndDifferentSubscriptionsSameDates2() {
- final TestSubscriptionBundleTimeline timeline = new TestSubscriptionBundleTimeline(null, null, null, new ArrayList<Entitlement>());
-
- final List<SubscriptionEvent> events = new ArrayList<SubscriptionEvent>();
- final UUID subscriptionId = UUID.fromString("35b3b340-31b2-46ea-b062-e9fc9fab3bc9");
- final UUID otherSubscriptionId = UUID.fromString("60b64e0c-cefd-48c3-8de9-c731a9558165");
-
- final DateTime effectiveDate = clock.getUTCNow();
-
- events.add(timeline.createEvent(subscriptionId, SubscriptionEventType.START_ENTITLEMENT, effectiveDate));
- events.add(timeline.createEvent(subscriptionId, SubscriptionEventType.START_BILLING, effectiveDate));
- events.add(timeline.createEvent(subscriptionId, SubscriptionEventType.STOP_BILLING, effectiveDate));
- events.add(timeline.createEvent(otherSubscriptionId, SubscriptionEventType.STOP_BILLING, effectiveDate));
- events.add(timeline.createEvent(subscriptionId, SubscriptionEventType.STOP_ENTITLEMENT, effectiveDate));
-
- SubscriptionEventOrdering.INSTANCE.reOrderSubscriptionEventsOnSameDateByType(events);
-
- Assert.assertEquals(events.get(0).getSubscriptionEventType(), SubscriptionEventType.START_ENTITLEMENT);
- Assert.assertEquals(events.get(1).getSubscriptionEventType(), SubscriptionEventType.START_BILLING);
- Assert.assertEquals(events.get(2).getSubscriptionEventType(), SubscriptionEventType.STOP_ENTITLEMENT);
- Assert.assertEquals(events.get(3).getSubscriptionEventType(), SubscriptionEventType.STOP_BILLING);
- Assert.assertEquals(events.get(3).getEntitlementId(), subscriptionId);
- Assert.assertEquals(events.get(4).getSubscriptionEventType(), SubscriptionEventType.STOP_BILLING);
- Assert.assertEquals(events.get(4).getEntitlementId(), otherSubscriptionId);
- }
-
- @Test(groups = "fast")
- public void testReOrderSubscriptionEventsOnInvalidOrderAndDifferentSubscriptionsDates() {
- final TestSubscriptionBundleTimeline timeline = new TestSubscriptionBundleTimeline(null, null, null, new ArrayList<Entitlement>());
-
- final List<SubscriptionEvent> events = new ArrayList<SubscriptionEvent>();
- final UUID subscriptionId = UUID.randomUUID();
-
- final UUID otherSubscriptionId = UUID.randomUUID();
- final DateTime effectiveDate = clock.getUTCNow();
- final DateTime otherEffectiveDate = clock.getUTCNow().plusDays(1);
-
- events.add(timeline.createEvent(subscriptionId, SubscriptionEventType.START_BILLING, effectiveDate));
- events.add(timeline.createEvent(subscriptionId, SubscriptionEventType.STOP_ENTITLEMENT, effectiveDate));
- events.add(timeline.createEvent(subscriptionId, SubscriptionEventType.STOP_BILLING, effectiveDate));
- events.add(timeline.createEvent(subscriptionId, SubscriptionEventType.START_ENTITLEMENT, effectiveDate));
-
- events.add(timeline.createEvent(otherSubscriptionId, SubscriptionEventType.START_BILLING, otherEffectiveDate));
- events.add(timeline.createEvent(otherSubscriptionId, SubscriptionEventType.START_ENTITLEMENT, otherEffectiveDate));
- events.add(timeline.createEvent(otherSubscriptionId, SubscriptionEventType.STOP_ENTITLEMENT, otherEffectiveDate));
- events.add(timeline.createEvent(otherSubscriptionId, SubscriptionEventType.STOP_BILLING, otherEffectiveDate));
- events.add(timeline.createEvent(otherSubscriptionId, SubscriptionEventType.PAUSE_ENTITLEMENT, otherEffectiveDate));
- events.add(timeline.createEvent(otherSubscriptionId, SubscriptionEventType.PAUSE_BILLING, otherEffectiveDate));
-
- SubscriptionEventOrdering.INSTANCE.reOrderSubscriptionEventsOnSameDateByType(events);
-
- Assert.assertEquals(events.get(0).getSubscriptionEventType(), SubscriptionEventType.START_ENTITLEMENT);
- Assert.assertEquals(events.get(1).getSubscriptionEventType(), SubscriptionEventType.START_BILLING);
- Assert.assertEquals(events.get(2).getSubscriptionEventType(), SubscriptionEventType.STOP_ENTITLEMENT);
- Assert.assertEquals(events.get(3).getSubscriptionEventType(), SubscriptionEventType.STOP_BILLING);
-
- Assert.assertEquals(events.get(4).getSubscriptionEventType(), SubscriptionEventType.START_ENTITLEMENT);
- Assert.assertEquals(events.get(5).getSubscriptionEventType(), SubscriptionEventType.START_BILLING);
- Assert.assertEquals(events.get(6).getSubscriptionEventType(), SubscriptionEventType.PAUSE_ENTITLEMENT);
- Assert.assertEquals(events.get(7).getSubscriptionEventType(), SubscriptionEventType.PAUSE_BILLING);
- Assert.assertEquals(events.get(8).getSubscriptionEventType(), SubscriptionEventType.STOP_ENTITLEMENT);
- Assert.assertEquals(events.get(9).getSubscriptionEventType(), SubscriptionEventType.STOP_BILLING);
- }
-
- @Test(groups = "fast")
- public void testReOrderSubscriptionEventsOnCorrectOrder() {
- final TestSubscriptionBundleTimeline timeline = new TestSubscriptionBundleTimeline(null, null, null, new ArrayList<Entitlement>());
-
- final List<SubscriptionEvent> events = new ArrayList<SubscriptionEvent>();
- final UUID subscriptionId = UUID.randomUUID();
- final DateTime effectiveDate = clock.getUTCNow();
- events.add(timeline.createEvent(subscriptionId, SubscriptionEventType.START_ENTITLEMENT, effectiveDate));
- events.add(timeline.createEvent(subscriptionId, SubscriptionEventType.START_BILLING, effectiveDate));
- events.add(timeline.createEvent(subscriptionId, SubscriptionEventType.STOP_ENTITLEMENT, effectiveDate));
- events.add(timeline.createEvent(subscriptionId, SubscriptionEventType.STOP_BILLING, effectiveDate));
-
- SubscriptionEventOrdering.INSTANCE.reOrderSubscriptionEventsOnSameDateByType(events);
-
- Assert.assertEquals(events.get(0).getSubscriptionEventType(), SubscriptionEventType.START_ENTITLEMENT);
- Assert.assertEquals(events.get(1).getSubscriptionEventType(), SubscriptionEventType.START_BILLING);
- Assert.assertEquals(events.get(2).getSubscriptionEventType(), SubscriptionEventType.STOP_ENTITLEMENT);
- Assert.assertEquals(events.get(3).getSubscriptionEventType(), SubscriptionEventType.STOP_BILLING);
- }
-
@Test(groups = "fast")
public void testOneSimpleEntitlement() throws CatalogApiException {
testOneSimpleEntitlementImpl(false);
@@ -275,7 +71,6 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
testOneSimpleEntitlementImpl(true);
}
-
private void testOneSimpleEntitlementImpl(boolean regressionFlagForOlderVersionThan_0_17_X) throws CatalogApiException {
clock.setDay(new LocalDate(2013, 1, 1));
@@ -348,18 +143,16 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
assertNull(events.get(3).getNextPhase());
}
-
- @Test(groups="fast", enabled = true)
+ @Test(groups="fast")
public void testOneSimpleEntitlementCancelImmediately() throws CatalogApiException {
testOneSimpleEntitlementCancelImmediatelyImpl(false);
}
- @Test(groups="fast", enabled = true)
+ @Test(groups="fast")
public void testOneSimpleEntitlementCancelImmediatelyWithRegression() throws CatalogApiException {
testOneSimpleEntitlementCancelImmediatelyImpl(true);
}
-
private void testOneSimpleEntitlementCancelImmediatelyImpl(boolean regressionFlagForOlderVersionThan_0_17_X) throws CatalogApiException {
clock.setDay(new LocalDate(2013, 1, 1));
@@ -518,7 +311,6 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
testOneEntitlementWithPauseResumeImpl(true);
}
-
private void testOneEntitlementWithPauseResumeImpl(final boolean regressionFlagForOlderVersionThan_0_17_X) throws CatalogApiException {
clock.setDay(new LocalDate(2013, 1, 1));
diff --git a/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestEntitlementDateHelper.java b/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestEntitlementDateHelper.java
index 3c4b79f..d53c82d 100644
--- a/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestEntitlementDateHelper.java
+++ b/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestEntitlementDateHelper.java
@@ -43,7 +43,7 @@ public class TestEntitlementDateHelper extends EntitlementTestSuiteNoDB {
public void beforeMethod() throws Exception {
super.beforeClass();
- dateHelper = new EntitlementDateHelper(clock);
+ dateHelper = new EntitlementDateHelper();
clock.resetDeltaFromReality();
}
@@ -55,7 +55,7 @@ public class TestEntitlementDateHelper extends EntitlementTestSuiteNoDB {
final DateTime referenceDateTime = new DateTime(2013, 1, 1, 15, 43, 25, 0, DateTimeZone.UTC);
createAccount(DateTimeZone.UTC, referenceDateTime);
- final DateTime targetDate = dateHelper.fromLocalDateAndReferenceTime(initialDate, internalCallContext);
+ final DateTime targetDate = dateHelper.fromLocalDateAndReferenceTime(initialDate, clock.getUTCNow(), internalCallContext);
final DateTime expectedDate = new DateTime(2013, 8, 7, 15, 43, 25, 0, DateTimeZone.UTC);
Assert.assertEquals(targetDate, expectedDate);
}
@@ -72,7 +72,7 @@ public class TestEntitlementDateHelper extends EntitlementTestSuiteNoDB {
createAccount(timeZoneUtcMinus8, referenceDateTime);
- final DateTime targetDate = dateHelper.fromLocalDateAndReferenceTime(inputDate, internalCallContext);
+ final DateTime targetDate = dateHelper.fromLocalDateAndReferenceTime(inputDate, clock.getUTCNow(), internalCallContext);
// Things to verify:
// 1. Verify the resulting DateTime brings us back into the correct LocalDate (in the account timezone)
@@ -98,7 +98,7 @@ public class TestEntitlementDateHelper extends EntitlementTestSuiteNoDB {
createAccount(timeZoneUtcPlus5, referenceDateTime);
- final DateTime targetDate = dateHelper.fromLocalDateAndReferenceTime(inputDate, internalCallContext);
+ final DateTime targetDate = dateHelper.fromLocalDateAndReferenceTime(inputDate, clock.getUTCNow(), internalCallContext);
// Things to verify:
// 1. Verify the resulting DateTime brings us back into the correct LocalDate (in the account timezone)
@@ -145,6 +145,7 @@ public class TestEntitlementDateHelper extends EntitlementTestSuiteNoDB {
private void createAccount(final DateTimeZone dateTimeZone, final DateTime referenceDateTime) throws AccountApiException {
final Account accountData = new MockAccountBuilder().externalKey(UUID.randomUUID().toString())
.timeZone(dateTimeZone)
+ .referenceTime(referenceDateTime)
.createdDate(referenceDateTime)
.build();
diff --git a/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestRegessionSubscriptionApi.java b/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestRegessionSubscriptionApi.java
index ae12fdc..6576c09 100644
--- a/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestRegessionSubscriptionApi.java
+++ b/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestRegessionSubscriptionApi.java
@@ -65,7 +65,7 @@ public class TestRegessionSubscriptionApi extends EntitlementTestSuiteWithEmbedd
final Account account = createAccount(getAccountData(7));
final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Shotgun", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
testListener.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK);
- final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, UUID.randomUUID().toString(), null, entitlementEffectiveDate, null, false, ImmutableList.<PluginProperty>of(), callContext);
+ final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, UUID.randomUUID().toString(), null, entitlementEffectiveDate, null, false, true, ImmutableList.<PluginProperty>of(), callContext);
// Because of the BlockingState event ENT_STARTED, the entitlement date should be correctly set
Assert.assertEquals(entitlement.getEffectiveStartDate(), entitlementEffectiveDate);
diff --git a/entitlement/src/test/java/org/killbill/billing/entitlement/block/TestBlockingApi.java b/entitlement/src/test/java/org/killbill/billing/entitlement/block/TestBlockingApi.java
index d96a38c..4ffb21b 100644
--- a/entitlement/src/test/java/org/killbill/billing/entitlement/block/TestBlockingApi.java
+++ b/entitlement/src/test/java/org/killbill/billing/entitlement/block/TestBlockingApi.java
@@ -21,18 +21,22 @@ package org.killbill.billing.entitlement.block;
import java.util.List;
import java.util.UUID;
+import org.joda.time.DateTime;
+import org.joda.time.LocalDate;
+import org.killbill.billing.ErrorCode;
import org.killbill.billing.account.api.Account;
+import org.killbill.billing.account.api.AccountApiException;
import org.killbill.billing.api.TestApiListener.NextEvent;
-import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.catalog.api.BillingPeriod;
import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
import org.killbill.billing.catalog.api.PriceListSet;
-import org.killbill.billing.catalog.api.ProductCategory;
import org.killbill.billing.entitlement.EntitlementTestSuiteWithEmbeddedDB;
import org.killbill.billing.entitlement.api.BlockingState;
import org.killbill.billing.entitlement.api.BlockingStateType;
import org.killbill.billing.entitlement.api.Entitlement;
import org.killbill.billing.entitlement.api.Entitlement.EntitlementState;
+import org.killbill.billing.entitlement.api.EntitlementApiException;
+import org.killbill.billing.entitlement.api.SubscriptionApiException;
import org.killbill.billing.junction.DefaultBlockingState;
import org.killbill.billing.payment.api.PluginProperty;
import org.testng.Assert;
@@ -44,6 +48,7 @@ import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableList;
import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.fail;
public class TestBlockingApi extends EntitlementTestSuiteWithEmbeddedDB {
@@ -138,7 +143,7 @@ public class TestBlockingApi extends EntitlementTestSuiteWithEmbeddedDB {
final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Shotgun", BillingPeriod.ANNUAL, PriceListSet.DEFAULT_PRICELIST_NAME, null);
testListener.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK);
- Entitlement baseEntitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, null, null, false, ImmutableList.<PluginProperty>of(), callContext);
+ Entitlement baseEntitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, null, null, false, true, ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
assertEquals(baseEntitlement.getState(), EntitlementState.BLOCKED);
@@ -150,7 +155,6 @@ public class TestBlockingApi extends EntitlementTestSuiteWithEmbeddedDB {
subscriptionApi.addBlockingState(state2, null, ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
-
baseEntitlement = entitlementApi.getEntitlementForId(baseEntitlement.getId(), callContext);
assertEquals(baseEntitlement.getState(), EntitlementState.BLOCKED);
@@ -164,7 +168,6 @@ public class TestBlockingApi extends EntitlementTestSuiteWithEmbeddedDB {
baseEntitlement = entitlementApi.getEntitlementForId(baseEntitlement.getId(), callContext);
assertEquals(baseEntitlement.getState(), EntitlementState.BLOCKED);
-
// Remove blocking at bundle level.
clock.addDays(1);
testListener.pushExpectedEvent(NextEvent.BLOCK);
@@ -183,7 +186,119 @@ public class TestBlockingApi extends EntitlementTestSuiteWithEmbeddedDB {
return input.getService().equals(service);
}
}));
-
Assert.assertEquals(history.size(), 4);
}
+
+ @Test(groups = "slow")
+ public void testCreateBaseSubscriptionOnBlockedChangeAcount() throws AccountApiException, EntitlementApiException, SubscriptionApiException {
+ final LocalDate initialDate = new LocalDate(2017, 5, 1);
+ clock.setDay(initialDate);
+
+ final Account account = createAccount(getAccountData(1));
+
+ testListener.pushExpectedEvent(NextEvent.BLOCK);
+ final BlockingState blockChangeAccount = new DefaultBlockingState(account.getId(), BlockingStateType.ACCOUNT, "State1", "Service1", true, false, false, clock.getUTCNow());
+ subscriptionApi.addBlockingState(blockChangeAccount, null, ImmutableList.<PluginProperty>of(), callContext);
+ assertListenerStatus();
+
+ // Try create subscription right now
+ try {
+ final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("shotgun-monthly", null);
+ entitlementApi.createBaseEntitlement(account.getId(), spec, "xyzqe", null, null, null, false, true, ImmutableList.<PluginProperty>of(), callContext);
+ fail("Should fail to create entitlement when ACCOUNT has been 'change' blocked");
+ } catch (final EntitlementApiException e) {
+ assertEquals(e.getCode(), ErrorCode.BLOCK_BLOCKED_ACTION.getCode());
+ }
+
+ // Try create subscription in the future
+ try {
+ final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("shotgun-monthly", null);
+ entitlementApi.createBaseEntitlement(account.getId(), spec, "xyzqe", null, initialDate.plusDays(3), null, false, true, ImmutableList.<PluginProperty>of(), callContext);
+ fail("Should fail to create entitlement when ACCOUNT has been 'change' blocked");
+ } catch (final EntitlementApiException e) {
+ assertEquals(e.getCode(), ErrorCode.BLOCK_BLOCKED_ACTION.getCode());
+ }
+
+ // Try create subscription in the past
+ final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("shotgun-monthly", null);
+ testListener.pushExpectedEvents(NextEvent.BLOCK, NextEvent.CREATE);
+ entitlementApi.createBaseEntitlement(account.getId(), spec, "xyzqe", null, initialDate.minusDays(3), null, false, true, ImmutableList.<PluginProperty>of(), callContext);
+ assertListenerStatus();
+ }
+
+ @Test(groups = "slow")
+ public void testCreateAOSubscriptionOnBlockedChangeAcount() throws AccountApiException, EntitlementApiException, SubscriptionApiException {
+ final LocalDate initialDate = new LocalDate(2017, 5, 1);
+ clock.setDay(initialDate);
+
+ final Account account = createAccount(getAccountData(1));
+
+ final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("shotgun-monthly", null);
+ testListener.pushExpectedEvents(NextEvent.BLOCK, NextEvent.CREATE);
+ final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, "xyzqe", null, initialDate.minusDays(3), null, false, true, ImmutableList.<PluginProperty>of(), callContext);
+ assertListenerStatus();
+
+ testListener.pushExpectedEvent(NextEvent.BLOCK);
+ final BlockingState blockChangeAccount = new DefaultBlockingState(account.getId(), BlockingStateType.ACCOUNT, "State1", "Service1", true, false, false, clock.getUTCNow());
+ subscriptionApi.addBlockingState(blockChangeAccount, null, ImmutableList.<PluginProperty>of(), callContext);
+ assertListenerStatus();
+
+ // Try create subscription right now
+ try {
+ final PlanPhaseSpecifier addOnSpec = new PlanPhaseSpecifier("Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
+ entitlementApi.addEntitlement(entitlement.getBundleId(), addOnSpec, null, null, null, false, ImmutableList.<PluginProperty>of(), callContext);
+ fail("Should fail to create ADD_ON");
+ } catch (final EntitlementApiException e) {
+ assertEquals(e.getCode(), ErrorCode.BLOCK_BLOCKED_ACTION.getCode());
+ }
+
+ // Try create subscription in the future
+ try {
+ final PlanPhaseSpecifier addOnSpec = new PlanPhaseSpecifier("Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
+ entitlementApi.addEntitlement(entitlement.getBundleId(), addOnSpec, null, initialDate.plusDays(2), null, false, ImmutableList.<PluginProperty>of(), callContext);
+ fail("Should fail to create ADD_ON");
+ } catch (final EntitlementApiException e) {
+ assertEquals(e.getCode(), ErrorCode.BLOCK_BLOCKED_ACTION.getCode());
+ }
+
+ // Try create subscription in the past
+ final PlanPhaseSpecifier addOnSpec = new PlanPhaseSpecifier("Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
+ testListener.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK);
+ entitlementApi.addEntitlement(entitlement.getBundleId(), addOnSpec, null, initialDate.minusDays(2), null, false, ImmutableList.<PluginProperty>of(), callContext);
+ assertListenerStatus();
+ }
+
+ @Test(groups = "slow")
+ public void testCreateAOSubscriptionOnFutureBlockedChangeAcount() throws AccountApiException, EntitlementApiException, SubscriptionApiException {
+ final LocalDate initialDate = new LocalDate(2017, 5, 1);
+ clock.setDay(initialDate);
+
+ final Account account = createAccount(getAccountData(1));
+
+ final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("shotgun-monthly", null);
+ testListener.pushExpectedEvents(NextEvent.BLOCK, NextEvent.CREATE);
+ final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, "xyzqe", null, initialDate.minusDays(3), null, false, true, ImmutableList.<PluginProperty>of(), callContext);
+ assertListenerStatus();
+
+ // Create future BlockingState
+ final LocalDate blockingChange = initialDate.plusDays(3);
+ final BlockingState blockChangeAccount = new DefaultBlockingState(account.getId(), BlockingStateType.ACCOUNT, "State1", "Service1", true, false, false, null);
+ subscriptionApi.addBlockingState(blockChangeAccount, blockingChange, ImmutableList.<PluginProperty>of(), callContext);
+
+ // Create ADD_ON in the future as well
+ try {
+ final PlanPhaseSpecifier addOnSpec = new PlanPhaseSpecifier("Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
+ testListener.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK);
+ entitlementApi.addEntitlement(entitlement.getBundleId(), addOnSpec, null, blockingChange, null, false, ImmutableList.<PluginProperty>of(), callContext);
+ assertListenerStatus();
+ } catch (final EntitlementApiException e) {
+ assertEquals(e.getCode(), ErrorCode.BLOCK_BLOCKED_ACTION.getCode());
+ }
+
+ // Create ADD_ON now (prior future BlockingState)
+ final PlanPhaseSpecifier addOnSpec = new PlanPhaseSpecifier("Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
+ entitlementApi.addEntitlement(entitlement.getBundleId(), addOnSpec, null, null, null, false, ImmutableList.<PluginProperty>of(), callContext);
+ assertListenerStatus();
+ }
+
}
diff --git a/entitlement/src/test/java/org/killbill/billing/entitlement/dao/TestDefaultBlockingStateDao.java b/entitlement/src/test/java/org/killbill/billing/entitlement/dao/TestDefaultBlockingStateDao.java
index c15abba..519e758 100644
--- a/entitlement/src/test/java/org/killbill/billing/entitlement/dao/TestDefaultBlockingStateDao.java
+++ b/entitlement/src/test/java/org/killbill/billing/entitlement/dao/TestDefaultBlockingStateDao.java
@@ -60,7 +60,7 @@ public class TestDefaultBlockingStateDao extends EntitlementTestSuiteWithEmbedde
// See TestEntitlementUtils for a more comprehensive test
final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Shotgun", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
testListener.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK);
- final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, null, null, false, ImmutableList.<PluginProperty>of(), callContext);
+ final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, null, null, false, true, ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
final BlockingStateType type = BlockingStateType.SUBSCRIPTION;
diff --git a/entitlement/src/test/java/org/killbill/billing/entitlement/engine/core/TestEntitlementUtils.java b/entitlement/src/test/java/org/killbill/billing/entitlement/engine/core/TestEntitlementUtils.java
index 1a0ced3..d2406a5 100644
--- a/entitlement/src/test/java/org/killbill/billing/entitlement/engine/core/TestEntitlementUtils.java
+++ b/entitlement/src/test/java/org/killbill/billing/entitlement/engine/core/TestEntitlementUtils.java
@@ -73,7 +73,7 @@ public class TestEntitlementUtils extends EntitlementTestSuiteWithEmbeddedDB {
// Create base entitlement
testListener.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK);
final PlanPhaseSpecifier baseSpec = new PlanPhaseSpecifier("Shotgun", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
- baseEntitlement = (DefaultEntitlement) entitlementApi.createBaseEntitlement(account.getId(), baseSpec, account.getExternalKey(), null, null, null, false, ImmutableList.<PluginProperty>of(), callContext);
+ baseEntitlement = (DefaultEntitlement) entitlementApi.createBaseEntitlement(account.getId(), baseSpec, account.getExternalKey(), null, null, null, false, true, ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
// Add ADD_ON
@@ -232,7 +232,7 @@ public class TestEntitlementUtils extends EntitlementTestSuiteWithEmbeddedDB {
@Test(groups = "slow", description = "Verify add-ons blocking states are added for EOT change plans")
public void testChangePlanEOT() throws Exception {
// Change plan EOT to Assault-Rifle (Telescopic-Scope is included)
- final DefaultEntitlement changedBaseEntitlement = (DefaultEntitlement) baseEntitlement.changePlanWithDate(new PlanSpecifier("Assault-Rifle", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME), null, new LocalDate(2013, 10, 7), ImmutableList.<PluginProperty>of(), callContext);
+ final DefaultEntitlement changedBaseEntitlement = (DefaultEntitlement) baseEntitlement.changePlanWithDate(new PlanPhaseSpecifier("Assault-Rifle", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME), null, new LocalDate(2013, 10, 7), ImmutableList.<PluginProperty>of(), callContext);
// No blocking event (EOT)
assertListenerStatus();
@@ -271,7 +271,7 @@ public class TestEntitlementUtils extends EntitlementTestSuiteWithEmbeddedDB {
assertListenerStatus();
// Change plan EOT to Assault-Rifle (Telescopic-Scope is included)
- final DefaultEntitlement changedBaseEntitlement = (DefaultEntitlement) baseEntitlement.changePlanWithDate(new PlanSpecifier("Assault-Rifle", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME), null, new LocalDate(2013, 10, 7), ImmutableList.<PluginProperty>of(), callContext);
+ final DefaultEntitlement changedBaseEntitlement = (DefaultEntitlement) baseEntitlement.changePlanWithDate(new PlanPhaseSpecifier("Assault-Rifle", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME), null, new LocalDate(2013, 10, 7), ImmutableList.<PluginProperty>of(), callContext);
// No blocking event (EOT)
assertListenerStatus();
@@ -290,7 +290,7 @@ public class TestEntitlementUtils extends EntitlementTestSuiteWithEmbeddedDB {
// Change plan IMM (upgrade) to Assault-Rifle (Telescopic-Scope is included)
testListener.pushExpectedEvents(NextEvent.CHANGE, NextEvent.CANCEL, NextEvent.BLOCK);
- final DefaultEntitlement changedBaseEntitlement = (DefaultEntitlement) baseEntitlement.changePlan(new PlanSpecifier("Assault-Rifle", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME), null, ImmutableList.<PluginProperty>of(), callContext);
+ final DefaultEntitlement changedBaseEntitlement = (DefaultEntitlement) baseEntitlement.changePlan(new PlanPhaseSpecifier("Assault-Rifle", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME), null, ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
// We need to add a 1s delay before invoking the eventsStreamBuilder in the checks below, because
diff --git a/entitlement/src/test/java/org/killbill/billing/entitlement/EntitlementTestSuiteWithEmbeddedDB.java b/entitlement/src/test/java/org/killbill/billing/entitlement/EntitlementTestSuiteWithEmbeddedDB.java
index 9f6f218..27c7b67 100644
--- a/entitlement/src/test/java/org/killbill/billing/entitlement/EntitlementTestSuiteWithEmbeddedDB.java
+++ b/entitlement/src/test/java/org/killbill/billing/entitlement/EntitlementTestSuiteWithEmbeddedDB.java
@@ -140,9 +140,6 @@ public class EntitlementTestSuiteWithEmbeddedDB extends GuicyKillbillTestSuiteWi
startTestFamework(testListener, clock, busService, subscriptionBaseService, entitlementService);
this.catalog = initCatalog(catalogService);
- // Make sure we start with a clean state
- assertListenerStatus();
-
configureShiro();
login("EntitlementUser");
}
@@ -174,12 +171,8 @@ public class EntitlementTestSuiteWithEmbeddedDB extends GuicyKillbillTestSuiteWi
@AfterMethod(groups = "slow")
public void afterMethod() throws Exception {
-
securityApi.logout();
- // Make sure we finish in a clean state
- assertListenerStatus();
-
stopTestFramework(testListener, busService, subscriptionBaseService, entitlementService);
}
@@ -267,6 +260,7 @@ public class EntitlementTestSuiteWithEmbeddedDB extends GuicyKillbillTestSuiteWi
}
protected AccountData getAccountData(final int billingDay) {
+
return new MockAccountBuilder().name(UUID.randomUUID().toString().substring(1, 8))
.firstNameLength(6)
.email(UUID.randomUUID().toString().substring(1, 8))
@@ -277,6 +271,7 @@ public class EntitlementTestSuiteWithEmbeddedDB extends GuicyKillbillTestSuiteWi
.billingCycleDayLocal(billingDay)
.currency(Currency.USD)
.paymentMethodId(UUID.randomUUID())
+ .referenceTime(clock.getUTCNow())
.timeZone(DateTimeZone.UTC)
.build();
}
@@ -289,6 +284,7 @@ public class EntitlementTestSuiteWithEmbeddedDB extends GuicyKillbillTestSuiteWi
return account;
}
+ @Override
protected void assertListenerStatus() {
testListener.assertListenerStatus();
}
invoice/pom.xml 8(+2 -6)
diff --git a/invoice/pom.xml b/invoice/pom.xml
index defab3b..bba2c29 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.19.0-SNAPSHOT</version>
+ <version>0.19.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-invoice</artifactId>
@@ -80,7 +80,7 @@
</dependency>
<dependency>
<groupId>org.antlr</groupId>
- <artifactId>stringtemplate</artifactId>
+ <artifactId>ST4</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
@@ -89,10 +89,6 @@
<scope>test</scope>
</dependency>
<dependency>
- <groupId>org.jdbi</groupId>
- <artifactId>jdbi</artifactId>
- </dependency>
- <dependency>
<groupId>org.joda</groupId>
<artifactId>joda-money</artifactId>
</dependency>
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/api/InvoiceApiHelper.java b/invoice/src/main/java/org/killbill/billing/invoice/api/InvoiceApiHelper.java
index cb7dfcb..5b8cbe7 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/api/InvoiceApiHelper.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/api/InvoiceApiHelper.java
@@ -82,10 +82,11 @@ public class InvoiceApiHelper {
final Iterable<Invoice> invoicesForPlugins = withAccountLock.prepareInvoices();
+ final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(accountId, context);
final List<InvoiceModelDao> invoiceModelDaos = new LinkedList<InvoiceModelDao>();
for (final Invoice invoiceForPlugin : invoicesForPlugins) {
// Call plugin
- final List<InvoiceItem> additionalInvoiceItems = invoicePluginDispatcher.getAdditionalInvoiceItems(invoiceForPlugin, isDryRun, context);
+ final List<InvoiceItem> additionalInvoiceItems = invoicePluginDispatcher.getAdditionalInvoiceItems(invoiceForPlugin, isDryRun, context, internalCallContext);
invoiceForPlugin.addInvoiceItems(additionalInvoiceItems);
// Transformation to InvoiceModelDao
@@ -98,7 +99,6 @@ public class InvoiceApiHelper {
invoiceModelDaos.add(invoiceModelDao);
}
- final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(accountId, context);
final List<InvoiceItemModelDao> createdInvoiceItems = dao.createInvoices(invoiceModelDaos, internalCallContext);
return fromInvoiceItemModelDao(createdInvoiceItems);
} catch (final LockFailedException e) {
@@ -151,6 +151,11 @@ public class InvoiceApiHelper {
final Map<UUID, BigDecimal> output = dao.computeItemAdjustments(invoiceToBeAdjusted.getId().toString(), input, context);
+ // Nothing to adjust
+ if (output.get(invoiceItemId) == null) {
+ return null;
+ }
+
// If we pass that stage, it means the validation succeeded so we just need to extract resulting amount and negate the result.
final BigDecimal amountToAdjust = output.get(invoiceItemId).negate();
// Finally, create the adjustment
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/api/user/DefaultInvoiceUserApi.java b/invoice/src/main/java/org/killbill/billing/invoice/api/user/DefaultInvoiceUserApi.java
index c9fbef9..3a0bcda 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/api/user/DefaultInvoiceUserApi.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/api/user/DefaultInvoiceUserApi.java
@@ -75,6 +75,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Function;
+import com.google.common.base.MoreObjects;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
@@ -267,7 +268,7 @@ public class DefaultInvoiceUserApi implements InvoiceUserApi {
}
return new ExternalChargeInvoiceItem(externalChargeItem.getId(), externalChargeItem.getInvoiceId(), externalChargeItem.getAccountId(),
- externalChargeItem.getDescription(), externalChargeItem.getStartDate(),
+ externalChargeItem.getDescription(), externalChargeItem.getStartDate(), externalChargeItem.getEndDate(),
externalChargeItem.getAmount(), externalChargeItem.getCurrency());
}
@@ -305,20 +306,35 @@ public class DefaultInvoiceUserApi implements InvoiceUserApi {
} else {
if (existingInvoicesForExternalCharges.get(invoiceIdForExternalCharge) == null) {
final Invoice existingInvoiceForExternalCharge = getInvoice(invoiceIdForExternalCharge, context);
+ if (InvoiceStatus.COMMITTED.equals(existingInvoiceForExternalCharge.getStatus())) {
+ throw new InvoiceApiException(ErrorCode.INVOICE_ALREADY_COMMITTED, existingInvoiceForExternalCharge.getId());
+ }
existingInvoicesForExternalCharges.put(invoiceIdForExternalCharge, existingInvoiceForExternalCharge);
}
invoiceForExternalCharge = existingInvoicesForExternalCharges.get(invoiceIdForExternalCharge);
}
+ final LocalDate startDate = MoreObjects.firstNonNull(charge.getStartDate(), effectiveDate);
+ final LocalDate endDate = charge.getEndDate();
+
final InvoiceItem externalCharge = new ExternalChargeInvoiceItem(UUIDs.randomUUID(),
context.getCreatedDate(),
invoiceForExternalCharge.getId(),
accountId,
charge.getBundleId(),
+ charge.getSubscriptionId(),
+ charge.getPlanName(),
+ charge.getPhaseName(),
+ charge.getPrettyPlanName(),
+ charge.getPrettyPhaseName(),
charge.getDescription(),
- effectiveDate,
+ startDate,
+ endDate,
charge.getAmount(),
- charge.getCurrency());
+ charge.getRate(),
+ charge.getCurrency(),
+ charge.getLinkedItemId());
+
invoiceForExternalCharge.addInvoiceItem(externalCharge);
}
@@ -432,7 +448,9 @@ public class DefaultInvoiceUserApi implements InvoiceUserApi {
effectiveDate,
description,
internalCallContextFactory.createInternalCallContext(accountId, context));
- invoice.addInvoiceItem(adjustmentItem);
+ if (adjustmentItem != null) {
+ invoice.addInvoiceItem(adjustmentItem);
+ }
return ImmutableList.<Invoice>of(invoice);
}
@@ -445,9 +463,9 @@ public class DefaultInvoiceUserApi implements InvoiceUserApi {
return InvoiceItemType.ITEM_ADJ.equals(invoiceItem.getInvoiceItemType());
}
});
- Preconditions.checkState(adjustmentInvoiceItems.size() == 1, "Should have created a single adjustment item: " + adjustmentInvoiceItems);
+ Preconditions.checkState(adjustmentInvoiceItems.size() <= 1, "Should have created a single adjustment item: " + adjustmentInvoiceItems);
- return adjustmentInvoiceItems.iterator().next();
+ return adjustmentInvoiceItems.iterator().hasNext() ? adjustmentInvoiceItems.iterator().next() : null;
}
@Override
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/calculator/InvoiceCalculatorUtils.java b/invoice/src/main/java/org/killbill/billing/invoice/calculator/InvoiceCalculatorUtils.java
index c7af0ef..88a88c3 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/calculator/InvoiceCalculatorUtils.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/calculator/InvoiceCalculatorUtils.java
@@ -69,13 +69,10 @@ public abstract class InvoiceCalculatorUtils {
InvoiceItemType.RECURRING.equals(invoiceItem.getInvoiceItemType());
}
- public static BigDecimal computeInvoiceBalance(final Currency currency,
- @Nullable final Iterable<InvoiceItem> invoiceItems,
- @Nullable final Iterable<InvoicePayment> invoicePayments,
- boolean writtenOffOrMigrated) {
- if (writtenOffOrMigrated) {
- return BigDecimal.ZERO;
- }
+ public static BigDecimal computeRawInvoiceBalance(final Currency currency,
+ @Nullable final Iterable<InvoiceItem> invoiceItems,
+ @Nullable final Iterable<InvoicePayment> invoicePayments) {
+
final BigDecimal amountPaid = computeInvoiceAmountPaid(currency, invoicePayments)
.add(computeInvoiceAmountRefunded(currency, invoicePayments));
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/config/MultiTenantInvoiceConfig.java b/invoice/src/main/java/org/killbill/billing/invoice/config/MultiTenantInvoiceConfig.java
index 45de5f6..70f9c13 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/config/MultiTenantInvoiceConfig.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/config/MultiTenantInvoiceConfig.java
@@ -17,6 +17,8 @@
package org.killbill.billing.invoice.config;
+import java.util.List;
+
import javax.inject.Inject;
import javax.inject.Named;
@@ -119,11 +121,39 @@ public class MultiTenantInvoiceConfig extends MultiTenantConfigBase implements I
}
@Override
+ public List<String> getInvoicePluginNames() {
+ return staticConfig.getInvoicePluginNames();
+ }
+
+ @Override
+ public List<String> getInvoicePluginNames(final InternalTenantContext tenantContext) {
+ final String result = getStringTenantConfig("getInvoicePluginNames", tenantContext);
+ if (result != null) {
+ return convertToListString(result, "getInvoicePluginNames");
+ }
+ return getInvoicePluginNames();
+ }
+
+ @Override
public boolean isInvoicingSystemEnabled() {
return staticConfig.isInvoicingSystemEnabled();
}
@Override
+ public String getParentAutoCommitUtcTime() {
+ return staticConfig.getParentAutoCommitUtcTime();
+ }
+
+ @Override
+ public String getParentAutoCommitUtcTime(final InternalTenantContext tenantContext) {
+ final String result = getStringTenantConfig("getParentAutoCommitUtcTime", tenantContext);
+ if (result != null) {
+ return result;
+ }
+ return getParentAutoCommitUtcTime();
+ }
+
+ @Override
public boolean isInvoicingSystemEnabled(final InternalTenantContext tenantContext) {
final String result = getStringTenantConfig("isInvoicingSystemEnabled", tenantContext);
if (result != null) {
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/dao/CBADao.java b/invoice/src/main/java/org/killbill/billing/invoice/dao/CBADao.java
index 48de156..162c204 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/dao/CBADao.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/dao/CBADao.java
@@ -64,7 +64,7 @@ public class CBADao {
if (balance.compareTo(BigDecimal.ZERO) < 0) {
// Current balance is negative, we need to generate a credit (positive CBA amount)
return buildCBAItem(invoice, balance, context);
- } else if (balance.compareTo(BigDecimal.ZERO) > 0 && invoice.getStatus() == InvoiceStatus.COMMITTED) {
+ } else if (balance.compareTo(BigDecimal.ZERO) > 0 && invoice.getStatus() == InvoiceStatus.COMMITTED && !invoice.isWrittenOff()) {
// Current balance is positive and the invoice is COMMITTED, we need to use some of the existing if available (negative CBA amount)
// PERF: in some codepaths, the CBA maybe have already been computed
BigDecimal accountCBA = accountCBAOrNull;
@@ -86,7 +86,7 @@ public class CBADao {
private BigDecimal getInvoiceBalance(final InvoiceModelDao invoice) {
final InvoiceModelDao parentInvoice = invoice.getParentInvoice();
- if ((parentInvoice != null) && (InvoiceModelDaoHelper.getBalance(parentInvoice).compareTo(BigDecimal.ZERO) == 0)) {
+ if ((parentInvoice != null) && (InvoiceModelDaoHelper.getRawBalanceForRegularInvoice(parentInvoice).compareTo(BigDecimal.ZERO) == 0)) {
final Iterable<InvoiceItemModelDao> items = Iterables.filter(parentInvoice.getInvoiceItems(), new Predicate<InvoiceItemModelDao>() {
@Override
public boolean apply(@Nullable final InvoiceItemModelDao input) {
@@ -105,7 +105,7 @@ public class CBADao {
}
- return InvoiceModelDaoHelper.getBalance(invoice);
+ return InvoiceModelDaoHelper.getRawBalanceForRegularInvoice(invoice);
}
// We let the code below rehydrate the invoice before we can add the CBA item
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/dao/DefaultInvoiceDao.java b/invoice/src/main/java/org/killbill/billing/invoice/dao/DefaultInvoiceDao.java
index 0afa55c..7b17866 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/dao/DefaultInvoiceDao.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/dao/DefaultInvoiceDao.java
@@ -26,12 +26,14 @@ import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.UUID;
import javax.annotation.Nullable;
import org.joda.time.DateTime;
import org.joda.time.LocalDate;
+import org.joda.time.LocalTime;
import org.killbill.billing.ErrorCode;
import org.killbill.billing.ObjectType;
import org.killbill.billing.account.api.Account;
@@ -40,7 +42,7 @@ import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.catalog.api.Currency;
import org.killbill.billing.entity.EntityPersistenceException;
import org.killbill.billing.invoice.InvoiceDispatcher.FutureAccountNotifications;
-import org.killbill.billing.invoice.InvoiceDispatcher.FutureAccountNotifications.SubscriptionNotification;
+import org.killbill.billing.invoice.InvoicePluginDispatcher;
import org.killbill.billing.invoice.api.DefaultInvoicePaymentErrorEvent;
import org.killbill.billing.invoice.api.DefaultInvoicePaymentInfoEvent;
import org.killbill.billing.invoice.api.Invoice;
@@ -285,24 +287,29 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
@Override
public List<InvoiceItemModelDao> createInvoices(final List<InvoiceModelDao> invoices,
final InternalCallContext context) {
- return createInvoices(invoices, new FutureAccountNotifications(ImmutableMap.<UUID, List<SubscriptionNotification>>of()), context);
+ return createInvoices(invoices, new FutureAccountNotifications(), context);
}
private List<InvoiceItemModelDao> createInvoices(final Iterable<InvoiceModelDao> invoices,
final FutureAccountNotifications callbackDateTimePerSubscriptions,
final InternalCallContext context) {
+ // Track invoices that are being created
final Collection<UUID> createdInvoiceIds = new HashSet<UUID>();
- final Collection<UUID> adjustedInvoiceIds = new HashSet<UUID>();
- final Collection<UUID> committedInvoiceIds = new HashSet<UUID>();
-
- final Collection<UUID> uniqueInvoiceIds = new HashSet<UUID>();
+ // Track invoices that already exist but are being committed -- AUTO_INVOICING_REUSE_DRAFT mode
+ final Collection<UUID> committedReusedInvoiceId = new HashSet<UUID>();
+ // Track all invoices that are referenced through all invoiceItems
+ final Collection<UUID> allInvoiceIds = new HashSet<UUID>();
+ // Track invoices that are committed but were not created or reused -- to sent the InvoiceAdjustment bus event
+ final Collection<UUID> adjustedCommittedInvoiceIds = new HashSet<UUID>();
+
+ final Collection<UUID> invoiceIdsReferencedFromItems = new HashSet<UUID>();
for (final InvoiceModelDao invoiceModelDao : invoices) {
for (final InvoiceItemModelDao invoiceItemModelDao : invoiceModelDao.getInvoiceItems()) {
- uniqueInvoiceIds.add(invoiceItemModelDao.getInvoiceId());
+ invoiceIdsReferencedFromItems.add(invoiceItemModelDao.getInvoiceId());
}
}
- if (Iterables.<InvoiceModelDao>isEmpty(invoices)) {
+ if (Iterables.isEmpty(invoices)) {
return ImmutableList.<InvoiceItemModelDao>of();
}
final UUID accountId = invoices.iterator().next().getAccountId();
@@ -319,43 +326,55 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
final List<InvoiceItemModelDao> createdInvoiceItems = new LinkedList<InvoiceItemModelDao>();
for (final InvoiceModelDao invoiceModelDao : invoices) {
invoiceByInvoiceId.put(invoiceModelDao.getId(), invoiceModelDao);
- final boolean isRealInvoice = uniqueInvoiceIds.remove(invoiceModelDao.getId());
+ final boolean isNotShellInvoice = invoiceIdsReferencedFromItems.remove(invoiceModelDao.getId());
- // Create the invoice if needed
- if (invoiceSqlDao.getById(invoiceModelDao.getId().toString(), context) == null) {
- // We only want to insert that invoice if there are real invoiceItems associated to it -- if not, this is just
- // a shell invoice and we only need to insert the invoiceItems -- for the already existing invoices
- if (isRealInvoice) {
+ final InvoiceModelDao invoiceOnDisk = invoiceSqlDao.getById(invoiceModelDao.getId().toString(), context);
+ if (isNotShellInvoice) {
+ // Create the invoice if this is not a shell invoice and it does not already exist
+ if (invoiceOnDisk == null) {
createAndRefresh(invoiceSqlDao, invoiceModelDao, context);
createdInvoiceIds.add(invoiceModelDao.getId());
+ } else if (invoiceOnDisk.getStatus() == InvoiceStatus.DRAFT && invoiceModelDao.getStatus() == InvoiceStatus.COMMITTED) {
+ invoiceSqlDao.updateStatus(invoiceModelDao.getId().toString(), InvoiceStatus.COMMITTED.toString(), context);
+ committedReusedInvoiceId.add(invoiceModelDao.getId());
}
}
// Create the invoice items if needed (note: they may not necessarily belong to that invoice)
for (final InvoiceItemModelDao invoiceItemModelDao : invoiceModelDao.getInvoiceItems()) {
- if (transInvoiceItemSqlDao.getById(invoiceItemModelDao.getId().toString(), context) == null) {
+ final InvoiceItemModelDao existingInvoiceItem = transInvoiceItemSqlDao.getById(invoiceItemModelDao.getId().toString(), context);
+ // Because of AUTO_INVOICING_REUSE_DRAFT we expect an invoice were items might already exist.
+ // Also for ALLOWED_INVOICE_ITEM_TYPES, we expect plugins to potentially modify the amount
+ if (existingInvoiceItem == null) {
createdInvoiceItems.add(createInvoiceItemFromTransaction(transInvoiceItemSqlDao, invoiceItemModelDao, context));
+ allInvoiceIds.add(invoiceItemModelDao.getInvoiceId());
+ } else if (InvoicePluginDispatcher.ALLOWED_INVOICE_ITEM_TYPES.contains(invoiceItemModelDao.getType()) &&
+ (invoiceItemModelDao.getAmount().compareTo(existingInvoiceItem.getAmount()) != 0)) {
+ checkAgainstExistingInvoiceItemState(existingInvoiceItem, invoiceItemModelDao);
- adjustedInvoiceIds.add(invoiceItemModelDao.getInvoiceId());
+ transInvoiceItemSqlDao.updateAmount(invoiceItemModelDao.getId().toString(), invoiceItemModelDao.getAmount(), context);
}
}
- final boolean wasInvoiceCreated = createdInvoiceIds.contains(invoiceModelDao.getId());
+ final boolean wasInvoiceCreatedOrCommitted = createdInvoiceIds.contains(invoiceModelDao.getId()) ||
+ committedReusedInvoiceId.contains(invoiceModelDao.getId());
if (InvoiceStatus.COMMITTED.equals(invoiceModelDao.getStatus())) {
- committedInvoiceIds.add(invoiceModelDao.getId());
-
- notifyOfFutureBillingEvents(entitySqlDaoWrapperFactory, invoiceModelDao.getAccountId(), callbackDateTimePerSubscriptions, context);
- if (wasInvoiceCreated) {
+ if (wasInvoiceCreatedOrCommitted) {
notifyBusOfInvoiceCreation(entitySqlDaoWrapperFactory, invoiceModelDao, context);
+ } else {
+ adjustedCommittedInvoiceIds.add(invoiceModelDao.getId());
}
- } else if (wasInvoiceCreated && invoiceModelDao.isParentInvoice()) {
+ } else if (wasInvoiceCreatedOrCommitted && invoiceModelDao.isParentInvoice()) {
// Commit queue
notifyOfParentInvoiceCreation(entitySqlDaoWrapperFactory, invoiceModelDao, context);
}
+
+ // We always add the future notifications when the callbackDateTimePerSubscriptions is not empty (incl. DRAFT invoices containing RECURRING items created using AUTO_INVOICING_DRAFT feature)
+ notifyOfFutureBillingEvents(entitySqlDaoWrapperFactory, invoiceModelDao.getAccountId(), callbackDateTimePerSubscriptions, context);
}
- for (final UUID adjustedInvoiceId : adjustedInvoiceIds) {
+ for (final UUID adjustedInvoiceId : allInvoiceIds) {
final boolean newInvoice = createdInvoiceIds.contains(adjustedInvoiceId);
if (newInvoice) {
// New invoice, so no associated payment yet: no need to refresh the invoice state
@@ -366,12 +385,11 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
cbaDao.doCBAComplexityFromTransaction(adjustedInvoiceId, invoicesTags, entitySqlDaoWrapperFactory, context);
}
- if (committedInvoiceIds.contains(adjustedInvoiceId) && !newInvoice) {
+ if (adjustedCommittedInvoiceIds.contains(adjustedInvoiceId)) {
// Notify the bus since the balance of the invoice changed (only if the invoice is COMMITTED)
notifyBusOfInvoiceAdjustment(entitySqlDaoWrapperFactory, adjustedInvoiceId, accountId, context.getUserToken(), context);
}
}
-
return createdInvoiceItems;
}
});
@@ -439,7 +457,21 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
BigDecimal accountBalance = BigDecimal.ZERO;
final List<InvoiceModelDao> invoices = invoiceDaoHelper.getAllInvoicesByAccountFromTransaction(invoicesTags, entitySqlDaoWrapperFactory, context);
for (final InvoiceModelDao cur : invoices) {
- accountBalance = accountBalance.add((new DefaultInvoice(cur)).getBalance());
+
+ // Skip DRAFT invoices
+ if (cur.getStatus().equals(InvoiceStatus.DRAFT)) {
+ continue;
+ }
+
+ final boolean hasZeroParentBalance =
+ cur.getParentInvoice() != null &&
+ (cur.getParentInvoice().isWrittenOff() ||
+ cur.getParentInvoice().getStatus() == InvoiceStatus.DRAFT ||
+ InvoiceModelDaoHelper.getRawBalanceForRegularInvoice(cur.getParentInvoice()).compareTo(BigDecimal.ZERO) == 0);
+
+
+ // invoices that are WRITTEN_OFF or paid children invoices are excluded from balance computation but the cba summation needs to be included
+ accountBalance = cur.isWrittenOff() || hasZeroParentBalance ? BigDecimal.ZERO : accountBalance.add(InvoiceModelDaoHelper.getRawBalanceForRegularInvoice(cur));
cba = cba.add(InvoiceModelDaoHelper.getCBAAmount(cur));
}
return accountBalance.subtract(cba);
@@ -865,7 +897,10 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
// Retrieve the invoice and make sure it belongs to the right account
final InvoiceModelDao invoice = transactional.getById(invoiceId.toString(), context);
- if (invoice == null || !invoice.getAccountId().equals(accountId)) {
+ if (invoice == null ||
+ !invoice.getAccountId().equals(accountId) ||
+ invoice.isMigrated() ||
+ invoice.getStatus() == InvoiceStatus.DRAFT) {
throw new InvoiceApiException(ErrorCode.INVOICE_NOT_FOUND, invoiceId);
}
@@ -884,7 +919,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
// Verify the final invoice balance is not negative
invoiceDaoHelper.populateChildren(invoice, invoicesTags, entitySqlDaoWrapperFactory, context);
- if (InvoiceModelDaoHelper.getBalance(invoice).compareTo(BigDecimal.ZERO) < 0) {
+ if (InvoiceModelDaoHelper.getRawBalanceForRegularInvoice(invoice).compareTo(BigDecimal.ZERO) < 0) {
throw new InvoiceApiException(ErrorCode.INVOICE_WOULD_BE_NEGATIVE);
}
@@ -965,19 +1000,18 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
private void notifyOfFutureBillingEvents(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory, final UUID accountId,
final FutureAccountNotifications callbackDateTimePerSubscriptions, final InternalCallContext internalCallContext) {
+ for (final LocalDate notificationDate : callbackDateTimePerSubscriptions.getNotificationsForTrigger().keySet()) {
+ final DateTime notificationDateTime = internalCallContext.toUTCDateTime(notificationDate);
+ final Set<UUID> subscriptionIds = callbackDateTimePerSubscriptions.getNotificationsForTrigger().get(notificationDate);
+ nextBillingDatePoster.insertNextBillingNotificationFromTransaction(entitySqlDaoWrapperFactory, accountId, subscriptionIds, notificationDateTime, internalCallContext);
+ }
+
final long dryRunNotificationTime = invoiceConfig.getDryRunNotificationSchedule(internalCallContext).getMillis();
- final boolean isInvoiceNotificationEnabled = dryRunNotificationTime > 0;
- for (final UUID subscriptionId : callbackDateTimePerSubscriptions.getNotifications().keySet()) {
- final List<SubscriptionNotification> callbackDateTimeUTC = callbackDateTimePerSubscriptions.getNotifications().get(subscriptionId);
- for (final SubscriptionNotification cur : callbackDateTimeUTC) {
- if (isInvoiceNotificationEnabled) {
- final DateTime curDryRunNotificationTime = cur.getEffectiveDate().minus(dryRunNotificationTime);
- final DateTime effectiveCurDryRunNotificationTime = (curDryRunNotificationTime.isAfter(clock.getUTCNow())) ? curDryRunNotificationTime : clock.getUTCNow();
- nextBillingDatePoster.insertNextBillingDryRunNotificationFromTransaction(entitySqlDaoWrapperFactory, accountId, subscriptionId, effectiveCurDryRunNotificationTime, cur.getEffectiveDate(), internalCallContext);
- }
- if (cur.isForInvoiceNotificationTrigger()) {
- nextBillingDatePoster.insertNextBillingNotificationFromTransaction(entitySqlDaoWrapperFactory, accountId, subscriptionId, cur.getEffectiveDate(), internalCallContext);
- }
+ if (dryRunNotificationTime > 0) {
+ for (final LocalDate notificationDate : callbackDateTimePerSubscriptions.getNotificationsForDryRun().keySet()) {
+ final DateTime notificationDateTime = internalCallContext.toUTCDateTime(notificationDate);
+ final Set<UUID> subscriptionIds = callbackDateTimePerSubscriptions.getNotificationsForDryRun().get(notificationDate);
+ nextBillingDatePoster.insertNextBillingDryRunNotificationFromTransaction(entitySqlDaoWrapperFactory, accountId, subscriptionIds, notificationDateTime, notificationDateTime.plusMillis((int) dryRunNotificationTime), internalCallContext);
}
}
}
@@ -1089,9 +1123,10 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
private void notifyBusOfInvoiceCreation(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory, final InvoiceModelDao invoice, final InternalCallContext context) {
try {
- final BigDecimal balance = InvoiceModelDaoHelper.getBalance(invoice);
+ // This is called for a new COMMITTED invoice (which cannot be writtenOff as it does not exist yet, so rawBalance == balance)
+ final BigDecimal rawBalance = InvoiceModelDaoHelper.getRawBalanceForRegularInvoice(invoice);
final DefaultInvoiceCreationEvent event = new DefaultInvoiceCreationEvent(invoice.getId(), invoice.getAccountId(),
- balance, invoice.getCurrency(),
+ rawBalance, invoice.getCurrency(),
context.getAccountRecordId(), context.getTenantRecordId(),
context.getUserToken());
eventBus.postFromTransaction(event, entitySqlDaoWrapperFactory.getHandle().getConnection());
@@ -1103,8 +1138,15 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
private void notifyOfParentInvoiceCreation(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory,
final InvoiceModelDao parentInvoice,
final InternalCallContext context) {
- final DateTime futureNotificationDate = parentInvoice.getCreatedDate().withTimeAtStartOfDay().plusDays(1);
- parentInvoiceCommitmentPoster.insertParentInvoiceFromTransactionInternal(entitySqlDaoWrapperFactory, parentInvoice.getId(), futureNotificationDate, context);
+ final DateTime now = clock.getUTCNow();
+ final LocalTime localTime = LocalTime.parse(invoiceConfig.getParentAutoCommitUtcTime(context));
+
+ DateTime targetFutureNotificationDate = now.withTime(localTime);
+ while (targetFutureNotificationDate.compareTo(now) < 0) {
+ targetFutureNotificationDate = targetFutureNotificationDate.plusDays(1);
+ }
+
+ parentInvoiceCommitmentPoster.insertParentInvoiceFromTransactionInternal(entitySqlDaoWrapperFactory, parentInvoice.getId(), targetFutureNotificationDate, context);
}
@Override
@@ -1203,6 +1245,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
null,
chargeDescription,
childCreatedDate.toLocalDate(),
+ childCreatedDate.toLocalDate(),
accountCBA,
childAccount.getCurrency());
invoiceForExternalCharge.addInvoiceItem(externalChargeItem);
@@ -1269,4 +1312,49 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
private List<Tag> getInvoicesTags(final InternalTenantContext context) {
return tagInternalApi.getTagsForAccountType(ObjectType.INVOICE, false, context);
}
+
+ private static void checkAgainstExistingInvoiceItemState(final InvoiceItemModelDao existingInvoiceItem, final InvoiceItemModelDao inputInvoiceItem) {
+ Preconditions.checkState(existingInvoiceItem.getAccountId().equals(inputInvoiceItem.getAccountId()), String.format("Unexpected account ID '%s' for invoice item '%s'",
+ inputInvoiceItem.getAccountId(), existingInvoiceItem.getId()));
+ if (existingInvoiceItem.getChildAccountId() != null) {
+ Preconditions.checkState(existingInvoiceItem.getChildAccountId().equals(inputInvoiceItem.getChildAccountId()), String.format("Unexpected child account ID '%s' for invoice item '%s'",
+ inputInvoiceItem.getChildAccountId(), existingInvoiceItem.getId()));
+ }
+ Preconditions.checkState(existingInvoiceItem.getInvoiceId().equals(inputInvoiceItem.getInvoiceId()), String.format("Unexpected invoice ID '%s' for invoice item '%s'",
+ inputInvoiceItem.getInvoiceId(), existingInvoiceItem.getId()));
+ if (existingInvoiceItem.getBundleId() != null) {
+ Preconditions.checkState(existingInvoiceItem.getBundleId().equals(inputInvoiceItem.getBundleId()), String.format("Unexpected bundle ID '%s' for invoice item '%s'",
+ inputInvoiceItem.getBundleId(), existingInvoiceItem.getId()));
+ }
+ if (existingInvoiceItem.getSubscriptionId() != null) {
+ Preconditions.checkState(existingInvoiceItem.getSubscriptionId().equals(inputInvoiceItem.getSubscriptionId()), String.format("Unexpected subscription ID '%s' for invoice item '%s'",
+ inputInvoiceItem.getSubscriptionId(), existingInvoiceItem.getId()));
+ }
+ if (existingInvoiceItem.getPlanName() != null) {
+ Preconditions.checkState(existingInvoiceItem.getPlanName().equals(inputInvoiceItem.getPlanName()), String.format("Unexpected plan name '%s' for invoice item '%s'",
+ inputInvoiceItem.getPlanName(), existingInvoiceItem.getId()));
+ }
+ if (existingInvoiceItem.getPhaseName() != null) {
+ Preconditions.checkState(existingInvoiceItem.getPhaseName().equals(inputInvoiceItem.getPhaseName()), String.format("Unexpected phase name '%s' for invoice item '%s'",
+ inputInvoiceItem.getPhaseName(), existingInvoiceItem.getId()));
+ }
+ if (existingInvoiceItem.getUsageName() != null) {
+ Preconditions.checkState(existingInvoiceItem.getUsageName().equals(inputInvoiceItem.getUsageName()), String.format("Unexpected usage name '%s' for invoice item '%s'",
+ inputInvoiceItem.getUsageName(), existingInvoiceItem.getId()));
+ }
+ if (existingInvoiceItem.getStartDate() != null) {
+ Preconditions.checkState(existingInvoiceItem.getStartDate().equals(inputInvoiceItem.getStartDate()), String.format("Unexpected startDate '%s' for invoice item '%s'",
+ inputInvoiceItem.getStartDate(), existingInvoiceItem.getId()));
+ }
+ if (existingInvoiceItem.getEndDate() != null) {
+ Preconditions.checkState(existingInvoiceItem.getEndDate().equals(inputInvoiceItem.getEndDate()), String.format("Unexpected endDate '%s' for invoice item '%s'",
+ inputInvoiceItem.getEndDate(), existingInvoiceItem.getId()));
+ }
+
+ Preconditions.checkState(existingInvoiceItem.getCurrency() == inputInvoiceItem.getCurrency(), String.format("Unexpected currency '%s' for invoice item '%s'",
+ inputInvoiceItem.getCurrency(), existingInvoiceItem.getId()));
+ Preconditions.checkState(existingInvoiceItem.getType() == inputInvoiceItem.getType(), String.format("Unexpected item type '%s' for invoice item '%s'",
+ inputInvoiceItem.getType(), existingInvoiceItem.getId()));
+ }
+
}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceDaoHelper.java b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceDaoHelper.java
index cfd92a1..3343234 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceDaoHelper.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceDaoHelper.java
@@ -175,9 +175,10 @@ public class InvoiceDaoHelper {
@Override
public boolean apply(final InvoiceModelDao in) {
final InvoiceModelDao invoice = (in.getParentInvoice() == null) ? in : in.getParentInvoice();
- final BigDecimal balance = InvoiceModelDaoHelper.getBalance(invoice);
+ final BigDecimal balance = InvoiceModelDaoHelper.getRawBalanceForRegularInvoice(invoice);
log.debug("Computed balance={} for invoice={}", balance, in);
- return InvoiceStatus.COMMITTED.equals(in.getStatus()) && (balance.compareTo(BigDecimal.ZERO) >= 1) &&
+ return InvoiceStatus.COMMITTED.equals(in.getStatus()) &&
+ (balance.compareTo(BigDecimal.ZERO) >= 1 && !in.isWrittenOff()) &&
(upToDate == null || in.getTargetDate() == null || !in.getTargetDate().isAfter(upToDate));
}
});
@@ -226,7 +227,7 @@ public class InvoiceDaoHelper {
}
public void populateChildren(final Iterable<InvoiceModelDao> invoices, final List<Tag> invoicesTags, final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory, final InternalTenantContext context) {
- if (Iterables.<InvoiceModelDao>isEmpty(invoices)) {
+ if (Iterables.isEmpty(invoices)) {
return;
}
@@ -241,7 +242,7 @@ public class InvoiceDaoHelper {
return !invoice.isParentInvoice();
}
});
- if (!Iterables.<InvoiceModelDao>isEmpty(nonParentInvoices)) {
+ if (!Iterables.isEmpty(nonParentInvoices)) {
setParentInvoice(nonParentInvoices,
invoicesTags,
entitySqlDaoWrapperFactory,
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceItemSqlDao.java b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceItemSqlDao.java
index 8c7ded7..7a864d9 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceItemSqlDao.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceItemSqlDao.java
@@ -27,38 +27,38 @@ import org.killbill.billing.invoice.api.InvoiceItem;
import org.killbill.billing.util.audit.ChangeType;
import org.killbill.billing.util.entity.dao.Audited;
import org.killbill.billing.util.entity.dao.EntitySqlDao;
-import org.killbill.billing.util.entity.dao.EntitySqlDaoStringTemplate;
+import org.killbill.commons.jdbi.template.KillBillSqlDaoStringTemplate;
import org.skife.jdbi.v2.sqlobject.Bind;
-import org.skife.jdbi.v2.sqlobject.BindBean;
+import org.killbill.commons.jdbi.binder.SmartBindBean;
import org.skife.jdbi.v2.sqlobject.SqlQuery;
import org.skife.jdbi.v2.sqlobject.SqlUpdate;
-@EntitySqlDaoStringTemplate
+@KillBillSqlDaoStringTemplate
public interface InvoiceItemSqlDao extends EntitySqlDao<InvoiceItemModelDao, InvoiceItem> {
@SqlQuery
List<InvoiceItemModelDao> getInvoiceItemsByInvoice(@Bind("invoiceId") final String invoiceId,
- @BindBean final InternalTenantContext context);
+ @SmartBindBean final InternalTenantContext context);
@SqlQuery
List<InvoiceItemModelDao> getInvoiceItemsBySubscription(@Bind("subscriptionId") final String subscriptionId,
- @BindBean final InternalTenantContext context);
+ @SmartBindBean final InternalTenantContext context);
@SqlQuery
List<InvoiceItemModelDao> getAdjustedOrRepairedInvoiceItemsByLinkedId(@Bind("linkedItemId") final String linkedItemId,
- @BindBean final InternalTenantContext context);
+ @SmartBindBean final InternalTenantContext context);
@SqlUpdate
@Audited(ChangeType.UPDATE)
void updateAmount(@Bind("id") String invoiceItemId,
@Bind("amount")BigDecimal amount,
- @BindBean final InternalCallContext context);
+ @SmartBindBean final InternalCallContext context);
@SqlQuery
List<InvoiceItemModelDao> getInvoiceItemsByParentInvoice(@Bind("parentInvoiceId") final String parentInvoiceId,
- @BindBean final InternalTenantContext context);
+ @SmartBindBean final InternalTenantContext context);
@SqlQuery
- BigDecimal getAccountCBA(@BindBean final InternalTenantContext context);
+ BigDecimal getAccountCBA(@SmartBindBean final InternalTenantContext context);
}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceModelDaoHelper.java b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceModelDaoHelper.java
index a240128..877e741 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceModelDaoHelper.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceModelDaoHelper.java
@@ -18,8 +18,6 @@ package org.killbill.billing.invoice.dao;
import java.math.BigDecimal;
-import javax.annotation.Nullable;
-
import org.killbill.billing.invoice.api.InvoiceItem;
import org.killbill.billing.invoice.api.InvoicePayment;
import org.killbill.billing.invoice.calculator.InvoiceCalculatorUtils;
@@ -33,22 +31,25 @@ public class InvoiceModelDaoHelper {
private InvoiceModelDaoHelper() {}
- public static BigDecimal getBalance(final InvoiceModelDao invoiceModelDao) {
- return InvoiceCalculatorUtils.computeInvoiceBalance(invoiceModelDao.getCurrency(),
- Iterables.transform(invoiceModelDao.getInvoiceItems(), new Function<InvoiceItemModelDao, InvoiceItem>() {
- @Override
- public InvoiceItem apply(final InvoiceItemModelDao input) {
- return InvoiceItemFactory.fromModelDao(input);
- }
- }),
- Iterables.transform(invoiceModelDao.getInvoicePayments(), new Function<InvoicePaymentModelDao, InvoicePayment>() {
- @Nullable
- @Override
- public InvoicePayment apply(final InvoicePaymentModelDao input) {
- return new DefaultInvoicePayment(input);
- }
- }),
- invoiceModelDao.isMigrated() || invoiceModelDao.isWrittenOff());
+ public static BigDecimal getRawBalanceForRegularInvoice(final InvoiceModelDao invoiceModelDao) {
+
+ if (invoiceModelDao.isMigrated()) {
+ return BigDecimal.ZERO;
+ }
+
+ return InvoiceCalculatorUtils.computeRawInvoiceBalance(invoiceModelDao.getCurrency(),
+ Iterables.transform(invoiceModelDao.getInvoiceItems(), new Function<InvoiceItemModelDao, InvoiceItem>() {
+ @Override
+ public InvoiceItem apply(final InvoiceItemModelDao input) {
+ return InvoiceItemFactory.fromModelDao(input);
+ }
+ }),
+ Iterables.transform(invoiceModelDao.getInvoicePayments(), new Function<InvoicePaymentModelDao, InvoicePayment>() {
+ @Override
+ public InvoicePayment apply(final InvoicePaymentModelDao input) {
+ return new DefaultInvoicePayment(input);
+ }
+ }));
}
public static BigDecimal getCBAAmount(final InvoiceModelDao invoiceModelDao) {
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceParentChildrenSqlDao.java b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceParentChildrenSqlDao.java
index 2973f53..dbaa0a9 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceParentChildrenSqlDao.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceParentChildrenSqlDao.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2014-2016 Groupon, Inc
- * Copyright 2014-2016 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
@@ -19,27 +19,26 @@ package org.killbill.billing.invoice.dao;
import java.util.Collection;
import java.util.List;
-import java.util.UUID;
import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.invoice.api.InvoiceParentChild;
import org.killbill.billing.util.entity.dao.EntitySqlDao;
-import org.killbill.billing.util.entity.dao.EntitySqlDaoStringTemplate;
-import org.killbill.billing.util.tag.dao.UUIDCollectionBinder;
+import org.killbill.commons.jdbi.binder.SmartBindBean;
+import org.killbill.commons.jdbi.template.KillBillSqlDaoStringTemplate;
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.unstable.BindIn;
-@EntitySqlDaoStringTemplate
+@KillBillSqlDaoStringTemplate
public interface InvoiceParentChildrenSqlDao extends EntitySqlDao<InvoiceParentChildModelDao, InvoiceParentChild> {
@SqlQuery
List<InvoiceParentChildModelDao> getChildInvoicesByParentInvoiceId(@Bind("parentInvoiceId") final String parentInvoiceId,
- @BindBean final InternalTenantContext context);
+ @SmartBindBean final InternalTenantContext context);
@SqlQuery
- List<InvoiceParentChildModelDao> getParentChildMappingsByChildInvoiceIds(@UUIDCollectionBinder final Collection<String> childInvoiceIds,
- @BindBean final InternalTenantContext context);
+ List<InvoiceParentChildModelDao> getParentChildMappingsByChildInvoiceIds(@BindIn("ids") final Collection<String> childInvoiceIds,
+ @SmartBindBean final InternalTenantContext context);
}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoicePaymentSqlDao.java b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoicePaymentSqlDao.java
index d763bd1..994eb58 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoicePaymentSqlDao.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoicePaymentSqlDao.java
@@ -27,47 +27,47 @@ import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.catalog.api.Currency;
import org.killbill.billing.invoice.api.InvoicePayment;
import org.killbill.billing.util.entity.dao.EntitySqlDao;
-import org.killbill.billing.util.entity.dao.EntitySqlDaoStringTemplate;
+import org.killbill.commons.jdbi.template.KillBillSqlDaoStringTemplate;
import org.skife.jdbi.v2.sqlobject.Bind;
-import org.skife.jdbi.v2.sqlobject.BindBean;
+import org.killbill.commons.jdbi.binder.SmartBindBean;
import org.skife.jdbi.v2.sqlobject.SqlQuery;
import org.skife.jdbi.v2.sqlobject.SqlUpdate;
-@EntitySqlDaoStringTemplate
+@KillBillSqlDaoStringTemplate
public interface InvoicePaymentSqlDao extends EntitySqlDao<InvoicePaymentModelDao, InvoicePayment> {
@SqlQuery
public List<InvoicePaymentModelDao> getByPaymentId(@Bind("paymentId") final String paymentId,
- @BindBean final InternalTenantContext context);
+ @SmartBindBean final InternalTenantContext context);
@SqlQuery
public List<InvoicePaymentModelDao> getAllPaymentsForInvoiceIncludedInit(@Bind("invoiceId") final String invoiceId,
- @BindBean final InternalTenantContext context);
+ @SmartBindBean final InternalTenantContext context);
@SqlQuery
List<InvoicePaymentModelDao> getInvoicePayments(@Bind("paymentId") final String paymentId,
- @BindBean final InternalTenantContext context);
+ @SmartBindBean final InternalTenantContext context);
@SqlQuery
InvoicePaymentModelDao getPaymentForCookieId(@Bind("paymentCookieId") final String paymentCookieId,
- @BindBean final InternalTenantContext context);
+ @SmartBindBean final InternalTenantContext context);
@SqlQuery
BigDecimal getRemainingAmountPaid(@Bind("invoicePaymentId") final String invoicePaymentId,
- @BindBean final InternalTenantContext context);
+ @SmartBindBean final InternalTenantContext context);
@SqlQuery
UUID getAccountIdFromInvoicePaymentId(@Bind("invoicePaymentId") final String invoicePaymentId,
- @BindBean final InternalTenantContext context);
+ @SmartBindBean final InternalTenantContext context);
@SqlQuery
List<InvoicePaymentModelDao> getChargeBacksByAccountId(@Bind("accountId") final String accountId,
- @BindBean final InternalTenantContext context);
+ @SmartBindBean final InternalTenantContext context);
@SqlQuery
List<InvoicePaymentModelDao> getChargebacksByPaymentId(@Bind("paymentId") final String paymentId,
- @BindBean final InternalTenantContext context);
+ @SmartBindBean final InternalTenantContext context);
@SqlUpdate
void updateAttempt(@Bind("recordId") Long recordId,
@@ -79,5 +79,5 @@ public interface InvoicePaymentSqlDao extends EntitySqlDao<InvoicePaymentModelDa
@Bind("paymentCookieId") final String paymentCookieId,
@Bind("linkedInvoicePaymentId") final String linkedInvoicePaymentId,
@Bind("success") final boolean success,
- @BindBean final InternalTenantContext context);
+ @SmartBindBean final InternalTenantContext context);
}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceSqlDao.java b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceSqlDao.java
index 9ed8f28..fe3e080 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceSqlDao.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceSqlDao.java
@@ -28,36 +28,37 @@ import org.killbill.billing.invoice.api.Invoice;
import org.killbill.billing.util.audit.ChangeType;
import org.killbill.billing.util.entity.dao.Audited;
import org.killbill.billing.util.entity.dao.EntitySqlDao;
-import org.killbill.billing.util.entity.dao.EntitySqlDaoStringTemplate;
-import org.killbill.billing.util.tag.dao.UUIDCollectionBinder;
+import org.killbill.commons.jdbi.binder.SmartBindBean;
+import org.killbill.commons.jdbi.template.KillBillSqlDaoStringTemplate;
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.unstable.BindIn;
-@EntitySqlDaoStringTemplate
+@KillBillSqlDaoStringTemplate
public interface InvoiceSqlDao extends EntitySqlDao<InvoiceModelDao, Invoice> {
@SqlQuery
List<InvoiceModelDao> getInvoicesBySubscription(@Bind("subscriptionId") final String subscriptionId,
- @BindBean final InternalTenantContext context);
+ @SmartBindBean final InternalTenantContext context);
@SqlQuery
UUID getInvoiceIdByPaymentId(@Bind("paymentId") final String paymentId,
- @BindBean final InternalTenantContext context);
+ @SmartBindBean final InternalTenantContext context);
@SqlUpdate
@Audited(ChangeType.UPDATE)
void updateStatus(@Bind("id") String invoiceId,
@Bind("status") String status,
- @BindBean final InternalCallContext context);
+ @SmartBindBean final InternalCallContext context);
+
@SqlQuery
InvoiceModelDao getParentDraftInvoice(@Bind("accountId") final String parentAccountId,
- @BindBean final InternalTenantContext context);
+ @SmartBindBean final InternalTenantContext context);
@SqlQuery
- List<InvoiceModelDao> getByIds(@UUIDCollectionBinder final Collection<String> invoiceIds,
- @BindBean final InternalTenantContext context);
+ List<InvoiceModelDao> getByIds(@BindIn("ids") final Collection<String> invoiceIds,
+ @SmartBindBean final InternalTenantContext context);
}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/generator/DefaultInvoiceGenerator.java b/invoice/src/main/java/org/killbill/billing/invoice/generator/DefaultInvoiceGenerator.java
index 33f7c25..2e46d79 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/generator/DefaultInvoiceGenerator.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/generator/DefaultInvoiceGenerator.java
@@ -35,6 +35,7 @@ import org.killbill.billing.catalog.api.Currency;
import org.killbill.billing.invoice.api.Invoice;
import org.killbill.billing.invoice.api.InvoiceApiException;
import org.killbill.billing.invoice.api.InvoiceItem;
+import org.killbill.billing.invoice.api.InvoiceStatus;
import org.killbill.billing.invoice.generator.InvoiceWithMetadata.SubscriptionFutureNotificationDates;
import org.killbill.billing.invoice.model.DefaultInvoice;
import org.killbill.billing.junction.BillingEventSet;
@@ -42,7 +43,10 @@ import org.killbill.billing.util.config.definition.InvoiceConfig;
import org.killbill.clock.Clock;
import com.google.common.base.Objects;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
import com.google.inject.Inject;
public class DefaultInvoiceGenerator implements InvoiceGenerator {
@@ -65,10 +69,13 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
* adjusts target date to the maximum invoice target date, if future invoices exist
*/
@Override
- public InvoiceWithMetadata generateInvoice(final ImmutableAccountData account, @Nullable final BillingEventSet events,
- @Nullable final List<Invoice> existingInvoices,
+ public InvoiceWithMetadata generateInvoice(final ImmutableAccountData account,
+ @Nullable final BillingEventSet events,
+ @Nullable final Iterable<Invoice> existingInvoices,
+ @Nullable final UUID targetInvoiceId,
final LocalDate targetDate,
- final Currency targetCurrency, final InternalCallContext context) throws InvoiceApiException {
+ final Currency targetCurrency,
+ final InternalCallContext context) throws InvoiceApiException {
if ((events == null) || (events.size() == 0) || events.isAccountAutoInvoiceOff()) {
return new InvoiceWithMetadata(null, ImmutableMap.<UUID, SubscriptionFutureNotificationDates>of());
}
@@ -77,16 +84,29 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
final LocalDate adjustedTargetDate = adjustTargetDate(existingInvoices, targetDate);
final LocalDate invoiceDate = context.toLocalDate(context.getCreatedDate());
- final DefaultInvoice invoice = new DefaultInvoice(account.getId(), invoiceDate, adjustedTargetDate, targetCurrency);
- final UUID invoiceId = invoice.getId();
+ final InvoiceStatus invoiceStatus = events.isAccountAutoInvoiceDraft() ? InvoiceStatus.DRAFT : InvoiceStatus.COMMITTED;
+ final DefaultInvoice invoice = targetInvoiceId != null ?
+ new DefaultInvoice(targetInvoiceId, account.getId(), null, invoiceDate, adjustedTargetDate, targetCurrency, false, invoiceStatus) :
+ new DefaultInvoice(account.getId(), invoiceDate, adjustedTargetDate, targetCurrency, invoiceStatus);
+
final Map<UUID, SubscriptionFutureNotificationDates> perSubscriptionFutureNotificationDates = new HashMap<UUID, SubscriptionFutureNotificationDates>();
- final List<InvoiceItem> fixedAndRecurringItems = recurringInvoiceItemGenerator.generateItems(account, invoiceId, events, existingInvoices, adjustedTargetDate, targetCurrency, perSubscriptionFutureNotificationDates, context);
+ final List<InvoiceItem> fixedAndRecurringItems = recurringInvoiceItemGenerator.generateItems(account, invoice.getId(), events, existingInvoices, adjustedTargetDate, targetCurrency, perSubscriptionFutureNotificationDates, context);
invoice.addInvoiceItems(fixedAndRecurringItems);
- final List<InvoiceItem> usageItems = usageInvoiceItemGenerator.generateItems(account, invoiceId, events, existingInvoices, adjustedTargetDate, targetCurrency, perSubscriptionFutureNotificationDates, context);
+ final List<InvoiceItem> usageItems = usageInvoiceItemGenerator.generateItems(account, invoice.getId(), events, existingInvoices, adjustedTargetDate, targetCurrency, perSubscriptionFutureNotificationDates, context);
invoice.addInvoiceItems(usageItems);
+ if (targetInvoiceId != null) {
+ final Invoice originalInvoice = Iterables.tryFind(existingInvoices, new Predicate<Invoice>() {
+ @Override
+ public boolean apply(final Invoice input) {
+ return input.getId().equals(targetInvoiceId);
+ }
+ }).orNull();
+ Preconditions.checkNotNull(originalInvoice, "Expecting to find an existing invoice matching the targetInvoiceId");
+ invoice.addInvoiceItems(originalInvoice.getInvoiceItems());
+ }
return new InvoiceWithMetadata(invoice.getInvoiceItems().isEmpty() ? null : invoice, perSubscriptionFutureNotificationDates);
}
@@ -99,7 +119,7 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
}
}
- private LocalDate adjustTargetDate(final List<Invoice> existingInvoices, final LocalDate targetDate) {
+ private LocalDate adjustTargetDate(final Iterable<Invoice> existingInvoices, final LocalDate targetDate) {
if (existingInvoices == null) {
return targetDate;
}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/generator/FixedAndRecurringInvoiceItemGenerator.java b/invoice/src/main/java/org/killbill/billing/invoice/generator/FixedAndRecurringInvoiceItemGenerator.java
index 6a22b6a..c73adba 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/generator/FixedAndRecurringInvoiceItemGenerator.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/generator/FixedAndRecurringInvoiceItemGenerator.java
@@ -83,7 +83,7 @@ public class FixedAndRecurringInvoiceItemGenerator extends InvoiceItemGenerator
}
public List<InvoiceItem> generateItems(final ImmutableAccountData account, final UUID invoiceId, final BillingEventSet eventSet,
- @Nullable final List<Invoice> existingInvoices, final LocalDate targetDate,
+ @Nullable final Iterable<Invoice> existingInvoices, final LocalDate targetDate,
final Currency targetCurrency, final Map<UUID, SubscriptionFutureNotificationDates> perSubscriptionFutureNotificationDate,
final InternalCallContext internalCallContext) throws InvoiceApiException {
final Multimap<UUID, LocalDate> createdItemsPerDayPerSubscription = LinkedListMultimap.<UUID, LocalDate>create();
@@ -105,7 +105,7 @@ public class FixedAndRecurringInvoiceItemGenerator extends InvoiceItemGenerator
// Generate list of proposed invoice items based on billing events from junction-- proposed items are ALL items since beginning of time
final List<InvoiceItem> proposedItems = new ArrayList<InvoiceItem>();
- processRecurringBillingEvents(invoiceId, account.getId(), eventSet, targetDate, targetCurrency, proposedItems, perSubscriptionFutureNotificationDate, existingInvoices, internalCallContext);
+ processRecurringBillingEvents(invoiceId, account.getId(), eventSet, targetDate, targetCurrency, proposedItems, perSubscriptionFutureNotificationDate, internalCallContext);
processFixedBillingEvents(invoiceId, account.getId(), eventSet, targetDate, targetCurrency, proposedItems, internalCallContext);
try {
@@ -124,7 +124,6 @@ public class FixedAndRecurringInvoiceItemGenerator extends InvoiceItemGenerator
private void processRecurringBillingEvents(final UUID invoiceId, final UUID accountId, final BillingEventSet events,
final LocalDate targetDate, final Currency currency, final List<InvoiceItem> proposedItems,
final Map<UUID, SubscriptionFutureNotificationDates> perSubscriptionFutureNotificationDate,
- @Nullable final List<Invoice> existingInvoices,
final InternalCallContext internalCallContext) throws InvoiceApiException {
if (events.isEmpty()) {
return;
@@ -141,11 +140,11 @@ public class FixedAndRecurringInvoiceItemGenerator extends InvoiceItemGenerator
if (!events.getSubscriptionIdsWithAutoInvoiceOff().
contains(thisEvent.getSubscription().getId())) { // don't consider events for subscriptions that have auto_invoice_off
final BillingEvent adjustedNextEvent = (thisEvent.getSubscription().getId() == nextEvent.getSubscription().getId()) ? nextEvent : null;
- final List<InvoiceItem> newProposedItems = processRecurringEvent(invoiceId, accountId, thisEvent, adjustedNextEvent, targetDate, currency, invoiceItemGeneratorLogger, events.getRecurringBillingMode(), perSubscriptionFutureNotificationDate, internalCallContext);
+ final List<InvoiceItem> newProposedItems = processRecurringEvent(invoiceId, accountId, thisEvent, adjustedNextEvent, targetDate, currency, invoiceItemGeneratorLogger, thisEvent.getPlan().getRecurringBillingMode(), perSubscriptionFutureNotificationDate, internalCallContext);
proposedItems.addAll(newProposedItems);
}
}
- final List<InvoiceItem> newProposedItems = processRecurringEvent(invoiceId, accountId, nextEvent, null, targetDate, currency, invoiceItemGeneratorLogger, events.getRecurringBillingMode(), perSubscriptionFutureNotificationDate, internalCallContext);
+ final List<InvoiceItem> newProposedItems = processRecurringEvent(invoiceId, accountId, nextEvent, null, targetDate, currency, invoiceItemGeneratorLogger, nextEvent.getPlan().getRecurringBillingMode(), perSubscriptionFutureNotificationDate, internalCallContext);
proposedItems.addAll(newProposedItems);
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/generator/InvoiceGenerator.java b/invoice/src/main/java/org/killbill/billing/invoice/generator/InvoiceGenerator.java
index e513a00..062be76 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/generator/InvoiceGenerator.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/generator/InvoiceGenerator.java
@@ -17,6 +17,7 @@
package org.killbill.billing.invoice.generator;
import java.util.List;
+import java.util.UUID;
import javax.annotation.Nullable;
@@ -30,6 +31,6 @@ import org.killbill.billing.invoice.api.InvoiceApiException;
import org.killbill.billing.junction.BillingEventSet;
public interface InvoiceGenerator {
- InvoiceWithMetadata generateInvoice(ImmutableAccountData account, @Nullable BillingEventSet events, @Nullable List<Invoice> existingInvoices,
- LocalDate targetDate, Currency targetCurrency, final InternalCallContext context) throws InvoiceApiException;
+ InvoiceWithMetadata generateInvoice(ImmutableAccountData account, @Nullable BillingEventSet events, @Nullable Iterable<Invoice> existingInvoices,
+ final UUID targetInvoiceId, LocalDate targetDate, Currency targetCurrency, final InternalCallContext context) throws InvoiceApiException;
}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/generator/InvoiceItemGenerator.java b/invoice/src/main/java/org/killbill/billing/invoice/generator/InvoiceItemGenerator.java
index eb2ce88..826a1b5 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/generator/InvoiceItemGenerator.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/generator/InvoiceItemGenerator.java
@@ -38,7 +38,7 @@ import org.slf4j.Logger;
public abstract class InvoiceItemGenerator {
public abstract List<InvoiceItem> generateItems(final ImmutableAccountData account, final UUID invoiceId, final BillingEventSet eventSet,
- @Nullable final List<Invoice> existingInvoices, final LocalDate targetDate,
+ @Nullable final Iterable<Invoice> existingInvoices, final LocalDate targetDate,
final Currency targetCurrency, Map<UUID, SubscriptionFutureNotificationDates> perSubscriptionFutureNotificationDate,
final InternalCallContext context) throws InvoiceApiException;
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/generator/UsageInvoiceItemGenerator.java b/invoice/src/main/java/org/killbill/billing/invoice/generator/UsageInvoiceItemGenerator.java
index eb18aaa..64e6c4c 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/generator/UsageInvoiceItemGenerator.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/generator/UsageInvoiceItemGenerator.java
@@ -71,7 +71,7 @@ public class UsageInvoiceItemGenerator extends InvoiceItemGenerator {
public List<InvoiceItem> generateItems(final ImmutableAccountData account,
final UUID invoiceId,
final BillingEventSet eventSet,
- @Nullable final List<Invoice> existingInvoices,
+ @Nullable final Iterable<Invoice> existingInvoices,
final LocalDate targetDate,
final Currency targetCurrency,
final Map<UUID, SubscriptionFutureNotificationDates> perSubscriptionFutureNotificationDates,
@@ -170,8 +170,8 @@ public class UsageInvoiceItemGenerator extends InvoiceItemGenerator {
}
}
- private Map<UUID, List<InvoiceItem>> extractPerSubscriptionExistingInArrearUsageItems(final Map<String, Usage> knownUsage, @Nullable final List<Invoice> existingInvoices) {
- if (existingInvoices == null || existingInvoices.isEmpty()) {
+ private Map<UUID, List<InvoiceItem>> extractPerSubscriptionExistingInArrearUsageItems(final Map<String, Usage> knownUsage, @Nullable final Iterable<Invoice> existingInvoices) {
+ if (existingInvoices == null || Iterables.isEmpty(existingInvoices)) {
return ImmutableMap.of();
}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/glue/DefaultInvoiceModule.java b/invoice/src/main/java/org/killbill/billing/invoice/glue/DefaultInvoiceModule.java
index d400a3f..eabea2e 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/glue/DefaultInvoiceModule.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/glue/DefaultInvoiceModule.java
@@ -26,7 +26,7 @@ import org.killbill.billing.invoice.ParkedAccountsManager;
import org.killbill.billing.invoice.api.DefaultInvoiceService;
import org.killbill.billing.invoice.api.InvoiceApiHelper;
import org.killbill.billing.invoice.api.InvoiceInternalApi;
-import org.killbill.billing.invoice.api.InvoiceNotifier;
+import org.killbill.billing.invoice.api.InvoiceListenerService;
import org.killbill.billing.invoice.api.InvoicePaymentApi;
import org.killbill.billing.invoice.api.InvoiceService;
import org.killbill.billing.invoice.api.InvoiceUserApi;
@@ -46,10 +46,8 @@ import org.killbill.billing.invoice.generator.InvoiceGenerator;
import org.killbill.billing.invoice.generator.UsageInvoiceItemGenerator;
import org.killbill.billing.invoice.notification.DefaultNextBillingDateNotifier;
import org.killbill.billing.invoice.notification.DefaultNextBillingDatePoster;
-import org.killbill.billing.invoice.notification.EmailInvoiceNotifier;
import org.killbill.billing.invoice.notification.NextBillingDateNotifier;
import org.killbill.billing.invoice.notification.NextBillingDatePoster;
-import org.killbill.billing.invoice.notification.NullInvoiceNotifier;
import org.killbill.billing.invoice.plugin.api.InvoicePluginApi;
import org.killbill.billing.invoice.template.bundles.DefaultResourceBundleFactory;
import org.killbill.billing.invoice.usage.RawUsageOptimizer;
@@ -102,8 +100,9 @@ public class DefaultInvoiceModule extends KillBillModule implements InvoiceModul
bind(InvoiceConfig.class).to(MultiTenantInvoiceConfig.class).asEagerSingleton();
}
- protected void installInvoiceService() {
+ protected void installInvoiceServices() {
bind(InvoiceService.class).to(DefaultInvoiceService.class).asEagerSingleton();
+ bind(InvoiceListenerService.class).to(InvoiceListener.class).asEagerSingleton();
}
protected void installResourceBundleFactory() {
@@ -119,14 +118,6 @@ public class DefaultInvoiceModule extends KillBillModule implements InvoiceModul
bind(InvoiceFormatterFactory.class).to(config.getInvoiceFormatterFactoryClass()).asEagerSingleton();
}
- protected void installInvoiceNotifier() {
- if (staticInvoiceConfig.isEmailNotificationsEnabled()) {
- bind(InvoiceNotifier.class).to(EmailInvoiceNotifier.class).asEagerSingleton();
- } else {
- bind(InvoiceNotifier.class).to(NullInvoiceNotifier.class).asEagerSingleton();
- }
- }
-
protected void installInvoiceDispatcher() {
bind(InvoiceDispatcher.class).asEagerSingleton();
}
@@ -154,8 +145,7 @@ public class DefaultInvoiceModule extends KillBillModule implements InvoiceModul
installConfig();
installInvoicePluginApi();
- installInvoiceService();
- installInvoiceNotifier();
+ installInvoiceServices();
installNotifiers();
installInvoiceDispatcher();
installInvoiceListener();
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java b/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java
index a300bf1..fabf9c7 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java
@@ -24,7 +24,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
-import java.util.Iterator;
+import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
@@ -46,14 +46,12 @@ import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.catalog.api.BillingActionPolicy;
import org.killbill.billing.catalog.api.CatalogApiException;
-import org.killbill.billing.catalog.api.Currency;
import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
import org.killbill.billing.entitlement.api.SubscriptionEventType;
import org.killbill.billing.events.BusInternalEvent;
import org.killbill.billing.events.EffectiveSubscriptionInternalEvent;
import org.killbill.billing.events.InvoiceNotificationInternalEvent;
-import org.killbill.billing.invoice.InvoiceDispatcher.FutureAccountNotifications.SubscriptionNotification;
import org.killbill.billing.invoice.api.DefaultInvoiceService;
import org.killbill.billing.invoice.api.DryRunArguments;
import org.killbill.billing.invoice.api.DryRunType;
@@ -61,7 +59,6 @@ import org.killbill.billing.invoice.api.Invoice;
import org.killbill.billing.invoice.api.InvoiceApiException;
import org.killbill.billing.invoice.api.InvoiceItem;
import org.killbill.billing.invoice.api.InvoiceItemType;
-import org.killbill.billing.invoice.api.InvoiceNotifier;
import org.killbill.billing.invoice.api.InvoiceStatus;
import org.killbill.billing.invoice.api.user.DefaultInvoiceNotificationInternalEvent;
import org.killbill.billing.invoice.api.user.DefaultNullInvoiceEvent;
@@ -87,7 +84,6 @@ import org.killbill.billing.junction.BillingEvent;
import org.killbill.billing.junction.BillingEventSet;
import org.killbill.billing.junction.BillingInternalApi;
import org.killbill.billing.subscription.api.SubscriptionBaseInternalApi;
-import org.killbill.billing.subscription.api.SubscriptionBaseTransitionType;
import org.killbill.billing.subscription.api.user.SubscriptionBaseApiException;
import org.killbill.billing.util.UUIDs;
import org.killbill.billing.util.api.TagApiException;
@@ -115,6 +111,7 @@ import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Ordering;
@@ -134,7 +131,6 @@ public class InvoiceDispatcher {
private final SubscriptionBaseInternalApi subscriptionApi;
private final InvoiceDao invoiceDao;
private final InternalCallContextFactory internalCallContextFactory;
- private final InvoiceNotifier invoiceNotifier;
private final InvoicePluginDispatcher invoicePluginDispatcher;
private final GlobalLocker locker;
private final PersistentBus eventBus;
@@ -150,7 +146,6 @@ public class InvoiceDispatcher {
final SubscriptionBaseInternalApi SubscriptionApi,
final InvoiceDao invoiceDao,
final InternalCallContextFactory internalCallContextFactory,
- final InvoiceNotifier invoiceNotifier,
final InvoicePluginDispatcher invoicePluginDispatcher,
final GlobalLocker locker,
final PersistentBus eventBus,
@@ -164,7 +159,6 @@ public class InvoiceDispatcher {
this.accountApi = accountApi;
this.invoiceDao = invoiceDao;
this.internalCallContextFactory = internalCallContextFactory;
- this.invoiceNotifier = invoiceNotifier;
this.invoicePluginDispatcher = invoicePluginDispatcher;
this.locker = locker;
this.eventBus = eventBus;
@@ -210,7 +204,7 @@ public class InvoiceDispatcher {
return processAccountFromNotificationOrBusEvent(accountId, targetDate, dryRunArguments, context);
} catch (final SubscriptionBaseApiException e) {
log.warn("Failed handling SubscriptionBase change.",
- new InvoiceApiException(ErrorCode.INVOICE_NO_ACCOUNT_ID_FOR_SUBSCRIPTION_ID, subscriptionId.toString()));
+ new InvoiceApiException(ErrorCode.INVOICE_NO_ACCOUNT_ID_FOR_SUBSCRIPTION_ID, subscriptionId.toString()));
return null;
}
}
@@ -268,7 +262,7 @@ public class InvoiceDispatcher {
final boolean upcomingInvoiceDryRun = isDryRun && DryRunType.UPCOMING_INVOICE.equals(dryRunArguments.getDryRunType());
LocalDate inputTargetDate = inputTargetDateMaybeNull;
- // A null inputTargetDate is only allowed in dryRun mode to have the system compute it
+ // A null inputTargetDate is only allowed in UPCOMING_INVOICE dryRun mode to have the system compute it
if (inputTargetDate == null && !upcomingInvoiceDryRun) {
inputTargetDate = clock.getUTCToday();
}
@@ -280,28 +274,59 @@ public class InvoiceDispatcher {
if (billingEvents.isEmpty()) {
return null;
}
- final Iterable<UUID> filteredSubscriptionIdsForDryRun = getFilteredSubscriptionIdsForDryRun(dryRunArguments, billingEvents);
- final List<LocalDate> candidateTargetDates = (inputTargetDate != null) ?
- ImmutableList.<LocalDate>of(inputTargetDate) :
- getUpcomingInvoiceCandidateDates(filteredSubscriptionIdsForDryRun, context);
- for (final LocalDate curTargetDate : candidateTargetDates) {
- final Invoice invoice = processAccountWithLockAndInputTargetDate(accountId, curTargetDate, billingEvents, isDryRun, context);
- if (invoice != null) {
- filterInvoiceItemsForDryRun(filteredSubscriptionIdsForDryRun, invoice);
-
- if (!isDryRun && parkedAccount) {
- try {
- log.info("Illegal invoicing state fixed for accountId='{}', unparking account", accountId);
- parkedAccountsManager.unparkAccount(accountId, context);
- } catch (final TagApiException ignored) {
- log.warn("Unable to unpark account", ignored);
- }
+
+ // Avoid pulling all invoices when AUTO_INVOICING_OFF is set since we will disable invoicing later
+ // (Note that we can't return right away as we send a NullInvoice event)
+ final List<Invoice> existingInvoices = billingEvents.isAccountAutoInvoiceOff() ?
+ ImmutableList.<Invoice>of() :
+ ImmutableList.<Invoice>copyOf(Collections2.transform(invoiceDao.getInvoicesByAccount(context),
+ new Function<InvoiceModelDao, Invoice>() {
+ @Override
+ public Invoice apply(final InvoiceModelDao input) {
+ return new DefaultInvoice(input);
+ }
+ }));
+ Invoice invoice;
+ if (!isDryRun) {
+ invoice = processAccountWithLockAndInputTargetDate(accountId, inputTargetDate, billingEvents, existingInvoices, false, context);
+ if (parkedAccount) {
+ try {
+ log.info("Illegal invoicing state fixed for accountId='{}', unparking account", accountId);
+ parkedAccountsManager.unparkAccount(accountId, context);
+ } catch (final TagApiException ignored) {
+ log.warn("Unable to unpark account", ignored);
}
+ }
+ } else /* Dry run use cases */ {
+
+ final NotificationQueue notificationQueue = notificationQueueService.getNotificationQueue(DefaultInvoiceService.INVOICE_SERVICE_NAME,
+ DefaultNextBillingDateNotifier.NEXT_BILLING_DATE_NOTIFIER_QUEUE);
+ final Iterable<NotificationEventWithMetadata<NextBillingDateNotificationKey>> futureNotifications = notificationQueue.getFutureNotificationForSearchKeys(context.getAccountRecordId(), context.getTenantRecordId());
+
+ final Map<UUID, DateTime> nextScheduledSubscriptionsEventMap = getNextTransitionsForSubscriptions(billingEvents);
+
+ // List of all existing invoice notifications
+ final List<LocalDate> allCandidateTargetDates = getUpcomingInvoiceCandidateDates(futureNotifications, nextScheduledSubscriptionsEventMap, ImmutableList.<UUID>of(), context);
- return invoice;
+ if (dryRunArguments.getDryRunType() == DryRunType.UPCOMING_INVOICE) {
+
+ final Iterable<UUID> filteredSubscriptionIdsForDryRun = getFilteredSubscriptionIdsFor_UPCOMING_INVOICE_DryRun(dryRunArguments, billingEvents);
+
+ // List of existing invoice notifications associated to the filter set of subscriptionIds
+ final List<LocalDate> filteredCandidateTargetDates = Iterables.isEmpty(filteredSubscriptionIdsForDryRun) ?
+ allCandidateTargetDates :
+ getUpcomingInvoiceCandidateDates(futureNotifications, nextScheduledSubscriptionsEventMap, filteredSubscriptionIdsForDryRun, context);
+
+ if (Iterables.isEmpty(filteredSubscriptionIdsForDryRun)) {
+ invoice = processDryRun_UPCOMING_INVOICE_Invoice(accountId, allCandidateTargetDates, billingEvents, existingInvoices, context);
+ } else {
+ invoice = processDryRun_UPCOMING_INVOICE_FILTERING_Invoice(accountId, filteredCandidateTargetDates, allCandidateTargetDates, billingEvents, existingInvoices, context);
+ }
+ } else /* DryRunType.TARGET_DATE, SUBSCRIPTION_ACTION */ {
+ invoice = processDryRun_TARGET_DATE_Invoice(accountId, inputTargetDate, allCandidateTargetDates, billingEvents, existingInvoices, context);
}
}
- return null;
+ return invoice;
} catch (final CatalogApiException e) {
log.warn("Failed to retrieve BillingEvents for accountId='{}', dryRunArguments='{}'", accountId, dryRunArguments, e);
return null;
@@ -317,32 +342,89 @@ public class InvoiceDispatcher {
parkAccount(accountId, context);
}
throw e;
+ } catch (final NoSuchNotificationQueue e) {
+ // Should not happen, notificationQ is only used for dry run mode
+ if (!isDryRun) {
+ log.warn("Missing notification queue, accountId='{}', dryRunArguments='{}', parking account", accountId, dryRunArguments, e);
+ parkAccount(accountId, context);
+ }
+ throw new InvoiceApiException(ErrorCode.UNEXPECTED_ERROR, "Failed to retrieve future notifications from notificationQ");
}
}
- private void parkAccount(final UUID accountId, final InternalCallContext context) {
- try {
- parkedAccountsManager.parkAccount(accountId, context);
- } catch (final TagApiException ignored) {
- log.warn("Unable to park account", ignored);
+ // Return a map of subscriptionId / localDate identifying what is the next upcoming billing transition (PHASE, PAUSE, ..)
+ private Map<UUID, DateTime> getNextTransitionsForSubscriptions(final BillingEventSet billingEvents) {
+
+ final DateTime now = clock.getUTCNow();
+ final Map<UUID, DateTime> result = new HashMap<UUID, DateTime>();
+ for (final BillingEvent evt : billingEvents) {
+ final UUID subscriptionId = evt.getSubscription().getId();
+ final DateTime evtEffectiveDate = evt.getEffectiveDate();
+ if (evtEffectiveDate.compareTo(now) <= 0) {
+ continue;
+ }
+ final DateTime nextUpcomingPerSubscriptionDate = result.get(subscriptionId);
+ if (nextUpcomingPerSubscriptionDate == null || nextUpcomingPerSubscriptionDate.compareTo(evtEffectiveDate) > 0) {
+ result.put(subscriptionId, evtEffectiveDate);
+ }
}
+ return result;
}
- private void filterInvoiceItemsForDryRun(final Iterable<UUID> filteredSubscriptionIdsForDryRun, final Invoice invoice) {
- if (!filteredSubscriptionIdsForDryRun.iterator().hasNext()) {
- return;
+ private Invoice processDryRun_UPCOMING_INVOICE_Invoice(final UUID accountId, final List<LocalDate> allCandidateTargetDates, final BillingEventSet billingEvents, final List<Invoice> existingInvoices, final InternalCallContext context) throws InvoiceApiException {
+ for (final LocalDate curTargetDate : allCandidateTargetDates) {
+ final Invoice invoice = processAccountWithLockAndInputTargetDate(accountId, curTargetDate, billingEvents, existingInvoices, true, context);
+ if (invoice != null) {
+ return invoice;
+ }
+ }
+ return null;
+ }
+
+ private Invoice processDryRun_UPCOMING_INVOICE_FILTERING_Invoice(final UUID accountId, final List<LocalDate> filteringCandidateTargetDates, final List<LocalDate> allCandidateTargetDates, final BillingEventSet billingEvents, final List<Invoice> existingInvoices, final InternalCallContext context) throws InvoiceApiException {
+ for (final LocalDate curTargetDate : filteringCandidateTargetDates) {
+ final Invoice invoice = processDryRun_TARGET_DATE_Invoice(accountId, curTargetDate, allCandidateTargetDates, billingEvents, existingInvoices, context);
+ if (invoice != null) {
+ return invoice;
+ }
}
+ return null;
+ }
+
+ private Invoice processDryRun_TARGET_DATE_Invoice(final UUID accountId, final LocalDate targetDate, final List<LocalDate> allCandidateTargetDates, final BillingEventSet billingEvents, final List<Invoice> existingInvoices, final InternalCallContext context) throws InvoiceApiException {
- final Iterator<InvoiceItem> it = invoice.getInvoiceItems().iterator();
- while (it.hasNext()) {
- final InvoiceItem cur = it.next();
- if (!Iterables.contains(filteredSubscriptionIdsForDryRun, cur.getSubscriptionId())) {
- it.remove();
+ LocalDate prevLocalDate = null;
+ for (final LocalDate cur : allCandidateTargetDates) {
+ if (cur.compareTo(targetDate) < 0) {
+ prevLocalDate = cur;
+ } else {
+ break;
}
}
+
+ // Generate a dryRun invoice for such date if required in such a way that dryRun invoice on our targetDate only contains items that we expect to see
+ final Invoice additionalInvoice = prevLocalDate != null ?
+ processAccountWithLockAndInputTargetDate(accountId, prevLocalDate, billingEvents, existingInvoices, true, context) :
+ null;
+
+ final List<Invoice> augmentedExistingInvoices = additionalInvoice != null ?
+ new ImmutableList.Builder().addAll(existingInvoices).add(additionalInvoice).build() :
+ existingInvoices;
+
+ final Invoice targetInvoice = processAccountWithLockAndInputTargetDate(accountId, targetDate, billingEvents, augmentedExistingInvoices, true, context);
+ // If our targetDate -- user specified -- did not align with any boundary, we return previous 'additionalInvoice' invoice
+ return targetInvoice != null ? targetInvoice : additionalInvoice;
+ }
+
+ private void parkAccount(final UUID accountId, final InternalCallContext context) {
+ try {
+ parkedAccountsManager.parkAccount(accountId, context);
+ } catch (final TagApiException ignored) {
+ log.warn("Unable to park account", ignored);
+ }
}
- private Iterable<UUID> getFilteredSubscriptionIdsForDryRun(@Nullable final DryRunArguments dryRunArguments, final BillingEventSet billingEvents) {
+ private Iterable<UUID> getFilteredSubscriptionIdsFor_UPCOMING_INVOICE_DryRun(@Nullable final DryRunArguments dryRunArguments, final BillingEventSet billingEvents) {
if (dryRunArguments == null ||
!dryRunArguments.getDryRunType().equals(DryRunType.UPCOMING_INVOICE) ||
(dryRunArguments.getSubscriptionId() == null && dryRunArguments.getBundleId() == null)) {
@@ -366,49 +448,46 @@ public class InvoiceDispatcher {
});
}
- private Invoice processAccountWithLockAndInputTargetDate(final UUID accountId, final LocalDate targetDate,
- final BillingEventSet billingEvents, final boolean isDryRun, final InternalCallContext context) throws InvoiceApiException {
+ private Invoice processAccountWithLockAndInputTargetDate(final UUID accountId,
+ final LocalDate targetDate,
+ final BillingEventSet billingEvents,
+ final List<Invoice> existingInvoices,
+ final boolean isDryRun,
+ final InternalCallContext internalCallContext) throws InvoiceApiException {
+ final ImmutableAccountData account;
try {
- final ImmutableAccountData account = accountApi.getImmutableAccountDataById(accountId, context);
-
- final List<Invoice> invoices = billingEvents.isAccountAutoInvoiceOff() ?
- ImmutableList.<Invoice>of() :
- ImmutableList.<Invoice>copyOf(Collections2.transform(invoiceDao.getInvoicesByAccount(context),
- new Function<InvoiceModelDao, Invoice>() {
- @Override
- public Invoice apply(final InvoiceModelDao input) {
- return new DefaultInvoice(input);
- }
- }));
-
- final Currency targetCurrency = account.getCurrency();
- final InvoiceWithMetadata invoiceWithMetadata = generator.generateInvoice(account, billingEvents, invoices, targetDate, targetCurrency, context);
- final DefaultInvoice invoice = invoiceWithMetadata.getInvoice();
+ account = accountApi.getImmutableAccountDataById(accountId, internalCallContext);
+ } catch (final AccountApiException e) {
+ log.error("Unable to generate invoice for accountId='{}', a future notification has NOT been recorded", accountId, e);
+ return null;
+ }
- // Compute future notifications
- final FutureAccountNotifications futureAccountNotifications = createNextFutureNotificationDate(invoiceWithMetadata, context);
+ final InvoiceWithMetadata invoiceWithMetadata = generateKillBillInvoice(account, targetDate, billingEvents, existingInvoices, internalCallContext);
+ final DefaultInvoice invoice = invoiceWithMetadata.getInvoice();
- //
+ // Compute future notifications
+ final FutureAccountNotifications futureAccountNotifications = createNextFutureNotificationDate(invoiceWithMetadata, billingEvents, internalCallContext);
- // If invoice comes back null, there is nothing new to generate, we can bail early
- //
- if (invoice == null) {
- if (isDryRun) {
- log.info("Generated null dryRun invoice for accountId='{}', targetDate='{}'", accountId, targetDate);
- } else {
- log.info("Generated null invoice for accountId='{}', targetDate='{}'", accountId, targetDate);
+ // If invoice comes back null, there is nothing new to generate, we can bail early
+ if (invoice == null) {
+ if (isDryRun) {
+ log.info("Generated null dryRun invoice for accountId='{}', targetDate='{}'", accountId, targetDate);
+ } else {
+ log.info("Generated null invoice for accountId='{}', targetDate='{}'", accountId, targetDate);
- final BusInternalEvent event = new DefaultNullInvoiceEvent(accountId, clock.getUTCToday(),
- context.getAccountRecordId(), context.getTenantRecordId(), context.getUserToken());
+ final BusInternalEvent event = new DefaultNullInvoiceEvent(accountId, clock.getUTCToday(),
+ internalCallContext.getAccountRecordId(), internalCallContext.getTenantRecordId(), internalCallContext.getUserToken());
- commitInvoiceAndSetFutureNotifications(account, null, futureAccountNotifications, context);
- postEvent(event);
- }
- return null;
+ commitInvoiceAndSetFutureNotifications(account, null, futureAccountNotifications, internalCallContext);
+ postEvent(event);
}
+ return null;
+ }
+ boolean success = false;
+ try {
// Generate missing credit (> 0 for generation and < 0 for use) prior we call the plugin
- final InvoiceItem cbaItemPreInvoicePlugins = computeCBAOnExistingInvoice(invoice, context);
+ final InvoiceItem cbaItemPreInvoicePlugins = computeCBAOnExistingInvoice(invoice, internalCallContext);
DefaultInvoice tmpInvoiceForInvoicePlugins = invoice;
if (cbaItemPreInvoicePlugins != null) {
tmpInvoiceForInvoicePlugins = (DefaultInvoice) tmpInvoiceForInvoicePlugins.clone();
@@ -417,17 +496,31 @@ public class InvoiceDispatcher {
//
// Ask external invoice plugins if additional items (tax, etc) shall be added to the invoice
//
- final CallContext callContext = buildCallContext(context);
- final List<InvoiceItem> additionalInvoiceItemsFromPlugins = invoicePluginDispatcher.getAdditionalInvoiceItems(tmpInvoiceForInvoicePlugins, isDryRun, callContext);
+ final CallContext callContext = buildCallContext(internalCallContext);
+ final List<InvoiceItem> additionalInvoiceItemsFromPlugins = invoicePluginDispatcher.getAdditionalInvoiceItems(tmpInvoiceForInvoicePlugins, isDryRun, callContext, internalCallContext);
if (additionalInvoiceItemsFromPlugins.isEmpty()) {
// PERF: avoid re-computing the CBA if no change was made
if (cbaItemPreInvoicePlugins != null) {
invoice.addInvoiceItem(cbaItemPreInvoicePlugins);
}
} else {
- invoice.addInvoiceItems(additionalInvoiceItemsFromPlugins);
+
+ // Add or update items from generated invoice
+ for (final InvoiceItem cur : additionalInvoiceItemsFromPlugins) {
+ final InvoiceItem exitingItem = Iterables.tryFind(tmpInvoiceForInvoicePlugins.getInvoiceItems(), new Predicate<InvoiceItem>() {
+ @Override
+ public boolean apply(final InvoiceItem input) {
+ return input.getId().equals(cur.getId());
+ }
+ }).orNull();
+ if (exitingItem != null) {
+ invoice.removeInvoiceItem(exitingItem);
+ }
+ invoice.addInvoiceItem(cur);
+ }
+
// Use credit after we call the plugin (https://github.com/killbill/killbill/issues/637)
- final InvoiceItem cbaItemPostInvoicePlugins = computeCBAOnExistingInvoice(invoice, context);
+ final InvoiceItem cbaItemPostInvoicePlugins = computeCBAOnExistingInvoice(invoice, internalCallContext);
if (cbaItemPostInvoicePlugins != null) {
invoice.addInvoiceItem(cbaItemPostInvoicePlugins);
}
@@ -448,77 +541,116 @@ public class InvoiceDispatcher {
invoiceModelDao.addInvoiceItems(invoiceItemModelDaos);
// Commit invoice on disk
- final boolean isThereAnyItemsLeft = commitInvoiceAndSetFutureNotifications(account, invoiceModelDao, futureAccountNotifications, context);
-
- final boolean isRealInvoiceWithNonEmptyItems = isThereAnyItemsLeft ? isRealInvoiceWithItems : false;
+ commitInvoiceAndSetFutureNotifications(account, invoiceModelDao, futureAccountNotifications, internalCallContext);
+ success = true;
+
+ try {
+ setChargedThroughDates(invoice.getInvoiceItems(FixedPriceInvoiceItem.class), invoice.getInvoiceItems(RecurringInvoiceItem.class), internalCallContext);
+ } catch (final SubscriptionBaseApiException e) {
+ log.error("Failed handling SubscriptionBase change.", e);
+ return null;
+ }
+ }
+ } finally {
+ // Make sure we always set future notifications in case of errors
+ if (!isDryRun && !success) {
+ commitInvoiceAndSetFutureNotifications(account, null, futureAccountNotifications, internalCallContext);
+ }
+ }
- setChargedThroughDates(invoice.getInvoiceItems(FixedPriceInvoiceItem.class), invoice.getInvoiceItems(RecurringInvoiceItem.class), context);
+ return invoice;
+ }
- if (InvoiceStatus.COMMITTED.equals(invoice.getStatus())) {
- notifyAccountIfEnabled(account, invoice, isRealInvoiceWithNonEmptyItems, context);
+ private InvoiceWithMetadata generateKillBillInvoice(final ImmutableAccountData account, final LocalDate targetDate, final BillingEventSet billingEvents, final List<Invoice> existingInvoices, final InternalCallContext context) throws InvoiceApiException {
+ final UUID targetInvoiceId;
+ // Filter out DRAFT invoices for computation of existing items unless Account is in AUTO_INVOICING_REUSE_DRAFT
+ if (billingEvents.isAccountAutoInvoiceReuseDraft()) {
+ final Invoice existingDraft = Iterables.tryFind(existingInvoices, new Predicate<Invoice>() {
+ @Override
+ public boolean apply(final Invoice input) {
+ return input.getStatus() == InvoiceStatus.DRAFT;
}
-
- }
- return invoice;
- } catch (final AccountApiException e) {
- log.error("Failed handling SubscriptionBase change.", e);
- return null;
- } catch (final SubscriptionBaseApiException e) {
- log.error("Failed handling SubscriptionBase change.", e);
- return null;
+ }).orNull();
+ targetInvoiceId = existingDraft != null ? existingDraft.getId() : null;
+ } else {
+ targetInvoiceId = null;
}
+
+ return generator.generateInvoice(account, billingEvents, existingInvoices, targetInvoiceId, targetDate, account.getCurrency(), context);
}
- private FutureAccountNotifications createNextFutureNotificationDate(final InvoiceWithMetadata invoiceWithMetadata, final InternalCallContext context) {
- final Map<UUID, List<SubscriptionNotification>> result = new HashMap<UUID, List<SubscriptionNotification>>();
+ private FutureAccountNotifications createNextFutureNotificationDate(final InvoiceWithMetadata invoiceWithMetadata, final BillingEventSet billingEvents, final InternalCallContext context) {
- for (final UUID subscriptionId : invoiceWithMetadata.getPerSubscriptionFutureNotificationDates().keySet()) {
+ final Map<LocalDate, Set<UUID>> notificationListForTrigger = new HashMap<LocalDate, Set<UUID>>();
- final List<SubscriptionNotification> perSubscriptionNotifications = new ArrayList<SubscriptionNotification>();
+ for (final UUID subscriptionId : invoiceWithMetadata.getPerSubscriptionFutureNotificationDates().keySet()) {
final SubscriptionFutureNotificationDates subscriptionFutureNotificationDates = invoiceWithMetadata.getPerSubscriptionFutureNotificationDates().get(subscriptionId);
- // Add next recurring date if any
+
if (subscriptionFutureNotificationDates.getNextRecurringDate() != null) {
- perSubscriptionNotifications.add(new SubscriptionNotification(context.toUTCDateTime(subscriptionFutureNotificationDates.getNextRecurringDate()), true));
+ Set<UUID> subscriptionsForDates = notificationListForTrigger.get(subscriptionFutureNotificationDates.getNextRecurringDate());
+ if (subscriptionsForDates == null) {
+ subscriptionsForDates = new HashSet<UUID>();
+ notificationListForTrigger.put(subscriptionFutureNotificationDates.getNextRecurringDate(), subscriptionsForDates);
+ }
+ subscriptionsForDates.add(subscriptionId);
}
- // Add next usage dates if any
+
if (subscriptionFutureNotificationDates.getNextUsageDates() != null) {
for (final UsageDef usageDef : subscriptionFutureNotificationDates.getNextUsageDates().keySet()) {
+
final LocalDate nextNotificationDateForUsage = subscriptionFutureNotificationDates.getNextUsageDates().get(usageDef);
- final DateTime subscriptionUsageCallbackDate = nextNotificationDateForUsage != null ? context.toUTCDateTime(nextNotificationDateForUsage) : null;
- perSubscriptionNotifications.add(new SubscriptionNotification(subscriptionUsageCallbackDate, true));
+ Set<UUID> subscriptionsForDates = notificationListForTrigger.get(nextNotificationDateForUsage);
+ if (subscriptionsForDates == null) {
+ subscriptionsForDates = new HashSet<UUID>();
+ notificationListForTrigger.put(nextNotificationDateForUsage, subscriptionsForDates);
+ }
+ subscriptionsForDates.add(subscriptionId);
}
}
- if (!perSubscriptionNotifications.isEmpty()) {
- result.put(subscriptionId, perSubscriptionNotifications);
- }
}
- // If dryRunNotification is enabled we also need to fetch the upcoming PHASE dates (we add SubscriptionNotification with isForInvoiceNotificationTrigger = false)
- final boolean isInvoiceNotificationEnabled = invoiceConfig.getDryRunNotificationSchedule(context).getMillis() > 0;
+ final long dryRunNotificationTime = invoiceConfig.getDryRunNotificationSchedule(context).getMillis();
+ final boolean isInvoiceNotificationEnabled = dryRunNotificationTime > 0;
+
+ final Map<LocalDate, Set<UUID>> notificationListForDryRun = isInvoiceNotificationEnabled ? new HashMap<LocalDate, Set<UUID>>() : ImmutableMap.<LocalDate, Set<UUID>>of();
if (isInvoiceNotificationEnabled) {
- final Map<UUID, DateTime> upcomingPhasesForSubscriptions = subscriptionApi.getNextFutureEventForSubscriptions(SubscriptionBaseTransitionType.PHASE, context);
- for (final UUID cur : upcomingPhasesForSubscriptions.keySet()) {
- final DateTime curDate = upcomingPhasesForSubscriptions.get(cur);
- List<SubscriptionNotification> resultValue = result.get(cur);
- if (resultValue == null) {
- resultValue = new ArrayList<SubscriptionNotification>();
+ for (final LocalDate curDate : notificationListForTrigger.keySet()) {
+ final LocalDate curDryRunDate = context.toLocalDate(context.toUTCDateTime(curDate).minus(dryRunNotificationTime));
+ Set<UUID> subscriptionsForDryRunDates = notificationListForDryRun.get(curDryRunDate);
+ if (subscriptionsForDryRunDates == null) {
+ subscriptionsForDryRunDates = new HashSet<UUID>();
+ notificationListForDryRun.put(curDryRunDate, subscriptionsForDryRunDates);
+ }
+ subscriptionsForDryRunDates.addAll(notificationListForTrigger.get(curDate));
+ }
+
+ final Map<UUID, DateTime> upcomingPhasesForSubscriptions = isInvoiceNotificationEnabled ?
+ getNextTransitionsForSubscriptions(billingEvents) :
+ ImmutableMap.<UUID, DateTime>of();
+
+ for (UUID curId : upcomingPhasesForSubscriptions.keySet()) {
+ final LocalDate curDryRunDate = context.toLocalDate(upcomingPhasesForSubscriptions.get(curId).minus(dryRunNotificationTime));
+ Set<UUID> subscriptionsForDryRunDates = notificationListForDryRun.get(curDryRunDate);
+ if (subscriptionsForDryRunDates == null) {
+ subscriptionsForDryRunDates = new HashSet<UUID>();
+ notificationListForDryRun.put(curDryRunDate, subscriptionsForDryRunDates);
}
- resultValue.add(new SubscriptionNotification(curDate, false));
- result.put(cur, resultValue);
+ subscriptionsForDryRunDates.add(curId);
}
}
- return new FutureAccountNotifications(result);
+
+ return new FutureAccountNotifications(notificationListForTrigger, notificationListForDryRun);
}
private List<InvoiceItemModelDao> transformToInvoiceModelDao(final List<InvoiceItem> invoiceItems) {
return Lists.transform(invoiceItems,
- new Function<InvoiceItem, InvoiceItemModelDao>() {
- @Override
- public InvoiceItemModelDao apply(final InvoiceItem input) {
- return new InvoiceItemModelDao(input);
- }
- });
+ new Function<InvoiceItem, InvoiceItemModelDao>() {
+ @Override
+ public InvoiceItemModelDao apply(final InvoiceItem input) {
+ return new InvoiceItemModelDao(input);
+ }
+ });
}
private Set<UUID> getUniqueInvoiceIds(final Invoice invoice) {
@@ -548,29 +680,16 @@ public class InvoiceDispatcher {
log.info(tmp.toString());
}
- private boolean commitInvoiceAndSetFutureNotifications(final ImmutableAccountData account,
- @Nullable final InvoiceModelDao invoiceModelDao,
- final FutureAccountNotifications futureAccountNotifications,
- final InternalCallContext context) throws SubscriptionBaseApiException, InvoiceApiException {
+ private void commitInvoiceAndSetFutureNotifications(final ImmutableAccountData account,
+ @Nullable final InvoiceModelDao invoiceModelDao,
+ final FutureAccountNotifications futureAccountNotifications,
+ final InternalCallContext context) {
final boolean isThereAnyItemsLeft = invoiceModelDao != null && !invoiceModelDao.getInvoiceItems().isEmpty();
if (isThereAnyItemsLeft) {
invoiceDao.createInvoice(invoiceModelDao, futureAccountNotifications, context);
} else {
invoiceDao.setFutureAccountNotificationsForEmptyInvoice(account.getId(), futureAccountNotifications, context);
}
- return isThereAnyItemsLeft;
- }
-
- private void notifyAccountIfEnabled(final ImmutableAccountData account, final Invoice invoice, final boolean isRealInvoiceWithNonEmptyItems, final InternalCallContext context) throws InvoiceApiException, AccountApiException {
- // Ideally we would retrieve the cached version, all the invoice code has been modified to only use ImmutableAccountData, except for the
- // isNotifiedForInvoice piece that should probably live outside of invoice code anyways... (see https://github.com/killbill/killbill-email-notifications-plugin)
- final Account fullAccount = accountApi.getAccountById(account.getId(), context);
-
- if (fullAccount.isNotifiedForInvoices() && isRealInvoiceWithNonEmptyItems) {
- // Need to re-hydrate the invoice object to get the invoice number (record id)
- // API_FIX InvoiceNotifier public API?
- invoiceNotifier.notify(fullAccount, new DefaultInvoice(invoiceDao.getById(invoice.getId(), context)), buildTenantContext(context));
- }
}
private InvoiceItem computeCBAOnExistingInvoice(final Invoice invoice, final InternalCallContext context) throws InvoiceApiException {
@@ -640,40 +759,48 @@ public class InvoiceDispatcher {
public static class FutureAccountNotifications {
- private final Map<UUID, List<SubscriptionNotification>> notifications;
+ private final Map<LocalDate, Set<UUID>> notificationListForTrigger;
+ private final Map<LocalDate, Set<UUID>> notificationListForDryRun;
- public FutureAccountNotifications(final Map<UUID, List<SubscriptionNotification>> notifications) {
- this.notifications = notifications;
+ public FutureAccountNotifications() {
+ this(ImmutableMap.<LocalDate, Set<UUID>>of(), ImmutableMap.<LocalDate, Set<UUID>>of());
}
- public Map<UUID, List<SubscriptionNotification>> getNotifications() {
- return notifications;
+ public FutureAccountNotifications(final Map<LocalDate, Set<UUID>> notificationListForTrigger, final Map<LocalDate, Set<UUID>> notificationListForDryRun) {
+ this.notificationListForTrigger = notificationListForTrigger;
+ this.notificationListForDryRun = notificationListForDryRun;
}
- public static class SubscriptionNotification {
+ public Map<LocalDate, Set<UUID>> getNotificationsForTrigger() {
+ return notificationListForTrigger;
+ }
- private final DateTime effectiveDate;
- private final boolean isForNotificationTrigger;
+ public Map<LocalDate, Set<UUID>> getNotificationsForDryRun() {
+ return notificationListForDryRun;
+ }
+ }
- public SubscriptionNotification(final DateTime effectiveDate, final boolean isForNotificationTrigger) {
- this.effectiveDate = effectiveDate;
- this.isForNotificationTrigger = isForNotificationTrigger;
- }
+ private List<LocalDate> getUpcomingInvoiceCandidateDates(final Iterable<NotificationEventWithMetadata<NextBillingDateNotificationKey>> futureNotifications,
+ final Map<UUID, DateTime> nextScheduledSubscriptionsEventMap,
+ final Iterable<UUID> filteredSubscriptionIds,
+ final InternalCallContext internalCallContext) {
- public DateTime getEffectiveDate() {
- return effectiveDate;
- }
+ final Iterable<DateTime> nextScheduledInvoiceDates = getNextScheduledInvoiceEffectiveDate(futureNotifications, filteredSubscriptionIds);
- public boolean isForInvoiceNotificationTrigger() {
- return isForNotificationTrigger;
+ final Iterable<DateTime> nextScheduledSubscriptionsEvents;
+ if (!Iterables.isEmpty(filteredSubscriptionIds)) {
+ List<DateTime> tmp = new ArrayList<DateTime>();
+ for (final UUID curSubscriptionId : nextScheduledSubscriptionsEventMap.keySet()) {
+ if (Iterables.contains(filteredSubscriptionIds, curSubscriptionId)) {
+ tmp.add(nextScheduledSubscriptionsEventMap.get(curSubscriptionId));
+ }
}
+ nextScheduledSubscriptionsEvents = tmp;
+ } else {
+ nextScheduledSubscriptionsEvents = nextScheduledSubscriptionsEventMap.values();
}
- }
- private List<LocalDate> getUpcomingInvoiceCandidateDates(final Iterable<UUID> filteredSubscriptionIds, final InternalCallContext internalCallContext) {
- final Iterable<DateTime> nextScheduledInvoiceDates = getNextScheduledInvoiceEffectiveDate(filteredSubscriptionIds, internalCallContext);
- final Iterable<DateTime> nextScheduledSubscriptionsEventDates = subscriptionApi.getFutureNotificationsForAccount(internalCallContext);
- return Lists.<DateTime, LocalDate>transform(UPCOMING_NOTIFICATION_DATE_ORDERING.sortedCopy(Iterables.<DateTime>concat(nextScheduledInvoiceDates, nextScheduledSubscriptionsEventDates)),
+ return Lists.<DateTime, LocalDate>transform(UPCOMING_NOTIFICATION_DATE_ORDERING.sortedCopy(Iterables.<DateTime>concat(nextScheduledInvoiceDates, nextScheduledSubscriptionsEvents)),
new Function<DateTime, LocalDate>() {
@Override
public LocalDate apply(final DateTime input) {
@@ -682,28 +809,30 @@ public class InvoiceDispatcher {
});
}
- private Iterable<DateTime> getNextScheduledInvoiceEffectiveDate(final Iterable<UUID> filteredSubscriptionIds, final InternalCallContext internalCallContext) {
- try {
- final NotificationQueue notificationQueue = notificationQueueService.getNotificationQueue(DefaultInvoiceService.INVOICE_SERVICE_NAME,
- DefaultNextBillingDateNotifier.NEXT_BILLING_DATE_NOTIFIER_QUEUE);
- final Iterable<NotificationEventWithMetadata<NextBillingDateNotificationKey>> futureNotifications = notificationQueue.getFutureNotificationForSearchKeys(internalCallContext.getAccountRecordId(), internalCallContext.getTenantRecordId());
-
- final Collection<DateTime> effectiveDates = new LinkedList<DateTime>();
- for (final NotificationEventWithMetadata<NextBillingDateNotificationKey> input : futureNotifications) {
- final boolean isEventForSubscription = !filteredSubscriptionIds.iterator().hasNext() || Iterables.contains(filteredSubscriptionIds, input.getEvent().getUuidKey());
-
- final boolean isEventDryRunForNotifications = input.getEvent().isDryRunForInvoiceNotification() != null ?
- input.getEvent().isDryRunForInvoiceNotification() : false;
- if (isEventForSubscription && !isEventDryRunForNotifications) {
- effectiveDates.add(input.getEffectiveDate());
+ private Iterable<DateTime> getNextScheduledInvoiceEffectiveDate(final Iterable<NotificationEventWithMetadata<NextBillingDateNotificationKey>> futureNotifications,
+ final Iterable<UUID> filteredSubscriptionIds) {
+ final Collection<DateTime> effectiveDates = new LinkedList<DateTime>();
+ for (final NotificationEventWithMetadata<NextBillingDateNotificationKey> input : futureNotifications) {
+
+ // If we don't specify a filter list of subscriptionIds, we look at all events.
+ boolean isEventForSubscription = Iterables.isEmpty(filteredSubscriptionIds);
+ // If we specify a filter, we keep the date if at least one of the subscriptions from the event list matches one of the subscription from our filter list
+ if (!Iterables.isEmpty(filteredSubscriptionIds)) {
+ for (final UUID curSubscriptionId : filteredSubscriptionIds) {
+ if (Iterables.contains(input.getEvent().getUuidKeys(), curSubscriptionId)) {
+ isEventForSubscription = true;
+ break;
+ }
}
-
}
- return effectiveDates;
- } catch (final NoSuchNotificationQueue noSuchNotificationQueue) {
- throw new IllegalStateException(noSuchNotificationQueue);
+ final boolean isEventDryRunForNotifications = input.getEvent().isDryRunForInvoiceNotification() != null ?
+ input.getEvent().isDryRunForInvoiceNotification() : false;
+ if (isEventForSubscription && !isEventDryRunForNotifications) {
+ effectiveDates.add(input.getEffectiveDate());
+ }
}
+ return effectiveDates;
}
private static final class TargetDateDryRunArguments implements DryRunArguments {
@@ -826,14 +955,17 @@ public class InvoiceDispatcher {
private boolean shouldIgnoreChildInvoice(final Invoice childInvoice, final BigDecimal childInvoiceAmount) {
switch (childInvoiceAmount.compareTo(BigDecimal.ZERO)) {
- case -1 :
+ case -1:
// do nothing if child invoice has negative amount because it's a credit and it will be use in next invoice
return true;
- case 1 : return false;
- case 0 :
+ case 1:
+ return false;
+ case 0:
// only ignore if amount == 0 and any item is not FIXED or RECURRING
for (InvoiceItem item : childInvoice.getInvoiceItems()) {
- if (item.getInvoiceItemType().equals(InvoiceItemType.FIXED) || item.getInvoiceItemType().equals(InvoiceItemType.RECURRING)) return false;
+ if (item.getInvoiceItemType().equals(InvoiceItemType.FIXED) || item.getInvoiceItemType().equals(InvoiceItemType.RECURRING)) {
+ return false;
+ }
}
}
@@ -861,7 +993,7 @@ public class InvoiceDispatcher {
if (parentInvoiceModelDao == null) {
throw new InvoiceApiException(ErrorCode.INVOICE_MISSING_PARENT_INVOICE, childInvoiceModelDao.getId());
- } else if (InvoiceModelDaoHelper.getBalance(parentInvoiceModelDao).compareTo(BigDecimal.ZERO) == 0) {
+ } else if (InvoiceModelDaoHelper.getRawBalanceForRegularInvoice(parentInvoiceModelDao).compareTo(BigDecimal.ZERO) == 0) {
// ignore item adjustments for paid invoices.
return;
}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/InvoiceListener.java b/invoice/src/main/java/org/killbill/billing/invoice/InvoiceListener.java
index 155e941..bf7e879 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/InvoiceListener.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/InvoiceListener.java
@@ -21,6 +21,7 @@ package org.killbill.billing.invoice;
import java.util.UUID;
import org.joda.time.DateTime;
+import org.killbill.billing.ErrorCode;
import org.killbill.billing.account.api.Account;
import org.killbill.billing.account.api.AccountApiException;
import org.killbill.billing.account.api.AccountInternalApi;
@@ -30,13 +31,21 @@ import org.killbill.billing.events.EffectiveSubscriptionInternalEvent;
import org.killbill.billing.events.InvoiceCreationInternalEvent;
import org.killbill.billing.invoice.api.InvoiceApiException;
import org.killbill.billing.invoice.api.InvoiceInternalApi;
+import org.killbill.billing.invoice.api.InvoiceListenerService;
import org.killbill.billing.invoice.api.user.DefaultInvoiceAdjustmentEvent;
+import org.killbill.billing.platform.api.LifecycleHandlerType;
+import org.killbill.billing.platform.api.LifecycleHandlerType.LifecycleLevel;
import org.killbill.billing.subscription.api.SubscriptionBaseTransitionType;
import org.killbill.billing.util.callcontext.CallOrigin;
import org.killbill.billing.util.callcontext.InternalCallContextFactory;
import org.killbill.billing.util.callcontext.UserType;
-import org.killbill.billing.util.config.definition.InvoiceConfig;
import org.killbill.clock.Clock;
+import org.killbill.notificationq.api.NotificationQueueService;
+import org.killbill.notificationq.api.NotificationQueueService.NoSuchNotificationQueue;
+import org.killbill.queue.retry.RetryableService;
+import org.killbill.queue.retry.RetryableSubscriber;
+import org.killbill.queue.retry.RetryableSubscriber.SubscriberAction;
+import org.killbill.queue.retry.RetryableSubscriber.SubscriberQueueHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -44,78 +53,157 @@ import com.google.common.eventbus.AllowConcurrentEvents;
import com.google.common.eventbus.Subscribe;
import com.google.inject.Inject;
-public class InvoiceListener {
+@SuppressWarnings("TypeMayBeWeakened")
+public class InvoiceListener extends RetryableService implements InvoiceListenerService {
+
+ public static final String INVOICE_LISTENER_SERVICE_NAME = "invoice-listener-service";
private static final Logger log = LoggerFactory.getLogger(InvoiceListener.class);
private final InvoiceDispatcher dispatcher;
private final InternalCallContextFactory internalCallContextFactory;
- private final AccountInternalApi accountApi;
private final InvoiceInternalApi invoiceApi;
- private final InvoiceConfig invoiceConfig;
- private final Clock clock;
+ private final RetryableSubscriber retryableSubscriber;
+ private final SubscriberQueueHandler subscriberQueueHandler = new SubscriberQueueHandler();
@Inject
- public InvoiceListener(final AccountInternalApi accountApi, final Clock clock, final InternalCallContextFactory internalCallContextFactory,
- final InvoiceConfig invoiceConfig, final InvoiceDispatcher dispatcher, InvoiceInternalApi invoiceApi) {
- this.accountApi = accountApi;
+ public InvoiceListener(final AccountInternalApi accountApi,
+ final InternalCallContextFactory internalCallContextFactory,
+ final InvoiceDispatcher dispatcher,
+ final InvoiceInternalApi invoiceApi,
+ final NotificationQueueService notificationQueueService,
+ final Clock clock) {
+ super(notificationQueueService);
this.dispatcher = dispatcher;
- this.invoiceConfig = invoiceConfig;
this.internalCallContextFactory = internalCallContextFactory;
- this.clock = clock;
this.invoiceApi = invoiceApi;
+
+ subscriberQueueHandler.subscribe(EffectiveSubscriptionInternalEvent.class,
+ new SubscriberAction<EffectiveSubscriptionInternalEvent>() {
+ @Override
+ public void run(final EffectiveSubscriptionInternalEvent event) {
+ try {
+ // Skip future uncancel event
+ // Skip events which are marked as not being the last one
+ if (event.getTransitionType() == SubscriptionBaseTransitionType.UNCANCEL ||
+ event.getRemainingEventsForUserOperation() > 0) {
+ return;
+ }
+ final InternalCallContext context = internalCallContextFactory.createInternalCallContext(event.getSearchKey2(), event.getSearchKey1(), "SubscriptionBaseTransition", CallOrigin.INTERNAL, UserType.SYSTEM, event.getUserToken());
+ dispatcher.processSubscriptionForInvoiceGeneration(event, context);
+ } catch (final InvoiceApiException e) {
+ log.warn("Unable to process event {}", event, e);
+ }
+ }
+ });
+ subscriberQueueHandler.subscribe(BlockingTransitionInternalEvent.class,
+ new SubscriberAction<BlockingTransitionInternalEvent>() {
+ @Override
+ public void run(final BlockingTransitionInternalEvent event) {
+ // We are only interested in blockBilling or unblockBilling transitions.
+ if (!event.isTransitionedToUnblockedBilling() && !event.isTransitionedToBlockedBilling()) {
+ return;
+ }
+
+ try {
+ final InternalCallContext context = internalCallContextFactory.createInternalCallContext(event.getSearchKey2(), event.getSearchKey1(), "SubscriptionBaseTransition", CallOrigin.INTERNAL, UserType.SYSTEM, event.getUserToken());
+ final UUID accountId = accountApi.getByRecordId(event.getSearchKey1(), context);
+ dispatcher.processAccountFromNotificationOrBusEvent(accountId, null, null, context);
+ } catch (final InvoiceApiException e) {
+ log.warn("Unable to process event {}", event, e);
+ } catch (final AccountApiException e) {
+ log.warn("Unable to process event {}", event, e);
+ }
+ }
+ });
+ subscriberQueueHandler.subscribe(InvoiceCreationInternalEvent.class,
+ new SubscriberAction<InvoiceCreationInternalEvent>() {
+ @Override
+ public void run(final InvoiceCreationInternalEvent event) {
+ try {
+ final InternalCallContext context = internalCallContextFactory.createInternalCallContext(event.getSearchKey2(), event.getSearchKey1(), "CreateParentInvoice", CallOrigin.INTERNAL, UserType.SYSTEM, event.getUserToken());
+ final Account account = accountApi.getAccountById(event.getAccountId(), context);
+
+ // catch children invoices and populate the parent summary invoice
+ if (isChildrenAccountAndPaymentDelegated(account)) {
+ dispatcher.processParentInvoiceForInvoiceGeneration(account, event.getInvoiceId(), context);
+ }
+
+ } catch (final InvoiceApiException e) {
+ log.warn("Unable to process event {}", event, e);
+ } catch (final AccountApiException e) {
+ log.warn("Unable to process event {}", event, e);
+ }
+ }
+ });
+ subscriberQueueHandler.subscribe(DefaultInvoiceAdjustmentEvent.class,
+ new SubscriberAction<DefaultInvoiceAdjustmentEvent>() {
+ @Override
+ public void run(final DefaultInvoiceAdjustmentEvent event) {
+ try {
+ final InternalCallContext context = internalCallContextFactory.createInternalCallContext(event.getSearchKey2(), event.getSearchKey1(), "AdjustParentInvoice", CallOrigin.INTERNAL, UserType.SYSTEM, event.getUserToken());
+ final Account account = accountApi.getAccountById(event.getAccountId(), context);
+
+ // catch children invoices and populate the parent summary invoice
+ if (isChildrenAccountAndPaymentDelegated(account)) {
+ dispatcher.processParentInvoiceForAdjustments(account, event.getInvoiceId(), context);
+ }
+ } catch (final InvoiceApiException e) {
+ log.warn("Unable to process event {}", event, e);
+ } catch (final AccountApiException e) {
+ log.warn("Unable to process event {}", event, e);
+ }
+ }
+ });
+ this.retryableSubscriber = new RetryableSubscriber(clock, this, subscriberQueueHandler);
+ }
+
+ @Override
+ public String getName() {
+ return INVOICE_LISTENER_SERVICE_NAME;
+ }
+
+ @LifecycleHandlerType(LifecycleLevel.INIT_SERVICE)
+ public void initialize() {
+ super.initialize("invoice-listener", subscriberQueueHandler);
+ }
+
+ @LifecycleHandlerType(LifecycleLevel.START_SERVICE)
+ public void start() {
+ super.start();
+ }
+
+ @LifecycleHandlerType(LifecycleLevel.STOP_SERVICE)
+ public void stop() throws NoSuchNotificationQueue {
+ super.stop();
}
@AllowConcurrentEvents
@Subscribe
public void handleSubscriptionTransition(final EffectiveSubscriptionInternalEvent event) {
- try {
- // Skip future uncancel event
- // Skip events which are marked as not being the last one
- if (event.getTransitionType() == SubscriptionBaseTransitionType.UNCANCEL ||
- event.getRemainingEventsForUserOperation() > 0) {
- return;
- }
- final InternalCallContext context = internalCallContextFactory.createInternalCallContext(event.getSearchKey2(), event.getSearchKey1(), "SubscriptionBaseTransition", CallOrigin.INTERNAL, UserType.SYSTEM, event.getUserToken());
- dispatcher.processSubscriptionForInvoiceGeneration(event, context);
- } catch (InvoiceApiException e) {
- log.warn("Unable to process event {}", event, e);
- }
+ retryableSubscriber.handleEvent(event);
}
@AllowConcurrentEvents
@Subscribe
public void handleBlockingStateTransition(final BlockingTransitionInternalEvent event) {
- // We are only interested in blockBilling or unblockBilling transitions.
- if (!event.isTransitionedToUnblockedBilling() && !event.isTransitionedToBlockedBilling()) {
- return;
- }
-
- try {
- final InternalCallContext context = internalCallContextFactory.createInternalCallContext(event.getSearchKey2(), event.getSearchKey1(), "SubscriptionBaseTransition", CallOrigin.INTERNAL, UserType.SYSTEM, event.getUserToken());
- final UUID accountId = accountApi.getByRecordId(event.getSearchKey1(), context);
- dispatcher.processAccountFromNotificationOrBusEvent(accountId, null, null, context);
- } catch (InvoiceApiException e) {
- log.warn("Unable to process event {}", event, e);
- } catch (AccountApiException e) {
- log.warn("Unable to process event {}", event, e);
- }
+ retryableSubscriber.handleEvent(event);
}
public void handleNextBillingDateEvent(final UUID subscriptionId, final DateTime eventDateTime, final UUID userToken, final Long accountRecordId, final Long tenantRecordId) {
+ final InternalCallContext context = internalCallContextFactory.createInternalCallContext(tenantRecordId, accountRecordId, "Next Billing Date", CallOrigin.INTERNAL, UserType.SYSTEM, userToken);
try {
- final InternalCallContext context = internalCallContextFactory.createInternalCallContext(tenantRecordId, accountRecordId, "Next Billing Date", CallOrigin.INTERNAL, UserType.SYSTEM, userToken);
dispatcher.processSubscriptionForInvoiceGeneration(subscriptionId, context.toLocalDate(eventDateTime), context);
- } catch (InvoiceApiException e) {
+ } catch (final InvoiceApiException e) {
log.warn("Unable to process subscriptionId='{}', eventDateTime='{}'", subscriptionId, eventDateTime, e);
}
}
public void handleEventForInvoiceNotification(final UUID subscriptionId, final DateTime eventDateTime, final UUID userToken, final Long accountRecordId, final Long tenantRecordId) {
+ final InternalCallContext context = internalCallContextFactory.createInternalCallContext(tenantRecordId, accountRecordId, "Next Billing Date", CallOrigin.INTERNAL, UserType.SYSTEM, userToken);
try {
- final InternalCallContext context = internalCallContextFactory.createInternalCallContext(tenantRecordId, accountRecordId, "Next Billing Date", CallOrigin.INTERNAL, UserType.SYSTEM, userToken);
dispatcher.processSubscriptionForInvoiceNotification(subscriptionId, context.toLocalDate(eventDateTime), context);
- } catch (InvoiceApiException e) {
+ } catch (final InvoiceApiException e) {
log.warn("Unable to process subscriptionId='{}', eventDateTime='{}'", subscriptionId, eventDateTime, e);
}
}
@@ -123,21 +211,7 @@ public class InvoiceListener {
@AllowConcurrentEvents
@Subscribe
public void handleChildrenInvoiceCreationEvent(final InvoiceCreationInternalEvent event) {
-
- try {
- final InternalCallContext context = internalCallContextFactory.createInternalCallContext(event.getSearchKey2(), event.getSearchKey1(), "CreateParentInvoice", CallOrigin.INTERNAL, UserType.SYSTEM, event.getUserToken());
- final Account account = accountApi.getAccountById(event.getAccountId(), context);
-
- // catch children invoices and populate the parent summary invoice
- if (isChildrenAccountAndPaymentDelegated(account)) {
- dispatcher.processParentInvoiceForInvoiceGeneration(account, event.getInvoiceId(), context);
- }
-
- } catch (InvoiceApiException e) {
- log.error(e.getMessage());
- } catch (AccountApiException e) {
- log.error(e.getMessage());
- }
+ retryableSubscriber.handleEvent(event);
}
private boolean isChildrenAccountAndPaymentDelegated(final Account account) {
@@ -148,29 +222,17 @@ public class InvoiceListener {
try {
final InternalCallContext context = internalCallContextFactory.createInternalCallContext(tenantRecordId, accountRecordId, "Commit Invoice", CallOrigin.INTERNAL, UserType.SYSTEM, userToken);
invoiceApi.commitInvoice(invoiceId, context);
- } catch (InvoiceApiException e) {
- log.error(e.getMessage());
+ } catch (final InvoiceApiException e) {
+ // In case we commit parent invoice earlier we expect to see an INVOICE_INVALID_STATUS status
+ if (ErrorCode.INVOICE_INVALID_STATUS.getCode() != e.getCode()) {
+ log.error(e.getMessage());
+ }
}
}
@AllowConcurrentEvents
@Subscribe
public void handleChildrenInvoiceAdjustmentEvent(final DefaultInvoiceAdjustmentEvent event) {
-
- try {
- final InternalCallContext context = internalCallContextFactory.createInternalCallContext(event.getSearchKey2(), event.getSearchKey1(), "AdjustParentInvoice", CallOrigin.INTERNAL, UserType.SYSTEM, event.getUserToken());
- final Account account = accountApi.getAccountById(event.getAccountId(), context);
-
- // catch children invoices and populate the parent summary invoice
- if (isChildrenAccountAndPaymentDelegated(account)) {
- dispatcher.processParentInvoiceForAdjustments(account, event.getInvoiceId(), context);
- }
-
- } catch (InvoiceApiException e) {
- log.error(e.getMessage());
- } catch (AccountApiException e) {
- log.error(e.getMessage());
- }
+ retryableSubscriber.handleEvent(event);
}
-
}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/InvoicePluginDispatcher.java b/invoice/src/main/java/org/killbill/billing/invoice/InvoicePluginDispatcher.java
index 82c62f5..45b12f8 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/InvoicePluginDispatcher.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/InvoicePluginDispatcher.java
@@ -21,10 +21,12 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
+import java.util.Set;
import javax.inject.Inject;
import org.killbill.billing.ErrorCode;
+import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.invoice.api.Invoice;
import org.killbill.billing.invoice.api.InvoiceApiException;
import org.killbill.billing.invoice.api.InvoiceItem;
@@ -34,36 +36,42 @@ import org.killbill.billing.invoice.plugin.api.InvoicePluginApi;
import org.killbill.billing.osgi.api.OSGIServiceRegistration;
import org.killbill.billing.payment.api.PluginProperty;
import org.killbill.billing.util.callcontext.CallContext;
+import org.killbill.billing.util.config.definition.InvoiceConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
public class InvoicePluginDispatcher {
private static final Logger log = LoggerFactory.getLogger(InvoicePluginDispatcher.class);
- private static final Collection<InvoiceItemType> ALLOWED_INVOICE_ITEM_TYPES = ImmutableList.<InvoiceItemType>of(InvoiceItemType.EXTERNAL_CHARGE,
+ public static final Collection<InvoiceItemType> ALLOWED_INVOICE_ITEM_TYPES = ImmutableList.<InvoiceItemType>of(InvoiceItemType.EXTERNAL_CHARGE,
InvoiceItemType.ITEM_ADJ,
InvoiceItemType.CREDIT_ADJ,
InvoiceItemType.TAX);
private final OSGIServiceRegistration<InvoicePluginApi> pluginRegistry;
+ private final InvoiceConfig invoiceConfig;
+
@Inject
- public InvoicePluginDispatcher(final OSGIServiceRegistration<InvoicePluginApi> pluginRegistry) {
+ public InvoicePluginDispatcher(final OSGIServiceRegistration<InvoicePluginApi> pluginRegistry,
+ final InvoiceConfig invoiceConfig) {
this.pluginRegistry = pluginRegistry;
+ this.invoiceConfig = invoiceConfig;
}
//
// If we have multiple plugins there is a question of plugin ordering and also a 'product' questions to decide whether
// subsequent plugins should have access to items added by previous plugins
//
- public List<InvoiceItem> getAdditionalInvoiceItems(final Invoice originalInvoice, final boolean isDryRun, final CallContext callContext) throws InvoiceApiException {
+ public List<InvoiceItem> getAdditionalInvoiceItems(final Invoice originalInvoice, final boolean isDryRun, final CallContext callContext, final InternalTenantContext tenantContext) throws InvoiceApiException {
// We clone the original invoice so plugins don't remove/add items
final Invoice clonedInvoice = (Invoice) ((DefaultInvoice) originalInvoice).clone();
final List<InvoiceItem> additionalInvoiceItems = new LinkedList<InvoiceItem>();
- final List<InvoicePluginApi> invoicePlugins = getInvoicePlugins();
+ final List<InvoicePluginApi> invoicePlugins = getInvoicePlugins(tenantContext);
for (final InvoicePluginApi invoicePlugin : invoicePlugins) {
final List<InvoiceItem> items = invoicePlugin.getAdditionalInvoiceItems(clonedInvoice, isDryRun, ImmutableList.<PluginProperty>of(), callContext);
if (items != null) {
@@ -83,11 +91,34 @@ public class InvoicePluginDispatcher {
}
}
- private List<InvoicePluginApi> getInvoicePlugins() {
+ private List<InvoicePluginApi> getInvoicePlugins(final InternalTenantContext tenantContext) {
+
+
+ final Collection<String> resultingPluginList = getResultingPluginNameList(tenantContext);
+
final List<InvoicePluginApi> invoicePlugins = new ArrayList<InvoicePluginApi>();
- for (final String name : pluginRegistry.getAllServices()) {
- invoicePlugins.add(pluginRegistry.getServiceForName(name));
+ for (final String name : resultingPluginList) {
+ final InvoicePluginApi serviceForName = pluginRegistry.getServiceForName(name);
+ invoicePlugins.add(serviceForName);
}
return invoicePlugins;
}
+
+ @VisibleForTesting
+ final Collection<String> getResultingPluginNameList(final InternalTenantContext tenantContext) {
+ final List<String> configuredPlugins = invoiceConfig.getInvoicePluginNames(tenantContext);
+ final Set<String> registeredPlugins = pluginRegistry.getAllServices();
+ // No configuration, we return undeterministic list of registered plugins
+ if (configuredPlugins == null || configuredPlugins.isEmpty()) {
+ return registeredPlugins;
+ } else {
+ final List<String> result = new ArrayList<String>(configuredPlugins.size());
+ for (final String name : configuredPlugins) {
+ if (pluginRegistry.getServiceForName(name) != null) {
+ result.add(name);
+ }
+ }
+ return result;
+ }
+ }
}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/InvoiceTagHandler.java b/invoice/src/main/java/org/killbill/billing/invoice/InvoiceTagHandler.java
index ab9b982..afb7750 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/InvoiceTagHandler.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/InvoiceTagHandler.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2016 Groupon, Inc
- * Copyright 2014-2016 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
@@ -24,10 +24,20 @@ import org.killbill.billing.ObjectType;
import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.events.ControlTagDeletionInternalEvent;
import org.killbill.billing.invoice.api.InvoiceApiException;
+import org.killbill.billing.platform.api.KillbillService;
+import org.killbill.billing.platform.api.LifecycleHandlerType;
+import org.killbill.billing.platform.api.LifecycleHandlerType.LifecycleLevel;
import org.killbill.billing.util.callcontext.CallOrigin;
import org.killbill.billing.util.callcontext.InternalCallContextFactory;
import org.killbill.billing.util.callcontext.UserType;
import org.killbill.billing.util.tag.ControlTagType;
+import org.killbill.clock.Clock;
+import org.killbill.notificationq.api.NotificationQueueService;
+import org.killbill.notificationq.api.NotificationQueueService.NoSuchNotificationQueue;
+import org.killbill.queue.retry.RetryableService;
+import org.killbill.queue.retry.RetryableSubscriber;
+import org.killbill.queue.retry.RetryableSubscriber.SubscriberAction;
+import org.killbill.queue.retry.RetryableSubscriber.SubscriberQueueHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -35,28 +45,64 @@ import com.google.common.eventbus.AllowConcurrentEvents;
import com.google.common.eventbus.Subscribe;
import com.google.inject.Inject;
-public class InvoiceTagHandler {
+@SuppressWarnings("TypeMayBeWeakened")
+public class InvoiceTagHandler extends RetryableService implements KillbillService {
+
+ private static final String INVOICE_TAG_HANDLER_SERVICE_NAME = "invoice-tag-handler-service";
private static final Logger log = LoggerFactory.getLogger(InvoiceTagHandler.class);
private final InvoiceDispatcher dispatcher;
- private final InternalCallContextFactory internalCallContextFactory;
+ private final RetryableSubscriber retryableSubscriber;
+
+ private final SubscriberQueueHandler subscriberQueueHandler = new SubscriberQueueHandler();
@Inject
- public InvoiceTagHandler(final InvoiceDispatcher dispatcher,
+ public InvoiceTagHandler(final Clock clock,
+ final InvoiceDispatcher dispatcher,
+ final NotificationQueueService notificationQueueService,
final InternalCallContextFactory internalCallContextFactory) {
+ super(notificationQueueService);
this.dispatcher = dispatcher;
- this.internalCallContextFactory = internalCallContextFactory;
+
+ final SubscriberAction<ControlTagDeletionInternalEvent> action = new SubscriberAction<ControlTagDeletionInternalEvent>() {
+ @Override
+ public void run(final ControlTagDeletionInternalEvent event) {
+ if (event.getTagDefinition().getName().equals(ControlTagType.AUTO_INVOICING_OFF.toString()) && event.getObjectType() == ObjectType.ACCOUNT) {
+ final UUID accountId = event.getObjectId();
+ final InternalCallContext context = internalCallContextFactory.createInternalCallContext(event.getSearchKey2(), event.getSearchKey1(), "InvoiceTagHandler", CallOrigin.INTERNAL, UserType.SYSTEM, event.getUserToken());
+ processUnpaid_AUTO_INVOICING_OFF_invoices(accountId, context);
+ }
+ }
+ };
+ subscriberQueueHandler.subscribe(ControlTagDeletionInternalEvent.class, action);
+ this.retryableSubscriber = new RetryableSubscriber(clock, this, subscriberQueueHandler);
+ }
+
+ @Override
+ public String getName() {
+ return INVOICE_TAG_HANDLER_SERVICE_NAME;
+ }
+
+ @LifecycleHandlerType(LifecycleLevel.INIT_SERVICE)
+ public void initialize() {
+ super.initialize("invoice-tag-handler", subscriberQueueHandler);
}
@AllowConcurrentEvents
@Subscribe
public void process_AUTO_INVOICING_OFF_removal(final ControlTagDeletionInternalEvent event) {
- if (event.getTagDefinition().getName().equals(ControlTagType.AUTO_INVOICING_OFF.toString()) && event.getObjectType() == ObjectType.ACCOUNT) {
- final UUID accountId = event.getObjectId();
- final InternalCallContext context = internalCallContextFactory.createInternalCallContext(event.getSearchKey2(), event.getSearchKey1(), "InvoiceTagHandler", CallOrigin.INTERNAL, UserType.SYSTEM, event.getUserToken());
- processUnpaid_AUTO_INVOICING_OFF_invoices(accountId, context);
- }
+ retryableSubscriber.handleEvent(event);
+ }
+
+ @LifecycleHandlerType(LifecycleLevel.START_SERVICE)
+ public void start() {
+ super.start();
+ }
+
+ @LifecycleHandlerType(LifecycleLevel.STOP_SERVICE)
+ public void stop() throws NoSuchNotificationQueue {
+ super.stop();
}
private void processUnpaid_AUTO_INVOICING_OFF_invoices(final UUID accountId, final InternalCallContext context) {
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/model/DefaultInvoice.java b/invoice/src/main/java/org/killbill/billing/invoice/model/DefaultInvoice.java
index c041450..bc4ccb0 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/model/DefaultInvoice.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/model/DefaultInvoice.java
@@ -39,6 +39,7 @@ import org.killbill.billing.invoice.dao.InvoiceModelDao;
import org.killbill.billing.invoice.dao.InvoicePaymentModelDao;
import org.killbill.billing.util.UUIDs;
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.collect.Collections2;
@@ -60,11 +61,8 @@ public class DefaultInvoice extends EntityBase implements Invoice, Cloneable {
private final Invoice parentInvoice;
- // Used to create a new invoice
- public DefaultInvoice(final UUID accountId, final LocalDate invoiceDate, final LocalDate targetDate, final Currency currency) {
- this(UUIDs.randomUUID(), accountId, null, invoiceDate, targetDate, currency, false, InvoiceStatus.COMMITTED);
- }
+ // Used to create a new invoice
public DefaultInvoice(final UUID accountId, final LocalDate invoiceDate, final LocalDate targetDate, final Currency currency, final InvoiceStatus status) {
this(UUIDs.randomUUID(), accountId, null, invoiceDate, targetDate, currency, false, status);
}
@@ -74,6 +72,11 @@ public class DefaultInvoice extends EntityBase implements Invoice, Cloneable {
this(invoiceId, null, accountId, invoiceNumber, invoiceDate, targetDate, currency, currency, isMigrationInvoice, false, status, false, null);
}
+ @VisibleForTesting
+ public DefaultInvoice(final UUID accountId, final LocalDate invoiceDate, final LocalDate targetDate, final Currency currency) {
+ this(UUIDs.randomUUID(), accountId, null, invoiceDate, targetDate, currency, false, InvoiceStatus.COMMITTED);
+ }
+
// This CTOR is used to return an existing invoice and must include everything (items, payments, tags,..)
public DefaultInvoice(final InvoiceModelDao invoiceModelDao, @Nullable final Catalog catalog) {
this(invoiceModelDao.getId(), invoiceModelDao.getCreatedDate(), invoiceModelDao.getAccountId(),
@@ -139,6 +142,10 @@ public class DefaultInvoice extends EntityBase implements Invoice, Cloneable {
return invoiceItems.add(item);
}
+ public boolean removeInvoiceItem(final InvoiceItem item) {
+ return invoiceItems.remove(item);
+ }
+
@Override
public boolean addInvoiceItems(final Collection<InvoiceItem> items) {
return this.invoiceItems.addAll(items);
@@ -251,12 +258,17 @@ public class DefaultInvoice extends EntityBase implements Invoice, Cloneable {
@Override
public BigDecimal getBalance() {
- return getStatus().equals(InvoiceStatus.DRAFT) || hasZeroParentBalance() ?
- BigDecimal.ZERO :
- InvoiceCalculatorUtils.computeInvoiceBalance(currency, invoiceItems, payments, isWrittenOff() || isMigrationInvoice());
+ if (isWrittenOff() ||
+ isMigrationInvoice() ||
+ getStatus() == InvoiceStatus.DRAFT ||
+ hasZeroParentBalance()) {
+ return BigDecimal.ZERO;
+ } else {
+ return InvoiceCalculatorUtils.computeRawInvoiceBalance(currency, invoiceItems, payments);
+ }
}
- private boolean hasZeroParentBalance() {
+ public boolean hasZeroParentBalance() {
return (parentInvoice != null) && (parentInvoice.getBalance().compareTo(BigDecimal.ZERO) == 0);
}
@@ -275,6 +287,16 @@ public class DefaultInvoice extends EntityBase implements Invoice, Cloneable {
}
@Override
+ public UUID getParentAccountId() {
+ return parentInvoice != null ? parentInvoice.getAccountId() : null;
+ }
+
+ @Override
+ public UUID getParentInvoiceId() {
+ return parentInvoice != null ? parentInvoice.getId() : null;
+ }
+
+ @Override
public String toString() {
return "DefaultInvoice [items=" + invoiceItems + ", payments=" + payments + ", id=" + id + ", accountId=" + accountId
+ ", invoiceDate=" + invoiceDate + ", targetDate=" + targetDate + ", currency=" + currency + ", amountPaid=" + getPaidAmount()
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/model/ExternalChargeInvoiceItem.java b/invoice/src/main/java/org/killbill/billing/invoice/model/ExternalChargeInvoiceItem.java
index c993dd5..e2dc4a5 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/model/ExternalChargeInvoiceItem.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/model/ExternalChargeInvoiceItem.java
@@ -27,21 +27,27 @@ import org.killbill.billing.catalog.api.Currency;
import org.killbill.billing.invoice.api.InvoiceItemType;
import org.killbill.billing.util.UUIDs;
-public class ExternalChargeInvoiceItem extends InvoiceItemBase {
+public class ExternalChargeInvoiceItem extends InvoiceItemCatalogBase {
public ExternalChargeInvoiceItem(final UUID invoiceId, final UUID accountId, @Nullable final UUID bundleId, @Nullable final String description,
- final LocalDate effectiveDate, final BigDecimal amount, final Currency currency) {
- this(UUIDs.randomUUID(), invoiceId, accountId, bundleId, description, effectiveDate, amount, currency);
+ final LocalDate startDate, final LocalDate endDate, final BigDecimal amount, final Currency currency) {
+ this(UUIDs.randomUUID(), invoiceId, accountId, bundleId, description, startDate, endDate, amount, currency);
}
public ExternalChargeInvoiceItem(final UUID id, final UUID invoiceId, final UUID accountId, @Nullable final UUID bundleId,
- @Nullable final String description, final LocalDate effectiveDate, final BigDecimal amount, final Currency currency) {
- this(id, null, invoiceId, accountId, bundleId, description, effectiveDate, amount, currency);
+ @Nullable final String description, final LocalDate startDate, final LocalDate endDate, final BigDecimal amount, final Currency currency) {
+ this(id, null, invoiceId, accountId, bundleId, description, startDate, endDate, amount, currency);
}
public ExternalChargeInvoiceItem(final UUID id, @Nullable final DateTime createdDate, final UUID invoiceId, final UUID accountId, @Nullable final UUID bundleId,
- @Nullable final String description, final LocalDate effectiveDate, final BigDecimal amount, final Currency currency) {
- super(id, createdDate, invoiceId, accountId, bundleId, null, description, effectiveDate, null, amount, null, currency, null);
+ @Nullable final String description, final LocalDate startDate, final LocalDate endDate, final BigDecimal amount, final Currency currency) {
+ super(id, createdDate, invoiceId, accountId, bundleId, null, description, null, null, null, startDate, endDate, amount, null, currency, null);
+ }
+
+ public ExternalChargeInvoiceItem(final UUID id, @Nullable final DateTime createdDate, final UUID invoiceId, final UUID accountId, final UUID bundleId, final UUID subscriptionId,
+ final String planName, final String phaseName, final String prettyPlanName, final String prettyPhaseName, @Nullable final String description, final LocalDate startDate, final LocalDate endDate,
+ final BigDecimal amount, final BigDecimal rate, final Currency currency, @Nullable final UUID linkedItemId) {
+ super(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, description, planName, phaseName, null, prettyPlanName, prettyPhaseName, null, startDate, endDate, amount, rate, currency, linkedItemId);
}
@Override
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/model/InvoiceItemBase.java b/invoice/src/main/java/org/killbill/billing/invoice/model/InvoiceItemBase.java
index b9eda76..0e6ea00 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/model/InvoiceItemBase.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/model/InvoiceItemBase.java
@@ -206,6 +206,9 @@ public abstract class InvoiceItemBase extends EntityBase implements InvoiceItem
final InvoiceItemBase that = (InvoiceItemBase) o;
+ if (getInvoiceItemType() != null ? !getInvoiceItemType().equals(that.getInvoiceItemType()) : that.getInvoiceItemType() != null) {
+ return false;
+ }
if (accountId != null ? !accountId.equals(that.accountId) : that.accountId != null) {
return false;
}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/model/InvoiceItemFactory.java b/invoice/src/main/java/org/killbill/billing/invoice/model/InvoiceItemFactory.java
index fa93938..e8eef20 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/model/InvoiceItemFactory.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/model/InvoiceItemFactory.java
@@ -80,7 +80,7 @@ public class InvoiceItemFactory {
final InvoiceItem item;
switch (type) {
case EXTERNAL_CHARGE:
- item = new ExternalChargeInvoiceItem(id, createdDate, invoiceId, accountId, bundleId, description, startDate, amount, currency);
+ item = new ExternalChargeInvoiceItem(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, prettyPlanName, prettyPlanPhaseName, description, startDate, endDate, amount, rate, currency, linkedItemId);
break;
case FIXED:
item = new FixedPriceInvoiceItem(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, prettyPlanName, prettyPlanPhaseName, description, startDate, amount, currency);
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/notification/DefaultNextBillingDateNotifier.java b/invoice/src/main/java/org/killbill/billing/invoice/notification/DefaultNextBillingDateNotifier.java
index 644cec3..47dc673 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/notification/DefaultNextBillingDateNotifier.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/notification/DefaultNextBillingDateNotifier.java
@@ -1,7 +1,9 @@
/*
* Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
*
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
@@ -25,45 +27,50 @@ import org.killbill.billing.subscription.api.SubscriptionBase;
import org.killbill.billing.subscription.api.SubscriptionBaseInternalApi;
import org.killbill.billing.subscription.api.user.SubscriptionBaseApiException;
import org.killbill.billing.util.callcontext.InternalCallContextFactory;
-import org.killbill.billing.util.config.definition.InvoiceConfig;
+import org.killbill.clock.Clock;
import org.killbill.notificationq.api.NotificationEvent;
import org.killbill.notificationq.api.NotificationQueue;
import org.killbill.notificationq.api.NotificationQueueService;
import org.killbill.notificationq.api.NotificationQueueService.NoSuchNotificationQueue;
import org.killbill.notificationq.api.NotificationQueueService.NotificationQueueAlreadyExists;
import org.killbill.notificationq.api.NotificationQueueService.NotificationQueueHandler;
+import org.killbill.queue.retry.RetryableHandler;
+import org.killbill.queue.retry.RetryableService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.inject.Inject;
-public class DefaultNextBillingDateNotifier implements NextBillingDateNotifier {
-
- private static final Logger log = LoggerFactory.getLogger(DefaultNextBillingDateNotifier.class);
+public class DefaultNextBillingDateNotifier extends RetryableService implements NextBillingDateNotifier {
public static final String NEXT_BILLING_DATE_NOTIFIER_QUEUE = "next-billing-date-queue";
+ private static final Logger log = LoggerFactory.getLogger(DefaultNextBillingDateNotifier.class);
+
+ private final Clock clock;
private final NotificationQueueService notificationQueueService;
private final SubscriptionBaseInternalApi subscriptionApi;
private final InvoiceListener listener;
- private final InternalCallContextFactory callContextFactory;
+ private final InternalCallContextFactory internalCallContextFactory;
private NotificationQueue nextBillingQueue;
@Inject
- public DefaultNextBillingDateNotifier(final NotificationQueueService notificationQueueService,
+ public DefaultNextBillingDateNotifier(final Clock clock,
+ final NotificationQueueService notificationQueueService,
final SubscriptionBaseInternalApi subscriptionApi,
final InvoiceListener listener,
- final InternalCallContextFactory callContextFactory) {
+ final InternalCallContextFactory internalCallContextFactory) {
+ super(notificationQueueService);
+ this.clock = clock;
this.notificationQueueService = notificationQueueService;
this.subscriptionApi = subscriptionApi;
this.listener = listener;
- this.callContextFactory = callContextFactory;
+ this.internalCallContextFactory = internalCallContextFactory;
}
@Override
public void initialize() throws NotificationQueueAlreadyExists {
-
final NotificationQueueHandler notificationQueueHandler = new NotificationQueueHandler() {
@Override
public void handleReadyNotification(final NotificationEvent notificationKey, final DateTime eventDate, final UUID userToken, final Long accountRecordId, final Long tenantRecordId) {
@@ -76,31 +83,37 @@ public class DefaultNextBillingDateNotifier implements NextBillingDateNotifier {
// Just to ensure compatibility with json that might not have that targetDate field (old versions < 0.13.6)
final DateTime targetDate = key.getTargetDate() != null ? key.getTargetDate() : eventDate;
+ final UUID firstSubscriptionId = key.getUuidKeys().iterator().next();
try {
- final SubscriptionBase subscription = subscriptionApi.getSubscriptionFromId(key.getUuidKey(), callContextFactory.createInternalTenantContext(tenantRecordId, accountRecordId));
+ final SubscriptionBase subscription = subscriptionApi.getSubscriptionFromId(firstSubscriptionId, internalCallContextFactory.createInternalTenantContext(tenantRecordId, accountRecordId));
if (subscription == null) {
- log.warn("Unable to retrieve subscriptionId='{}' for event {}", key.getUuidKey(), key);
+ log.warn("Unable to retrieve subscriptionId='{}' for event {}", firstSubscriptionId, key);
return;
}
if (key.isDryRunForInvoiceNotification() != null && // Just to ensure compatibility with json that might not have that field (old versions < 0.13.6)
key.isDryRunForInvoiceNotification()) {
- processEventForInvoiceNotification(key.getUuidKey(), targetDate, userToken, accountRecordId, tenantRecordId);
+ processEventForInvoiceNotification(firstSubscriptionId, targetDate, userToken, accountRecordId, tenantRecordId);
} else {
- processEventForInvoiceGeneration(key.getUuidKey(), targetDate, userToken, accountRecordId, tenantRecordId);
+ processEventForInvoiceGeneration(firstSubscriptionId, targetDate, userToken, accountRecordId, tenantRecordId);
}
} catch (SubscriptionBaseApiException e) {
- log.warn("Error retrieving subscriptionId='{}'", key.getUuidKey(), e);
+ log.warn("Error retrieving subscriptionId='{}'", firstSubscriptionId, e);
}
}
};
+ final NotificationQueueHandler retryableHandler = new RetryableHandler(clock, this, notificationQueueHandler);
nextBillingQueue = notificationQueueService.createNotificationQueue(DefaultInvoiceService.INVOICE_SERVICE_NAME,
NEXT_BILLING_DATE_NOTIFIER_QUEUE,
- notificationQueueHandler);
+ retryableHandler);
+
+ super.initialize(nextBillingQueue, notificationQueueHandler);
}
@Override
public void start() {
+ super.start();
+
nextBillingQueue.startQueue();
}
@@ -110,6 +123,8 @@ public class DefaultNextBillingDateNotifier implements NextBillingDateNotifier {
nextBillingQueue.stopQueue();
notificationQueueService.deleteNotificationQueue(nextBillingQueue.getServiceName(), nextBillingQueue.getQueueName());
}
+
+ super.stop();
}
private void processEventForInvoiceGeneration(final UUID subscriptionId, final DateTime eventDateTime, final UUID userToken, final Long accountRecordId, final Long tenantRecordId) {
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/notification/DefaultNextBillingDatePoster.java b/invoice/src/main/java/org/killbill/billing/invoice/notification/DefaultNextBillingDatePoster.java
index 6dac86e..fb269c5 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/notification/DefaultNextBillingDatePoster.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/notification/DefaultNextBillingDatePoster.java
@@ -33,12 +33,16 @@ import org.killbill.notificationq.api.NotificationQueueService.NoSuchNotificatio
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableList;
import com.google.inject.Inject;
public class DefaultNextBillingDatePoster implements NextBillingDatePoster {
private static final Logger log = LoggerFactory.getLogger(DefaultNextBillingDatePoster.class);
+ private static Joiner JOINER = Joiner.on(",");
+
private final NotificationQueueService notificationQueueService;
@Inject
@@ -47,20 +51,29 @@ public class DefaultNextBillingDatePoster implements NextBillingDatePoster {
}
@Override
- public void insertNextBillingNotificationFromTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory, final UUID accountId,
- final UUID subscriptionId, final DateTime futureNotificationTime, final InternalCallContext internalCallContext) {
- insertNextBillingFromTransactionInternal(entitySqlDaoWrapperFactory, subscriptionId, Boolean.FALSE, futureNotificationTime, futureNotificationTime, internalCallContext);
+ public void insertNextBillingNotificationFromTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory,
+ final UUID accountId,
+ final Iterable<UUID> subscriptionIds,
+ final DateTime futureNotificationTime,
+ final InternalCallContext internalCallContext) {
+ insertNextBillingFromTransactionInternal(entitySqlDaoWrapperFactory, subscriptionIds, Boolean.FALSE, futureNotificationTime, futureNotificationTime, internalCallContext);
}
@Override
- public void insertNextBillingDryRunNotificationFromTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory, final UUID accountId,
- final UUID subscriptionId, final DateTime futureNotificationTime, final DateTime targetDate, final InternalCallContext internalCallContext) {
- insertNextBillingFromTransactionInternal(entitySqlDaoWrapperFactory, subscriptionId, Boolean.TRUE, futureNotificationTime, targetDate, internalCallContext);
+ public void insertNextBillingDryRunNotificationFromTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory,
+ final UUID accountId,
+ final Iterable<UUID> subscriptionIds,
+ final DateTime futureNotificationTime,
+ final DateTime targetDate,
+ final InternalCallContext internalCallContext) {
+ insertNextBillingFromTransactionInternal(entitySqlDaoWrapperFactory, subscriptionIds, Boolean.TRUE, futureNotificationTime, targetDate, internalCallContext);
}
private void insertNextBillingFromTransactionInternal(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory,
- final UUID subscriptionId, final Boolean isDryRunForInvoiceNotification,
- final DateTime futureNotificationTime, final DateTime targetDate,
+ final Iterable<UUID> subscriptionIds,
+ final Boolean isDryRunForInvoiceNotification,
+ final DateTime futureNotificationTime,
+ final DateTime targetDate,
final InternalCallContext internalCallContext) {
final NotificationQueue nextBillingQueue;
try {
@@ -70,7 +83,7 @@ public class DefaultNextBillingDatePoster implements NextBillingDatePoster {
// If we see existing notification for the same date (and isDryRunForInvoiceNotification mode), we don't insert a new notification
final Iterable<NotificationEventWithMetadata<NextBillingDateNotificationKey>> futureNotifications = nextBillingQueue.getFutureNotificationFromTransactionForSearchKeys(internalCallContext.getAccountRecordId(), internalCallContext.getTenantRecordId(), entitySqlDaoWrapperFactory.getHandle().getConnection());
- boolean existingFutureNotificationWithSameDate = false;
+ NotificationEventWithMetadata<NextBillingDateNotificationKey> existingNotificationForEffectiveDate = null;
for (final NotificationEventWithMetadata<NextBillingDateNotificationKey> input : futureNotifications) {
final boolean isEventDryRunForNotifications = input.getEvent().isDryRunForInvoiceNotification() != null ?
input.getEvent().isDryRunForInvoiceNotification() : false;
@@ -81,25 +94,28 @@ public class DefaultNextBillingDatePoster implements NextBillingDatePoster {
if (notificationEffectiveLocaleDate.compareTo(eventEffectiveLocaleDate) == 0 &&
((isDryRunForInvoiceNotification && isEventDryRunForNotifications) ||
(!isDryRunForInvoiceNotification && !isEventDryRunForNotifications))) {
- existingFutureNotificationWithSameDate = true;
+ existingNotificationForEffectiveDate = input;
}
// Go through all results to close the connection
}
- if (!existingFutureNotificationWithSameDate) {
- log.info("Queuing next billing date notification at {} for subscriptionId {}", futureNotificationTime.toString(), subscriptionId.toString());
+ if (existingNotificationForEffectiveDate == null) {
+ log.info("Queuing next billing date notification at {} for subscriptionId {}", futureNotificationTime.toString(), JOINER.join(subscriptionIds));
+ final NextBillingDateNotificationKey newNotificationEvent = new NextBillingDateNotificationKey(null, subscriptionIds, targetDate, isDryRunForInvoiceNotification);
nextBillingQueue.recordFutureNotificationFromTransaction(entitySqlDaoWrapperFactory.getHandle().getConnection(), futureNotificationTime,
- new NextBillingDateNotificationKey(subscriptionId, targetDate, isDryRunForInvoiceNotification), internalCallContext.getUserToken(),
+ newNotificationEvent, internalCallContext.getUserToken(),
internalCallContext.getAccountRecordId(), internalCallContext.getTenantRecordId());
- } else if (log.isDebugEnabled()) {
- log.debug("********************* SKIPPING Queuing next billing date notification at {} for subscriptionId {} *******************", futureNotificationTime.toString(), subscriptionId.toString());
+ } else {
+ log.info("Updating next billing date notification event at {} for subscriptionId {}", futureNotificationTime.toString(), JOINER.join(subscriptionIds));
+ final NextBillingDateNotificationKey updateNotificationEvent = new NextBillingDateNotificationKey(existingNotificationForEffectiveDate.getEvent(), subscriptionIds);
+ nextBillingQueue.updateFutureNotificationFromTransaction(entitySqlDaoWrapperFactory.getHandle().getConnection(), existingNotificationForEffectiveDate.getRecordId(), updateNotificationEvent, internalCallContext.getAccountRecordId(), internalCallContext.getTenantRecordId());
}
} catch (final NoSuchNotificationQueue e) {
log.error("Attempting to put items on a non-existent queue (NextBillingDateNotifier).", e);
} catch (final IOException e) {
- log.error("Failed to serialize notificationKey for subscriptionId {}", subscriptionId);
+ log.error("Failed to serialize notificationKey for subscriptionId {}", subscriptionIds);
}
}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/notification/NextBillingDateNotificationKey.java b/invoice/src/main/java/org/killbill/billing/invoice/notification/NextBillingDateNotificationKey.java
index 664d415..1b64c60 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/notification/NextBillingDateNotificationKey.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/notification/NextBillingDateNotificationKey.java
@@ -23,21 +23,35 @@ import org.killbill.notificationq.DefaultUUIDNotificationKey;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
public class NextBillingDateNotificationKey extends DefaultUUIDNotificationKey {
private Boolean isDryRunForInvoiceNotification;
private DateTime targetDate;
+ private final Iterable<UUID> uuidKeys;
@JsonCreator
- public NextBillingDateNotificationKey(@JsonProperty("uuidKey") final UUID uuidKey,
+ public NextBillingDateNotificationKey(@Deprecated @JsonProperty("uuidKey") final UUID uuidKey,
+ @JsonProperty("uuidKeys") final Iterable<UUID> uuidKeys,
@JsonProperty("targetDate") final DateTime targetDate,
@JsonProperty("isDryRunForInvoiceNotification") final Boolean isDryRunForInvoiceNotification) {
super(uuidKey);
+ this.uuidKeys = uuidKeys;
this.targetDate = targetDate;
this.isDryRunForInvoiceNotification = isDryRunForInvoiceNotification;
}
+ public NextBillingDateNotificationKey(NextBillingDateNotificationKey existing,
+ final Iterable<UUID> newUUIDKeys) {
+ super(null);
+ this.uuidKeys = ImmutableSet.copyOf(Iterables.concat(existing.getUuidKeys(), newUUIDKeys));
+ this.targetDate = existing.getTargetDate();
+ this.isDryRunForInvoiceNotification = existing.isDryRunForInvoiceNotification();
+ }
+
@JsonProperty("isDryRunForInvoiceNotification")
public Boolean isDryRunForInvoiceNotification() {
return isDryRunForInvoiceNotification;
@@ -46,4 +60,13 @@ public class NextBillingDateNotificationKey extends DefaultUUIDNotificationKey {
public DateTime getTargetDate() {
return targetDate;
}
+
+ public final Iterable<UUID> getUuidKeys() {
+ // Deprecated mode
+ if (uuidKeys == null || !uuidKeys.iterator().hasNext()) {
+ return ImmutableList.of(getUuidKey());
+ } else {
+ return uuidKeys;
+ }
+ }
}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/notification/NextBillingDatePoster.java b/invoice/src/main/java/org/killbill/billing/invoice/notification/NextBillingDatePoster.java
index 96836bd..d2dce63 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/notification/NextBillingDatePoster.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/notification/NextBillingDatePoster.java
@@ -27,8 +27,8 @@ import org.killbill.billing.util.entity.dao.EntitySqlDaoWrapperFactory;
public interface NextBillingDatePoster {
void insertNextBillingNotificationFromTransaction(EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory, UUID accountId,
- UUID subscriptionId, DateTime futureNotificationTime, InternalCallContext internalCallContext);
+ Iterable<UUID> subscriptionId, DateTime futureNotificationTime, InternalCallContext internalCallContext);
void insertNextBillingDryRunNotificationFromTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory, final UUID accountId,
- final UUID subscriptionId, final DateTime futureNotificationTime, final DateTime targetDate, final InternalCallContext internalCallContext);
+ final Iterable<UUID> subscriptionId, final DateTime futureNotificationTime, final DateTime targetDate, final InternalCallContext internalCallContext);
}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/notification/ParentInvoiceCommitmentPoster.java b/invoice/src/main/java/org/killbill/billing/invoice/notification/ParentInvoiceCommitmentPoster.java
index 43870c3..4a5d7e9 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/notification/ParentInvoiceCommitmentPoster.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/notification/ParentInvoiceCommitmentPoster.java
@@ -57,18 +57,21 @@ public class ParentInvoiceCommitmentPoster {
// If we see existing notification for the same date we don't insert a new notification
final Iterable<NotificationEventWithMetadata<ParentInvoiceCommitmentNotificationKey>> futureNotifications = commitInvoiceQueue.getFutureNotificationFromTransactionForSearchKeys(internalCallContext.getAccountRecordId(), internalCallContext.getTenantRecordId(), entitySqlDaoWrapperFactory.getHandle().getConnection());
- boolean existingFutureNotificationWithSameDate = false;
+ boolean existingFutureNotificationWithSameDateAndInvoiceId = false;
for (final NotificationEventWithMetadata<ParentInvoiceCommitmentNotificationKey> input : futureNotifications) {
+
+
+
final LocalDate notificationEffectiveLocaleDate = internalCallContext.toLocalDate(futureNotificationTime);
final LocalDate eventEffectiveLocaleDate = internalCallContext.toLocalDate(input.getEffectiveDate());
- if (notificationEffectiveLocaleDate.compareTo(eventEffectiveLocaleDate) == 0) {
- existingFutureNotificationWithSameDate = true;
+ if (notificationEffectiveLocaleDate.compareTo(eventEffectiveLocaleDate) == 0 && input.getEvent().getUuidKey().equals(invoiceId)) {
+ existingFutureNotificationWithSameDateAndInvoiceId = true;
}
// Go through all results to close the connection
}
- if (!existingFutureNotificationWithSameDate) {
+ if (!existingFutureNotificationWithSameDateAndInvoiceId) {
log.info("Queuing parent invoice commitment notification at {} for invoiceId {}", futureNotificationTime.toString(), invoiceId.toString());
commitInvoiceQueue.recordFutureNotificationFromTransaction(entitySqlDaoWrapperFactory.getHandle().getConnection(), futureNotificationTime,
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/provider/DefaultNoOpInvoiceProviderPlugin.java b/invoice/src/main/java/org/killbill/billing/invoice/provider/DefaultNoOpInvoiceProviderPlugin.java
index 6b2883d..503d5e9 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/provider/DefaultNoOpInvoiceProviderPlugin.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/provider/DefaultNoOpInvoiceProviderPlugin.java
@@ -30,11 +30,8 @@ import com.google.inject.Inject;
public class DefaultNoOpInvoiceProviderPlugin implements NoOpInvoicePluginApi {
- private final Clock clock;
-
@Inject
- public DefaultNoOpInvoiceProviderPlugin(final Clock clock) {
- this.clock = clock;
+ public DefaultNoOpInvoiceProviderPlugin() {
}
@Override
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/provider/NoOpInvoiceProviderPluginProvider.java b/invoice/src/main/java/org/killbill/billing/invoice/provider/NoOpInvoiceProviderPluginProvider.java
index 1901ffb..c4444af 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/provider/NoOpInvoiceProviderPluginProvider.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/provider/NoOpInvoiceProviderPluginProvider.java
@@ -45,7 +45,7 @@ public class NoOpInvoiceProviderPluginProvider implements Provider<DefaultNoOpIn
@Override
public DefaultNoOpInvoiceProviderPlugin get() {
- final DefaultNoOpInvoiceProviderPlugin plugin = new DefaultNoOpInvoiceProviderPlugin(clock);
+ final DefaultNoOpInvoiceProviderPlugin plugin = new DefaultNoOpInvoiceProviderPlugin();
final OSGIServiceDescriptor desc = new OSGIServiceDescriptor() {
@Override
public String getPluginSymbolicName() {
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/template/formatters/DefaultInvoiceFormatter.java b/invoice/src/main/java/org/killbill/billing/invoice/template/formatters/DefaultInvoiceFormatter.java
index 44f2a30..f913ce3 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/template/formatters/DefaultInvoiceFormatter.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/template/formatters/DefaultInvoiceFormatter.java
@@ -347,6 +347,16 @@ public class DefaultInvoiceFormatter implements InvoiceFormatter {
return invoice.isParentInvoice();
}
+ @Override
+ public UUID getParentAccountId() {
+ return invoice.getParentAccountId();
+ }
+
+ @Override
+ public UUID getParentInvoiceId() {
+ return invoice.getParentInvoiceId();
+ }
+
// Expose the fields for children classes. This is useful for further customization of the invoices
@SuppressWarnings("UnusedDeclaration")
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/tree/SubscriptionItemTree.java b/invoice/src/main/java/org/killbill/billing/invoice/tree/SubscriptionItemTree.java
index df593b8..d194140 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/tree/SubscriptionItemTree.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/tree/SubscriptionItemTree.java
@@ -1,7 +1,9 @@
/*
* Copyright 2010-2014 Ning, Inc.
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
*
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
@@ -16,11 +18,10 @@
package org.killbill.billing.invoice.tree;
+import java.math.BigDecimal;
import java.util.Comparator;
-import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
-import java.util.Map;
import java.util.UUID;
import javax.annotation.Nullable;
@@ -44,8 +45,8 @@ public class SubscriptionItemTree {
private final List<Item> items = new LinkedList<Item>();
private final List<Item> existingFullyAdjustedItems = new LinkedList<Item>();
- private final List<InvoiceItem> existingFixedItems = new LinkedList<InvoiceItem>();
- private final Map<LocalDate, InvoiceItem> remainingFixedItems = new HashMap<LocalDate, InvoiceItem>();
+ private final List<InvoiceItem> existingIgnoredItems = new LinkedList<InvoiceItem>();
+ private final List<InvoiceItem> remainingIgnoredItems = new LinkedList<InvoiceItem>();
private final List<InvoiceItem> pendingItemAdj = new LinkedList<InvoiceItem>();
private final UUID targetInvoiceId;
@@ -91,7 +92,12 @@ public class SubscriptionItemTree {
switch (invoiceItem.getInvoiceItemType()) {
case RECURRING:
- root.addExistingItem(new ItemsNodeInterval(root, new Item(invoiceItem, targetInvoiceId, ItemAction.ADD)));
+ if (invoiceItem.getAmount().compareTo(BigDecimal.ZERO) == 0) {
+ // Nothing to repair -- https://github.com/killbill/killbill/issues/783
+ existingIgnoredItems.add(invoiceItem);
+ } else {
+ root.addExistingItem(new ItemsNodeInterval(root, new Item(invoiceItem, targetInvoiceId, ItemAction.ADD)));
+ }
break;
case REPAIR_ADJ:
@@ -99,7 +105,7 @@ public class SubscriptionItemTree {
break;
case FIXED:
- existingFixedItems.add(invoiceItem);
+ existingIgnoredItems.add(invoiceItem);
break;
case ITEM_ADJ:
@@ -158,6 +164,17 @@ public class SubscriptionItemTree {
public void mergeProposedItem(final InvoiceItem invoiceItem) {
Preconditions.checkState(!isBuilt, "Tree already built, unable to add new invoiceItem=%s", invoiceItem);
+ // Check if it was an existing item ignored for tree purposes (e.g. FIXED or $0 RECURRING, both of which aren't repaired)
+ final InvoiceItem existingItem = Iterables.tryFind(existingIgnoredItems, new Predicate<InvoiceItem>() {
+ @Override
+ public boolean apply(final InvoiceItem input) {
+ return input.matches(invoiceItem);
+ }
+ }).orNull();
+ if (existingItem != null) {
+ return;
+ }
+
switch (invoiceItem.getInvoiceItemType()) {
case RECURRING:
// merged means we've either matched the proposed to an existing, or triggered a repair
@@ -168,15 +185,7 @@ public class SubscriptionItemTree {
break;
case FIXED:
- final InvoiceItem existingItem = Iterables.tryFind(existingFixedItems, new Predicate<InvoiceItem>() {
- @Override
- public boolean apply(final InvoiceItem input) {
- return input.matches(invoiceItem);
- }
- }).orNull();
- if (existingItem == null) {
- remainingFixedItems.put(invoiceItem.getStartDate(), invoiceItem);
- }
+ remainingIgnoredItems.add(invoiceItem);
break;
default:
@@ -204,7 +213,7 @@ public class SubscriptionItemTree {
public List<InvoiceItem> getView() {
final List<InvoiceItem> tmp = new LinkedList<InvoiceItem>();
- tmp.addAll(remainingFixedItems.values());
+ tmp.addAll(remainingIgnoredItems);
tmp.addAll(Collections2.filter(Collections2.transform(items, new Function<Item, InvoiceItem>() {
@Override
public InvoiceItem apply(final Item input) {
@@ -278,8 +287,8 @@ public class SubscriptionItemTree {
sb.append(", isMerged=").append(isMerged);
sb.append(", items=").append(items);
sb.append(", existingFullyAdjustedItems=").append(existingFullyAdjustedItems);
- sb.append(", existingFixedItems=").append(existingFixedItems);
- sb.append(", remainingFixedItems=").append(remainingFixedItems);
+ sb.append(", existingIgnoredItems=").append(existingIgnoredItems);
+ sb.append(", remainingIgnoredItems=").append(remainingIgnoredItems);
sb.append(", pendingItemAdj=").append(pendingItemAdj);
sb.append('}');
return sb.toString();
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalUsageInArrear.java b/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalUsageInArrear.java
index c79db5c..26f5714 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalUsageInArrear.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalUsageInArrear.java
@@ -144,7 +144,9 @@ public class ContiguousIntervalUsageInArrear {
numberOfPeriod++;
nextBillCycleDate = bid.getFutureBillingDateFor(numberOfPeriod);
}
- if (closedInterval && endDate.isAfter(transitionTimes.get(transitionTimes.size() - 1))) {
+ if (closedInterval &&
+ transitionTimes.size() > 0 &&
+ endDate.isAfter(transitionTimes.get(transitionTimes.size() - 1))) {
transitionTimes.add(endDate);
}
isBuilt.set(true);
diff --git a/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoiceItemSqlDao.sql.stg b/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoiceItemSqlDao.sql.stg
index c99d95c..c7596b4 100644
--- a/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoiceItemSqlDao.sql.stg
+++ b/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoiceItemSqlDao.sql.stg
@@ -1,4 +1,4 @@
-group InvoiceItemSqlDao: EntitySqlDao;
+import "org/killbill/billing/util/entity/dao/EntitySqlDao.sql.stg"
tableName() ::= "invoice_items"
@@ -46,27 +46,27 @@ tableValues() ::= <<
getInvoiceItemsByInvoice() ::= <<
- SELECT <allTableFields()>
+ SELECT <allTableFields("")>
FROM <tableName()>
WHERE invoice_id = :invoiceId
- <AND_CHECK_TENANT()>
+ <AND_CHECK_TENANT("")>
;
>>
getInvoiceItemsBySubscription() ::= <<
- SELECT <allTableFields()>
+ SELECT <allTableFields("")>
FROM <tableName()>
WHERE subscription_id = :subscriptionId
- <AND_CHECK_TENANT()>
+ <AND_CHECK_TENANT("")>
;
>>
getAdjustedOrRepairedInvoiceItemsByLinkedId() ::= <<
- SELECT <allTableFields()>
+ SELECT <allTableFields("")>
FROM <tableName()>
WHERE linked_item_id = :linkedItemId
AND type IN ('ITEM_ADJ', 'REPAIR_ADJ')
- <AND_CHECK_TENANT()>
+ <AND_CHECK_TENANT("")>
;
>>
@@ -74,7 +74,7 @@ updateAmount() ::= <<
UPDATE <tableName()>
SET amount = :amount
WHERE id = :id
- <AND_CHECK_TENANT()>;
+ <AND_CHECK_TENANT("")>;
>>
getInvoiceItemsByParentInvoice() ::= <<
@@ -83,7 +83,7 @@ getInvoiceItemsByParentInvoice() ::= <<
INNER JOIN invoice_parent_children invRel ON invRel.child_invoice_id = items.invoice_id
WHERE invRel.parent_invoice_id = :parentInvoiceId
<AND_CHECK_TENANT("items.")>
- <defaultOrderBy()>
+ <defaultOrderBy("")>
;
>>
diff --git a/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoiceParentChildrenSqlDao.sql.stg b/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoiceParentChildrenSqlDao.sql.stg
index 2a96b7a..93cd6e9 100644
--- a/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoiceParentChildrenSqlDao.sql.stg
+++ b/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoiceParentChildrenSqlDao.sql.stg
@@ -1,4 +1,4 @@
-group InvoiceParentChildrenSqlDao: EntitySqlDao;
+import "org/killbill/billing/util/entity/dao/EntitySqlDao.sql.stg"
tableName() ::= "invoice_parent_children"
@@ -29,17 +29,17 @@ allTableValues() ::= <<
>>
getChildInvoicesByParentInvoiceId() ::= <<
- SELECT <allTableFields()>
+ SELECT <allTableFields("")>
FROM <tableName()>
WHERE parent_invoice_id = :parentInvoiceId
- <AND_CHECK_TENANT()>
- <defaultOrderBy()>
+ <AND_CHECK_TENANT("")>
+ <defaultOrderBy("")>
>>
getParentChildMappingsByChildInvoiceIds(ids) ::= <<
- SELECT <allTableFields()>
+ SELECT <allTableFields("")>
FROM <tableName()>
- WHERE child_invoice_id in (<ids: {id | :id_<i0>}; separator="," >)
- <AND_CHECK_TENANT()>
- <defaultOrderBy()>
+ WHERE child_invoice_id in (<ids>)
+ <AND_CHECK_TENANT("")>
+ <defaultOrderBy("")>
>>
diff --git a/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoicePaymentSqlDao.sql.stg b/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoicePaymentSqlDao.sql.stg
index 7fb6bb6..140ff05 100644
--- a/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoicePaymentSqlDao.sql.stg
+++ b/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoicePaymentSqlDao.sql.stg
@@ -1,4 +1,4 @@
-group InvoicePayment: EntitySqlDao;
+import "org/killbill/billing/util/entity/dao/EntitySqlDao.sql.stg"
tableName() ::= "invoice_payments"
@@ -45,41 +45,41 @@ AND payment_id IS NOT NULL
>>
getByPaymentId() ::= <<
- SELECT <allTableFields()>
+ SELECT <allTableFields("")>
FROM <tableName()>
WHERE payment_id = :paymentId
- <AND_CHECK_TENANT()>
- <defaultOrderBy()>
+ <AND_CHECK_TENANT("")>
+ <defaultOrderBy("")>
;
>>
getPaymentForCookieId() ::= <<
- SELECT <allTableFields()>
+ SELECT <allTableFields("")>
FROM <tableName()>
WHERE payment_cookie_id = :paymentCookieId
- <AND_CHECK_TENANT()>
- <defaultOrderBy()>
+ <AND_CHECK_TENANT("")>
+ <defaultOrderBy("")>
LIMIT 1
;
>>
getAllPaymentsForInvoiceIncludedInit() ::= <<
- SELECT <allTableFields()>
+ SELECT <allTableFields("")>
FROM <tableName()>
WHERE invoice_id = :invoiceId
- <AND_CHECK_TENANT()>
- <defaultOrderBy()>
+ <AND_CHECK_TENANT("")>
+ <defaultOrderBy("")>
;
>>
getInvoicePayments() ::= <<
- SELECT <allTableFields()>
+ SELECT <allTableFields("")>
FROM <tableName()>
WHERE payment_id = :paymentId
- <AND_CHECK_TENANT()>
- <defaultOrderBy()>
+ <AND_CHECK_TENANT("")>
+ <defaultOrderBy("")>
;
>>
@@ -88,7 +88,7 @@ getRemainingAmountPaid() ::= <<
FROM <tableName()>
WHERE (id = :invoicePaymentId OR linked_invoice_payment_id = :invoicePaymentId)
AND success
- <AND_CHECK_TENANT()>
+ <AND_CHECK_TENANT("")>
;
>>
@@ -114,12 +114,12 @@ getChargeBacksByAccountId() ::= <<
>>
getChargebacksByPaymentId() ::= <<
- SELECT <allTableFields()>
+ SELECT <allTableFields("")>
FROM <tableName()>
WHERE type = 'CHARGED_BACK'
AND linked_invoice_payment_id IN (SELECT id FROM invoice_payments WHERE payment_id = :paymentId)
AND success
- <AND_CHECK_TENANT()>
+ <AND_CHECK_TENANT("")>
;
>>
diff --git a/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoiceSqlDao.sql.stg b/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoiceSqlDao.sql.stg
index 812767f..5639dbe 100644
--- a/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoiceSqlDao.sql.stg
+++ b/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoiceSqlDao.sql.stg
@@ -1,4 +1,4 @@
-group InvoiceDao: EntitySqlDao;
+import "org/killbill/billing/util/entity/dao/EntitySqlDao.sql.stg"
tableName() ::= "invoices"
@@ -59,23 +59,24 @@ updateStatus() ::= <<
UPDATE <tableName()>
SET status = :status
WHERE id = :id
- <AND_CHECK_TENANT()>;
+ <AND_CHECK_TENANT("")>;
>>
getParentDraftInvoice() ::= <<
- SELECT <allTableFields()>
+ SELECT <allTableFields("")>
FROM <tableName()>
WHERE account_id = :accountId
AND status = 'DRAFT'
- <AND_CHECK_TENANT()>
- <defaultOrderBy()>
+ <AND_CHECK_TENANT("")>
+ <defaultOrderBy("")>
>>
+
getByIds(ids) ::= <<
select
<allTableFields("t.")>
from <tableName()> t
-where <idField("t.")> in (<ids: {id | :id_<i0>}; separator="," >)
+where <idField("t.")> in (<ids>)
<AND_CHECK_TENANT("t.")>
;
>>
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/api/migration/TestDefaultInvoiceMigrationApi.java b/invoice/src/test/java/org/killbill/billing/invoice/api/migration/TestDefaultInvoiceMigrationApi.java
index f5849cd..4447c42 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/api/migration/TestDefaultInvoiceMigrationApi.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/api/migration/TestDefaultInvoiceMigrationApi.java
@@ -81,7 +81,7 @@ public class TestDefaultInvoiceMigrationApi extends InvoiceTestSuiteWithEmbedded
Assert.assertEquals(invoice.getInvoiceItems().size(), 1);
Assert.assertEquals(invoice.getInvoiceItems().get(0).getAmount().compareTo(MIGRATION_INVOICE_AMOUNT), 0);
Assert.assertEquals(invoice.getInvoiceItems().get(0).getType(), InvoiceItemType.RECURRING);
- Assert.assertEquals(InvoiceModelDaoHelper.getBalance(invoice).compareTo(BigDecimal.ZERO), 0);
+ Assert.assertEquals(InvoiceModelDaoHelper.getRawBalanceForRegularInvoice(invoice).compareTo(BigDecimal.ZERO), 0);
Assert.assertEquals(invoice.getCurrency(), MIGRATION_INVOICE_CURRENCY);
Assert.assertTrue(invoice.isMigrated());
@@ -108,7 +108,7 @@ public class TestDefaultInvoiceMigrationApi extends InvoiceTestSuiteWithEmbedded
public void testBalance() throws InvoiceApiException {
final InvoiceModelDao migrationInvoice = invoiceDao.getById(migrationInvoiceId, internalCallContext);
final InvoiceModelDao regularInvoice = invoiceDao.getById(regularInvoiceId, internalCallContext);
- final BigDecimal balanceOfAllInvoices = InvoiceModelDaoHelper.getBalance(migrationInvoice).add(InvoiceModelDaoHelper.getBalance(regularInvoice));
+ final BigDecimal balanceOfAllInvoices = InvoiceModelDaoHelper.getRawBalanceForRegularInvoice(migrationInvoice).add(InvoiceModelDaoHelper.getRawBalanceForRegularInvoice(regularInvoice));
final BigDecimal accountBalance = invoiceUserApi.getAccountBalance(accountId, callContext);
Assert.assertEquals(accountBalance.compareTo(balanceOfAllInvoices), 0);
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/api/user/TestDefaultInvoiceUserApi.java b/invoice/src/test/java/org/killbill/billing/invoice/api/user/TestDefaultInvoiceUserApi.java
index 170933f..52b0e08 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/api/user/TestDefaultInvoiceUserApi.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/api/user/TestDefaultInvoiceUserApi.java
@@ -27,6 +27,7 @@ import javax.annotation.Nullable;
import org.killbill.billing.ErrorCode;
import org.killbill.billing.ObjectType;
import org.killbill.billing.account.api.Account;
+import org.killbill.billing.api.TestApiListener.NextEvent;
import org.killbill.billing.callcontext.DefaultCallContext;
import org.killbill.billing.catalog.api.Currency;
import org.killbill.billing.invoice.InvoiceTestSuiteWithEmbeddedDB;
@@ -35,6 +36,7 @@ import org.killbill.billing.invoice.api.InvoiceApiException;
import org.killbill.billing.invoice.api.InvoiceItem;
import org.killbill.billing.invoice.api.InvoiceItemType;
import org.killbill.billing.invoice.api.InvoiceStatus;
+import org.killbill.billing.invoice.api.InvoiceUserApi;
import org.killbill.billing.invoice.model.ExternalChargeInvoiceItem;
import org.killbill.billing.util.api.TagApiException;
import org.killbill.billing.util.callcontext.CallContext;
@@ -71,7 +73,7 @@ public class TestDefaultInvoiceUserApi extends InvoiceTestSuiteWithEmbeddedDB {
// Post an external charge
final BigDecimal externalChargeAmount = BigDecimal.TEN;
- final InvoiceItem externalCharge = new ExternalChargeInvoiceItem(null, accountId, null, "description", clock.getUTCToday(), externalChargeAmount, accountCurrency);
+ final InvoiceItem externalCharge = new ExternalChargeInvoiceItem(null, accountId, null, "description", clock.getUTCToday(), clock.getUTCToday(), externalChargeAmount, accountCurrency);
final InvoiceItem externalChargeInvoiceItem = invoiceUserApi.insertExternalCharges(accountId, clock.getUTCToday(), ImmutableList.<InvoiceItem>of(externalCharge), true, callContext).get(0);
verifyExternalChargeOnNewInvoice(accountBalance, null, externalChargeAmount, externalChargeInvoiceItem);
@@ -86,7 +88,7 @@ public class TestDefaultInvoiceUserApi extends InvoiceTestSuiteWithEmbeddedDB {
// Post an external charge
final BigDecimal externalChargeAmount = BigDecimal.TEN;
final UUID bundleId = UUID.randomUUID();
- final InvoiceItem externalCharge = new ExternalChargeInvoiceItem(null, accountId, bundleId, UUID.randomUUID().toString(), clock.getUTCToday(), externalChargeAmount, accountCurrency);
+ final InvoiceItem externalCharge = new ExternalChargeInvoiceItem(null, accountId, bundleId, UUID.randomUUID().toString(), clock.getUTCToday(), clock.getUTCToday(),externalChargeAmount, accountCurrency);
final InvoiceItem externalChargeInvoiceItem = invoiceUserApi.insertExternalCharges(accountId, clock.getUTCToday(), ImmutableList.<InvoiceItem>of(externalCharge), true, callContext).get(0);
verifyExternalChargeOnNewInvoice(accountBalance, bundleId, externalChargeAmount, externalChargeInvoiceItem);
}
@@ -111,64 +113,30 @@ public class TestDefaultInvoiceUserApi extends InvoiceTestSuiteWithEmbeddedDB {
Assert.assertEquals(adjustedAccountBalance, initialAccountBalance.add(externalChargeAmount));
}
- @Test(groups = "slow")
- public void testPostExternalChargeOnExistingInvoice() throws Exception {
- // Verify the initial invoice balance
- final BigDecimal invoiceBalance = invoiceUserApi.getInvoice(invoiceId, callContext).getBalance();
- Assert.assertEquals(invoiceBalance.compareTo(BigDecimal.ZERO), 1);
-
- // Verify the initial account balance
- final BigDecimal accountBalance = invoiceUserApi.getAccountBalance(accountId, callContext);
- Assert.assertEquals(accountBalance, invoiceBalance);
- // Post an external charge
- final BigDecimal externalChargeAmount = BigDecimal.TEN;
- final InvoiceItem externalCharge = new ExternalChargeInvoiceItem(invoiceId, accountId, null, UUID.randomUUID().toString(), clock.getUTCToday(), externalChargeAmount, accountCurrency);
- final InvoiceItem externalChargeInvoiceItem = invoiceUserApi.insertExternalCharges(accountId, clock.getUTCToday(), ImmutableList.<InvoiceItem>of(externalCharge), true, callContext).get(0);
- verifyExternalChargeOnExistingInvoice(invoiceBalance, null, externalChargeAmount, externalChargeInvoiceItem);
- }
@Test(groups = "slow")
public void testOriginalAmountCharged() throws Exception {
- final Invoice initialInvoice = invoiceUserApi.getInvoice(invoiceId, callContext);
- final BigDecimal originalAmountCharged = initialInvoice.getOriginalChargedAmount();
- final BigDecimal amountCharged = initialInvoice.getChargedAmount();
- Assert.assertEquals(originalAmountCharged.compareTo(amountCharged), 0);
-
- ((ClockMock) clock).addDays(1);
-
- // Sleep at least one sec to make sure created_date for the external charge is different than the created date for the invoice itself
- CallContext newCallContextLater = new DefaultCallContext(accountId, callContext.getTenantId(), callContext.getUserName(), callContext.getCallOrigin(), callContext.getUserType(), callContext.getUserToken(), clock);
// Post an external charge
final BigDecimal externalChargeAmount = BigDecimal.TEN;
- final InvoiceItem externalCharge = new ExternalChargeInvoiceItem(invoiceId, accountId, null, UUID.randomUUID().toString(), clock.getUTCToday(), externalChargeAmount, accountCurrency);
- final InvoiceItem externalChargeInvoiceItem = invoiceUserApi.insertExternalCharges(accountId, clock.getUTCToday(), ImmutableList.<InvoiceItem>of(externalCharge), true, newCallContextLater).get(0);
+ final InvoiceItem externalCharge = new ExternalChargeInvoiceItem(null, accountId, null, UUID.randomUUID().toString(), clock.getUTCToday(), null, externalChargeAmount, accountCurrency);
+ final InvoiceItem externalChargeInvoiceItem = invoiceUserApi.insertExternalCharges(accountId, clock.getUTCToday(), ImmutableList.<InvoiceItem>of(externalCharge), true, callContext).get(0);
- final Invoice newInvoice = invoiceUserApi.getInvoice(invoiceId, callContext);
- final BigDecimal newOriginalAmountCharged = newInvoice.getOriginalChargedAmount();
+ final Invoice newInvoice = invoiceUserApi.getInvoice(externalChargeInvoiceItem.getInvoiceId(), callContext);
final BigDecimal newAmountCharged = newInvoice.getChargedAmount();
- final BigDecimal expectedChargedAmount = newInvoice.getOriginalChargedAmount().add(externalChargeInvoiceItem.getAmount());
-
- Assert.assertEquals(originalAmountCharged.compareTo(newOriginalAmountCharged), 0);
- Assert.assertEquals(newAmountCharged.compareTo(expectedChargedAmount), 0);
+ Assert.assertEquals(newInvoice.getOriginalChargedAmount().compareTo(externalChargeAmount), 0);
+ Assert.assertEquals(newAmountCharged.compareTo(externalChargeAmount), 0);
}
@Test(groups = "slow")
- public void testPostExternalChargeForBundleOnExistingInvoice() throws Exception {
- // Verify the initial invoice balance
- final BigDecimal invoiceBalance = invoiceUserApi.getInvoice(invoiceId, callContext).getBalance();
- Assert.assertEquals(invoiceBalance.compareTo(BigDecimal.ZERO), 1);
-
- // Verify the initial account balance
- final BigDecimal accountBalance = invoiceUserApi.getAccountBalance(accountId, callContext);
- Assert.assertEquals(accountBalance, invoiceBalance);
+ public void testPostExternalChargeForBundle() throws Exception {
// Post an external charge
final BigDecimal externalChargeAmount = BigDecimal.TEN;
final UUID bundleId = UUID.randomUUID();
- final InvoiceItem externalCharge = new ExternalChargeInvoiceItem(invoiceId, accountId, bundleId, UUID.randomUUID().toString(), clock.getUTCToday(), externalChargeAmount, accountCurrency);
+ final InvoiceItem externalCharge = new ExternalChargeInvoiceItem(null, accountId, bundleId, UUID.randomUUID().toString(), clock.getUTCToday(), null, externalChargeAmount, accountCurrency);
final InvoiceItem externalChargeInvoiceItem = invoiceUserApi.insertExternalCharges(accountId, clock.getUTCToday(), ImmutableList.<InvoiceItem>of(externalCharge), true, callContext).get(0);
- verifyExternalChargeOnExistingInvoice(invoiceBalance, bundleId, externalChargeAmount, externalChargeInvoiceItem);
+ Assert.assertEquals(externalChargeInvoiceItem.getBundleId(), bundleId);
}
private void verifyExternalChargeOnExistingInvoice(final BigDecimal initialInvoiceBalance, @Nullable final UUID bundleId,
@@ -262,6 +230,9 @@ public class TestDefaultInvoiceUserApi extends InvoiceTestSuiteWithEmbeddedDB {
// Verify the adjusted account balance
final BigDecimal adjustedAccountBalance = invoiceUserApi.getAccountBalance(accountId, callContext);
Assert.assertEquals(adjustedAccountBalance, adjustedInvoiceBalance);
+
+ // Verify idempotency
+ Assert.assertNull(invoiceUserApi.insertInvoiceItemAdjustment(accountId, invoiceId, invoiceItem.getId(), clock.getUTCToday(), null, callContext));
}
@Test(groups = "slow")
@@ -361,10 +332,29 @@ public class TestDefaultInvoiceUserApi extends InvoiceTestSuiteWithEmbeddedDB {
Assert.assertEquals(creditInvoice.getStatus(), InvoiceStatus.DRAFT);
Assert.assertEquals(creditInvoiceItem.getInvoiceId(), creditInvoice.getId());
+ // Verify DRAFT invoice is not taken into consideration when computing accountBalance
+ final BigDecimal accountBalance2 = invoiceUserApi.getAccountBalance(accountId, callContext);
+ Assert.assertEquals(accountBalance2, accountBalance);
+
// move invoice from DRAFT to COMMITTED
invoiceUserApi.commitInvoice(creditInvoice.getId(), callContext);
creditInvoice = invoiceUserApi.getInvoice(invoiceId, callContext);
Assert.assertEquals(creditInvoice.getStatus(), InvoiceStatus.COMMITTED);
+ try {
+ final InvoiceItem externalCharge = new ExternalChargeInvoiceItem(invoiceId, accountId, null, "Initial external charge", clock.getUTCToday(), null, new BigDecimal("12.33"), accountCurrency);
+ invoiceUserApi.insertExternalCharges(accountId, clock.getUTCToday(), ImmutableList.of(externalCharge), true, callContext);
+ Assert.fail("Should fail to add external charge on already committed invoice");
+ } catch (final InvoiceApiException e) {
+ Assert.assertEquals(e.getCode(), ErrorCode.INVOICE_ALREADY_COMMITTED.getCode());
+ }
+
+ try {
+ invoiceUserApi.insertCreditForInvoice(accountId, invoiceId, creditAmount,
+ clock.getUTCToday(), accountCurrency, null, callContext);
+ Assert.fail("Should fail to add credit on already committed invoice");
+ } catch (final InvoiceApiException e) {
+ Assert.assertEquals(e.getCode(), ErrorCode.INVOICE_ALREADY_COMMITTED.getCode());
+ }
}
}
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/api/user/TestInvoiceFlagBehaviors.java b/invoice/src/test/java/org/killbill/billing/invoice/api/user/TestInvoiceFlagBehaviors.java
new file mode 100644
index 0000000..2810693
--- /dev/null
+++ b/invoice/src/test/java/org/killbill/billing/invoice/api/user/TestInvoiceFlagBehaviors.java
@@ -0,0 +1,194 @@
+/*
+ * 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.invoice.api.user;
+
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.UUID;
+
+import org.killbill.billing.ObjectType;
+import org.killbill.billing.account.api.Account;
+import org.killbill.billing.invoice.InvoiceTestSuiteWithEmbeddedDB;
+import org.killbill.billing.invoice.api.Invoice;
+import org.killbill.billing.invoice.api.InvoiceItem;
+import org.killbill.billing.invoice.api.InvoiceStatus;
+import org.killbill.billing.invoice.model.ExternalChargeInvoiceItem;
+import org.killbill.billing.invoice.model.FixedPriceInvoiceItem;
+import org.killbill.billing.util.tag.ControlTagType;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+
+import static org.testng.Assert.assertEquals;
+
+public class TestInvoiceFlagBehaviors extends InvoiceTestSuiteWithEmbeddedDB {
+
+ private UUID accountId;
+
+ @Override
+ @BeforeMethod(groups = "slow")
+ public void beforeMethod() throws Exception {
+ super.beforeMethod();
+ final Account account = invoiceUtil.createAccount(callContext);
+ accountId = account.getId();
+ }
+
+ @Test(groups = "slow", description = "Verify invoice/account balance with a WRITTEN_OFF invoice. Verify account credit is not used against such invoice")
+ public void testWrittenOffInvoiceBeforeAccountCredit() throws Exception {
+
+ // Create new invoice with one charge and expect account credit to be used
+ final List<InvoiceItem> items = invoiceUserApi.insertExternalCharges(accountId, clock.getUTCToday(), ImmutableList.<InvoiceItem>of(new ExternalChargeInvoiceItem(UUID.randomUUID(), clock.getUTCNow(), null, accountId, null, null, null, null, BigDecimal.TEN, accountCurrency)), true, callContext);
+ assertEquals(items.size(), 1);
+
+ // Check both invoice and account balance is 10.00
+ final UUID invoiceId = items.get(0).getInvoiceId();
+
+ final Invoice invoice0 = invoiceUserApi.getInvoice(invoiceId, callContext);
+ assertEquals(invoice0.getBalance().compareTo(BigDecimal.TEN), 0);
+ final BigDecimal accountBalance0 = invoiceUserApi.getAccountBalance(accountId, callContext);
+ assertEquals(accountBalance0.compareTo(BigDecimal.TEN), 0);
+
+ final BigDecimal accountCBA0 = invoiceUserApi.getAccountCBA(accountId, callContext);
+ assertEquals(accountCBA0.compareTo(BigDecimal.ZERO), 0);
+
+ // Tag invoice with WRITTEN_OFF and expect balance to now show as Zero
+ tagUserApi.addTag(invoiceId, ObjectType.INVOICE, ControlTagType.WRITTEN_OFF.getId(), callContext);
+
+ // Check both invoice and account balance is NOW 0
+ final Invoice invoice1 = invoiceUserApi.getInvoice(invoiceId, callContext);
+ assertEquals(invoice1.getBalance().compareTo(BigDecimal.ZERO), 0);
+
+ final BigDecimal accountBalance1 = invoiceUserApi.getAccountBalance(accountId, callContext);
+ assertEquals(accountBalance1.compareTo(BigDecimal.ZERO), 0);
+
+ final BigDecimal accountCBA1 = invoiceUserApi.getAccountCBA(accountId, callContext);
+ assertEquals(accountCBA1.compareTo(BigDecimal.ZERO), 0);
+
+ // Add credit on the account
+ invoiceUserApi.insertCredit(accountId, BigDecimal.TEN, null, accountCurrency, true, null, callContext);
+
+ final Invoice invoice2 = invoiceUserApi.getInvoice(invoiceId, callContext);
+ assertEquals(invoice2.getBalance().compareTo(BigDecimal.ZERO), 0);
+ assertEquals(invoice2.getInvoiceItems().size(), 1);
+
+ final BigDecimal accountBalance2 = invoiceUserApi.getAccountBalance(accountId, callContext);
+ assertEquals(accountBalance2.compareTo(new BigDecimal("-10.00")), 0);
+
+ final BigDecimal accountCBA2 = invoiceUserApi.getAccountCBA(accountId, callContext);
+ assertEquals(accountCBA2.compareTo(BigDecimal.TEN), 0);
+
+
+ }
+
+ @Test(groups = "slow", description = "Verify invoice/account balance with a WRITTEN_OFF invoice. Verify behavior when WRITTEN_OFF tag is added after credit was added to invoice" )
+ public void testWrittenOffInvoiceWithAccountCredit() throws Exception {
+
+ // Add credit on the account
+ invoiceUserApi.insertCredit(accountId, BigDecimal.TEN, null, accountCurrency, true, null, callContext);
+
+ final BigDecimal accountBalance0 = invoiceUserApi.getAccountBalance(accountId, callContext);
+ assertEquals(accountBalance0.compareTo(new BigDecimal("-10.0")), 0);
+
+ final BigDecimal accountCBA0 = invoiceUserApi.getAccountCBA(accountId, callContext);
+ assertEquals(accountCBA0.compareTo(BigDecimal.TEN), 0);
+
+ // Create new invoice with one charge and expect account credit to be used
+ final List<InvoiceItem> items = invoiceUserApi.insertExternalCharges(accountId, clock.getUTCToday(), ImmutableList.<InvoiceItem>of(new ExternalChargeInvoiceItem(UUID.randomUUID(), clock.getUTCNow(), null, accountId, null, null, null, null, new BigDecimal("13.5"), accountCurrency)), true, callContext);
+ assertEquals(items.size(), 1);
+
+ final BigDecimal accountBalance1 = invoiceUserApi.getAccountBalance(accountId, callContext);
+ assertEquals(accountBalance1.compareTo(new BigDecimal("3.5")), 0);
+
+ final BigDecimal accountCBA1 = invoiceUserApi.getAccountCBA(accountId, callContext);
+ assertEquals(accountCBA1.compareTo(BigDecimal.ZERO), 0);
+
+ // Tag invoice with WRITTEN_OFF and expect balance to now show as Zero
+ tagUserApi.addTag(items.get(0).getInvoiceId(), ObjectType.INVOICE, ControlTagType.WRITTEN_OFF.getId(), callContext);
+
+ final BigDecimal accountBalance2 = invoiceUserApi.getAccountBalance(accountId, callContext);
+ assertEquals(accountBalance2.compareTo(BigDecimal.ZERO), 0);
+
+ final BigDecimal accountCBA2 = invoiceUserApi.getAccountCBA(accountId, callContext);
+ assertEquals(accountCBA2.compareTo(BigDecimal.ZERO), 0);
+ }
+
+
+ @Test(groups = "slow", description = "Verify invoice/account balance with migrated invoice. Verify account credit is not consumed and that invoice/account balance does not take into account migrated invoice.")
+ public void testMigratedInvoiceWithAccountCredit() throws Exception {
+
+ // Add credit on the account
+ invoiceUserApi.insertCredit(accountId, BigDecimal.TEN, null, accountCurrency, true, null, callContext);
+
+ final UUID invoiceId = invoiceUserApi.createMigrationInvoice(accountId, null, ImmutableList.<InvoiceItem>of(new FixedPriceInvoiceItem(UUID.randomUUID(), clock.getUTCNow(), null, accountId, null, null, "foo", "bar", null, null, BigDecimal.ONE, accountCurrency)), callContext);
+
+ final Invoice invoice1 = invoiceUserApi.getInvoice(invoiceId, callContext);
+ assertEquals(invoice1.getBalance().compareTo(BigDecimal.ZERO), 0);
+
+ // Verify credit is **not applied** against migration invoice
+ final BigDecimal accountBalance0 = invoiceUserApi.getAccountBalance(accountId, callContext);
+ assertEquals(accountBalance0.compareTo(new BigDecimal("-10.0")), 0);
+
+ final BigDecimal accountCBA0 = invoiceUserApi.getAccountCBA(accountId, callContext);
+ assertEquals(accountCBA0.compareTo(BigDecimal.TEN), 0);
+
+ }
+
+ @Test(groups = "slow", description = "Verify invoice/account balance with DRAFT invoice. Verify that invoice/account balance are ZERO in DRAFT mode but becomes visible after it hasa been COMMITTED." )
+ public void testDraftInvoiceWithAccountCredit() throws Exception {
+
+ // Add credit on the account
+ invoiceUserApi.insertCredit(accountId, BigDecimal.TEN, null, accountCurrency, true, null, callContext);
+
+ // Create new invoice with one charge and expect account credit to be used
+ final List<InvoiceItem> items = invoiceUserApi.insertExternalCharges(accountId, clock.getUTCToday(), ImmutableList.<InvoiceItem>of(new ExternalChargeInvoiceItem(UUID.randomUUID(), clock.getUTCNow(), null, accountId, null, null, null, null, new BigDecimal("4.0"), accountCurrency)), false, callContext);
+ assertEquals(items.size(), 1);
+
+ final UUID invoiceId = items.get(0).getInvoiceId();
+
+ final Invoice invoice1 = invoiceUserApi.getInvoice(invoiceId, callContext);
+ assertEquals(invoice1.getStatus(), InvoiceStatus.DRAFT);
+
+ // Verify CBA was *NOT* applied against DRAFT invoice
+ assertEquals(invoice1.getInvoiceItems().size(), 1);
+ // And balance is ZERO because DRAFT mode
+ assertEquals(invoice1.getBalance().compareTo(BigDecimal.ZERO), 0);
+
+ // Verify credit is not applied against migration invoice
+ final BigDecimal accountBalance0 = invoiceUserApi.getAccountBalance(accountId, callContext);
+ assertEquals(accountBalance0.compareTo(new BigDecimal("-10.0")), 0);
+
+ final BigDecimal accountCBA0 = invoiceUserApi.getAccountCBA(accountId, callContext);
+ assertEquals(accountCBA0.compareTo(BigDecimal.TEN), 0);
+
+ invoiceUserApi.commitInvoice(invoiceId, callContext);
+
+ final Invoice invoice2 = invoiceUserApi.getInvoice(invoiceId, callContext);
+ assertEquals(invoice2.getStatus(), InvoiceStatus.COMMITTED);
+
+ // Verify this time credit was applied against COMMITTED invoice
+ assertEquals(invoice2.getBalance().compareTo(BigDecimal.ZERO), 0);
+
+ final BigDecimal accountBalance1 = invoiceUserApi.getAccountBalance(accountId, callContext);
+ assertEquals(accountBalance1.compareTo(new BigDecimal("-6.0")), 0);
+
+ final BigDecimal accountCBA1 = invoiceUserApi.getAccountCBA(accountId, callContext);
+ assertEquals(accountCBA1.compareTo(new BigDecimal("6.0")), 0);
+ }
+
+}
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/dao/MockInvoiceDao.java b/invoice/src/test/java/org/killbill/billing/invoice/dao/MockInvoiceDao.java
index 8ddb1f2..0e1ea27 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/dao/MockInvoiceDao.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/dao/MockInvoiceDao.java
@@ -68,7 +68,7 @@ public class MockInvoiceDao extends MockEntityDaoBase<InvoiceModelDao, Invoice,
}
try {
eventBus.post(new DefaultInvoiceCreationEvent(invoice.getId(), invoice.getAccountId(),
- InvoiceModelDaoHelper.getBalance(invoice), invoice.getCurrency(),
+ InvoiceModelDaoHelper.getRawBalanceForRegularInvoice(invoice), invoice.getCurrency(),
context.getAccountRecordId(), context.getTenantRecordId(), context.getUserToken()));
} catch (final PersistentBus.EventBusException ex) {
throw new RuntimeException(ex);
@@ -260,7 +260,7 @@ public class MockInvoiceDao extends MockEntityDaoBase<InvoiceModelDao, Invoice,
for (final InvoiceModelDao invoice : getAll(context)) {
if (accountId.equals(invoice.getAccountId())) {
- balance = balance.add(InvoiceModelDaoHelper.getBalance(invoice));
+ balance = balance.add(InvoiceModelDaoHelper.getRawBalanceForRegularInvoice(invoice));
}
}
@@ -272,7 +272,7 @@ public class MockInvoiceDao extends MockEntityDaoBase<InvoiceModelDao, Invoice,
final List<InvoiceModelDao> unpaidInvoices = new ArrayList<InvoiceModelDao>();
for (final InvoiceModelDao invoice : getAll(context)) {
- if (accountId.equals(invoice.getAccountId()) && (InvoiceModelDaoHelper.getBalance(invoice).compareTo(BigDecimal.ZERO) > 0) && !invoice.isMigrated()) {
+ if (accountId.equals(invoice.getAccountId()) && (InvoiceModelDaoHelper.getRawBalanceForRegularInvoice(invoice).compareTo(BigDecimal.ZERO) > 0) && !invoice.isMigrated()) {
unpaidInvoices.add(invoice);
}
}
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceDao.java b/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceDao.java
index 1cb8bbf..73737e0 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceDao.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceDao.java
@@ -127,7 +127,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
assertTrue(thisInvoice.getInvoiceDate().compareTo(invoiceDate) == 0);
assertEquals(thisInvoice.getCurrency(), Currency.USD);
assertEquals(thisInvoice.getInvoiceItems().size(), 0);
- assertTrue(InvoiceModelDaoHelper.getBalance(thisInvoice).compareTo(BigDecimal.ZERO) == 0);
+ assertTrue(InvoiceModelDaoHelper.getRawBalanceForRegularInvoice(thisInvoice).compareTo(BigDecimal.ZERO) == 0);
}
@Test(groups = "slow")
@@ -147,7 +147,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
final InvoiceModelDao savedInvoice = invoiceDao.getById(invoiceId, context);
assertNotNull(savedInvoice);
- assertEquals(InvoiceModelDaoHelper.getBalance(savedInvoice).compareTo(new BigDecimal("21.00")), 0);
+ assertEquals(InvoiceModelDaoHelper.getRawBalanceForRegularInvoice(savedInvoice).compareTo(new BigDecimal("21.00")), 0);
assertEquals(savedInvoice.getInvoiceItems().size(), 1);
final BigDecimal paymentAmount = new BigDecimal("11.00");
@@ -159,7 +159,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
final InvoiceModelDao retrievedInvoice = invoiceDao.getById(invoiceId, context);
assertNotNull(retrievedInvoice);
assertEquals(retrievedInvoice.getInvoiceItems().size(), 1);
- assertEquals(InvoiceModelDaoHelper.getBalance(retrievedInvoice).compareTo(new BigDecimal("10.00")), 0);
+ assertEquals(InvoiceModelDaoHelper.getRawBalanceForRegularInvoice(retrievedInvoice).compareTo(new BigDecimal("10.00")), 0);
}
@Test(groups = "slow")
@@ -501,6 +501,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
assertEquals(invoices.size(), 0);
}
+
@Test(groups = "slow")
public void testAccountBalance() throws EntityPersistenceException {
final UUID accountId = account.getId();
@@ -668,16 +669,16 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
final RecurringInvoiceItem item2 = new RecurringInvoiceItem(invoice.getId(), accountId, bundleId, UUID.randomUUID(), "test plan", "test phase B", startDate,
endDate, amount, amount, Currency.USD);
invoiceUtil.createInvoiceItem(item2, context);
- BigDecimal balancePriorRefund = invoiceDao.getAccountBalance(accountId, context);
- assertEquals(balancePriorRefund.compareTo(new BigDecimal("20.00")), 0);
+ BigDecimal accountBalance = invoiceDao.getAccountBalance(accountId, context);
+ assertEquals(accountBalance.compareTo(new BigDecimal("20.00")), 0);
// Pay the whole thing
final UUID paymentId = UUID.randomUUID();
final BigDecimal payment1 = amount;
final InvoicePayment payment = new DefaultInvoicePayment(InvoicePaymentType.ATTEMPT, paymentId, invoice.getId(), new DateTime(), payment1, Currency.USD, Currency.USD, null, true);
invoiceUtil.createPayment(payment, context);
- balancePriorRefund = invoiceDao.getAccountBalance(accountId, context);
- assertEquals(balancePriorRefund.compareTo(new BigDecimal("0.00")), 0);
+ accountBalance = invoiceDao.getAccountBalance(accountId, context);
+ assertEquals(accountBalance.compareTo(new BigDecimal("0.00")), 0);
// Repair the item (And add CBA item that should be generated)
final InvoiceItem repairItem = new RepairAdjInvoiceItem(invoice.getId(), accountId, startDate, endDate, amount.negate(), Currency.USD, item2.getId());
@@ -691,13 +692,13 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
itemAdjustment.put(item2.getId(), null);
invoiceDao.createRefund(paymentId, refundAmount, true, itemAdjustment, UUID.randomUUID().toString(), context);
- balancePriorRefund = invoiceDao.getAccountBalance(accountId, context);
+ accountBalance = invoiceDao.getAccountBalance(accountId, context);
final boolean partialRefund = refundAmount.compareTo(amount) < 0;
final BigDecimal cba = invoiceDao.getAccountCBA(accountId, context);
final InvoiceModelDao savedInvoice = invoiceDao.getById(invoice.getId(), context);
- final BigDecimal expectedCba = balancePriorRefund.compareTo(BigDecimal.ZERO) < 0 ? balancePriorRefund.negate() : BigDecimal.ZERO;
+ final BigDecimal expectedCba = accountBalance.compareTo(BigDecimal.ZERO) < 0 ? accountBalance.negate() : BigDecimal.ZERO;
assertEquals(cba.compareTo(expectedCba), 0);
// Let's re-calculate them from invoice
@@ -706,12 +707,12 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
if (partialRefund) {
// IB = 20 (rec) - 20 (repair) + 20 (cba) - (20 -7) = 7; AB = IB - CBA = 7 - 20 = -13
- assertEquals(balancePriorRefund.compareTo(new BigDecimal("-13.0")), 0);
+ assertEquals(accountBalance.compareTo(new BigDecimal("-13.0")), 0);
assertEquals(savedInvoice.getInvoiceItems().size(), 4);
assertEquals(balanceAfterRefund.compareTo(new BigDecimal("-13.0")), 0);
assertEquals(cbaAfterRefund.compareTo(expectedCba), 0);
} else {
- assertEquals(balancePriorRefund.compareTo(new BigDecimal("0.0")), 0);
+ assertEquals(accountBalance.compareTo(new BigDecimal("0.0")), 0);
assertEquals(savedInvoice.getInvoiceItems().size(), 4);
assertEquals(balanceAfterRefund.compareTo(BigDecimal.ZERO), 0);
assertEquals(cbaAfterRefund.compareTo(expectedCba), 0);
@@ -817,7 +818,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
final String description = UUID.randomUUID().toString();
final InvoiceModelDao invoiceForExternalCharge = new InvoiceModelDao(accountId, clock.getUTCToday(), clock.getUTCToday(), Currency.USD, false);
- final InvoiceItemModelDao externalCharge = new InvoiceItemModelDao(new ExternalChargeInvoiceItem(invoiceForExternalCharge.getId(), accountId, bundleId, description, clock.getUTCToday(), new BigDecimal("15.0"), Currency.USD));
+ final InvoiceItemModelDao externalCharge = new InvoiceItemModelDao(new ExternalChargeInvoiceItem(invoiceForExternalCharge.getId(), accountId, bundleId, description, clock.getUTCToday(), clock.getUTCToday(), new BigDecimal("15.0"), Currency.USD));
invoiceForExternalCharge.addInvoiceItem(externalCharge);
final InvoiceItemModelDao charge = invoiceDao.createInvoices(ImmutableList.<InvoiceModelDao>of(invoiceForExternalCharge), context).get(0);
@@ -854,7 +855,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
final String description = UUID.randomUUID().toString();
final InvoiceModelDao draftInvoiceForExternalCharge = new InvoiceModelDao(accountId, clock.getUTCToday(), clock.getUTCToday(), Currency.USD, false, InvoiceStatus.DRAFT);
- final InvoiceItemModelDao externalCharge = new InvoiceItemModelDao(new ExternalChargeInvoiceItem(draftInvoiceForExternalCharge.getId(), accountId, bundleId, description, clock.getUTCToday(), new BigDecimal("15.0"), Currency.USD));
+ final InvoiceItemModelDao externalCharge = new InvoiceItemModelDao(new ExternalChargeInvoiceItem(draftInvoiceForExternalCharge.getId(), accountId, bundleId, description, clock.getUTCToday(), clock.getUTCToday(), new BigDecimal("15.0"), Currency.USD));
draftInvoiceForExternalCharge.addInvoiceItem(externalCharge);
final InvoiceItemModelDao charge = invoiceDao.createInvoices(ImmutableList.<InvoiceModelDao>of(draftInvoiceForExternalCharge), context).get(0);
@@ -983,7 +984,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
assertEquals(invoices.size(), 1);
final InvoiceModelDao invoice = invoices.get(0);
- assertTrue(InvoiceModelDaoHelper.getBalance(invoice).compareTo(BigDecimal.ZERO) == 0);
+ assertTrue(InvoiceModelDaoHelper.getRawBalanceForRegularInvoice(invoice).compareTo(BigDecimal.ZERO) == 0);
final List<InvoiceItemModelDao> invoiceItems = invoice.getInvoiceItems();
assertEquals(invoiceItems.size(), 2);
boolean foundCredit = false;
@@ -1058,7 +1059,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
assertEquals(invoices.size(), 1);
final InvoiceModelDao invoice = invoices.get(0);
- assertTrue(InvoiceModelDaoHelper.getBalance(invoice).compareTo(expectedBalance) == 0);
+ assertTrue(InvoiceModelDaoHelper.getRawBalanceForRegularInvoice(invoice).compareTo(expectedBalance) == 0);
final List<InvoiceItemModelDao> invoiceItems = invoice.getInvoiceItems();
assertEquals(invoiceItems.size(), expectCBA ? 3 : 2);
boolean foundCredit = false;
@@ -1207,7 +1208,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
final BillingEventSet events = new MockBillingEventSet();
events.add(event1);
- final InvoiceWithMetadata invoiceWithMetadata1 = generator.generateInvoice(account, events, invoiceList, targetDate, Currency.USD, context);
+ final InvoiceWithMetadata invoiceWithMetadata1 = generator.generateInvoice(account, events, invoiceList, null, targetDate, Currency.USD, context);
final Invoice invoice1 = invoiceWithMetadata1.getInvoice();
assertEquals(invoice1.getBalance(), KillBillMoney.of(TEN, invoice1.getCurrency()));
invoiceList.add(invoice1);
@@ -1226,7 +1227,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
// second invoice should be for one half (14/28 days) the difference between the rate plans
// this is a temporary state, since it actually contains an adjusting item that properly belong to invoice 1
- final InvoiceWithMetadata invoiceWithMetadata2 = generator.generateInvoice(account, events, invoiceList, targetDate, Currency.USD, context);
+ final InvoiceWithMetadata invoiceWithMetadata2 = generator.generateInvoice(account, events, invoiceList, null, targetDate, Currency.USD, context);
final Invoice invoice2 = invoiceWithMetadata2.getInvoice();
assertEquals(invoice2.getBalance(), KillBillMoney.of(FIVE, invoice2.getCurrency()));
@@ -1236,10 +1237,10 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
invoiceUtil.createInvoice(invoice2, context);
final InvoiceModelDao savedInvoice1 = invoiceDao.getById(invoice1.getId(), context);
- assertEquals(InvoiceModelDaoHelper.getBalance(savedInvoice1), KillBillMoney.of(TEN, savedInvoice1.getCurrency()));
+ assertEquals(InvoiceModelDaoHelper.getRawBalanceForRegularInvoice(savedInvoice1), KillBillMoney.of(TEN, savedInvoice1.getCurrency()));
final InvoiceModelDao savedInvoice2 = invoiceDao.getById(invoice2.getId(), context);
- assertEquals(InvoiceModelDaoHelper.getBalance(savedInvoice2), KillBillMoney.of(FIVE, savedInvoice2.getCurrency()));
+ assertEquals(InvoiceModelDaoHelper.getRawBalanceForRegularInvoice(savedInvoice2), KillBillMoney.of(FIVE, savedInvoice2.getCurrency()));
}
@Test(groups = "slow")
@@ -1260,7 +1261,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
events.add(event);
final LocalDate targetDate = invoiceUtil.buildDate(2011, 1, 15);
- final InvoiceWithMetadata invoiceWithMetadata = generator.generateInvoice(account, events, null, targetDate, Currency.USD, context);
+ final InvoiceWithMetadata invoiceWithMetadata = generator.generateInvoice(account, events, null, null, targetDate, Currency.USD, context);
final Invoice invoice = invoiceWithMetadata.getInvoice();
assertNotNull(invoice);
@@ -1302,7 +1303,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
events.add(event1);
final UUID accountId = account.getId();
- final InvoiceWithMetadata invoiceWithMetadata1 = generator.generateInvoice(account, events, null, new LocalDate(effectiveDate1), Currency.USD, context);
+ final InvoiceWithMetadata invoiceWithMetadata1 = generator.generateInvoice(account, events, null, null, new LocalDate(effectiveDate1), Currency.USD, context);
final Invoice invoice1 = invoiceWithMetadata1.getInvoice();
assertNotNull(invoice1);
assertEquals(invoice1.getNumberOfItems(), 1);
@@ -1319,7 +1320,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
"testEvent2", 2L, SubscriptionBaseTransitionType.PHASE);
events.add(event2);
- final InvoiceWithMetadata invoiceWithMetadata2 = generator.generateInvoice(account, events, invoiceList, new LocalDate(effectiveDate2), Currency.USD, context);
+ final InvoiceWithMetadata invoiceWithMetadata2 = generator.generateInvoice(account, events, invoiceList, null, new LocalDate(effectiveDate2), Currency.USD, context);
final Invoice invoice2 = invoiceWithMetadata2.getInvoice();
assertNotNull(invoice2);
assertEquals(invoice2.getNumberOfItems(), 1);
@@ -1330,7 +1331,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
//invoiceUtil.createInvoice(invoice2, invoice2.getTargetDate().getDayOfMonth(), callcontext);
final DateTime effectiveDate3 = effectiveDate2.plusMonths(1);
- final InvoiceWithMetadata invoiceWithMetadata3 = generator.generateInvoice(account, events, invoiceList, new LocalDate(effectiveDate3), Currency.USD, context);
+ final InvoiceWithMetadata invoiceWithMetadata3 = generator.generateInvoice(account, events, invoiceList, null, new LocalDate(effectiveDate3), Currency.USD, context);
final Invoice invoice3 = invoiceWithMetadata3.getInvoice();
assertNotNull(invoice3);
assertEquals(invoice3.getNumberOfItems(), 1);
@@ -1342,7 +1343,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
@Test(groups = "slow")
public void testInvoiceForEmptyEventSet() throws InvoiceApiException {
final BillingEventSet events = new MockBillingEventSet();
- final InvoiceWithMetadata invoiceWithMetadata = generator.generateInvoice(account, events, null, new LocalDate(), Currency.USD, context);
+ final InvoiceWithMetadata invoiceWithMetadata = generator.generateInvoice(account, events, null, null, new LocalDate(), Currency.USD, context);
final Invoice invoice = invoiceWithMetadata.getInvoice();
assertNull(invoice);
}
@@ -1377,7 +1378,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
"testEvent2", 2L, SubscriptionBaseTransitionType.CHANGE);
events.add(event2);
- final InvoiceWithMetadata invoiceWithMetadata = generator.generateInvoice(account, events, null, new LocalDate(effectiveDate2), Currency.USD, context);
+ final InvoiceWithMetadata invoiceWithMetadata = generator.generateInvoice(account, events, null, null, new LocalDate(effectiveDate2), Currency.USD, context);
final Invoice invoice = invoiceWithMetadata.getInvoice();
assertNotNull(invoice);
assertEquals(invoice.getNumberOfItems(), 2);
@@ -1388,7 +1389,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
assertNotNull(savedInvoice);
assertEquals(savedInvoice.getInvoiceItems().size(), 2);
- assertEquals(InvoiceModelDaoHelper.getBalance(savedInvoice).compareTo(cheapAmount), 0);
+ assertEquals(InvoiceModelDaoHelper.getRawBalanceForRegularInvoice(savedInvoice).compareTo(cheapAmount), 0);
}
@Test(groups = "slow")
@@ -1440,6 +1441,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
final Plan plan = Mockito.mock(Plan.class);
Mockito.when(plan.getName()).thenReturn("plan");
+ Mockito.when(plan.getRecurringBillingMode()).thenReturn(BillingMode.IN_ADVANCE);
final PlanPhase phase1 = Mockito.mock(PlanPhase.class);
Mockito.when(phase1.getName()).thenReturn("plan-phase1");
@@ -1449,7 +1451,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
BillingPeriod.MONTHLY, 31, BillingMode.IN_ADVANCE,
"new-event", 1L, SubscriptionBaseTransitionType.CREATE);
events.add(event1);
- final InvoiceWithMetadata newInvoiceWithMetadata = generator.generateInvoice(account, events, invoices, targetDate, Currency.USD, context);
+ final InvoiceWithMetadata newInvoiceWithMetadata = generator.generateInvoice(account, events, invoices, null, targetDate, Currency.USD, context);
final Invoice newInvoice = newInvoiceWithMetadata.getInvoice();
invoiceUtil.createInvoice(newInvoice, context);
@@ -1469,6 +1471,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
final Plan plan = Mockito.mock(Plan.class);
Mockito.when(plan.getName()).thenReturn("plan");
+ Mockito.when(plan.getRecurringBillingMode()).thenReturn(BillingMode.IN_ADVANCE);
final PlanPhase phase1 = Mockito.mock(PlanPhase.class);
Mockito.when(phase1.getName()).thenReturn("plan-phase1");
@@ -1485,7 +1488,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
"testEvent1", 1L, SubscriptionBaseTransitionType.CHANGE);
events.add(event1);
- InvoiceWithMetadata invoiceWithMetadata1 = generator.generateInvoice(account, events, invoices, new LocalDate(targetDate1), Currency.USD, context);
+ InvoiceWithMetadata invoiceWithMetadata1 = generator.generateInvoice(account, events, invoices, null, new LocalDate(targetDate1), Currency.USD, context);
Invoice invoice1 = invoiceWithMetadata1.getInvoice();
invoices.add(invoice1);
invoiceUtil.createInvoice(invoice1, context);
@@ -1497,7 +1500,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
BillingPeriod.MONTHLY, 31, BillingMode.IN_ADVANCE,
"testEvent2", 2L, SubscriptionBaseTransitionType.CHANGE);
events.add(event2);
- InvoiceWithMetadata invoiceWithMetadata2 = generator.generateInvoice(account, events, invoices, new LocalDate(targetDate2), Currency.USD, context);
+ InvoiceWithMetadata invoiceWithMetadata2 = generator.generateInvoice(account, events, invoices, null, new LocalDate(targetDate2), Currency.USD, context);
Invoice invoice2 = invoiceWithMetadata2.getInvoice();
invoiceUtil.createInvoice(invoice2, context);
invoice2 = new DefaultInvoice(invoiceDao.getById(invoice2.getId(), context));
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceItemDao.java b/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceItemDao.java
index d5e3631..61836c7 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceItemDao.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceItemDao.java
@@ -185,8 +185,9 @@ public class TestInvoiceItemDao extends InvoiceTestSuiteWithEmbeddedDB {
final UUID bundleId = UUID.randomUUID();
final String description = UUID.randomUUID().toString();
final LocalDate startDate = new LocalDate(2012, 4, 1);
+ final LocalDate endDate = new LocalDate(2012, 5, 1);
final InvoiceItem externalChargeInvoiceItem = new ExternalChargeInvoiceItem(invoiceId, accountId, bundleId, description,
- startDate, TEN, Currency.USD);
+ startDate, endDate, TEN, Currency.USD);
invoiceUtil.createInvoiceItem(externalChargeInvoiceItem, context);
final InvoiceItemModelDao savedItem = invoiceUtil.getInvoiceItemById(externalChargeInvoiceItem.getId(), context);
@@ -222,7 +223,7 @@ public class TestInvoiceItemDao extends InvoiceTestSuiteWithEmbeddedDB {
private void createAndVerifyExternalCharge(final BigDecimal amount, final Currency currency) throws EntityPersistenceException {
final InvoiceItem externalChargeInvoiceItem = new ExternalChargeInvoiceItem(UUID.randomUUID(), account.getId(), UUID.randomUUID(),
- UUID.randomUUID().toString(), new LocalDate(2012, 4, 1), amount, currency);
+ UUID.randomUUID().toString(), new LocalDate(2012, 4, 1), new LocalDate(2012, 5, 1), amount, currency);
invoiceUtil.createInvoiceItem(externalChargeInvoiceItem, context);
final InvoiceItemModelDao savedItem = invoiceUtil.getInvoiceItemById(externalChargeInvoiceItem.getId(), context);
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/generator/TestDefaultInvoiceGenerator.java b/invoice/src/test/java/org/killbill/billing/invoice/generator/TestDefaultInvoiceGenerator.java
index bdf3a39..45f8c46 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/generator/TestDefaultInvoiceGenerator.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/generator/TestDefaultInvoiceGenerator.java
@@ -127,14 +127,14 @@ public class TestDefaultInvoiceGenerator extends InvoiceTestSuiteNoDB {
@Test(groups = "fast")
public void testWithNullEventSetAndNullInvoiceSet() throws InvoiceApiException {
- final InvoiceWithMetadata invoiceWithMetadata = generator.generateInvoice(account, null, null, clock.getUTCToday(), Currency.USD, internalCallContext);
+ final InvoiceWithMetadata invoiceWithMetadata = generator.generateInvoice(account, null, null, null, clock.getUTCToday(), Currency.USD, internalCallContext);
assertNull(invoiceWithMetadata.getInvoice());
}
@Test(groups = "fast")
public void testWithEmptyEventSet() throws InvoiceApiException {
final BillingEventSet events = new MockBillingEventSet();
- final InvoiceWithMetadata invoiceWithMetadata = generator.generateInvoice(account, events, null, clock.getUTCToday(), Currency.USD, internalCallContext);
+ final InvoiceWithMetadata invoiceWithMetadata = generator.generateInvoice(account, events, null, null, clock.getUTCToday(), Currency.USD, internalCallContext);
assertNull(invoiceWithMetadata.getInvoice());
}
@@ -153,7 +153,7 @@ public class TestDefaultInvoiceGenerator extends InvoiceTestSuiteNoDB {
events.add(event);
final LocalDate targetDate = invoiceUtil.buildDate(2011, 10, 3);
- final InvoiceWithMetadata invoiceWithMetadata = generator.generateInvoice(account, events, null, targetDate, Currency.USD, internalCallContext);
+ final InvoiceWithMetadata invoiceWithMetadata = generator.generateInvoice(account, events, null, null, targetDate, Currency.USD, internalCallContext);
final Invoice invoice = invoiceWithMetadata.getInvoice();
assertNotNull(invoice);
assertEquals(invoice.getNumberOfItems(), 2);
@@ -184,7 +184,7 @@ public class TestDefaultInvoiceGenerator extends InvoiceTestSuiteNoDB {
events.add(event);
final LocalDate targetDate = invoiceUtil.buildDate(2011, 10, 3);
- final InvoiceWithMetadata invoiceWithMetadata = generator.generateInvoice(account, events, null, targetDate, Currency.USD, internalCallContext);
+ final InvoiceWithMetadata invoiceWithMetadata = generator.generateInvoice(account, events, null, null, targetDate, Currency.USD, internalCallContext);
final Invoice invoice = invoiceWithMetadata.getInvoice();
assertNotNull(invoice);
assertEquals(invoice.getNumberOfItems(), 2);
@@ -217,7 +217,7 @@ public class TestDefaultInvoiceGenerator extends InvoiceTestSuiteNoDB {
// Target date is the next BCD, in local time
final LocalDate targetDate = invoiceUtil.buildDate(2012, 8, bcdLocal);
- final InvoiceWithMetadata invoiceWithMetadata = generator.generateInvoice(account, events, null, targetDate, Currency.USD, internalCallContext);
+ final InvoiceWithMetadata invoiceWithMetadata = generator.generateInvoice(account, events, null, null, targetDate, Currency.USD, internalCallContext);
final Invoice invoice = invoiceWithMetadata.getInvoice();
assertNotNull(invoice);
assertEquals(invoice.getNumberOfItems(), 2);
@@ -241,7 +241,7 @@ public class TestDefaultInvoiceGenerator extends InvoiceTestSuiteNoDB {
// Set a target date of today (start date)
final LocalDate targetDate = startDate;
- final InvoiceWithMetadata invoiceWithMetadata = generator.generateInvoice(account, events, null, targetDate, Currency.USD, internalCallContext);
+ final InvoiceWithMetadata invoiceWithMetadata = generator.generateInvoice(account, events, null, null, targetDate, Currency.USD, internalCallContext);
final Invoice invoice = invoiceWithMetadata.getInvoice();
assertNotNull(invoice);
assertEquals(invoice.getNumberOfItems(), 1);
@@ -263,7 +263,7 @@ public class TestDefaultInvoiceGenerator extends InvoiceTestSuiteNoDB {
events.add(event);
final LocalDate targetDate = invoiceUtil.buildDate(2011, 10, 3);
- final InvoiceWithMetadata invoiceWithMetadata = generator.generateInvoice(account, events, null, targetDate, Currency.USD, internalCallContext);
+ final InvoiceWithMetadata invoiceWithMetadata = generator.generateInvoice(account, events, null, null, targetDate, Currency.USD, internalCallContext);
final Invoice invoice = invoiceWithMetadata.getInvoice();
assertNotNull(invoice);
assertEquals(invoice.getNumberOfItems(), 2);
@@ -295,7 +295,7 @@ public class TestDefaultInvoiceGenerator extends InvoiceTestSuiteNoDB {
events.add(event2);
final LocalDate targetDate = invoiceUtil.buildDate(2011, 10, 3);
- final InvoiceWithMetadata invoiceWithMetadata = generator.generateInvoice(account, events, null, targetDate, Currency.USD, internalCallContext);
+ final InvoiceWithMetadata invoiceWithMetadata = generator.generateInvoice(account, events, null, null, targetDate, Currency.USD, internalCallContext);
final Invoice invoice = invoiceWithMetadata.getInvoice();
assertNotNull(invoice);
assertEquals(invoice.getNumberOfItems(), 2);
@@ -321,7 +321,7 @@ public class TestDefaultInvoiceGenerator extends InvoiceTestSuiteNoDB {
final LocalDate targetDate = invoiceUtil.buildDate(2011, 12, 3);
final UUID accountId = UUID.randomUUID();
- final InvoiceWithMetadata invoiceWithMetadata = generator.generateInvoice(account, events, null, targetDate, Currency.USD, internalCallContext);
+ final InvoiceWithMetadata invoiceWithMetadata = generator.generateInvoice(account, events, null, null, targetDate, Currency.USD, internalCallContext);
final Invoice invoice = invoiceWithMetadata.getInvoice();
assertNotNull(invoice);
assertEquals(invoice.getNumberOfItems(), 4);
@@ -362,7 +362,7 @@ public class TestDefaultInvoiceGenerator extends InvoiceTestSuiteNoDB {
events.add(event3);
final LocalDate targetDate = invoiceUtil.buildDate(2011, 12, 3);
- final InvoiceWithMetadata invoiceWithMetadata = generator.generateInvoice(account, events, null, targetDate, Currency.USD, internalCallContext);
+ final InvoiceWithMetadata invoiceWithMetadata = generator.generateInvoice(account, events, null, null, targetDate, Currency.USD, internalCallContext);
final Invoice invoice = invoiceWithMetadata.getInvoice();
assertNotNull(invoice);
assertEquals(invoice.getNumberOfItems(), 4);
@@ -384,13 +384,13 @@ public class TestDefaultInvoiceGenerator extends InvoiceTestSuiteNoDB {
events.add(event1);
LocalDate targetDate = invoiceUtil.buildDate(2011, 12, 1);
- final InvoiceWithMetadata invoiceWithMetadata1 = generator.generateInvoice(account, events, null, targetDate, Currency.USD, internalCallContext);
+ final InvoiceWithMetadata invoiceWithMetadata1 = generator.generateInvoice(account, events, null, null, targetDate, Currency.USD, internalCallContext);
final List<Invoice> existingInvoices = new ArrayList<Invoice>();
final Invoice invoice1 = invoiceWithMetadata1.getInvoice();
existingInvoices.add(invoice1);
targetDate = invoiceUtil.buildDate(2011, 12, 3);
- final InvoiceWithMetadata invoiceWithMetadata2 = generator.generateInvoice(account, events, existingInvoices, targetDate, Currency.USD, internalCallContext);
+ final InvoiceWithMetadata invoiceWithMetadata2 = generator.generateInvoice(account, events, existingInvoices, null, targetDate, Currency.USD, internalCallContext);
final Invoice invoice2 = invoiceWithMetadata2.getInvoice();
assertNull(invoice2);
}
@@ -563,7 +563,7 @@ public class TestDefaultInvoiceGenerator extends InvoiceTestSuiteNoDB {
final LocalDate targetDate = invoiceUtil.buildDate(2011, 1, 1);
events.add(createBillingEvent(UUID.randomUUID(), UUID.randomUUID(), targetDate, plan, planPhase, 1));
- final InvoiceWithMetadata invoiceWithMetadata = generator.generateInvoice(account, events, null, targetDate, Currency.USD, internalCallContext);
+ final InvoiceWithMetadata invoiceWithMetadata = generator.generateInvoice(account, events, null, null, targetDate, Currency.USD, internalCallContext);
final Invoice invoice = invoiceWithMetadata.getInvoice();
assertNotNull(invoice);
assertEquals(invoice.getInvoiceItems().size(), 1);
@@ -580,7 +580,7 @@ public class TestDefaultInvoiceGenerator extends InvoiceTestSuiteNoDB {
events.add(createBillingEvent(UUID.randomUUID(), UUID.randomUUID(), startDate, plan, planPhase, startDate.getDayOfMonth()));
- final InvoiceWithMetadata invoiceWithMetadata = generator.generateInvoice(account, events, null, targetDate, Currency.USD, internalCallContext);
+ final InvoiceWithMetadata invoiceWithMetadata = generator.generateInvoice(account, events, null, null, targetDate, Currency.USD, internalCallContext);
final Invoice invoice = invoiceWithMetadata.getInvoice();
final RecurringInvoiceItem item = (RecurringInvoiceItem) invoice.getInvoiceItems().get(0);
@@ -618,14 +618,14 @@ public class TestDefaultInvoiceGenerator extends InvoiceTestSuiteNoDB {
events.add(event2);
events.add(event1);
- final InvoiceWithMetadata invoiceWithMetadata1 = generator.generateInvoice(account, events, null, new LocalDate("2012-02-01"), Currency.USD, internalCallContext);
+ final InvoiceWithMetadata invoiceWithMetadata1 = generator.generateInvoice(account, events, null, null, new LocalDate("2012-02-01"), Currency.USD, internalCallContext);
final Invoice invoice1 = invoiceWithMetadata1.getInvoice();
assertNotNull(invoice1);
assertEquals(invoice1.getNumberOfItems(), 1);
final List<Invoice> invoiceList = new ArrayList<Invoice>();
invoiceList.add(invoice1);
- final InvoiceWithMetadata invoiceWithMetadata2 = generator.generateInvoice(account, events, invoiceList, new LocalDate("2012-04-05"), Currency.USD, internalCallContext);
+ final InvoiceWithMetadata invoiceWithMetadata2 = generator.generateInvoice(account, events, invoiceList, null, new LocalDate("2012-04-05"), Currency.USD, internalCallContext);
final Invoice invoice2 = invoiceWithMetadata2.getInvoice();
assertNotNull(invoice2);
assertEquals(invoice2.getNumberOfItems(), 1);
@@ -650,7 +650,7 @@ public class TestDefaultInvoiceGenerator extends InvoiceTestSuiteNoDB {
events.add(event1);
// ensure both components are invoiced
- final InvoiceWithMetadata invoiceWithMetadata1 = generator.generateInvoice(account, events, null, startDate, Currency.USD, internalCallContext);
+ final InvoiceWithMetadata invoiceWithMetadata1 = generator.generateInvoice(account, events, null, null, startDate, Currency.USD, internalCallContext);
final Invoice invoice1 = invoiceWithMetadata1.getInvoice();
assertNotNull(invoice1);
assertEquals(invoice1.getNumberOfItems(), 2);
@@ -663,7 +663,7 @@ public class TestDefaultInvoiceGenerator extends InvoiceTestSuiteNoDB {
final LocalDate currentDate = startDate.plusMonths(1);
// ensure that only the recurring price is invoiced
- final InvoiceWithMetadata invoiceWithMetadata2 = generator.generateInvoice(account, events, invoiceList, currentDate, Currency.USD, internalCallContext);
+ final InvoiceWithMetadata invoiceWithMetadata2 = generator.generateInvoice(account, events, invoiceList, null, currentDate, Currency.USD, internalCallContext);
final Invoice invoice2 = invoiceWithMetadata2.getInvoice();
assertNotNull(invoice2);
assertEquals(invoice2.getNumberOfItems(), 1);
@@ -689,7 +689,7 @@ public class TestDefaultInvoiceGenerator extends InvoiceTestSuiteNoDB {
events.add(event1);
// ensure that a single invoice item is generated for the fixed cost
- final InvoiceWithMetadata invoiceWithMetadata1 = generator.generateInvoice(account, events, null, startDate, Currency.USD, internalCallContext);
+ final InvoiceWithMetadata invoiceWithMetadata1 = generator.generateInvoice(account, events, null, null, startDate, Currency.USD, internalCallContext);
final Invoice invoice1 = invoiceWithMetadata1.getInvoice();assertNotNull(invoice1);
assertEquals(invoice1.getNumberOfItems(), 1);
assertEquals(invoice1.getBalance(), KillBillMoney.of(fixedCost1, invoice1.getCurrency()));
@@ -703,7 +703,7 @@ public class TestDefaultInvoiceGenerator extends InvoiceTestSuiteNoDB {
events.add(event2);
// ensure that a single invoice item is generated for the fixed cost
- final InvoiceWithMetadata invoiceWithMetadata2 = generator.generateInvoice(account, events, invoiceList, phaseChangeDate, Currency.USD, internalCallContext);
+ final InvoiceWithMetadata invoiceWithMetadata2 = generator.generateInvoice(account, events, invoiceList, null, phaseChangeDate, Currency.USD, internalCallContext);
final Invoice invoice2 = invoiceWithMetadata2.getInvoice();
assertEquals(invoice2.getNumberOfItems(), 1);
assertEquals(invoice2.getBalance(), KillBillMoney.of(fixedCost2, invoice2.getCurrency()));
@@ -735,7 +735,7 @@ public class TestDefaultInvoiceGenerator extends InvoiceTestSuiteNoDB {
final LocalDate discountPhaseEndDate = trialPhaseEndDate.plusMonths(6);
events.add(createBillingEvent(subscriptionId, bundleId, discountPhaseEndDate, plan1, phase3, BILL_CYCLE_DAY));
- final InvoiceWithMetadata invoiceWithMetadata1 = generator.generateInvoice(account, events, null, creationDate, Currency.USD, internalCallContext);
+ final InvoiceWithMetadata invoiceWithMetadata1 = generator.generateInvoice(account, events, null, null, creationDate, Currency.USD, internalCallContext);
final Invoice invoice1 = invoiceWithMetadata1.getInvoice();
assertNotNull(invoice1);
assertEquals(invoice1.getNumberOfItems(), 1);
@@ -744,7 +744,7 @@ public class TestDefaultInvoiceGenerator extends InvoiceTestSuiteNoDB {
final List<Invoice> invoiceList = new ArrayList<Invoice>();
invoiceList.add(invoice1);
- final InvoiceWithMetadata invoiceWithMetadata2 = generator.generateInvoice(account, events, invoiceList, trialPhaseEndDate, Currency.USD, internalCallContext);
+ final InvoiceWithMetadata invoiceWithMetadata2 = generator.generateInvoice(account, events, invoiceList, null, trialPhaseEndDate, Currency.USD, internalCallContext);
final Invoice invoice2 = invoiceWithMetadata2.getInvoice();
assertNotNull(invoice2);
assertEquals(invoice2.getNumberOfItems(), 1);
@@ -753,7 +753,7 @@ public class TestDefaultInvoiceGenerator extends InvoiceTestSuiteNoDB {
invoiceList.add(invoice2);
LocalDate targetDate = new LocalDate(trialPhaseEndDate.getYear(), trialPhaseEndDate.getMonthOfYear(), BILL_CYCLE_DAY);
- final InvoiceWithMetadata invoiceWithMetadata3 = generator.generateInvoice(account, events, invoiceList, targetDate, Currency.USD, internalCallContext);
+ final InvoiceWithMetadata invoiceWithMetadata3 = generator.generateInvoice(account, events, invoiceList, null, targetDate, Currency.USD, internalCallContext);
final Invoice invoice3 = invoiceWithMetadata3.getInvoice();
assertNotNull(invoice3);
assertEquals(invoice3.getNumberOfItems(), 1);
@@ -762,7 +762,7 @@ public class TestDefaultInvoiceGenerator extends InvoiceTestSuiteNoDB {
invoiceList.add(invoice3);
targetDate = targetDate.plusMonths(6);
- final InvoiceWithMetadata invoiceWithMetadata4 = generator.generateInvoice(account, events, invoiceList, targetDate, Currency.USD, internalCallContext);
+ final InvoiceWithMetadata invoiceWithMetadata4 = generator.generateInvoice(account, events, invoiceList, null, targetDate, Currency.USD, internalCallContext);
final Invoice invoice4 = invoiceWithMetadata4.getInvoice();
assertNotNull(invoice4);
assertEquals(invoice4.getNumberOfItems(), 7);
@@ -775,7 +775,7 @@ public class TestDefaultInvoiceGenerator extends InvoiceTestSuiteNoDB {
final Plan plan1 = new MockPlan();
final PlanPhase phase1 = createMockMonthlyPlanPhase(null, ZERO, PhaseType.TRIAL);
events.add(createBillingEvent(UUID.randomUUID(), UUID.randomUUID(), clock.getUTCToday(), plan1, phase1, 1));
- generator.generateInvoice(account, events, null, targetDate, Currency.USD, internalCallContext);
+ generator.generateInvoice(account, events, null, null, targetDate, Currency.USD, internalCallContext);
}
@Test(groups = "fast")
@@ -795,7 +795,7 @@ public class TestDefaultInvoiceGenerator extends InvoiceTestSuiteNoDB {
events.add(createBillingEvent(baseSubscription.getId(), baseSubscription.getBundleId(), april25, basePlan, basePlanEvergreen, 25));
// generate invoice
- final InvoiceWithMetadata invoiceWithMetadata1 = generator.generateInvoice(account, events, null, april25, Currency.USD, internalCallContext);
+ final InvoiceWithMetadata invoiceWithMetadata1 = generator.generateInvoice(account, events, null, null, april25, Currency.USD, internalCallContext);
final Invoice invoice1 = invoiceWithMetadata1.getInvoice();
assertNotNull(invoice1);
assertEquals(invoice1.getNumberOfItems(), 1);
@@ -817,7 +817,7 @@ public class TestDefaultInvoiceGenerator extends InvoiceTestSuiteNoDB {
events.add(createBillingEvent(addOnSubscription2.getId(), baseSubscription.getBundleId(), april28, addOn2Plan, addOn2PlanPhaseEvergreen, 25));
// generate invoice
- final InvoiceWithMetadata invoiceWithMetadata2 = generator.generateInvoice(account, events, invoices, april28, Currency.USD, internalCallContext);
+ final InvoiceWithMetadata invoiceWithMetadata2 = generator.generateInvoice(account, events, invoices, null, april28, Currency.USD, internalCallContext);
final Invoice invoice2 = invoiceWithMetadata2.getInvoice();
invoices.add(invoice2);
@@ -836,7 +836,7 @@ public class TestDefaultInvoiceGenerator extends InvoiceTestSuiteNoDB {
// generate invoice
final LocalDate may1 = new LocalDate(2012, 5, 1);
- final InvoiceWithMetadata invoiceWithMetadata3 = generator.generateInvoice(account, newEvents, invoices, may1, Currency.USD, internalCallContext);
+ final InvoiceWithMetadata invoiceWithMetadata3 = generator.generateInvoice(account, newEvents, invoices, null, may1, Currency.USD, internalCallContext);
final Invoice invoice3 = invoiceWithMetadata3.getInvoice();
assertNotNull(invoice3);
assertEquals(invoice3.getNumberOfItems(), 3);
@@ -859,7 +859,7 @@ public class TestDefaultInvoiceGenerator extends InvoiceTestSuiteNoDB {
final BillingEventSet events = new MockBillingEventSet();
events.add(createBillingEvent(originalSubscription.getId(), originalSubscription.getBundleId(), april25, originalPlan, originalPlanEvergreen, 25));
- final InvoiceWithMetadata invoiceWithMetadata1 = generator.generateInvoice(account, events, null, april25, Currency.USD, internalCallContext);
+ final InvoiceWithMetadata invoiceWithMetadata1 = generator.generateInvoice(account, events, null, null, april25, Currency.USD, internalCallContext);
final Invoice invoice1 = invoiceWithMetadata1.getInvoice();
printDetailInvoice(invoice1);
@@ -882,7 +882,7 @@ public class TestDefaultInvoiceGenerator extends InvoiceTestSuiteNoDB {
events.add(createBillingEvent(newSubscription.getId(), originalSubscription.getBundleId(), april25, newPlan, newPlanEvergreen, 25));
// generate a new invoice
- final InvoiceWithMetadata invoiceWithMetadata2 = generator.generateInvoice(account, events, invoices, april25, Currency.USD, internalCallContext);
+ final InvoiceWithMetadata invoiceWithMetadata2 = generator.generateInvoice(account, events, invoices, null, april25, Currency.USD, internalCallContext);
final Invoice invoice2 = invoiceWithMetadata2.getInvoice();
printDetailInvoice(invoice2);
@@ -947,7 +947,7 @@ public class TestDefaultInvoiceGenerator extends InvoiceTestSuiteNoDB {
// Generate a new invoice
- final InvoiceWithMetadata invoiceWithMetadata = generator.generateInvoice(account, events, existingInvoices, targetDate, currency, internalCallContext);
+ final InvoiceWithMetadata invoiceWithMetadata = generator.generateInvoice(account, events, existingInvoices, null, targetDate, currency, internalCallContext);
final Invoice invoice = invoiceWithMetadata.getInvoice();
assertEquals(invoice.getNumberOfItems(), 7);
assertEquals(invoice.getInvoiceItems().get(0).getInvoiceItemType(), InvoiceItemType.RECURRING);
@@ -982,7 +982,7 @@ public class TestDefaultInvoiceGenerator extends InvoiceTestSuiteNoDB {
existingInvoices.add(invoice);
// Generate next invoice (no-op)
- final InvoiceWithMetadata newInvoiceWithMetdata = generator.generateInvoice(account, events, existingInvoices, targetDate, currency, internalCallContext);
+ final InvoiceWithMetadata newInvoiceWithMetdata = generator.generateInvoice(account, events, existingInvoices, null, targetDate, currency, internalCallContext);
final Invoice newInvoice = newInvoiceWithMetdata.getInvoice();
assertNull(newInvoice);
}
@@ -1004,7 +1004,7 @@ public class TestDefaultInvoiceGenerator extends InvoiceTestSuiteNoDB {
final LocalDate targetDate = invoiceUtil.buildDate(2011, 10, 3);
final UUID accountId = UUID.randomUUID();
- final InvoiceWithMetadata invoiceWithMetadata = generator.generateInvoice(account, events, null, targetDate, Currency.USD, internalCallContext);
+ final InvoiceWithMetadata invoiceWithMetadata = generator.generateInvoice(account, events, null, null, targetDate, Currency.USD, internalCallContext);
assertNull(invoiceWithMetadata.getInvoice());
}
@@ -1033,7 +1033,7 @@ public class TestDefaultInvoiceGenerator extends InvoiceTestSuiteNoDB {
eventSet.add(createBillingEvent(subscriptionId2, bundleId, startDate, plan2, plan2phase1, 1));
// generate the first invoice
- final InvoiceWithMetadata invoiceWithMetadata1 = generator.generateInvoice(account, eventSet, invoices, startDate, currency, internalCallContext);
+ final InvoiceWithMetadata invoiceWithMetadata1 = generator.generateInvoice(account, eventSet, invoices, null, startDate, currency, internalCallContext);
final Invoice invoice1 = invoiceWithMetadata1.getInvoice();
assertNotNull(invoice1);
assertTrue(invoice1.getBalance().compareTo(FIFTEEN.add(TWELVE)) == 0);
@@ -1045,7 +1045,7 @@ public class TestDefaultInvoiceGenerator extends InvoiceTestSuiteNoDB {
eventSet.addSubscriptionWithAutoInvoiceOff(subscriptionId1);
final LocalDate targetDate2 = startDate.plusMonths(1);
- final InvoiceWithMetadata invoiceWithMetadata2 = generator.generateInvoice(account, eventSet, invoices, targetDate2, currency, internalCallContext);
+ final InvoiceWithMetadata invoiceWithMetadata2 = generator.generateInvoice(account, eventSet, invoices, null, targetDate2, currency, internalCallContext);
final Invoice invoice2 = invoiceWithMetadata2.getInvoice();
assertNotNull(invoice2);
assertTrue(invoice2.getBalance().compareTo(TWELVE) == 0);
@@ -1054,12 +1054,39 @@ public class TestDefaultInvoiceGenerator extends InvoiceTestSuiteNoDB {
final LocalDate targetDate3 = targetDate2.plusMonths(1);
eventSet.clearSubscriptionsWithAutoInvoiceOff();
eventSet.add(subscription1creation);
- final InvoiceWithMetadata invoiceWithMetadata3 = generator.generateInvoice(account, eventSet, invoices, targetDate3, currency, internalCallContext);
+ final InvoiceWithMetadata invoiceWithMetadata3 = generator.generateInvoice(account, eventSet, invoices, null, targetDate3, currency, internalCallContext);
final Invoice invoice3 = invoiceWithMetadata3.getInvoice();
assertNotNull(invoice3);
assertTrue(invoice3.getBalance().compareTo(FIFTEEN.multiply(TWO).add(TWELVE)) == 0);
}
+ @Test(groups = "fast")
+ public void testAutoInvoiceDraftAccount() throws Exception {
+ final MockBillingEventSet events = new MockBillingEventSet();
+ events.setAccountAutoInvoiceDraft(true);
+
+ final SubscriptionBase sub = createSubscription();
+ final LocalDate startDate = invoiceUtil.buildDate(2011, 9, 1);
+
+ final Plan plan = new MockPlan();
+ final BigDecimal rate1 = TEN;
+ final PlanPhase phase = createMockMonthlyPlanPhase(rate1);
+
+ final BillingEvent event = createBillingEvent(sub.getId(), sub.getBundleId(), startDate, plan, phase, 1);
+ events.add(event);
+
+ final LocalDate targetDate = invoiceUtil.buildDate(2011, 10, 3);
+ final UUID accountId = UUID.randomUUID();
+ final InvoiceWithMetadata invoiceWithMetadata = generator.generateInvoice(account, events, null, null, targetDate, Currency.USD, internalCallContext);
+
+ assertNotNull(invoiceWithMetadata.getInvoice());
+ assertEquals(invoiceWithMetadata.getInvoice().getStatus(), InvoiceStatus.DRAFT);
+ assertEquals(invoiceWithMetadata.getInvoice().getInvoiceItems().size(), 2);
+
+ }
+
+
+
@Test(groups = "fast", description = "https://github.com/killbill/killbill/issues/654")
public void testCancelEOTWithFullItemAdjustment() throws CatalogApiException, InvoiceApiException {
final BigDecimal rate = new BigDecimal("39.95");
@@ -1077,7 +1104,7 @@ public class TestDefaultInvoiceGenerator extends InvoiceTestSuiteNoDB {
events.add(event);
final LocalDate targetDate = invoiceUtil.buildDate(2016, 10, 9);
- final InvoiceWithMetadata invoiceWithMetadata = generator.generateInvoice(account, events, null, targetDate, Currency.USD, internalCallContext);
+ final InvoiceWithMetadata invoiceWithMetadata = generator.generateInvoice(account, events, null, null, targetDate, Currency.USD, internalCallContext);
final Invoice invoice = invoiceWithMetadata.getInvoice();
assertNotNull(invoice);
@@ -1091,7 +1118,7 @@ public class TestDefaultInvoiceGenerator extends InvoiceTestSuiteNoDB {
// Cancel EOT and Add the item adjustment
final BillingEvent event2 = invoiceUtil.createMockBillingEvent(account, sub, endDate.toDateTimeAtStartOfDay(),
- null, phase,
+ plan, phase,
ZERO, null, Currency.USD, BillingPeriod.NO_BILLING_PERIOD, 9,
BillingMode.IN_ADVANCE, "Cancel", 2L,
SubscriptionBaseTransitionType.CANCEL);
@@ -1103,7 +1130,7 @@ public class TestDefaultInvoiceGenerator extends InvoiceTestSuiteNoDB {
final List<Invoice> existingInvoices = new ArrayList<Invoice>();
existingInvoices.add(invoice);
- final InvoiceWithMetadata invoiceWithMetadata2 = generator.generateInvoice(account, events, existingInvoices, targetDate, Currency.USD, internalCallContext);
+ final InvoiceWithMetadata invoiceWithMetadata2 = generator.generateInvoice(account, events, existingInvoices, null, targetDate, Currency.USD, internalCallContext);
final Invoice invoice2 = invoiceWithMetadata2.getInvoice();
assertNull(invoice2);
@@ -1365,7 +1392,7 @@ public class TestDefaultInvoiceGenerator extends InvoiceTestSuiteNoDB {
final LocalDate targetDate, final int expectedNumberOfItems,
final BigDecimal expectedAmount) throws InvoiceApiException {
final Currency currency = Currency.USD;
- final InvoiceWithMetadata invoiceWithMetadata = generator.generateInvoice(account, events, existingInvoices, targetDate, currency, internalCallContext);
+ final InvoiceWithMetadata invoiceWithMetadata = generator.generateInvoice(account, events, existingInvoices, null, targetDate, currency, internalCallContext);
final Invoice invoice = invoiceWithMetadata.getInvoice();
assertNotNull(invoice);
assertEquals(invoice.getNumberOfItems(), expectedNumberOfItems);
@@ -1377,7 +1404,7 @@ public class TestDefaultInvoiceGenerator extends InvoiceTestSuiteNoDB {
private void testNullInvoiceGeneration(final BillingEventSet events, final List<Invoice> existingInvoices, final LocalDate targetDate) throws InvoiceApiException {
final Currency currency = Currency.USD;
- final InvoiceWithMetadata invoiceWithMetadata = generator.generateInvoice(account, events, existingInvoices, targetDate, currency, internalCallContext);
+ final InvoiceWithMetadata invoiceWithMetadata = generator.generateInvoice(account, events, existingInvoices, null, targetDate, currency, internalCallContext);
final Invoice invoice = invoiceWithMetadata.getInvoice();
assertNull(invoice);
}
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/glue/TestInvoiceModule.java b/invoice/src/test/java/org/killbill/billing/invoice/glue/TestInvoiceModule.java
index bc4ddeb..5a8fdd3 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/glue/TestInvoiceModule.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/glue/TestInvoiceModule.java
@@ -25,14 +25,12 @@ import org.killbill.billing.mock.glue.MockTenantModule;
import org.killbill.billing.platform.api.KillbillConfigSource;
import org.killbill.billing.subscription.api.SubscriptionBaseInternalApi;
import org.killbill.billing.usage.glue.UsageModule;
-import org.killbill.billing.util.email.EmailModule;
import org.killbill.billing.util.email.templates.TemplateModule;
import org.killbill.billing.util.glue.CacheModule;
import org.killbill.billing.util.glue.CallContextModule;
import org.killbill.billing.util.glue.ConfigModule;
import org.killbill.billing.util.glue.CustomFieldModule;
import org.killbill.billing.util.glue.MemoryGlobalLockerModule;
-import org.killbill.billing.util.glue.TagStoreModule;
import org.mockito.Mockito;
public class TestInvoiceModule extends DefaultInvoiceModule {
@@ -56,7 +54,6 @@ public class TestInvoiceModule extends DefaultInvoiceModule {
install(new CacheModule(configSource));
install(new ConfigModule(configSource));
install(new TemplateModule(configSource));
- install(new EmailModule(configSource));
install(new MockTenantModule(configSource));
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/MockBillingEventSet.java b/invoice/src/test/java/org/killbill/billing/invoice/MockBillingEventSet.java
index 431a8dd..2826c25 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/MockBillingEventSet.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/MockBillingEventSet.java
@@ -35,11 +35,16 @@ public class MockBillingEventSet extends TreeSet<BillingEvent> implements Billin
private static final long serialVersionUID = 1L;
private boolean isAccountInvoiceOff;
+ private boolean isAccountAutoInvoiceDraft;
+ private boolean isAccountAutoInvoiceReuseDraft;
+
private List<UUID> subscriptionIdsWithAutoInvoiceOff;
public MockBillingEventSet() {
super();
this.isAccountInvoiceOff = false;
+ this.isAccountAutoInvoiceDraft = false;
+ this.isAccountAutoInvoiceReuseDraft = false;
this.subscriptionIdsWithAutoInvoiceOff = new ArrayList<UUID>();
}
@@ -53,8 +58,13 @@ public class MockBillingEventSet extends TreeSet<BillingEvent> implements Billin
}
@Override
- public BillingMode getRecurringBillingMode() {
- return BillingMode.IN_ADVANCE;
+ public boolean isAccountAutoInvoiceDraft() {
+ return isAccountAutoInvoiceDraft;
+ }
+
+ @Override
+ public boolean isAccountAutoInvoiceReuseDraft() {
+ return isAccountAutoInvoiceReuseDraft;
}
@Override
@@ -71,6 +81,11 @@ public class MockBillingEventSet extends TreeSet<BillingEvent> implements Billin
this.isAccountInvoiceOff = isAccountInvoiceOff;
}
+ public void setAccountAutoInvoiceDraft(final boolean isAccountAutoInvoiceDraft) {
+ this.isAccountAutoInvoiceDraft = isAccountAutoInvoiceDraft;
+
+ }
+
public void setSubscriptionIdsWithAutoInvoiceOff(final List<UUID> subscriptionIdsWithAutoInvoiceOff) {
this.subscriptionIdsWithAutoInvoiceOff = subscriptionIdsWithAutoInvoiceOff;
}
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/model/TestExternalChargeInvoiceItem.java b/invoice/src/test/java/org/killbill/billing/invoice/model/TestExternalChargeInvoiceItem.java
index 67ff4f8..3bcf72f 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/model/TestExternalChargeInvoiceItem.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/model/TestExternalChargeInvoiceItem.java
@@ -41,15 +41,16 @@ public class TestExternalChargeInvoiceItem extends InvoiceTestSuiteNoDB {
final BigDecimal amount = BigDecimal.TEN;
final Currency currency = Currency.GBP;
final ExternalChargeInvoiceItem item = new ExternalChargeInvoiceItem(id, invoiceId, accountId, bundleId, description,
- effectiveDate, amount, currency);
+ effectiveDate, effectiveDate, amount, currency);
Assert.assertEquals(item.getAccountId(), accountId);
Assert.assertEquals(item.getAmount().compareTo(amount), 0);
Assert.assertEquals(item.getBundleId(), bundleId);
Assert.assertEquals(item.getCurrency(), currency);
Assert.assertEquals(item.getInvoiceItemType(), InvoiceItemType.EXTERNAL_CHARGE);
Assert.assertEquals(item.getDescription(), description);
+ Assert.assertEquals(item.getStartDate().compareTo(effectiveDate), 0);
+ Assert.assertEquals(item.getEndDate().compareTo(effectiveDate), 0);
Assert.assertNull(item.getPlanName());
- Assert.assertNull(item.getEndDate());
Assert.assertNull(item.getLinkedItemId());
Assert.assertNull(item.getPhaseName());
Assert.assertNull(item.getRate());
@@ -58,15 +59,15 @@ public class TestExternalChargeInvoiceItem extends InvoiceTestSuiteNoDB {
Assert.assertEquals(item, item);
final ExternalChargeInvoiceItem otherItem = new ExternalChargeInvoiceItem(id, invoiceId, UUID.randomUUID(), bundleId,
- description, effectiveDate, amount, currency);
+ description, effectiveDate, effectiveDate, amount, currency);
Assert.assertNotEquals(otherItem, item);
// Check comparison (done by start date)
final ExternalChargeInvoiceItem itemBefore = new ExternalChargeInvoiceItem(id, invoiceId, accountId, bundleId, description,
- effectiveDate.minusDays(1), amount, currency);
+ effectiveDate.minusDays(1), effectiveDate.minusDays(1), amount, currency);
Assert.assertFalse(itemBefore.matches(item));
final ExternalChargeInvoiceItem itemAfter = new ExternalChargeInvoiceItem(id, invoiceId, accountId, bundleId, description,
- effectiveDate.plusDays(1), amount, currency);
+ effectiveDate.plusDays(1), effectiveDate.minusDays(1), amount, currency);
Assert.assertFalse(itemAfter.matches(item));
}
}
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/notification/TestNextBillingDateNotificationKey.java b/invoice/src/test/java/org/killbill/billing/invoice/notification/TestNextBillingDateNotificationKey.java
index a7c6830..b837bdf 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/notification/TestNextBillingDateNotificationKey.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/notification/TestNextBillingDateNotificationKey.java
@@ -24,18 +24,21 @@ import org.killbill.billing.util.jackson.ObjectMapper;
import org.testng.Assert;
import org.testng.annotations.Test;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+
public class TestNextBillingDateNotificationKey {
private static final ObjectMapper mapper = new ObjectMapper();
@Test(groups = "fast")
- public void testBasic() throws Exception {
+ public void testBasicWithUUIDKey() throws Exception {
final UUID uuidKey = UUID.randomUUID();
final DateTime targetDate = new DateTime();
final Boolean isDryRunForInvoiceNotification = Boolean.FALSE;
- final NextBillingDateNotificationKey key = new NextBillingDateNotificationKey(uuidKey, targetDate, isDryRunForInvoiceNotification);
+ final NextBillingDateNotificationKey key = new NextBillingDateNotificationKey(uuidKey, null, targetDate, isDryRunForInvoiceNotification);
final String json = mapper.writeValueAsString(key);
final NextBillingDateNotificationKey result = mapper.readValue(json, NextBillingDateNotificationKey.class);
@@ -44,6 +47,28 @@ public class TestNextBillingDateNotificationKey {
Assert.assertEquals(result.isDryRunForInvoiceNotification(), isDryRunForInvoiceNotification);
}
+
+ @Test(groups = "fast")
+ public void testBasicWithUUIDKeys() throws Exception {
+
+ final UUID uuidKey1 = UUID.randomUUID();
+ final UUID uuidKey2 = UUID.randomUUID();
+ final DateTime targetDate = new DateTime();
+ final Boolean isDryRunForInvoiceNotification = Boolean.FALSE;
+
+ final NextBillingDateNotificationKey key = new NextBillingDateNotificationKey(null, ImmutableList.of(uuidKey1, uuidKey2), targetDate, isDryRunForInvoiceNotification);
+ final String json = mapper.writeValueAsString(key);
+
+ final NextBillingDateNotificationKey result = mapper.readValue(json, NextBillingDateNotificationKey.class);
+ Assert.assertNull(result.getUuidKey());
+ Assert.assertEquals(result.getTargetDate().compareTo(targetDate), 0);
+ Assert.assertEquals(result.isDryRunForInvoiceNotification(), isDryRunForInvoiceNotification);
+ Assert.assertNotNull(result.getUuidKeys());
+
+ Assert.assertTrue(Iterables.contains(result.getUuidKeys(), uuidKey1));
+ Assert.assertTrue(Iterables.contains(result.getUuidKeys(), uuidKey2));
+ }
+
@Test(groups = "fast")
public void testWithMissingFields() throws Exception {
final String json = "{\"uuidKey\":\"a38c363f-b25b-4287-8ebc-55964e116d2f\"}";
@@ -52,5 +77,9 @@ public class TestNextBillingDateNotificationKey {
Assert.assertNull(result.getTargetDate());
Assert.assertNull(result.isDryRunForInvoiceNotification());
+ // Compatibility mode : Although the uuidKeys is not in the json, we verify the getter return the right result
+ Assert.assertNotNull(result.getUuidKeys());
+ Assert.assertEquals(result.getUuidKeys().iterator().next().toString(), "a38c363f-b25b-4287-8ebc-55964e116d2f");
+
}
}
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/notification/TestNextBillingDateNotifier.java b/invoice/src/test/java/org/killbill/billing/invoice/notification/TestNextBillingDateNotifier.java
index 763bd45..b620fab 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/notification/TestNextBillingDateNotifier.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/notification/TestNextBillingDateNotifier.java
@@ -31,6 +31,8 @@ import org.killbill.notificationq.api.NotificationQueue;
import org.testng.Assert;
import org.testng.annotations.Test;
+import com.google.common.collect.ImmutableList;
+
import static org.awaitility.Awaitility.await;
import static java.util.concurrent.TimeUnit.MINUTES;
@@ -47,7 +49,7 @@ public class TestNextBillingDateNotifier extends InvoiceTestSuiteWithEmbeddedDB
final NotificationQueue nextBillingQueue = notificationQueueService.getNotificationQueue(DefaultInvoiceService.INVOICE_SERVICE_NAME, DefaultNextBillingDateNotifier.NEXT_BILLING_DATE_NOTIFIER_QUEUE);
- nextBillingQueue.recordFutureNotification(now, new NextBillingDateNotificationKey(subscriptionId, now, Boolean.FALSE), internalCallContext.getUserToken(), accountRecordId, internalCallContext.getTenantRecordId());
+ nextBillingQueue.recordFutureNotification(now, new NextBillingDateNotificationKey(null, ImmutableList.<UUID>of(subscriptionId), now, Boolean.FALSE), internalCallContext.getUserToken(), accountRecordId, internalCallContext.getTenantRecordId());
// Move time in the future after the notification effectiveDate
clock.setDeltaFromReality(3000);
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/proRations/InvoiceTestUtils.java b/invoice/src/test/java/org/killbill/billing/invoice/proRations/InvoiceTestUtils.java
index 693d0b7..42fccdb 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/proRations/InvoiceTestUtils.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/proRations/InvoiceTestUtils.java
@@ -28,8 +28,6 @@ import org.killbill.billing.account.api.AccountApiException;
import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.catalog.api.Currency;
import org.killbill.billing.entity.EntityPersistenceException;
-import org.killbill.billing.invoice.InvoiceDispatcher.FutureAccountNotifications;
-import org.killbill.billing.invoice.InvoiceDispatcher.FutureAccountNotifications.SubscriptionNotification;
import org.killbill.billing.invoice.TestInvoiceHelper;
import org.killbill.billing.invoice.api.Invoice;
import org.killbill.billing.invoice.api.InvoiceApiException;
@@ -47,7 +45,6 @@ import org.mockito.Mockito;
import org.testng.Assert;
import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
public class InvoiceTestUtils {
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceDispatcher.java b/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceDispatcher.java
index 5a86cc3..a07977a 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceDispatcher.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceDispatcher.java
@@ -37,18 +37,14 @@ import org.killbill.billing.catalog.api.Currency;
import org.killbill.billing.catalog.api.PhaseType;
import org.killbill.billing.catalog.api.Plan;
import org.killbill.billing.catalog.api.PlanPhase;
-import org.killbill.billing.invoice.InvoiceDispatcher.FutureAccountNotifications;
-import org.killbill.billing.invoice.InvoiceDispatcher.FutureAccountNotifications.SubscriptionNotification;
import org.killbill.billing.invoice.TestInvoiceHelper.DryRunFutureDateArguments;
import org.killbill.billing.invoice.api.DryRunArguments;
import org.killbill.billing.invoice.api.Invoice;
import org.killbill.billing.invoice.api.InvoiceApiException;
import org.killbill.billing.invoice.api.InvoiceItem;
import org.killbill.billing.invoice.api.InvoiceItemType;
-import org.killbill.billing.invoice.api.InvoiceNotifier;
import org.killbill.billing.invoice.dao.InvoiceItemModelDao;
import org.killbill.billing.invoice.dao.InvoiceModelDao;
-import org.killbill.billing.invoice.notification.NullInvoiceNotifier;
import org.killbill.billing.junction.BillingEventSet;
import org.killbill.billing.subscription.api.SubscriptionBase;
import org.killbill.billing.subscription.api.SubscriptionBaseTransitionType;
@@ -64,13 +60,13 @@ import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
public class TestInvoiceDispatcher extends InvoiceTestSuiteWithEmbeddedDB {
private Account account;
private SubscriptionBase subscription;
private InternalCallContext context;
+ private InvoiceDispatcher dispatcher;
@Override
@BeforeMethod(groups = "slow")
@@ -79,6 +75,11 @@ public class TestInvoiceDispatcher extends InvoiceTestSuiteWithEmbeddedDB {
account = invoiceUtil.createAccount(callContext);
subscription = invoiceUtil.createSubscription();
context = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
+
+ dispatcher = new InvoiceDispatcher(generator, accountApi, billingApi, subscriptionApi, invoiceDao,
+ internalCallContextFactory, invoicePluginDispatcher, locker, busService.getBus(),
+ notificationQueueService, invoiceConfig, clock, parkedAccountsManager);
+
}
@Test(groups = "slow")
@@ -99,10 +100,9 @@ public class TestInvoiceDispatcher extends InvoiceTestSuiteWithEmbeddedDB {
final LocalDate target = internalCallContext.toLocalDate(effectiveDate);
- final InvoiceNotifier invoiceNotifier = new NullInvoiceNotifier();
final InvoiceDispatcher dispatcher = new InvoiceDispatcher(generator, accountApi, billingApi, subscriptionApi, invoiceDao,
- internalCallContextFactory, invoiceNotifier, invoicePluginDispatcher, locker, busService.getBus(),
- null, invoiceConfig, clock, parkedAccountsManager);
+ internalCallContextFactory, invoicePluginDispatcher, locker, busService.getBus(),
+ notificationQueueService, invoiceConfig, clock, parkedAccountsManager);
Invoice invoice = dispatcher.processAccountFromNotificationOrBusEvent(accountId, target, new DryRunFutureDateArguments(), context);
Assert.assertNotNull(invoice);
@@ -143,10 +143,9 @@ public class TestInvoiceDispatcher extends InvoiceTestSuiteWithEmbeddedDB {
final LocalDate target = internalCallContext.toLocalDate(effectiveDate);
- final InvoiceNotifier invoiceNotifier = new NullInvoiceNotifier();
final InvoiceDispatcher dispatcher = new InvoiceDispatcher(generator, accountApi, billingApi, subscriptionApi, invoiceDao,
- internalCallContextFactory, invoiceNotifier, invoicePluginDispatcher, locker, busService.getBus(),
- null, invoiceConfig, clock, parkedAccountsManager);
+ internalCallContextFactory, invoicePluginDispatcher, locker, busService.getBus(),
+ notificationQueueService, invoiceConfig, clock, parkedAccountsManager);
// Verify initial tags state for account
Assert.assertTrue(tagUserApi.getTagsForAccount(accountId, true, callContext).isEmpty());
@@ -291,11 +290,9 @@ public class TestInvoiceDispatcher extends InvoiceTestSuiteWithEmbeddedDB {
31, BillingMode.IN_ADVANCE, "CHANGE", 3L, SubscriptionBaseTransitionType.CHANGE));
Mockito.when(billingApi.getBillingEventsForAccountAndUpdateAccountBCD(Mockito.<UUID>any(), Mockito.<DryRunArguments>any(), Mockito.<InternalCallContext>any())).thenReturn(events);
- final InvoiceNotifier invoiceNotifier = new NullInvoiceNotifier();
final InvoiceDispatcher dispatcher = new InvoiceDispatcher(generator, accountApi, billingApi, subscriptionApi, invoiceDao,
- internalCallContextFactory, invoiceNotifier, invoicePluginDispatcher, locker, busService.getBus(),
- null, invoiceConfig, clock, parkedAccountsManager);
-
+ internalCallContextFactory, invoicePluginDispatcher, locker, busService.getBus(),
+ notificationQueueService, invoiceConfig, clock, parkedAccountsManager);
final Invoice invoice = dispatcher.processAccountFromNotificationOrBusEvent(account.getId(), new LocalDate("2012-07-30"), null, context);
Assert.assertNotNull(invoice);
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceHelper.java b/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceHelper.java
index dc90d85..b1846ee 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceHelper.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceHelper.java
@@ -57,7 +57,6 @@ import org.killbill.billing.invoice.api.DryRunType;
import org.killbill.billing.invoice.api.Invoice;
import org.killbill.billing.invoice.api.InvoiceApiException;
import org.killbill.billing.invoice.api.InvoiceItem;
-import org.killbill.billing.invoice.api.InvoiceNotifier;
import org.killbill.billing.invoice.api.InvoicePayment;
import org.killbill.billing.invoice.dao.InvoiceDao;
import org.killbill.billing.invoice.dao.InvoiceItemModelDao;
@@ -68,7 +67,6 @@ import org.killbill.billing.invoice.dao.InvoicePaymentModelDao;
import org.killbill.billing.invoice.dao.InvoicePaymentSqlDao;
import org.killbill.billing.invoice.dao.InvoiceSqlDao;
import org.killbill.billing.invoice.generator.InvoiceGenerator;
-import org.killbill.billing.invoice.notification.NullInvoiceNotifier;
import org.killbill.billing.junction.BillingEvent;
import org.killbill.billing.junction.BillingEventSet;
import org.killbill.billing.junction.BillingInternalApi;
@@ -86,6 +84,7 @@ import org.killbill.billing.util.currency.KillBillMoney;
import org.killbill.billing.util.dao.NonEntityDao;
import org.killbill.clock.Clock;
import org.killbill.commons.locker.GlobalLocker;
+import org.killbill.notificationq.api.NotificationQueueService;
import org.mockito.Mockito;
import org.skife.jdbi.v2.IDBI;
import org.testng.Assert;
@@ -163,6 +162,7 @@ public class TestInvoiceHelper {
private final MutableInternalCallContext internalCallContext;
private final InternalCallContextFactory internalCallContextFactory;
private final InvoiceConfig invoiceConfig;
+ private final NotificationQueueService notificationQueueService;
// Low level SqlDao used by the tests to directly insert rows
private final InvoicePaymentSqlDao invoicePaymentSqlDao;
private final InvoiceItemSqlDao invoiceItemSqlDao;
@@ -172,7 +172,7 @@ public class TestInvoiceHelper {
@Inject
public TestInvoiceHelper(final InvoiceGenerator generator, final IDBI dbi,
final BillingInternalApi billingApi, final AccountInternalApi accountApi, final ImmutableAccountInternalApi immutableAccountApi, final InvoicePluginDispatcher invoicePluginDispatcher, final AccountUserApi accountUserApi, final SubscriptionBaseInternalApi subscriptionApi, final BusService busService,
- final InvoiceDao invoiceDao, final GlobalLocker locker, final Clock clock, final NonEntityDao nonEntityDao, final CacheControllerDispatcher cacheControllerDispatcher, final MutableInternalCallContext internalCallContext, final InvoiceConfig invoiceConfig,
+ final InvoiceDao invoiceDao, final GlobalLocker locker, final Clock clock, final NonEntityDao nonEntityDao, final NotificationQueueService notificationQueueService, final MutableInternalCallContext internalCallContext, final InvoiceConfig invoiceConfig,
final ParkedAccountsManager parkedAccountsManager, final InternalCallContextFactory internalCallContextFactory) {
this.generator = generator;
this.billingApi = billingApi;
@@ -186,6 +186,7 @@ public class TestInvoiceHelper {
this.locker = locker;
this.clock = clock;
this.nonEntityDao = nonEntityDao;
+ this.notificationQueueService = notificationQueueService;
this.parkedAccountsManager = parkedAccountsManager;
this.internalCallContext = internalCallContext;
this.internalCallContextFactory = internalCallContextFactory;
@@ -211,10 +212,9 @@ public class TestInvoiceHelper {
Mockito.when(billingApi.getBillingEventsForAccountAndUpdateAccountBCD(Mockito.<UUID>any(), Mockito.<DryRunArguments>any(), Mockito.<InternalCallContext>any())).thenReturn(events);
- final InvoiceNotifier invoiceNotifier = new NullInvoiceNotifier();
final InvoiceDispatcher dispatcher = new InvoiceDispatcher(generator, accountApi, billingApi, subscriptionApi,
- invoiceDao, internalCallContextFactory, invoiceNotifier, invoicePluginDispatcher, locker, busService.getBus(),
- null, invoiceConfig, clock, parkedAccountsManager);
+ invoiceDao, internalCallContextFactory, invoicePluginDispatcher, locker, busService.getBus(),
+ notificationQueueService, invoiceConfig, clock, parkedAccountsManager);
Invoice invoice = dispatcher.processAccountFromNotificationOrBusEvent(account.getId(), targetDate, new DryRunFutureDateArguments(), internalCallContext);
Assert.assertNotNull(invoice);
@@ -316,7 +316,7 @@ public class TestInvoiceHelper {
public void verifyInvoice(final UUID invoiceId, final double balance, final double cbaAmount, final InternalTenantContext context) throws InvoiceApiException {
final InvoiceModelDao invoice = invoiceDao.getById(invoiceId, context);
- Assert.assertEquals(InvoiceModelDaoHelper.getBalance(invoice).doubleValue(), balance);
+ Assert.assertEquals(InvoiceModelDaoHelper.getRawBalanceForRegularInvoice(invoice).doubleValue(), balance);
Assert.assertEquals(InvoiceModelDaoHelper.getCBAAmount(invoice).doubleValue(), cbaAmount);
}
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceNotificationQListener.java b/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceNotificationQListener.java
index 128616f..ef72b84 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceNotificationQListener.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceNotificationQListener.java
@@ -26,6 +26,7 @@ import org.killbill.billing.account.api.AccountInternalApi;
import org.killbill.billing.invoice.api.InvoiceInternalApi;
import org.killbill.clock.Clock;
import org.killbill.billing.util.callcontext.InternalCallContextFactory;
+import org.killbill.notificationq.api.NotificationQueueService;
public class TestInvoiceNotificationQListener extends InvoiceListener {
@@ -33,8 +34,13 @@ public class TestInvoiceNotificationQListener extends InvoiceListener {
UUID latestSubscriptionId = null;
@Inject
- public TestInvoiceNotificationQListener(final AccountInternalApi accountApi, final Clock clock, final InternalCallContextFactory internalCallContextFactory, final InvoiceDispatcher dispatcher, final InvoiceInternalApi invoiceApi) {
- super(accountApi, clock, internalCallContextFactory, null, dispatcher, invoiceApi);
+ public TestInvoiceNotificationQListener(final AccountInternalApi accountApi,
+ final Clock clock,
+ final InternalCallContextFactory internalCallContextFactory,
+ final InvoiceDispatcher dispatcher,
+ final InvoiceInternalApi invoiceApi,
+ final NotificationQueueService notificationQueueService) {
+ super(accountApi, internalCallContextFactory, dispatcher, invoiceApi, notificationQueueService, clock);
}
@Override
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/TestInvoicePluginDispatcher.java b/invoice/src/test/java/org/killbill/billing/invoice/TestInvoicePluginDispatcher.java
new file mode 100644
index 0000000..78d1b74
--- /dev/null
+++ b/invoice/src/test/java/org/killbill/billing/invoice/TestInvoicePluginDispatcher.java
@@ -0,0 +1,139 @@
+/*
+ * 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.invoice;
+
+import java.util.Collection;
+import java.util.Iterator;
+
+import org.killbill.billing.callcontext.InternalCallContext;
+import org.killbill.billing.invoice.plugin.api.InvoicePluginApi;
+import org.killbill.billing.invoice.provider.DefaultNoOpInvoiceProviderPlugin;
+import org.killbill.billing.osgi.api.OSGIServiceDescriptor;
+import org.killbill.billing.osgi.api.OSGIServiceRegistration;
+import org.killbill.billing.platform.api.KillbillConfigSource;
+import org.killbill.billing.tenant.api.TenantInternalApi;
+import org.mockito.Mockito;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableMap;
+import com.google.inject.Inject;
+
+import static org.testng.Assert.assertEquals;
+
+public class TestInvoicePluginDispatcher extends InvoiceTestSuiteNoDB {
+
+ private final String PLUGIN_1 = "plugin1";
+ private final String PLUGIN_2 = "plugin2";
+ private final String PLUGIN_3 = "plugin3";
+
+ @Inject
+ protected InvoicePluginDispatcher invoicePluginDispatcher;
+ @Inject
+ OSGIServiceRegistration<InvoicePluginApi> pluginRegistry;
+
+ @Inject
+ TenantInternalApi tenantInternalApi;
+
+ protected KillbillConfigSource getConfigSource() {
+ return getConfigSource("/resource.properties", ImmutableMap.<String, String>builder()
+ .put("org.killbill.invoice.plugin", Joiner.on(",").join(PLUGIN_1, PLUGIN_2))
+ .build());
+ }
+
+ @Override
+ @BeforeMethod(groups = "fast")
+ public void beforeMethod() {
+ super.beforeMethod();
+ for (final String name : pluginRegistry.getAllServices()) {
+ pluginRegistry.unregisterService(name);
+ }
+ }
+
+ @Test(groups = "fast")
+ public void testWithNoConfig() throws Exception {
+
+ // We Use the per-tenant config and specify a empty list of plugins
+ Mockito.when(tenantInternalApi.getTenantConfig(Mockito.any(InternalCallContext.class))).thenReturn("{\"org.killbill.invoice.plugin\":\"\"}");
+ // We register one plugin
+ registerPlugin(PLUGIN_1);
+
+ final Collection<String> result = invoicePluginDispatcher.getResultingPluginNameList(internalCallContext);
+ // Se expect to seee the list of registered plugins
+ assertEquals(result.size(), 1);
+ final Iterator<String> iterator = result.iterator();
+ assertEquals(iterator.next(), PLUGIN_1);
+ }
+
+ @Test(groups = "fast")
+ public void testWithNoRegistration() throws Exception {
+ // Nothing has been registered, we see nothing
+ final Collection<String> result = invoicePluginDispatcher.getResultingPluginNameList(internalCallContext);
+ assertEquals(result.size(), 0);
+ }
+
+ @Test(groups = "fast")
+ public void testWithCorrectOrder() throws Exception {
+ // 3 plugins registered in correct order but only 2 got specified in config
+ registerPlugin(PLUGIN_1);
+ registerPlugin(PLUGIN_2);
+ registerPlugin(PLUGIN_3);
+
+ final Collection<String> result = invoicePluginDispatcher.getResultingPluginNameList(internalCallContext);
+ assertEquals(result.size(), 2);
+ final Iterator<String> iterator = result.iterator();
+ assertEquals(iterator.next(), PLUGIN_1);
+ assertEquals(iterator.next(), PLUGIN_2);
+ }
+
+ @Test(groups = "fast")
+ public void testWithIncorrectCorrectOrder() throws Exception {
+
+ // 3 plugins registered in *incorrect* order and only 2 got specified in config
+ registerPlugin(PLUGIN_2);
+ registerPlugin(PLUGIN_3);
+ registerPlugin(PLUGIN_1);
+
+ final Collection<String> result = invoicePluginDispatcher.getResultingPluginNameList(internalCallContext);
+ assertEquals(result.size(), 2);
+ final Iterator<String> iterator = result.iterator();
+ assertEquals(iterator.next(), PLUGIN_1);
+ assertEquals(iterator.next(), PLUGIN_2);
+ }
+
+
+ private void registerPlugin(final String plugin) {
+ pluginRegistry.registerService(new OSGIServiceDescriptor() {
+ @Override
+ public String getPluginSymbolicName() {
+ return plugin;
+ }
+
+ @Override
+ public String getPluginName() {
+ return plugin;
+ }
+
+ @Override
+ public String getRegistrationName() {
+ return plugin;
+ }
+ }, new DefaultNoOpInvoiceProviderPlugin());
+ }
+}
\ No newline at end of file
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/tree/TestSubscriptionItemTree.java b/invoice/src/test/java/org/killbill/billing/invoice/tree/TestSubscriptionItemTree.java
index 26f4170..49f3ce2 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/tree/TestSubscriptionItemTree.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/tree/TestSubscriptionItemTree.java
@@ -1493,6 +1493,32 @@ public class TestSubscriptionItemTree extends InvoiceTestSuiteNoDB {
Assert.assertEquals(previousExistingSize, 3);
}
+ @Test(groups = "fast")
+ public void testWithFreeRecurring() {
+ final LocalDate startDate = new LocalDate(2012, 8, 1);
+ final LocalDate endDate = new LocalDate(2012, 9, 1);
+
+ final BigDecimal monthlyRate1 = new BigDecimal("12.00");
+ final BigDecimal monthlyRate2 = new BigDecimal("24.00");
+
+ final SubscriptionItemTree tree = new SubscriptionItemTree(subscriptionId, invoiceId);
+ final InvoiceItem freeMonthly = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, BigDecimal.ZERO, BigDecimal.ZERO, currency);
+ tree.addItem(freeMonthly);
+ final InvoiceItem payingMonthly1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, monthlyRate1, monthlyRate1, currency);
+ tree.addItem(payingMonthly1);
+ tree.flatten(true);
+
+ final InvoiceItem proposedPayingMonthly2 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, monthlyRate2, monthlyRate2, currency);
+ tree.mergeProposedItem(proposedPayingMonthly2);
+ tree.buildForMerge();
+
+ final List<InvoiceItem> expectedResult = Lists.newLinkedList();
+ expectedResult.add(proposedPayingMonthly2);
+ final InvoiceItem repair = new RepairAdjInvoiceItem(invoiceId, accountId, startDate, endDate, monthlyRate1.negate(), currency, payingMonthly1.getId());
+ expectedResult.add(repair);
+ verifyResult(tree.getView(), expectedResult);
+ }
+
private void printTreeJSON(final SubscriptionItemTree tree) throws IOException {
final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
tree.getRoot().jsonSerializeTree(OBJECT_MAPPER, outputStream);
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/usage/TestContiguousIntervalConsumableInArrear.java b/invoice/src/test/java/org/killbill/billing/invoice/usage/TestContiguousIntervalConsumableInArrear.java
index 5614cc0..6ad767e 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/usage/TestContiguousIntervalConsumableInArrear.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/usage/TestContiguousIntervalConsumableInArrear.java
@@ -116,6 +116,37 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
assertEquals(result.compareTo(BigDecimal.TEN.add(BigDecimal.TEN)), 0);
}
+
+ @Test(groups = "fast")
+ public void testComputeBilledUsageSizeOneWith_ALL_TIERS() throws CatalogApiException {
+
+ final DefaultTieredBlock block1 = createDefaultTieredBlock("unit", 1, 10, new BigDecimal("1.5"));
+ final DefaultTier tier1 = createDefaultTierWithBlocks(block1);
+
+ final DefaultTieredBlock block2 = createDefaultTieredBlock("unit", 1, 100, new BigDecimal("1.0"));
+ final DefaultTier tier2 = createDefaultTierWithBlocks(block2);
+
+
+ final DefaultTieredBlock block3 = createDefaultTieredBlock("unit", 1, 1000, new BigDecimal("0.5"));
+ final DefaultTier tier3 = createDefaultTierWithBlocks(block3);
+ final DefaultUsage usage = createConsumableInArrearUsage(usageName, BillingPeriod.MONTHLY, TierBlockPolicy.ALL_TIERS, tier1, tier2, tier3);
+
+ final LocalDate targetDate = new LocalDate(2014, 03, 20);
+
+ final ContiguousIntervalUsageInArrear intervalConsumableInArrear = createContiguousIntervalConsumableInArrear(usage, ImmutableList.<RawUsage>of(), targetDate, false,
+ createMockBillingEvent(targetDate.toDateTimeAtStartOfDay(DateTimeZone.UTC),
+ BillingPeriod.MONTHLY,
+ Collections.<Usage>emptyList())
+ );
+
+ final BigDecimal result = intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("unit", 111L));
+
+ // 111 = 10 (tier1) + 100 (tier2) + 1 (tier3) => 10 * 1.5 + 100 * 1 + 1 * 0.5 = 115.5
+ assertEquals(result, new BigDecimal("115.5"));
+ }
+
+
+
@Test(groups = "fast")
public void testComputeBilledUsageWith_ALL_TIERS() throws CatalogApiException {
@@ -140,6 +171,8 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
assertEquals(result, new BigDecimal("15"));
}
+
+
@Test(groups = "fast")
public void testComputeBilledUsageWith_TOP_TIER() throws CatalogApiException {
@@ -180,11 +213,40 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
final BigDecimal inputLastTier = intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("unit", 300000L));
// 300000 units => (tier3) : 300000 / 1000 + 300000 % 1000 = 300 units => $150
assertEquals(inputLastTier, new BigDecimal("150.0"));
+ }
+
+
+ @Test(groups = "fast")
+ public void testComputeBilledUsageSizeOneWith_TOP_TIER() throws CatalogApiException {
+
+ final DefaultTieredBlock block1 = createDefaultTieredBlock("unit", 1, 10, new BigDecimal("1.5"));
+ final DefaultTier tier1 = createDefaultTierWithBlocks(block1);
+
+ final DefaultTieredBlock block2 = createDefaultTieredBlock("unit", 1, 100, new BigDecimal("1.0"));
+ final DefaultTier tier2 = createDefaultTierWithBlocks(block2);
+
+ final DefaultTieredBlock block3 = createDefaultTieredBlock("unit", 1, 1000, new BigDecimal("0.5"));
+ final DefaultTier tier3 = createDefaultTierWithBlocks(block3);
+ final DefaultUsage usage = createConsumableInArrearUsage(usageName, BillingPeriod.MONTHLY, TierBlockPolicy.TOP_TIER, tier1, tier2, tier3);
+
+ final LocalDate targetDate = new LocalDate(2014, 03, 20);
+
+ final ContiguousIntervalUsageInArrear intervalConsumableInArrear = createContiguousIntervalConsumableInArrear(usage, ImmutableList.<RawUsage>of(), targetDate, false,
+ createMockBillingEvent(targetDate.toDateTimeAtStartOfDay(DateTimeZone.UTC),
+ BillingPeriod.MONTHLY,
+ Collections.<Usage>emptyList())
+ );
+
+ final BigDecimal result = intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("unit", 111L));
+
+ // 111 = 111 * 0.5 =
+ assertEquals(result, new BigDecimal("55.5"));
}
+
@Test(groups = "fast")
public void testComputeMissingItems() throws CatalogApiException {
@@ -322,4 +384,35 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
Assert.assertEquals(rolledUpUsage.get(1).getRolledUpUnits().get(1).getAmount(), new Long(21L));
}
+
+
+ @Test(groups = "fast", description="See https://github.com/killbill/killbill/issues/706")
+ public void testWithRawUsageStartDateAfterEndDate() throws CatalogApiException {
+
+ final LocalDate startDate = new LocalDate(2014, 10, 16);
+ final LocalDate endDate = startDate;
+ final LocalDate targetDate = endDate;
+
+ final LocalDate rawUsageStartDate = new LocalDate(2015, 10, 16);
+
+ final List<RawUsage> rawUsages = new ArrayList<RawUsage>();
+ rawUsages.add(new DefaultRawUsage(subscriptionId, startDate, "unit", 130L));
+
+ final DefaultTieredBlock block = createDefaultTieredBlock("unit", 100, 10, BigDecimal.ONE);
+ final DefaultTier tier = createDefaultTierWithBlocks(block);
+ final DefaultUsage usage = createConsumableInArrearUsage(usageName, BillingPeriod.MONTHLY, TierBlockPolicy.ALL_TIERS, tier);
+
+
+ final BillingEvent event1 = createMockBillingEvent(startDate.toDateTimeAtStartOfDay(DateTimeZone.UTC),BillingPeriod.MONTHLY, Collections.<Usage>emptyList());
+ final BillingEvent event2 = createMockBillingEvent(new LocalDate(2014, 10, 16).toDateTimeAtStartOfDay(DateTimeZone.UTC), BillingPeriod.MONTHLY, Collections.<Usage>emptyList());
+
+
+ final ContiguousIntervalUsageInArrear intervalConsumableInArrear = new ContiguousIntervalUsageInArrear(usage, accountId, invoiceId, rawUsages, targetDate, rawUsageStartDate, internalCallContext);
+ intervalConsumableInArrear.addBillingEvent(event1);
+ intervalConsumableInArrear.addBillingEvent(event2);
+
+ final ContiguousIntervalUsageInArrear res = intervalConsumableInArrear.build(true);
+ assertEquals(res.getTransitionTimes().size(), 0);
+ }
+
}
jaxrs/pom.xml 7(+6 -1)
diff --git a/jaxrs/pom.xml b/jaxrs/pom.xml
index 850569d..7b9ead4 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.19.0-SNAPSHOT</version>
+ <version>0.19.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-jaxrs</artifactId>
@@ -101,6 +101,11 @@
</dependency>
<dependency>
<groupId>org.kill-bill.billing</groupId>
+ <artifactId>killbill-platform-server</artifactId>
+ <classifier>classes</classifier>
+ </dependency>
+ <dependency>
+ <groupId>org.kill-bill.billing</groupId>
<artifactId>killbill-platform-test</artifactId>
<scope>test</scope>
</dependency>
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/glue/DefaultJaxrsModule.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/glue/DefaultJaxrsModule.java
index 083a739..be201dd 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/glue/DefaultJaxrsModule.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/glue/DefaultJaxrsModule.java
@@ -41,4 +41,5 @@ public class DefaultJaxrsModule extends KillBillModule {
bind(JaxrsExecutors.class).asEagerSingleton();
bind(JaxrsService.class).to(DefaultJaxrsService.class).asEagerSingleton();
}
+
}
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 fe81d21..d9b0043 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
@@ -54,6 +54,7 @@ public class AccountJson extends JsonBase {
private final Boolean isPaymentDelegatedToParent;
@ApiModelProperty(dataType = "java.util.UUID")
private final String paymentMethodId;
+ private final DateTime referenceTime;
private final String timeZone;
private final String address1;
private final String address2;
@@ -82,6 +83,7 @@ public class AccountJson extends JsonBase {
this.parentAccountId = account.getParentAccountId() != null ? account.getParentAccountId().toString() : null;
this.isPaymentDelegatedToParent = account.isPaymentDelegatedToParent();
this.paymentMethodId = account.getPaymentMethodId() != null ? account.getPaymentMethodId().toString() : null;
+ this.referenceTime = account.getReferenceTime();
this.timeZone = account.getTimeZone() != null ? account.getTimeZone().toString() : null;
this.address1 = account.getAddress1();
this.address2 = account.getAddress2();
@@ -108,6 +110,7 @@ public class AccountJson extends JsonBase {
@JsonProperty("parentAccountId") final String parentAccountId,
@JsonProperty("isPaymentDelegatedToParent") final Boolean isPaymentDelegatedToParent,
@JsonProperty("paymentMethodId") final String paymentMethodId,
+ @JsonProperty("referenceTime") final DateTime referenceTime,
@JsonProperty("timeZone") final String timeZone,
@JsonProperty("address1") final String address1,
@JsonProperty("address2") final String address2,
@@ -136,6 +139,7 @@ public class AccountJson extends JsonBase {
this.parentAccountId = parentAccountId;
this.isPaymentDelegatedToParent = isPaymentDelegatedToParent;
this.paymentMethodId = paymentMethodId;
+ this.referenceTime = referenceTime;
this.timeZone = timeZone;
this.address1 = address1;
this.address2 = address2;
@@ -297,7 +301,7 @@ public class AccountJson extends JsonBase {
@Override
public DateTime getReferenceTime() {
- return null;
+ return referenceTime;
}
@Override
@@ -371,6 +375,10 @@ public class AccountJson extends JsonBase {
return paymentMethodId;
}
+ public DateTime getReferenceTime() {
+ return referenceTime;
+ }
+
public String getTimeZone() {
return timeZone;
}
@@ -440,6 +448,7 @@ public class AccountJson extends JsonBase {
", parentAccountId=" + parentAccountId + '\'' +
", isPaymentDelegatedToParent=" + isPaymentDelegatedToParent + '\'' +
", paymentMethodId='" + paymentMethodId + '\'' +
+ ", referenceTime='" + referenceTime + '\'' +
", timeZone='" + timeZone + '\'' +
", address1='" + address1 + '\'' +
", address2='" + address2 + '\'' +
@@ -539,10 +548,12 @@ public class AccountJson extends JsonBase {
if (state != null ? !state.equals(that.state) : that.state != null) {
return false;
}
+ if (referenceTime != null ? referenceTime.compareTo(that.referenceTime) != 0 : that.referenceTime != null) {
+ return false;
+ }
if (timeZone != null ? !timeZone.equals(that.timeZone) : that.timeZone != null) {
return false;
}
-
return true;
}
@@ -560,6 +571,7 @@ public class AccountJson extends JsonBase {
result = 31 * result + (parentAccountId != null ? parentAccountId.hashCode() : 0);
result = 31 * result + (isPaymentDelegatedToParent != null ? isPaymentDelegatedToParent.hashCode() : 0);
result = 31 * result + (paymentMethodId != null ? paymentMethodId.hashCode() : 0);
+ result = 31 * result + (referenceTime != null ? referenceTime.hashCode() : 0);
result = 31 * result + (timeZone != null ? timeZone.hashCode() : 0);
result = 31 * result + (address1 != null ? address1.hashCode() : 0);
result = 31 * result + (address2 != null ? address2.hashCode() : 0);
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/CatalogJson.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/CatalogJson.java
index 1fb466f..d5d36bf 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/CatalogJson.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/CatalogJson.java
@@ -28,8 +28,8 @@ import java.util.List;
import java.util.Map;
import org.joda.time.DateTime;
+import org.killbill.billing.catalog.VersionedCatalog;
import org.killbill.billing.catalog.api.BillingPeriod;
-import org.killbill.billing.catalog.api.Catalog;
import org.killbill.billing.catalog.api.CatalogApiException;
import org.killbill.billing.catalog.api.Currency;
import org.killbill.billing.catalog.api.CurrencyValueNull;
@@ -45,6 +45,7 @@ import org.killbill.billing.catalog.api.Product;
import org.killbill.billing.catalog.api.Tier;
import org.killbill.billing.catalog.api.TieredBlock;
import org.killbill.billing.catalog.api.TimeUnit;
+import org.killbill.billing.catalog.api.Unit;
import org.killbill.billing.catalog.api.Usage;
import com.fasterxml.jackson.annotation.JsonCreator;
@@ -58,6 +59,7 @@ public class CatalogJson {
private final String name;
private final Date effectiveDate;
private final List<Currency> currencies;
+ private final List<UnitJson> units;
private final List<ProductJson> products;
private final List<PriceListJson> priceLists;
@@ -65,22 +67,31 @@ public class CatalogJson {
public CatalogJson(@JsonProperty("name") final String name,
@JsonProperty("effectiveDate") final Date effectiveDate,
@JsonProperty("currencies") final List<Currency> currencies,
+ @JsonProperty("units") final List<UnitJson> units,
@JsonProperty("products") final List<ProductJson> products,
@JsonProperty("priceLists") final List<PriceListJson> priceLists) {
this.name = name;
this.effectiveDate = effectiveDate;
this.currencies = currencies;
+ this.units = units;
this.products = products;
this.priceLists = priceLists;
}
- public CatalogJson(final Catalog catalog, final DateTime requestedDate) throws CatalogApiException {
+ public CatalogJson(final VersionedCatalog catalog, final DateTime requestedDate) throws CatalogApiException {
name = catalog.getCatalogName();
effectiveDate = catalog.getStandaloneCatalogEffectiveDate(requestedDate);
currencies = Arrays.asList(catalog.getSupportedCurrencies(requestedDate));
priceLists = new ArrayList<PriceListJson>();
+ List<UnitJson> units = new ArrayList<UnitJson>();
+ for (final Unit unit : catalog.getUnits(requestedDate)) {
+ final UnitJson unitJson = new UnitJson(unit.getName(), unit.getPrettyName());
+ units.add(unitJson);
+ }
+ this.units = units;
+
final Collection<Plan> plans = catalog.getPlans(requestedDate);
final Map<String, ProductJson> productMap = new HashMap<String, ProductJson>();
for (final Plan plan : plans) {
@@ -220,6 +231,10 @@ public class CatalogJson {
return currencies;
}
+ public List<UnitJson> getUnits() {
+ return units;
+ }
+
public List<PriceListJson> getPriceLists() {
return priceLists;
}
@@ -230,6 +245,7 @@ public class CatalogJson {
sb.append("name='").append(name).append('\'');
sb.append(", effectiveDate='").append(effectiveDate).append('\'');
sb.append(", currencies='").append(currencies).append('\'');
+ sb.append(", units='").append(units).append('\'');
sb.append(", products=").append(products);
sb.append(", priceLists=").append(priceLists);
sb.append('}');
@@ -256,6 +272,9 @@ public class CatalogJson {
if (currencies != null ? !currencies.equals(that.currencies) : that.currencies != null) {
return false;
}
+ if (units != null ? !units.equals(that.units) : that.units != null) {
+ return false;
+ }
if (products != null ? !products.equals(that.products) : that.products != null) {
return false;
}
@@ -271,10 +290,60 @@ public class CatalogJson {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + (effectiveDate != null ? effectiveDate.hashCode() : 0);
result = 31 * result + (currencies != null ? currencies.hashCode() : 0);
+ result = 31 * result + (units != null ? units.hashCode() : 0);
result = 31 * result + (products != null ? products.hashCode() : 0);
return result;
}
+ public static class UnitJson {
+
+ private final String name;
+ private final String prettyName;
+
+ @JsonCreator
+ public UnitJson(@JsonProperty("name") final String name,
+ @JsonProperty("prettyName") final String prettyName) {
+ this.name = name;
+ this.prettyName = prettyName;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getPrettyName() {
+ return prettyName;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("UnitJson{");
+ sb.append("name='").append(name).append('\'');
+ sb.append(", prettyName='").append(prettyName).append('\'');
+ sb.append('}');
+ return sb.toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ UnitJson unitJson = (UnitJson) o;
+
+ if (name != null ? !name.equals(unitJson.name) : unitJson.name != null) return false;
+ return prettyName != null ? prettyName.equals(unitJson.prettyName) : unitJson.prettyName == null;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = name != null ? name.hashCode() : 0;
+ result = 31 * result + (prettyName != null ? prettyName.hashCode() : 0);
+ return result;
+ }
+
+ }
+
public static class ProductJson {
private final String type;
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/InvoiceJson.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/InvoiceJson.java
index 90373da..36af2b2 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/InvoiceJson.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/InvoiceJson.java
@@ -56,6 +56,8 @@ public class InvoiceJson extends JsonBase {
private final List<CreditJson> credits;
private final String status;
private final Boolean isParentInvoice;
+ private final String parentInvoiceId;
+ private final String parentAccountId;
@JsonCreator
public InvoiceJson(@JsonProperty("amount") final BigDecimal amount,
@@ -73,6 +75,8 @@ public class InvoiceJson extends JsonBase {
@JsonProperty("credits") final List<CreditJson> credits,
@JsonProperty("items") final List<InvoiceItemJson> items,
@JsonProperty("isParentInvoice") final Boolean isParentInvoice,
+ @JsonProperty("parentInvoiceId") final String parentInvoiceId,
+ @JsonProperty("parentAccountId") final String parentAccountId,
@JsonProperty("auditLogs") @Nullable final List<AuditLogJson> auditLogs) {
super(auditLogs);
this.amount = amount;
@@ -90,6 +94,8 @@ public class InvoiceJson extends JsonBase {
this.credits = credits;
this.items = items;
this.isParentInvoice = isParentInvoice;
+ this.parentInvoiceId = parentInvoiceId;
+ this.parentAccountId = parentAccountId;
}
public InvoiceJson(final Invoice input) {
@@ -99,7 +105,10 @@ public class InvoiceJson extends JsonBase {
public InvoiceJson(final Invoice input, final String bundleKeys, final List<CreditJson> credits, final List<AuditLog> auditLogs) {
this(input.getChargedAmount(), input.getCurrency().toString(), input.getStatus().toString(), input.getCreditedAmount(), input.getRefundedAmount(),
input.getId().toString(), input.getInvoiceDate(), input.getTargetDate(), String.valueOf(input.getInvoiceNumber()),
- input.getBalance(), input.getAccountId().toString(), bundleKeys, credits, null, input.isParentInvoice(), toAuditLogJson(auditLogs));
+ input.getBalance(), input.getAccountId().toString(), bundleKeys, credits, null, input.isParentInvoice(),
+ input.getParentInvoiceId() != null ? input.getParentInvoiceId().toString() : null,
+ input.getParentAccountId() != null ? input.getParentAccountId().toString() : null,
+ toAuditLogJson(auditLogs));
}
public InvoiceJson(final Invoice input, final boolean withItems, final List<InvoiceItem> childItems, @Nullable final AccountAuditLogs accountAuditLogs) {
@@ -133,6 +142,9 @@ public class InvoiceJson extends JsonBase {
this.bundleKeys = null;
this.credits = null;
this.isParentInvoice = input.isParentInvoice();
+ this.parentInvoiceId = input.getParentInvoiceId() != null ? input.getParentInvoiceId().toString() : null;
+ this.parentAccountId = input.getParentAccountId() != null ? input.getParentAccountId().toString() : null;
+
}
public BigDecimal getAmount() {
@@ -195,6 +207,14 @@ public class InvoiceJson extends JsonBase {
return isParentInvoice;
}
+ public String getParentInvoiceId() {
+ return parentInvoiceId;
+ }
+
+ public String getParentAccountId() {
+ return parentAccountId;
+ }
+
@Override
public String toString() {
return "InvoiceJson{" +
@@ -213,6 +233,8 @@ public class InvoiceJson extends JsonBase {
", bundleKeys='" + bundleKeys + '\'' +
", credits=" + credits +
", isParentInvoice=" + isParentInvoice +
+ ", parentInvoiceId=" + parentInvoiceId +
+ ", parentAccountId=" + parentAccountId +
'}';
}
@@ -272,7 +294,12 @@ public class InvoiceJson extends JsonBase {
if (isParentInvoice != null ? !isParentInvoice.equals(that.isParentInvoice) : that.isParentInvoice != null) {
return false;
}
-
+ if (parentInvoiceId != null ? !parentInvoiceId.equals(that.parentInvoiceId) : that.parentInvoiceId != null) {
+ return false;
+ }
+ if (parentAccountId != null ? !parentAccountId.equals(that.parentAccountId) : that.parentAccountId != null) {
+ return false;
+ }
return true;
}
@@ -293,6 +320,8 @@ public class InvoiceJson extends JsonBase {
result = 31 * result + (bundleKeys != null ? bundleKeys.hashCode() : 0);
result = 31 * result + (credits != null ? credits.hashCode() : 0);
result = 31 * result + (isParentInvoice != null ? isParentInvoice.hashCode() : 0);
+ result = 31 * result + (parentInvoiceId != null ? parentInvoiceId.hashCode() : 0);
+ result = 31 * result + (parentAccountId != null ? parentAccountId.hashCode() : 0);
return result;
}
}
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/NotificationJson.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/NotificationJson.java
index 22fe656..2f5fbbe 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/NotificationJson.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/NotificationJson.java
@@ -33,23 +33,27 @@ public class NotificationJson {
private final String objectType;
@ApiModelProperty(dataType = "java.util.UUID")
private final String objectId;
+ private String metaData;
@JsonCreator
public NotificationJson(@JsonProperty("eventType") final String eventType,
@JsonProperty("accountId") final String accountId,
@JsonProperty("objectType") final String objectType,
- @JsonProperty("objectId") final String objectId) {
+ @JsonProperty("objectId") final String objectId,
+ @JsonProperty("metaData") final String metaData) {
this.eventType = eventType;
this.accountId = accountId;
this.objectType = objectType;
this.objectId = objectId;
+ this.metaData = metaData;
}
public NotificationJson(final ExtBusEvent event) {
this(event.getEventType().toString(),
event.getAccountId() != null ? event.getAccountId().toString() : null,
event.getObjectType().toString(),
- event.getObjectId() != null ? event.getObjectId().toString() : null);
+ event.getObjectId() != null ? event.getObjectId().toString() : null,
+ event.getMetaData());
}
public String getEventType() {
@@ -67,4 +71,8 @@ public class NotificationJson {
public String getObjectId() {
return objectId;
}
+
+ public String getMetaData() {
+ return metaData;
+ }
}
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/PaymentAttemptJson.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/PaymentAttemptJson.java
index f8150da..17763cf 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/PaymentAttemptJson.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/PaymentAttemptJson.java
@@ -1,5 +1,6 @@
/*
- * Copyright 2016 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
@@ -85,13 +86,14 @@ public class PaymentAttemptJson extends JsonBase {
public PaymentAttemptJson(final PaymentAttempt paymentAttempt, final String paymentExternalKey, @Nullable final List<AuditLog> attemptsLogs) {
this(paymentAttempt.getAccountId().toString(),
- paymentAttempt.getPaymentMethodId().toString(),
+ // Could be null if aborted in the priorCall
+ paymentAttempt.getPaymentMethodId() != null ? paymentAttempt.getPaymentMethodId().toString() : null,
paymentExternalKey,
paymentAttempt.getTransactionId() != null ? paymentAttempt.getTransactionId().toString() : null,
paymentAttempt.getTransactionExternalKey(),
paymentAttempt.getTransactionType().toString(),
paymentAttempt.getEffectiveDate(),
- paymentAttempt.getStateName() != null ? paymentAttempt.getStateName() : null,
+ paymentAttempt.getStateName(),
paymentAttempt.getAmount(),
paymentAttempt.getCurrency() != null ? paymentAttempt.getCurrency().toString() : null,
paymentAttempt.getPluginName(),
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 e6233f1..77d27f4 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
@@ -100,7 +100,7 @@ public class PhasePriceOverrideJson {
TierPriceOverrideJson tierPriceOverrideJson = new TierPriceOverrideJson(blockPriceOverridesJson);
tierPriceOverridesJson.add(tierPriceOverrideJson);
}
- final UsagePriceOverrideJson usagePriceOverrideJson = new UsagePriceOverrideJson(usage.getName(), usage.getUsageType(),usage.getBillingMode(), tierPriceOverridesJson);
+ final UsagePriceOverrideJson usagePriceOverrideJson = new UsagePriceOverrideJson(usage.getName(), usage.getUsageType(), usage.getBillingMode(), usage.getTierBlockPolicy(), tierPriceOverridesJson);
this.usagePriceOverrides.add(usagePriceOverrideJson);
}
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 f65431f..8afb59e 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
@@ -98,7 +98,7 @@ public class SubscriptionJson extends JsonBase {
@JsonCreator
public EventSubscriptionJson(@JsonProperty("eventId") final String eventId,
@JsonProperty("billingPeriod") final String billingPeriod,
- @JsonProperty("effectiveDt") final LocalDate effectiveDate,
+ @JsonProperty("effectiveDate") final LocalDate effectiveDate,
@JsonProperty("plan") final String plan,
@JsonProperty("product") final String product,
@JsonProperty("priceList") final String priceList,
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/TagDefinitionJson.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/TagDefinitionJson.java
index 453883e..779931c 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/TagDefinitionJson.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/TagDefinitionJson.java
@@ -17,6 +17,7 @@
package org.killbill.billing.jaxrs.json;
import java.util.List;
+import java.util.Set;
import javax.annotation.Nullable;
@@ -29,6 +30,7 @@ import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.base.Function;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
import io.swagger.annotations.ApiModelProperty;
public class TagDefinitionJson extends JsonBase {
@@ -40,14 +42,14 @@ public class TagDefinitionJson extends JsonBase {
private final String name;
@ApiModelProperty(required = true)
private final String description;
- private final List<String> applicableObjectTypes;
+ private final Set<String> applicableObjectTypes;
@JsonCreator
public TagDefinitionJson(@JsonProperty("id") final String id,
@JsonProperty("isControlTag") final Boolean isControlTag,
@JsonProperty("name") final String name,
@JsonProperty("description") @Nullable final String description,
- @JsonProperty("applicableObjectTypes") @Nullable final List<String> applicableObjectTypes,
+ @JsonProperty("applicableObjectTypes") @Nullable final Set<String> applicableObjectTypes,
@JsonProperty("auditLogs") @Nullable final List<AuditLogJson> auditLogs) {
super(auditLogs);
this.id = id;
@@ -62,7 +64,7 @@ public class TagDefinitionJson extends JsonBase {
tagDefinition.isControlTag(),
tagDefinition.getName(),
tagDefinition.getDescription(),
- ImmutableList.<String>copyOf(Collections2.transform(tagDefinition.getApplicableObjectTypes(), new Function<ObjectType, String>() {
+ ImmutableSet.<String>copyOf(Collections2.transform(tagDefinition.getApplicableObjectTypes(), new Function<ObjectType, String>() {
@Override
public String apply(@Nullable final ObjectType input) {
if (input == null) {
@@ -92,7 +94,7 @@ public class TagDefinitionJson extends JsonBase {
return description;
}
- public List<String> getApplicableObjectTypes() {
+ public Set<String> getApplicableObjectTypes() {
return applicableObjectTypes;
}
@@ -156,4 +158,13 @@ public class TagDefinitionJson extends JsonBase {
result = 31 * result + (applicableObjectTypes != null ? applicableObjectTypes.hashCode() : 0);
return result;
}
+
+ public static Set<ObjectType> toObjectType(final Set<String> applicableObjectTypes) {
+ return ImmutableSet.copyOf(Collections2.transform(applicableObjectTypes, new Function<String, ObjectType>() {
+ @Override
+ public ObjectType apply(final String input) {
+ return ObjectType.valueOf(input);
+ }
+ }));
+ }
}
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/UsagePriceOverrideJson.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/UsagePriceOverrideJson.java
index c101f63..bbf1a50 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/UsagePriceOverrideJson.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/UsagePriceOverrideJson.java
@@ -22,6 +22,7 @@ import java.util.List;
import javax.annotation.Nullable;
import org.killbill.billing.catalog.api.BillingMode;
+import org.killbill.billing.catalog.api.TierBlockPolicy;
import org.killbill.billing.catalog.api.UsageType;
import com.fasterxml.jackson.annotation.JsonCreator;
@@ -35,16 +36,20 @@ public class UsagePriceOverrideJson {
private final BillingMode billingMode;
+ private final TierBlockPolicy tierBlockPolicy;
+
private final List<TierPriceOverrideJson> tierPriceOverrides;
@JsonCreator
public UsagePriceOverrideJson(@JsonProperty("usageName") final String usageName,
- @Nullable @JsonProperty("usageType") final UsageType usageType,
- @Nullable @JsonProperty("billingMode") final BillingMode billingMode,
- @Nullable @JsonProperty("tierPriceOverrides") final List<TierPriceOverrideJson> tierPriceOverrides ) {
+ @Nullable @JsonProperty("usageType") final UsageType usageType,
+ @Nullable @JsonProperty("billingMode") final BillingMode billingMode,
+ @Nullable @JsonProperty("tierBlockPolicy") final TierBlockPolicy tierBlockPolicy,
+ @Nullable @JsonProperty("tierPriceOverrides") final List<TierPriceOverrideJson> tierPriceOverrides) {
this.usageName = usageName;
this.usageType = usageType;
this.billingMode = billingMode;
+ this.tierBlockPolicy = tierBlockPolicy;
this.tierPriceOverrides = tierPriceOverrides;
}
@@ -60,6 +65,10 @@ public class UsagePriceOverrideJson {
return billingMode;
}
+ public TierBlockPolicy getTierBlockPolicy() {
+ return tierBlockPolicy;
+ }
+
public List<TierPriceOverrideJson> getTierPriceOverrides() {
return tierPriceOverrides;
}
@@ -70,6 +79,7 @@ public class UsagePriceOverrideJson {
"usageName='" + usageName + '\'' +
"usageType='" + usageType + '\'' +
", billingMode=" + billingMode +
+ ", tierBlockPolicy=" + tierBlockPolicy +
", tierPriceOverrides=" + tierPriceOverrides +
'}';
}
@@ -94,6 +104,9 @@ public class UsagePriceOverrideJson {
if (billingMode != null ? !billingMode.equals(that.billingMode) : that.billingMode != null) {
return false;
}
+ if (tierBlockPolicy != null ? !tierBlockPolicy.equals(that.tierBlockPolicy) : that.tierBlockPolicy != null) {
+ return false;
+ }
if (tierPriceOverrides != null ? !tierPriceOverrides.equals(that.tierPriceOverrides) : that.tierPriceOverrides != null) {
return false;
}
@@ -105,6 +118,7 @@ public class UsagePriceOverrideJson {
int result = usageName != null ? usageName.hashCode() : 0;
result = 31 * result + (usageType != null ? usageType.hashCode() : 0);
result = 31 * result + (billingMode != null ? billingMode.hashCode() : 0);
+ result = 31 * result + (tierBlockPolicy != null ? tierBlockPolicy.hashCode() : 0);
result = 31 * result + (tierPriceOverrides != null ? tierPriceOverrides.hashCode() : 0);
return result;
}
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/mappers/ExceptionMapperBase.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/mappers/ExceptionMapperBase.java
index 95a7a37..c4b79cd 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/mappers/ExceptionMapperBase.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/mappers/ExceptionMapperBase.java
@@ -38,7 +38,6 @@ import org.killbill.billing.subscription.api.SubscriptionBillingApiException;
import org.killbill.billing.subscription.api.timeline.SubscriptionBaseRepairException;
import org.killbill.billing.util.api.TagApiException;
import org.killbill.billing.util.api.TagDefinitionApiException;
-import org.killbill.billing.util.email.EmailApiException;
import org.killbill.billing.util.jackson.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -75,9 +74,6 @@ public abstract class ExceptionMapperBase {
} else if (cause instanceof CatalogApiException) {
final CatalogApiExceptionMapper mapper = new CatalogApiExceptionMapper(uriInfo);
return mapper.toResponse((CatalogApiException) cause);
- } else if (cause instanceof EmailApiException) {
- final EmailApiExceptionMapper mapper = new EmailApiExceptionMapper(uriInfo);
- return mapper.toResponse((EmailApiException) cause);
} else if (cause instanceof EntitlementApiException) {
final EntitlementApiExceptionMapper mapper = new EntitlementApiExceptionMapper(uriInfo);
return mapper.toResponse((EntitlementApiException) cause);
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 e7276f2..c65a637 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
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2016 Groupon, Inc
- * Copyright 2014-2016 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
@@ -59,16 +59,22 @@ import org.killbill.billing.account.api.AccountData;
import org.killbill.billing.account.api.AccountEmail;
import org.killbill.billing.account.api.AccountUserApi;
import org.killbill.billing.account.api.MutableAccountData;
+import org.killbill.billing.catalog.api.BillingActionPolicy;
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.entitlement.api.BlockingState;
import org.killbill.billing.entitlement.api.BlockingStateType;
+import org.killbill.billing.entitlement.api.Entitlement.EntitlementActionPolicy;
import org.killbill.billing.entitlement.api.EntitlementApiException;
+import org.killbill.billing.entitlement.api.Subscription;
import org.killbill.billing.entitlement.api.SubscriptionApi;
import org.killbill.billing.entitlement.api.SubscriptionApiException;
import org.killbill.billing.entitlement.api.SubscriptionBundle;
import org.killbill.billing.invoice.api.Invoice;
import org.killbill.billing.invoice.api.InvoiceApiException;
+import org.killbill.billing.invoice.api.InvoiceItem;
+import org.killbill.billing.invoice.api.InvoiceItemType;
import org.killbill.billing.invoice.api.InvoicePayment;
import org.killbill.billing.invoice.api.InvoicePaymentApi;
import org.killbill.billing.invoice.api.InvoiceUserApi;
@@ -115,6 +121,7 @@ import org.killbill.billing.util.callcontext.CallContext;
import org.killbill.billing.util.callcontext.TenantContext;
import org.killbill.billing.util.config.definition.JaxrsConfig;
import org.killbill.billing.util.config.definition.PaymentConfig;
+import org.killbill.billing.util.customfield.CustomField;
import org.killbill.billing.util.entity.Pagination;
import org.killbill.billing.util.tag.ControlTagType;
import org.killbill.billing.util.tag.Tag;
@@ -360,6 +367,7 @@ public class AccountResource extends JaxRsResourceBase {
return uriBuilder.buildResponse(uriInfo, AccountResource.class, "getAccount", account.getId(), request);
}
+
@TimedResource
@PUT
@Consumes(APPLICATION_JSON)
@@ -386,25 +394,72 @@ public class AccountResource extends JaxRsResourceBase {
return getAccount(accountIdStr, false, false, new AuditMode(AuditLevel.NONE.toString()), request);
}
- // Not supported
+
@TimedResource
@DELETE
@Path("/{accountId:" + UUID_PATTERN + "}")
@Produces(APPLICATION_JSON)
- @ApiOperation(value = "Delete account", hidden = true)
+ @ApiOperation(value = "Close account")
@ApiResponses(value = {@ApiResponse(code = 400, message = "Invalid account id supplied")})
- public Response cancelAccount(@PathParam("accountId") final String accountId,
- @javax.ws.rs.core.Context final HttpServletRequest request) {
- /*
- try {
- accountUserApi.cancelAccount(accountId);
- return Response.status(Status.NO_CONTENT).build();
- } catch (AccountApiException e) {
- log.info(String.format("Failed to cancel account %s", accountId), e);
- return Response.status(Status.BAD_REQUEST).build();
+ public Response closeAccount(@PathParam(QUERY_ACCOUNT_ID) final String accountIdStr,
+ @QueryParam(QUERY_CANCEL_ALL_SUBSCRIPTIONS) @DefaultValue("false") final Boolean cancelAllSubscriptions,
+ @QueryParam(QUERY_WRITE_OFF_UNPAID_INVOICES) @DefaultValue("false") final Boolean writeOffUnpaidInvoices,
+ @QueryParam(QUERY_ITEM_ADJUST_UNPAID_INVOICES) @DefaultValue("false") final Boolean itemAdjustUnpaidInvoices,
+ @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 SubscriptionApiException, AccountApiException, EntitlementApiException, InvoiceApiException, TagApiException {
+
+ final UUID accountId = UUID.fromString(accountIdStr);
+ final CallContext callContext = context.createCallContextWithAccountId(accountId, createdBy, reason, comment, request);
+
+ if (cancelAllSubscriptions) {
+ final List<SubscriptionBundle> bundles = subscriptionApi.getSubscriptionBundlesForAccountId(accountId, callContext);
+ final Iterable<Subscription> subscriptions = Iterables.concat(Iterables.transform(bundles, new Function<SubscriptionBundle, List<Subscription>>() {
+ @Override
+ public List<Subscription> apply(final SubscriptionBundle input) {
+ return input.getSubscriptions();
+ }
+ }));
+
+ final Iterable<Subscription> toBeCancelled = Iterables.filter(subscriptions, new Predicate<Subscription>() {
+ @Override
+ public boolean apply(final Subscription input) {
+ return input.getLastActiveProductCategory() != ProductCategory.ADD_ON && input.getBillingEndDate() == null;
+ }
+ });
+ for (final Subscription cur : toBeCancelled) {
+ cur.cancelEntitlementWithPolicyOverrideBillingPolicy(EntitlementActionPolicy.IMMEDIATE, BillingActionPolicy.END_OF_TERM, ImmutableList.<PluginProperty>of(), callContext);
+ }
}
- */
- return Response.status(Status.INTERNAL_SERVER_ERROR).build();
+
+ final Collection<Invoice> unpaidInvoices = writeOffUnpaidInvoices || itemAdjustUnpaidInvoices ? invoiceApi.getUnpaidInvoicesByAccountId(accountId, null, callContext) : ImmutableList.<Invoice>of();
+ if (writeOffUnpaidInvoices) {
+ for (final Invoice cur : unpaidInvoices) {
+ invoiceApi.tagInvoiceAsWrittenOff(cur.getId(), callContext);
+ }
+ } else if (itemAdjustUnpaidInvoices) {
+
+ final List<InvoiceItemType> ADJUSTABLE_TYPES = ImmutableList.<InvoiceItemType>of(InvoiceItemType.EXTERNAL_CHARGE,
+ InvoiceItemType.FIXED,
+ InvoiceItemType.RECURRING,
+ InvoiceItemType.TAX,
+ InvoiceItemType.USAGE,
+ InvoiceItemType.PARENT_SUMMARY);
+ final String description = comment != null ? comment : "Close Account";
+ for (final Invoice invoice : unpaidInvoices) {
+ for (final InvoiceItem item : invoice.getInvoiceItems()) {
+ if (ADJUSTABLE_TYPES.contains(item.getInvoiceItemType())) {
+ invoiceApi.insertInvoiceItemAdjustment(accountId, invoice.getId(), item.getId(), clock.getUTCToday(), description, callContext);
+ }
+ }
+ }
+ }
+
+ final BlockingStateJson blockingState = new BlockingStateJson(accountIdStr, "CLOSE_ACCOUNT", "account-service", true, false, false, null, BlockingStateType.ACCOUNT, null);
+ addBlockingState(blockingState, accountIdStr, BlockingStateType.ACCOUNT, null, ImmutableList.<String>of(), createdBy, reason, comment, request);
+
+ return Response.status(Status.OK).build();
}
@TimedResource
@@ -766,6 +821,7 @@ public class AccountResource extends JaxRsResourceBase {
@PathParam("accountId") final String accountIdStr,
@QueryParam(QUERY_PAYMENT_METHOD_IS_DEFAULT) @DefaultValue("false") final Boolean isDefault,
@QueryParam(QUERY_PAY_ALL_UNPAID_INVOICES) @DefaultValue("false") final Boolean payAllUnpaidInvoices,
+ @QueryParam(QUERY_PAYMENT_CONTROL_PLUGIN_NAME) final List<String> paymentControlPluginNames,
@QueryParam(QUERY_PLUGIN_PROPERTY) final List<String> pluginPropertiesString,
@HeaderParam(HDR_CREATED_BY) final String createdBy,
@HeaderParam(HDR_REASON) final String reason,
@@ -790,7 +846,9 @@ public class AccountResource extends JaxRsResourceBase {
return Response.status(Status.BAD_REQUEST).build();
}
- final UUID paymentMethodId = paymentApi.addPaymentMethod(account, data.getExternalKey(), data.getPluginName(), isDefault, data.getPluginDetail(), pluginProperties, callContext);
+
+ final PaymentOptions paymentOptions = createControlPluginApiPaymentOptions(paymentControlPluginNames);
+ final UUID paymentMethodId = paymentApi.addPaymentMethodWithPaymentControl(account, data.getExternalKey(), data.getPluginName(), isDefault, data.getPluginDetail(), pluginProperties, paymentOptions, callContext);
if (payAllUnpaidInvoices && unpaidInvoices.size() > 0) {
for (final Invoice invoice : unpaidInvoices) {
createPurchaseForInvoice(account, invoice.getId(), invoice.getBalance(), paymentMethodId, false, null, null, pluginProperties, callContext);
@@ -809,6 +867,7 @@ public class AccountResource extends JaxRsResourceBase {
public Response getPaymentMethods(@PathParam("accountId") final String accountIdStr,
@QueryParam(QUERY_WITH_PLUGIN_INFO) @DefaultValue("false") final Boolean withPluginInfo,
@QueryParam(QUERY_PLUGIN_PROPERTY) final List<String> pluginPropertiesString,
+ @QueryParam(QUERY_INCLUDED_DELETED) @DefaultValue("false") final Boolean includedDeleted,
@QueryParam(QUERY_AUDIT) @DefaultValue("NONE") final AuditMode auditMode,
@javax.ws.rs.core.Context final HttpServletRequest request) throws AccountApiException, PaymentApiException {
final Iterable<PluginProperty> pluginProperties = extractPluginProperties(pluginPropertiesString);
@@ -816,7 +875,7 @@ public class AccountResource extends JaxRsResourceBase {
final TenantContext tenantContext = context.createTenantContextWithAccountId(accountId, request);
final Account account = accountUserApi.getAccountById(accountId, tenantContext);
- final List<PaymentMethod> methods = paymentApi.getAccountPaymentMethods(account.getId(), withPluginInfo, pluginProperties, tenantContext);
+ final List<PaymentMethod> methods = paymentApi.getAccountPaymentMethods(account.getId(), includedDeleted, withPluginInfo, pluginProperties, tenantContext);
final AccountAuditLogs accountAuditLogs = auditUserApi.getAccountAuditLogs(account.getId(), auditMode.getLevel(), tenantContext);
final List<PaymentMethodJson> json = new ArrayList<PaymentMethodJson>(Collections2.transform(methods, new Function<PaymentMethod, PaymentMethodJson>() {
@Override
@@ -1025,17 +1084,17 @@ public class AccountResource extends JaxRsResourceBase {
final Payment result;
switch (transactionType) {
case AUTHORIZE:
- result = paymentApi.createAuthorizationWithPaymentControl(account, paymentMethodId, paymentId, json.getAmount(), currency,
+ result = paymentApi.createAuthorizationWithPaymentControl(account, paymentMethodId, paymentId, json.getAmount(), currency, json.getEffectiveDate(),
json.getPaymentExternalKey(), json.getTransactionExternalKey(),
pluginProperties, paymentOptions, callContext);
break;
case PURCHASE:
- result = paymentApi.createPurchaseWithPaymentControl(account, paymentMethodId, paymentId, json.getAmount(), currency,
+ result = paymentApi.createPurchaseWithPaymentControl(account, paymentMethodId, paymentId, json.getAmount(), currency, json.getEffectiveDate(),
json.getPaymentExternalKey(), json.getTransactionExternalKey(),
pluginProperties, paymentOptions, callContext);
break;
case CREDIT:
- result = paymentApi.createCreditWithPaymentControl(account, paymentMethodId, paymentId, json.getAmount(), currency,
+ result = paymentApi.createCreditWithPaymentControl(account, paymentMethodId, paymentId, json.getAmount(), currency, json.getEffectiveDate(),
json.getPaymentExternalKey(), json.getTransactionExternalKey(),
pluginProperties, paymentOptions, callContext);
break;
@@ -1135,6 +1194,26 @@ public class AccountResource extends JaxRsResourceBase {
}
@TimedResource
+ @GET
+ @Path("/{accountId:" + UUID_PATTERN + "}/" + ALL_CUSTOM_FIELDS)
+ @Produces(APPLICATION_JSON)
+ @ApiOperation(value = "Retrieve account customFields", response = CustomFieldJson.class, responseContainer = "List")
+ @ApiResponses(value = {@ApiResponse(code = 400, message = "Invalid account id supplied"),
+ @ApiResponse(code = 404, message = "Account not found")})
+ public Response getAllCustomFields(@PathParam(ID_PARAM_NAME) final String accountIdString,
+ @QueryParam(QUERY_OBJECT_TYPE) final ObjectType objectType,
+ @QueryParam(QUERY_AUDIT) @DefaultValue("NONE") final AuditMode auditMode,
+ @javax.ws.rs.core.Context final HttpServletRequest request) {
+ final UUID accountId = UUID.fromString(accountIdString);
+
+ final TenantContext tenantContext = context.createTenantContextWithAccountId(accountId, request);
+ final List<CustomField> customFields = objectType != null ?
+ customFieldUserApi.getCustomFieldsForAccountType(accountId, objectType, tenantContext) :
+ customFieldUserApi.getCustomFieldsForAccount(accountId, tenantContext);
+ return createCustomFieldResponse(customFields, auditMode, tenantContext);
+ }
+
+ @TimedResource
@POST
@Path("/{accountId:" + UUID_PATTERN + "}/" + CUSTOM_FIELDS)
@Consumes(APPLICATION_JSON)
@@ -1153,6 +1232,25 @@ public class AccountResource extends JaxRsResourceBase {
comment, request), uriInfo, request);
}
+
+ @TimedResource
+ @PUT
+ @Path("/{accountId:" + UUID_PATTERN + "}/" + CUSTOM_FIELDS)
+ @Consumes(APPLICATION_JSON)
+ @Produces(APPLICATION_JSON)
+ @ApiOperation(value = "Modify custom fields to account")
+ @ApiResponses(value = {@ApiResponse(code = 400, message = "Invalid account id supplied")})
+ public Response modifyCustomFields(@PathParam(ID_PARAM_NAME) final String accountIdStr,
+ final List<CustomFieldJson> customFields,
+ @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 CustomFieldApiException {
+ final UUID accountId = UUID.fromString(accountIdStr);
+ return super.modifyCustomFields(accountId, customFields, context.createCallContextWithAccountId(accountId, createdBy, reason,
+ comment, request));
+ }
+
@TimedResource
@DELETE
@Path("/{accountId:" + UUID_PATTERN + "}/" + CUSTOM_FIELDS)
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 6c02300..f85946b 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
@@ -61,6 +61,7 @@ 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.server.healthchecks.KillbillHealthcheck;
import org.killbill.billing.tenant.api.Tenant;
import org.killbill.billing.tenant.api.TenantApiException;
import org.killbill.billing.tenant.api.TenantUserApi;
@@ -115,6 +116,7 @@ public class AdminResource extends JaxRsResourceBase {
private final RecordIdApi recordIdApi;
private final PersistentBus persistentBus;
private final NotificationQueueService notificationQueueService;
+ private final KillbillHealthcheck killbillHealthcheck;
@Inject
public AdminResource(final JaxrsUriBuilder uriBuilder,
@@ -130,6 +132,7 @@ public class AdminResource extends JaxRsResourceBase {
final RecordIdApi recordIdApi,
final PersistentBus persistentBus,
final NotificationQueueService notificationQueueService,
+ final KillbillHealthcheck killbillHealthcheck,
final Clock clock,
final Context context) {
super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, null, clock, context);
@@ -140,6 +143,7 @@ public class AdminResource extends JaxRsResourceBase {
this.cacheControllerDispatcher = cacheControllerDispatcher;
this.persistentBus = persistentBus;
this.notificationQueueService = notificationQueueService;
+ this.killbillHealthcheck = killbillHealthcheck;
}
@GET
@@ -422,6 +426,26 @@ public class AdminResource extends JaxRsResourceBase {
return Response.status(Status.OK).build();
}
+ @POST
+ @Path("/" + HEALTHCHECK)
+ @Produces(APPLICATION_JSON)
+ @ApiOperation(value = "Put the host out of rotation")
+ @ApiResponses(value = {})
+ public Response putInRotation(@javax.ws.rs.core.Context final HttpServletRequest request) {
+ killbillHealthcheck.putInRotation();
+ return Response.status(Status.OK).build();
+ }
+
+ @DELETE
+ @Path("/" + HEALTHCHECK)
+ @Produces(APPLICATION_JSON)
+ @ApiOperation(value = "Put the host out of rotation")
+ @ApiResponses(value = {})
+ public Response putOutOfRotation(@javax.ws.rs.core.Context final HttpServletRequest request) {
+ killbillHealthcheck.putOutOfRotation();
+ return Response.status(Status.OK).build();
+ }
+
private Iterable<NotificationEventWithMetadata<NotificationEvent>> getNotifications(@Nullable final String queueName,
@Nullable final String serviceName,
final boolean includeInProcessing,
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 8f37bb6..cff17bc 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
@@ -324,6 +324,26 @@ public class BundleResource extends JaxRsResourceBase {
}
@TimedResource
+ @PUT
+ @Path("/{bundleId:" + UUID_PATTERN + "}/" + CUSTOM_FIELDS)
+ @Consumes(APPLICATION_JSON)
+ @Produces(APPLICATION_JSON)
+ @ApiOperation(value = "Modify custom fields to bundle")
+ @ApiResponses(value = {@ApiResponse(code = 400, message = "Invalid bundle id supplied")})
+ public Response modifyCustomFields(@PathParam(ID_PARAM_NAME) final String id,
+ final List<CustomFieldJson> customFields,
+ @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 CustomFieldApiException {
+ return super.modifyCustomFields(UUID.fromString(id), customFields,
+ context.createCallContextNoAccountId(createdBy, reason, comment, request));
+ }
+
+
+
+
+ @TimedResource
@DELETE
@Path("/{bundleId:" + UUID_PATTERN + "}/" + CUSTOM_FIELDS)
@Consumes(APPLICATION_JSON)
@@ -391,6 +411,35 @@ public class BundleResource extends JaxRsResourceBase {
return uriBuilder.buildResponse(uriInfo, BundleResource.class, "getBundle", newBundleId, request);
}
+
+ @TimedResource
+ @PUT
+ @Path("/{bundleId:" + UUID_PATTERN + "}/" + RENAME_KEY)
+ @Consumes(APPLICATION_JSON)
+ @ApiOperation(value = "Update a bundle externalKey")
+ @ApiResponses(value = {@ApiResponse(code = 400, message = "Invalid argumnent supplied"),
+ @ApiResponse(code = 404, message = "Bundle not found")})
+ public Response renameExternalKey(final BundleJson json,
+ @PathParam(ID_PARAM_NAME) final String id,
+ /* @QueryParam(QUERY_PLUGIN_PROPERTY) final List<String> pluginPropertiesString, */
+ @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 EntitlementApiException {
+
+ verifyNonNullOrEmpty(json, "BundleJson body should be specified");
+ verifyNonNullOrEmpty(json.getExternalKey(), "BundleJson externalKey needs to be set");
+
+ final UUID bundleId = UUID.fromString(id);
+
+ final CallContext callContext = context.createCallContextNoAccountId(createdBy, reason, comment, request);
+ subscriptionApi.updateExternalKey(bundleId, json.getExternalKey(), callContext);
+ return uriBuilder.buildResponse(uriInfo, BundleResource.class, "getBundle", bundleId, request);
+ }
+
+
+
@TimedResource
@POST
@Path("/{bundleId:" + UUID_PATTERN + "}/" + TAGS)
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 b2be1ca..4eea0e9 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
@@ -22,6 +22,7 @@ import java.util.List;
import javax.annotation.Nullable;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.POST;
@@ -49,9 +50,12 @@ import org.killbill.billing.jaxrs.json.SimplePlanJson;
import org.killbill.billing.jaxrs.util.Context;
import org.killbill.billing.jaxrs.util.JaxrsUriBuilder;
import org.killbill.billing.payment.api.PaymentApi;
+import org.killbill.billing.tenant.api.TenantApiException;
+import org.killbill.billing.tenant.api.TenantKV.TenantKey;
import org.killbill.billing.util.api.AuditUserApi;
import org.killbill.billing.util.api.CustomFieldUserApi;
import org.killbill.billing.util.api.TagUserApi;
+import org.killbill.billing.util.cache.Cachable.CacheType;
import org.killbill.billing.util.callcontext.CallContext;
import org.killbill.billing.util.callcontext.TenantContext;
import org.killbill.clock.Clock;
@@ -62,6 +66,7 @@ import com.google.inject.Inject;
import com.google.inject.Singleton;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
@@ -216,8 +221,23 @@ public class CatalogResource extends JaxRsResourceBase {
simplePlan.getTrialLength(),
simplePlan.getTrialTimeUnit(),
simplePlan.getAvailableBaseProducts());
- catalogUserApi.addSimplePlan(desc, clock.getUTCNow(), callContext);
+ catalogUserApi.addSimplePlan(desc, null, callContext);
return uriBuilder.buildResponse(uriInfo, CatalogResource.class, null, null, request);
}
+
+ @DELETE
+ @ApiOperation(value = "Delete all versions for a per tenant catalog")
+ @ApiResponses(value = {})
+ public Response deleteCatalog(@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 TenantApiException, CatalogApiException {
+ final CallContext callContext = context.createCallContextNoAccountId(createdBy, reason, comment, request);
+ catalogUserApi.deleteCatalog(callContext);
+ return Response.status(Status.OK).build();
+ }
+
+
}
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 1dc5bdf..bc87d1e 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
@@ -87,7 +87,7 @@ public abstract class ComboPaymentResource extends JaxRsResourceBase {
}
// Get all payment methods for account
- final List<PaymentMethod> accountPaymentMethods = paymentApi.getAccountPaymentMethods(account.getId(), false, ImmutableList.<PluginProperty>of(), callContext);
+ final List<PaymentMethod> accountPaymentMethods = paymentApi.getAccountPaymentMethods(account.getId(), false, false, ImmutableList.<PluginProperty>of(), callContext);
// If we were specified a paymentMethod id and we find it, we return it
if (paymentMethodJson.getPaymentMethodId() != null) {
@@ -123,13 +123,4 @@ public abstract class ComboPaymentResource extends JaxRsResourceBase {
paymentData.getPluginDetail(), pluginProperties, callContext);
}
- protected Payment getPaymentByIdOrKey(@Nullable final String paymentIdStr, @Nullable final String externalKey, final Iterable<PluginProperty> pluginProperties, final TenantContext tenantContext) throws PaymentApiException {
- Preconditions.checkArgument(paymentIdStr != null || externalKey != null, "Need to set either paymentId or payment externalKey");
- if (paymentIdStr != null) {
- final UUID paymentId = UUID.fromString(paymentIdStr);
- return paymentApi.getPayment(paymentId, false, false, pluginProperties, tenantContext);
- } else {
- return paymentApi.getPaymentByExternalKey(externalKey, false, false, pluginProperties, tenantContext);
- }
- }
}
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 440d83a..a98cea9 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
@@ -19,6 +19,7 @@
package org.killbill.billing.jaxrs.resources;
import java.math.BigDecimal;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
@@ -33,11 +34,13 @@ 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;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.UriInfo;
import org.killbill.billing.ObjectType;
@@ -51,6 +54,7 @@ import org.killbill.billing.jaxrs.json.CustomFieldJson;
import org.killbill.billing.jaxrs.json.InvoiceItemJson;
import org.killbill.billing.jaxrs.json.InvoicePaymentJson;
import org.killbill.billing.jaxrs.json.InvoicePaymentTransactionJson;
+import org.killbill.billing.jaxrs.json.PaymentTransactionJson;
import org.killbill.billing.jaxrs.json.TagJson;
import org.killbill.billing.jaxrs.util.Context;
import org.killbill.billing.jaxrs.util.JaxrsUriBuilder;
@@ -191,11 +195,11 @@ public class InvoicePaymentResource extends JaxRsResourceBase {
Iterables.addAll(pluginPropertiesForExternalRefund, pluginProperties);
pluginPropertiesForExternalRefund.add(new PluginProperty("IPCD_PAYMENT_ID", paymentUuid, false));
- result = paymentApi.createCreditWithPaymentControl(account, externalPaymentMethodId, null, json.getAmount(), account.getCurrency(),
+ result = paymentApi.createCreditWithPaymentControl(account, externalPaymentMethodId, null, json.getAmount(), account.getCurrency(), json.getEffectiveDate(),
paymentExternalKey, transactionExternalKey, pluginPropertiesForExternalRefund,
createInvoicePaymentControlPluginApiPaymentOptions(true), callContext);
} else {
- result = paymentApi.createRefundWithPaymentControl(account, payment.getId(), json.getAmount(), account.getCurrency(), transactionExternalKey,
+ result = paymentApi.createRefundWithPaymentControl(account, payment.getId(), json.getAmount(), account.getCurrency(), json.getEffectiveDate(), transactionExternalKey,
pluginProperties, createInvoicePaymentControlPluginApiPaymentOptions(false), callContext);
}
@@ -226,7 +230,7 @@ public class InvoicePaymentResource extends JaxRsResourceBase {
final Account account = accountUserApi.getAccountById(payment.getAccountId(), callContext);
final String transactionExternalKey = json.getTransactionExternalKey() != null ? json.getTransactionExternalKey() : UUIDs.randomUUID().toString();
- final Payment result = paymentApi.createChargebackWithPaymentControl(account, payment.getId(), json.getAmount(), account.getCurrency(),
+ final Payment result = paymentApi.createChargebackWithPaymentControl(account, payment.getId(), json.getAmount(), account.getCurrency(), json.getEffectiveDate(),
transactionExternalKey, createInvoicePaymentControlPluginApiPaymentOptions(false), callContext);
return uriBuilder.buildResponse(uriInfo, InvoicePaymentResource.class, "getInvoicePayment", result.getId(), request);
}
@@ -254,10 +258,66 @@ public class InvoicePaymentResource extends JaxRsResourceBase {
final Payment payment = paymentApi.getPayment(paymentUuid, false, false, ImmutableList.<PluginProperty>of(), callContext);
final Account account = accountUserApi.getAccountById(payment.getAccountId(), callContext);
- final Payment result = paymentApi.createChargebackReversalWithPaymentControl(account, payment.getId(), json.getTransactionExternalKey(), createInvoicePaymentControlPluginApiPaymentOptions(false), callContext);
+ final Payment result = paymentApi.createChargebackReversalWithPaymentControl(account, payment.getId(), json.getEffectiveDate(), json.getTransactionExternalKey(), createInvoicePaymentControlPluginApiPaymentOptions(false), callContext);
return uriBuilder.buildResponse(uriInfo, InvoicePaymentResource.class, "getInvoicePayment", result.getId(), request);
}
+
+ @TimedResource(name = "completeInvoicePaymentTransaction")
+ @PUT
+ @Path("/{paymentId:" + UUID_PATTERN + "}")
+ @Consumes(APPLICATION_JSON)
+ @Produces(APPLICATION_JSON)
+ @ApiOperation(value = "Complete an existing transaction")
+ @ApiResponses(value = {@ApiResponse(code = 201, message = "Payment transaction created successfully"),
+ @ApiResponse(code = 400, message = "Invalid paymentId supplied"),
+ @ApiResponse(code = 404, message = "Account or payment not found"),
+ @ApiResponse(code = 402, message = "Transaction declined by gateway"),
+ @ApiResponse(code = 422, message = "Payment is aborted by a control plugin"),
+ @ApiResponse(code = 502, message = "Failed to submit payment transaction"),
+ @ApiResponse(code = 503, message = "Payment in unknown status, failed to receive gateway response"),
+ @ApiResponse(code = 504, message = "Payment operation timeout")})
+ public Response completeInvoicePaymentTransaction(final PaymentTransactionJson json,
+ @PathParam("paymentId") final String paymentIdStr,
+ @QueryParam(QUERY_PAYMENT_CONTROL_PLUGIN_NAME) final List<String> paymentControlPluginNames,
+ @QueryParam(QUERY_PLUGIN_PROPERTY) final List<String> pluginPropertiesString,
+ @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 PaymentApiException, AccountApiException {
+
+ final TenantContext tenantContext = context.createTenantContextNoAccountId(request);
+
+ final UUID paymentId = UUID.fromString(paymentIdStr);
+
+ final Payment payment = paymentApi.getPayment(paymentId, false, false, ImmutableList.<PluginProperty>of(), tenantContext);
+ final List<InvoicePayment> invoicePayments = invoicePaymentApi.getInvoicePayments(paymentId, tenantContext);
+
+ final InvoicePayment originalInvoicePaymentAttempt = Iterables.tryFind(invoicePayments, new Predicate<InvoicePayment>() {
+ @Override
+ public boolean apply(final InvoicePayment input) {
+ return input.getType() == InvoicePaymentType.ATTEMPT && !input.isSuccess();
+ }
+ }).orNull();
+
+ final UUID invoiceId = originalInvoicePaymentAttempt != null ? originalInvoicePaymentAttempt.getInvoiceId() : null;
+ if (invoiceId == null) {
+ return Response.status(Status.NOT_FOUND).build();
+ }
+
+ final PluginProperty invoiceProperty = new PluginProperty("IPCD_INVOICE_ID" /* InvoicePaymentControlPluginApi.PROP_IPCD_INVOICE_ID (contract with plugin) */,
+ invoiceId.toString(), false);
+ final Iterable<PluginProperty> pluginProperties = extractPluginProperties(pluginPropertiesString, invoiceProperty);
+
+ final List<String> controlPluginNames = new ArrayList<String>();
+ controlPluginNames.add("__INVOICE_PAYMENT_CONTROL_PLUGIN__");
+ controlPluginNames.addAll(paymentControlPluginNames);
+
+ return completeTransactionInternal(json, payment, controlPluginNames, pluginProperties, tenantContext, createdBy, reason, comment, uriInfo, request);
+ }
+
+
@TimedResource
@GET
@Path("/{paymentId:" + UUID_PATTERN + "}/" + CUSTOM_FIELDS)
@@ -288,6 +348,25 @@ public class InvoicePaymentResource extends JaxRsResourceBase {
context.createCallContextNoAccountId(createdBy, reason, comment, request), uriInfo, request);
}
+
+ @TimedResource
+ @PUT
+ @Path("/{paymentId:" + UUID_PATTERN + "}/" + CUSTOM_FIELDS)
+ @Consumes(APPLICATION_JSON)
+ @Produces(APPLICATION_JSON)
+ @ApiOperation(value = "Modify custom fields to payment")
+ @ApiResponses(value = {@ApiResponse(code = 400, message = "Invalid payment id supplied")})
+ public Response modifyCustomFields(@PathParam(ID_PARAM_NAME) final String id,
+ final List<CustomFieldJson> customFields,
+ @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 CustomFieldApiException {
+ return super.modifyCustomFields(UUID.fromString(id), customFields,
+ context.createCallContextNoAccountId(createdBy, reason, comment, request));
+ }
+
+
@TimedResource
@DELETE
@Path("/{paymentId:" + UUID_PATTERN + "}/" + CUSTOM_FIELDS)
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 cf30e0c..2bb186c 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
@@ -73,7 +73,6 @@ import org.killbill.billing.invoice.api.DryRunType;
import org.killbill.billing.invoice.api.Invoice;
import org.killbill.billing.invoice.api.InvoiceApiException;
import org.killbill.billing.invoice.api.InvoiceItem;
-import org.killbill.billing.invoice.api.InvoiceNotifier;
import org.killbill.billing.invoice.api.InvoicePayment;
import org.killbill.billing.invoice.api.InvoiceUserApi;
import org.killbill.billing.jaxrs.json.CustomFieldJson;
@@ -136,7 +135,6 @@ public class InvoiceResource extends JaxRsResourceBase {
private static final String LOCALE_PARAM_NAME = "locale";
private final InvoiceUserApi invoiceApi;
- private final InvoiceNotifier invoiceNotifier;
private final TenantUserApi tenantApi;
private final Locale defaultLocale;
@@ -151,7 +149,6 @@ public class InvoiceResource extends JaxRsResourceBase {
public InvoiceResource(final AccountUserApi accountUserApi,
final InvoiceUserApi invoiceApi,
final PaymentApi paymentApi,
- final InvoiceNotifier invoiceNotifier,
final Clock clock,
final JaxrsUriBuilder uriBuilder,
final TagUserApi tagUserApi,
@@ -161,7 +158,6 @@ public class InvoiceResource extends JaxRsResourceBase {
final Context context) {
super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, null, clock, context);
this.invoiceApi = invoiceApi;
- this.invoiceNotifier = invoiceNotifier;
this.tenantApi = tenantApi;
this.defaultLocale = Locale.getDefault();
}
@@ -341,12 +337,11 @@ public class InvoiceResource extends JaxRsResourceBase {
final Account account = accountUserApi.getAccountById(accountId, callContext);
final Iterable<InvoiceItem> sanitizedInvoiceItems = validateSanitizeAndTranformInputItems(account.getCurrency(), items);
- final LocalDate resolvedTargetDate = toLocalDateDefaultToday(account, targetDate, callContext);
+ final LocalDate resolvedTargetDate = toLocalDateDefaultToday(account, targetDate, callContext);
final UUID invoiceId = invoiceApi.createMigrationInvoice(accountId, resolvedTargetDate, sanitizedInvoiceItems, callContext);
return uriBuilder.buildResponse(uriInfo, InvoiceResource.class, "getInvoice", invoiceId, request);
}
-
@TimedResource
@POST
@Path("/" + DRY_RUN)
@@ -479,7 +474,11 @@ public class InvoiceResource extends JaxRsResourceBase {
callContext);
}
- return uriBuilder.buildResponse(uriInfo, InvoiceResource.class, "getInvoice", adjustmentItem.getInvoiceId(), request);
+ if (adjustmentItem == null) {
+ return Response.status(Status.NOT_FOUND).build();
+ } else {
+ return uriBuilder.buildResponse(uriInfo, InvoiceResource.class, "getInvoice", adjustmentItem.getInvoiceId(), request);
+ }
}
@TimedResource
@@ -646,6 +645,7 @@ public class InvoiceResource extends JaxRsResourceBase {
@ApiResponses(value = {@ApiResponse(code = 400, message = "Invalid account id or invoice id supplied"),
@ApiResponse(code = 404, message = "Account not found")})
public Response createInstantPayment(final InvoicePaymentJson payment,
+ @PathParam("invoiceId") final String invoiceId,
@QueryParam(QUERY_PAYMENT_EXTERNAL) @DefaultValue("false") final Boolean externalPayment,
@QueryParam(QUERY_PLUGIN_PROPERTY) final List<String> pluginPropertiesString,
@HeaderParam(HDR_CREATED_BY) final String createdBy,
@@ -659,7 +659,6 @@ public class InvoiceResource extends JaxRsResourceBase {
payment.getPurchasedAmount(), "InvoicePaymentJson purchasedAmount needs to be set");
Preconditions.checkArgument(!externalPayment || payment.getPaymentMethodId() == null, "InvoicePaymentJson should not contain a paymentMethodId when this is an external payment");
-
final Iterable<PluginProperty> pluginProperties = extractPluginProperties(pluginPropertiesString);
final CallContext callContext = context.createCallContextNoAccountId(createdBy, reason, comment, request);
@@ -667,44 +666,14 @@ public class InvoiceResource extends JaxRsResourceBase {
final UUID paymentMethodId = externalPayment ? null :
(payment.getPaymentMethodId() != null ? UUID.fromString(payment.getPaymentMethodId()) : account.getPaymentMethodId());
- final UUID invoiceId = UUID.fromString(payment.getTargetInvoiceId());
-
- final Payment result = createPurchaseForInvoice(account, invoiceId, payment.getPurchasedAmount(), paymentMethodId, externalPayment,
- (payment.getPaymentExternalKey() != null) ? payment.getPaymentExternalKey() : null, null, pluginProperties, callContext);
+ final Payment result = createPurchaseForInvoice(account, UUID.fromString(invoiceId), payment.getPurchasedAmount(), paymentMethodId, externalPayment,
+ payment.getPaymentExternalKey(), null, pluginProperties, callContext);
return result != null ?
uriBuilder.buildResponse(uriInfo, InvoicePaymentResource.class, "getInvoicePayment", result.getId(), request) :
Response.status(Status.NO_CONTENT).build();
}
@TimedResource
- @POST
- @Path("/{invoiceId:" + UUID_PATTERN + "}/" + EMAIL_NOTIFICATIONS)
- @Consumes(APPLICATION_JSON)
- @Produces(APPLICATION_JSON)
- @ApiOperation(value = "Trigger an email notification for invoice")
- @ApiResponses(value = {@ApiResponse(code = 400, message = "Invalid invoice id supplied"),
- @ApiResponse(code = 404, message = "Account or invoice not found")})
- public Response triggerEmailNotificationForInvoice(@PathParam("invoiceId") final String invoiceId,
- @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 InvoiceApiException, AccountApiException {
- final CallContext callContext = context.createCallContextNoAccountId(createdBy, reason, comment, request);
-
- final Invoice invoice = invoiceApi.getInvoice(UUID.fromString(invoiceId), callContext);
- if (invoice == null) {
- throw new InvoiceApiException(ErrorCode.INVOICE_NOT_FOUND, invoiceId);
- }
-
- final Account account = accountUserApi.getAccountById(invoice.getAccountId(), callContext);
-
- // Send the email (synchronous send)
- invoiceNotifier.notify(account, invoice, callContext);
-
- return Response.status(Status.OK).build();
- }
-
- @TimedResource
@GET
@Path("/" + INVOICE_TRANSLATION + "/{locale:" + ANYTHING_PATTERN + "}/")
@Produces(TEXT_PLAIN)
@@ -931,6 +900,24 @@ public class InvoiceResource extends JaxRsResourceBase {
context.createCallContextNoAccountId(createdBy, reason, comment, request), uriInfo, request);
}
+
+ @TimedResource
+ @PUT
+ @Path("/{invoiceId:" + UUID_PATTERN + "}/" + CUSTOM_FIELDS)
+ @Consumes(APPLICATION_JSON)
+ @Produces(APPLICATION_JSON)
+ @ApiOperation(value = "Modify custom fields to invoice")
+ @ApiResponses(value = {@ApiResponse(code = 400, message = "Invalid invoice id supplied")})
+ public Response modifyCustomFields(@PathParam(ID_PARAM_NAME) final String id,
+ final List<CustomFieldJson> customFields,
+ @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 CustomFieldApiException {
+ return super.modifyCustomFields(UUID.fromString(id), customFields,
+ context.createCallContextNoAccountId(createdBy, reason, comment, request));
+ }
+
@TimedResource
@DELETE
@Path("/{invoiceId:" + UUID_PATTERN + "}/" + CUSTOM_FIELDS)
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 dd706db..7923488 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
@@ -18,6 +18,9 @@
package org.killbill.billing.jaxrs.resources;
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.QueryParam;
+
public interface JaxrsResource {
public static final String API_PREFIX = "";
@@ -93,6 +96,10 @@ public interface JaxrsResource {
public static final String QUERY_ACCOUNT_ID = "accountId";
+ public static final String QUERY_CANCEL_ALL_SUBSCRIPTIONS = "cancelAllSubscriptions";
+ public static final String QUERY_WRITE_OFF_UNPAID_INVOICES = "writeOffUnpaidInvoices";
+ public static final String QUERY_ITEM_ADJUST_UNPAID_INVOICES = "itemAdjustUnpaidInvoices";
+
public static final String QUERY_BLOCKING_STATE_TYPES = "blockingStateTypes";
public static final String QUERY_BLOCKING_STATE_SVCS = "blockingStateSvcs";
@@ -136,6 +143,8 @@ public interface JaxrsResource {
public static final String QUERY_BUNDLE_TRANSFER_CANCEL_IMM = "cancelImmediately";
public static final String QUERY_BUNDLES_FILTER = "bundlesFilter";
+ public static final String QUERY_BUNDLES_RENAME_KEY_IF_EXIST_UNUSED = "renameKeyIfExistsAndUnused";
+
public static final String QUERY_DELETE_DEFAULT_PM_WITH_AUTO_PAY_OFF = "deleteDefaultPmWithAutoPayOff";
public static final String QUERY_FORCE_DEFAULT_PM_DELETION = "forceDefaultPmDeletion";
@@ -214,6 +223,7 @@ public interface JaxrsResource {
public static final String TAGS = "tags";
public static final String TAGS_PATH = PREFIX + "/" + TAGS;
+ public static final String ALL_CUSTOM_FIELDS = "allCustomFields";
public static final String CUSTOM_FIELDS = "customFields";
public static final String CUSTOM_FIELDS_PATH = PREFIX + "/" + CUSTOM_FIELDS;
@@ -250,9 +260,14 @@ public interface JaxrsResource {
public static final String CBA_REBALANCING = "cbaRebalancing";
+
+ public static final String UNDO_CHANGE_PLAN = "undoChangePlan";
+ public static final String UNDO_CANCEL = "uncancel";
+
public static final String PAUSE = "pause";
public static final String RESUME = "resume";
public static final String BLOCK = "block";
+ public static final String RENAME_KEY = "renameKey";
public static final String AUTHORIZATION = "authorization";
public static final String CAPTURE = "capture";
@@ -278,6 +293,7 @@ public interface JaxrsResource {
public static final String TRANSFER_CREDIT = "transferCredit";
public static final String CACHE = "cache";
+ public static final String HEALTHCHECK = "healthcheck";
public static final String QUERY_INCLUDED_DELETED = "includedDeleted";
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 79a3744..0fc62db 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
@@ -52,6 +52,7 @@ import org.killbill.billing.ObjectType;
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.catalog.api.Currency;
import org.killbill.billing.entitlement.api.BlockingState;
import org.killbill.billing.entitlement.api.BlockingStateType;
import org.killbill.billing.entitlement.api.EntitlementApiException;
@@ -64,6 +65,7 @@ import org.killbill.billing.jaxrs.json.BillingExceptionJson.StackTraceElementJso
import org.killbill.billing.jaxrs.json.BlockingStateJson;
import org.killbill.billing.jaxrs.json.CustomFieldJson;
import org.killbill.billing.jaxrs.json.JsonBase;
+import org.killbill.billing.jaxrs.json.PaymentTransactionJson;
import org.killbill.billing.jaxrs.json.PluginPropertyJson;
import org.killbill.billing.jaxrs.json.TagJson;
import org.killbill.billing.jaxrs.util.Context;
@@ -119,7 +121,6 @@ public abstract class JaxRsResourceBase implements JaxrsResource {
// Catalog API don't quite support multiple catalogs per tenant
protected static final String catalogName = "unused";
-
protected static final ObjectMapper mapper = new ObjectMapper();
protected final JaxrsUriBuilder uriBuilder;
@@ -183,9 +184,6 @@ public abstract class JaxRsResourceBase implements JaxrsResource {
return Response.status(Status.OK).build();
}
-
-
-
protected Response getTags(final UUID accountId, final UUID taggedObjectId, final AuditMode auditMode, final boolean includeDeleted, final TenantContext context) throws TagDefinitionApiException {
final List<Tag> tags = tagUserApi.getTagsForObject(taggedObjectId, getObjectType(), includeDeleted, context);
return createTagResponse(accountId, tags, auditMode, context);
@@ -208,7 +206,6 @@ public abstract class JaxRsResourceBase implements JaxrsResource {
return Response.status(Response.Status.OK).entity(result).build();
}
-
protected Response createTags(final UUID id,
final String tagList,
final UriInfo uriInfo,
@@ -241,8 +238,11 @@ public abstract class JaxRsResourceBase implements JaxrsResource {
protected Response getCustomFields(final UUID id, final AuditMode auditMode, final TenantContext context) {
final List<CustomField> fields = customFieldUserApi.getCustomFieldsForObject(id, getObjectType(), context);
+ return createCustomFieldResponse(fields, auditMode, context);
+ }
- final List<CustomFieldJson> result = new LinkedList<CustomFieldJson>();
+ protected Response createCustomFieldResponse(final Iterable<CustomField> fields, final AuditMode auditMode, final TenantContext context) {
+ final Collection<CustomFieldJson> result = new LinkedList<CustomFieldJson>();
for (final CustomField cur : fields) {
// TODO PIERRE - Bulk API
final List<AuditLog> auditLogs = auditUserApi.getAuditLogs(cur.getId(), ObjectType.CUSTOM_FIELD, auditMode.getLevel(), context);
@@ -268,6 +268,23 @@ public abstract class JaxRsResourceBase implements JaxrsResource {
return uriBuilder.buildResponse(uriInfo, this.getClass(), "getCustomFields", id, request);
}
+
+ protected Response modifyCustomFields(final UUID id,
+ final List<CustomFieldJson> customFields,
+ final CallContext context) throws CustomFieldApiException {
+ final LinkedList<CustomField> input = new LinkedList<CustomField>();
+ for (final CustomFieldJson cur : customFields) {
+ verifyNonNullOrEmpty(cur.getCustomFieldId(), "CustomFieldJson id needs to be set");
+ verifyNonNullOrEmpty(cur.getValue(), "CustomFieldJson value needs to be set");
+ input.add(new StringCustomField(UUID.fromString(cur.getCustomFieldId()), cur.getName(), cur.getValue(), getObjectType(), id, context.getCreatedDate()));
+ }
+
+ customFieldUserApi.updateCustomFields(input, context);
+ return Response.status(Response.Status.OK).build();
+ }
+
+
+
/**
* @param id the if of the object for which the custom fields apply
* @param customFieldList a comma separated list of custom field ids or null if they should all be removed
@@ -350,17 +367,88 @@ public abstract class JaxRsResourceBase implements JaxrsResource {
}
protected void validatePaymentMethodForAccount(final UUID accountId, final UUID paymentMethodId, final CallContext callContext) throws PaymentApiException {
- if (paymentMethodId == null) {
- throw new PaymentApiException(ErrorCode.PAYMENT_NO_DEFAULT_PAYMENT_METHOD, accountId);
+ if (paymentMethodId != null) {
+ final PaymentMethod paymentMethod = paymentApi.getPaymentMethodById(paymentMethodId, false, false, ImmutableList.<PluginProperty>of(), callContext);
+ if (!paymentMethod.getAccountId().equals(accountId)) {
+ throw new PaymentApiException(ErrorCode.PAYMENT_NO_SUCH_PAYMENT_METHOD, paymentMethodId);
+ }
}
- final PaymentMethod paymentMethod = paymentApi.getPaymentMethodById(paymentMethodId, false, false, ImmutableList.<PluginProperty>of(), callContext);
- if (!paymentMethod.getAccountId().equals(accountId)) {
- throw new PaymentApiException(ErrorCode.PAYMENT_NO_SUCH_PAYMENT_METHOD, paymentMethodId);
+ }
+
+ protected Payment getPaymentByIdOrKey(@Nullable final String paymentIdStr, @Nullable final String externalKey, final Iterable<PluginProperty> pluginProperties, final TenantContext tenantContext) throws PaymentApiException {
+ Preconditions.checkArgument(paymentIdStr != null || externalKey != null, "Need to set either paymentId or payment externalKey");
+ if (paymentIdStr != null) {
+ final UUID paymentId = UUID.fromString(paymentIdStr);
+ return paymentApi.getPayment(paymentId, false, false, pluginProperties, tenantContext);
+ } else {
+ return paymentApi.getPaymentByExternalKey(externalKey, false, false, pluginProperties, tenantContext);
+ }
+ }
+
+
+ protected Response completeTransactionInternal(final PaymentTransactionJson json,
+ final Payment initialPayment,
+ final List<String> paymentControlPluginNames,
+ final Iterable<PluginProperty> pluginProperties,
+ final TenantContext contextNoAccountId,
+ final String createdBy,
+ final String reason,
+ final String comment,
+ final UriInfo uriInfo,
+ final HttpServletRequest request) throws PaymentApiException, AccountApiException {
+
+ final Account account = accountUserApi.getAccountById(initialPayment.getAccountId(), contextNoAccountId);
+ final BigDecimal amount = json == null ? null : json.getAmount();
+ final Currency currency = json == null || json.getCurrency() == null ? null : Currency.valueOf(json.getCurrency());
+
+ final CallContext callContext = context.createCallContextWithAccountId(account.getId(), createdBy, reason, comment, request);
+
+ final PaymentTransaction pendingOrSuccessTransaction = lookupPendingOrSuccessTransaction(initialPayment,
+ json != null ? json.getTransactionId() : null,
+ json != null ? json.getTransactionExternalKey() : null,
+ 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(), request);
+ }
+
+ final PaymentTransaction pendingTransaction = pendingOrSuccessTransaction;
+ final PaymentOptions paymentOptions = createControlPluginApiPaymentOptions(paymentControlPluginNames);
+ final Payment result;
+ switch (pendingTransaction.getTransactionType()) {
+ case AUTHORIZE:
+ result = paymentApi.createAuthorizationWithPaymentControl(account, initialPayment.getPaymentMethodId(), initialPayment.getId(), amount, currency, null,
+ initialPayment.getExternalKey(), pendingTransaction.getExternalKey(),
+ pluginProperties, paymentOptions, callContext);
+ break;
+ case CAPTURE:
+ result = paymentApi.createCaptureWithPaymentControl(account, initialPayment.getId(), amount, currency, null, pendingTransaction.getExternalKey(),
+ pluginProperties, paymentOptions, callContext);
+ break;
+ case PURCHASE:
+ result = paymentApi.createPurchaseWithPaymentControl(account, initialPayment.getPaymentMethodId(), initialPayment.getId(), amount, currency, null,
+ initialPayment.getExternalKey(), pendingTransaction.getExternalKey(),
+ pluginProperties, paymentOptions, callContext);
+ break;
+ case CREDIT:
+ result = paymentApi.createCreditWithPaymentControl(account, initialPayment.getPaymentMethodId(), initialPayment.getId(), amount, currency, null,
+ initialPayment.getExternalKey(), pendingTransaction.getExternalKey(),
+ pluginProperties, paymentOptions, callContext);
+ break;
+ case REFUND:
+ result = paymentApi.createRefundWithPaymentControl(account, initialPayment.getId(), amount, currency, null,
+ pendingTransaction.getExternalKey(), pluginProperties, paymentOptions, callContext);
+ break;
+ default:
+ return Response.status(Status.PRECONDITION_FAILED).entity("TransactionType " + pendingTransaction.getTransactionType() + " cannot be completed").build();
}
+ return createPaymentResponse(uriInfo, result, pendingTransaction.getTransactionType(), pendingTransaction.getExternalKey(), request);
+
}
+
protected PaymentTransaction lookupPendingOrSuccessTransaction(final Payment initialPayment, @Nullable final String transactionId, @Nullable final String transactionExternalKey, @Nullable final String transactionType) throws PaymentApiException {
- final Collection<PaymentTransaction> pendingTransaction = Collections2.filter(initialPayment.getTransactions(), new Predicate<PaymentTransaction>() {
+ final Collection<PaymentTransaction> pendingTransaction = Collections2.filter(initialPayment.getTransactions(), new Predicate<PaymentTransaction>() {
@Override
public boolean apply(final PaymentTransaction input) {
if (input.getTransactionStatus() != TransactionStatus.PENDING && input.getTransactionStatus() != TransactionStatus.SUCCESS) {
@@ -477,11 +565,14 @@ public abstract class JaxRsResourceBase implements JaxrsResource {
invoiceId.toString(), false);
properties.add(invoiceProperty);
try {
- return paymentApi.createPurchaseWithPaymentControl(account, paymentMethodId, null, amountToPay, account.getCurrency(), paymentExternalKey, transactionExternalKey,
+ return paymentApi.createPurchaseWithPaymentControl(account, paymentMethodId, null, amountToPay, account.getCurrency(), null, paymentExternalKey, transactionExternalKey,
properties, createInvoicePaymentControlPluginApiPaymentOptions(externalPayment), callContext);
} catch (final PaymentApiException e) {
- if (e.getCode() == ErrorCode.PAYMENT_PLUGIN_EXCEPTION.getCode() &&
- e.getMessage().contains("Aborted Payment for invoice")) {
+
+ if (e.getCode() == ErrorCode.PAYMENT_PLUGIN_EXCEPTION.getCode() /* &&
+ e.getMessage().contains("Invalid amount") */) { /* Plugin received bad input */
+ throw e;
+ } else if (e.getCode() == ErrorCode.PAYMENT_PLUGIN_API_ABORTED.getCode()) { /* Plugin aborted the call (e.g invoice already paid) */
return null;
}
throw e;
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 43dde2f..e765694 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
@@ -32,6 +32,7 @@ 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;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
@@ -101,13 +102,14 @@ public class PaymentMethodResource extends JaxRsResourceBase {
@ApiResponse(code = 404, message = "Account or payment method not found")})
public Response getPaymentMethod(@PathParam("paymentMethodId") final String paymentMethodId,
@QueryParam(QUERY_PLUGIN_PROPERTY) final List<String> pluginPropertiesString,
+ @QueryParam(QUERY_INCLUDED_DELETED) @DefaultValue("false") final Boolean includedDeleted,
@QueryParam(QUERY_AUDIT) @DefaultValue("NONE") final AuditMode auditMode,
@QueryParam(QUERY_WITH_PLUGIN_INFO) @DefaultValue("false") final Boolean withPluginInfo,
@javax.ws.rs.core.Context final HttpServletRequest request) throws AccountApiException, PaymentApiException {
final Iterable<PluginProperty> pluginProperties = extractPluginProperties(pluginPropertiesString);
final TenantContext tenantContext = context.createTenantContextNoAccountId(request);
- final PaymentMethod paymentMethod = paymentApi.getPaymentMethodById(UUID.fromString(paymentMethodId), false, withPluginInfo, pluginProperties, tenantContext);
+ final PaymentMethod paymentMethod = paymentApi.getPaymentMethodById(UUID.fromString(paymentMethodId), includedDeleted, withPluginInfo, pluginProperties, tenantContext);
final Account account = accountUserApi.getAccountById(paymentMethod.getAccountId(), tenantContext);
final AccountAuditLogs accountAuditLogs = auditUserApi.getAccountAuditLogs(paymentMethod.getAccountId(), auditMode.getLevel(), tenantContext);
final PaymentMethodJson json = PaymentMethodJson.toPaymentMethodJson(account, paymentMethod, accountAuditLogs);
@@ -122,13 +124,14 @@ public class PaymentMethodResource extends JaxRsResourceBase {
@ApiResponses(value = {@ApiResponse(code = 404, message = "Account or payment method not found")})
public Response getPaymentMethodByKey(@QueryParam(QUERY_EXTERNAL_KEY) final String externalKey,
@QueryParam(QUERY_PLUGIN_PROPERTY) final List<String> pluginPropertiesString,
+ @QueryParam(QUERY_INCLUDED_DELETED) @DefaultValue("false") final Boolean includedDeleted,
@QueryParam(QUERY_AUDIT) @DefaultValue("NONE") final AuditMode auditMode,
@QueryParam(QUERY_WITH_PLUGIN_INFO) @DefaultValue("false") final Boolean withPluginInfo,
@javax.ws.rs.core.Context final HttpServletRequest request) throws AccountApiException, PaymentApiException {
final Iterable<PluginProperty> pluginProperties = extractPluginProperties(pluginPropertiesString);
final TenantContext tenantContext = context.createTenantContextNoAccountId(request);
- final PaymentMethod paymentMethod = paymentApi.getPaymentMethodByExternalKey(externalKey, false, withPluginInfo, pluginProperties, tenantContext);
+ final PaymentMethod paymentMethod = paymentApi.getPaymentMethodByExternalKey(externalKey, includedDeleted, withPluginInfo, pluginProperties, tenantContext);
final Account account = accountUserApi.getAccountById(paymentMethod.getAccountId(), tenantContext);
final AccountAuditLogs accountAuditLogs = auditUserApi.getAccountAuditLogs(paymentMethod.getAccountId(), auditMode.getLevel(), tenantContext);
final PaymentMethodJson json = PaymentMethodJson.toPaymentMethodJson(account, paymentMethod, accountAuditLogs);
@@ -306,6 +309,25 @@ public class PaymentMethodResource extends JaxRsResourceBase {
context.createCallContextNoAccountId(createdBy, reason, comment, request), uriInfo, request);
}
+
+ @TimedResource
+ @PUT
+ @Path("/{paymentMethodId:" + UUID_PATTERN + "}/" + CUSTOM_FIELDS)
+ @Consumes(APPLICATION_JSON)
+ @Produces(APPLICATION_JSON)
+ @ApiOperation(value = "Modify custom fields to payment method")
+ @ApiResponses(value = {@ApiResponse(code = 400, message = "Invalid payment method id supplied")})
+ public Response modifyCustomFields(@PathParam("paymentMethodId") final String paymentMethodId,
+ final List<CustomFieldJson> customFields,
+ @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 CustomFieldApiException {
+ return super.modifyCustomFields(UUID.fromString(paymentMethodId), customFields,
+ context.createCallContextNoAccountId(createdBy, reason, comment, request));
+ }
+
+
@TimedResource
@DELETE
@Path("/{paymentMethodId:" + UUID_PATTERN + "}/" + CUSTOM_FIELDS)
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 add62fe..4e11dc8 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
@@ -17,7 +17,6 @@
package org.killbill.billing.jaxrs.resources;
-import java.math.BigDecimal;
import java.net.URI;
import java.util.HashMap;
import java.util.List;
@@ -59,9 +58,7 @@ 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.PaymentOptions;
-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.api.AuditUserApi;
import org.killbill.billing.util.api.CustomFieldApiException;
@@ -263,9 +260,11 @@ public class PaymentResource extends ComboPaymentResource {
@HeaderParam(HDR_COMMENT) final String comment,
@javax.ws.rs.core.Context final UriInfo uriInfo,
@javax.ws.rs.core.Context final HttpServletRequest request) throws PaymentApiException, AccountApiException {
- return completeTransactionInternal(json, paymentIdStr, paymentControlPluginNames, pluginPropertiesString, createdBy, reason, comment, uriInfo, request);
+ return completeTransactionInternalWithoutPayment(json, paymentIdStr, paymentControlPluginNames, pluginPropertiesString, createdBy, reason, comment, uriInfo, request);
}
+
+
@TimedResource(name = "completeTransaction")
@PUT
@Consumes(APPLICATION_JSON)
@@ -286,7 +285,7 @@ public class PaymentResource extends ComboPaymentResource {
@HeaderParam(HDR_COMMENT) final String comment,
@javax.ws.rs.core.Context final UriInfo uriInfo,
@javax.ws.rs.core.Context final HttpServletRequest request) throws PaymentApiException, AccountApiException {
- return completeTransactionInternal(json, null, paymentControlPluginNames, pluginPropertiesString, createdBy, reason, comment, uriInfo, request);
+ return completeTransactionInternalWithoutPayment(json, null, paymentControlPluginNames, pluginPropertiesString, createdBy, reason, comment, uriInfo, request);
}
@@ -294,71 +293,6 @@ public class PaymentResource extends ComboPaymentResource {
- private Response completeTransactionInternal(final PaymentTransactionJson json,
- @Nullable final String paymentIdStr,
- final List<String> paymentControlPluginNames,
- final Iterable<String> pluginPropertiesString,
- final String createdBy,
- final String reason,
- final String comment,
- final UriInfo uriInfo,
- final HttpServletRequest request) throws PaymentApiException, AccountApiException {
-
- final Iterable<PluginProperty> pluginPropertiesFromBody = extractPluginProperties(json.getProperties());
-
- final Iterable<PluginProperty> pluginPropertiesFromQuery = extractPluginProperties(pluginPropertiesString);
-
- final Iterable<PluginProperty> pluginProperties = Iterables.concat(pluginPropertiesFromQuery, pluginPropertiesFromBody);
-
- final CallContext callContext = context.createCallContextNoAccountId(createdBy, reason, comment, request);
- final Payment initialPayment = getPaymentByIdOrKey(paymentIdStr, json == null ? null : json.getPaymentExternalKey(), pluginProperties, callContext);
-
- final Account account = accountUserApi.getAccountById(initialPayment.getAccountId(), callContext);
- final BigDecimal amount = json == null ? null : json.getAmount();
- final Currency currency = json == null || json.getCurrency() == null ? null : Currency.valueOf(json.getCurrency());
-
- final PaymentTransaction pendingOrSuccessTransaction = lookupPendingOrSuccessTransaction(initialPayment,
- json != null ? json.getTransactionId() : null,
- json != null ? json.getTransactionExternalKey() : null,
- 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(), request);
- }
-
-
- final PaymentTransaction pendingTransaction = pendingOrSuccessTransaction;
- final PaymentOptions paymentOptions = createControlPluginApiPaymentOptions(paymentControlPluginNames);
- final Payment result;
- switch (pendingTransaction.getTransactionType()) {
- case AUTHORIZE:
- result = paymentApi.createAuthorizationWithPaymentControl(account, initialPayment.getPaymentMethodId(), initialPayment.getId(), amount, currency,
- initialPayment.getExternalKey(), pendingTransaction.getExternalKey(),
- pluginProperties, paymentOptions, callContext);
- break;
- case CAPTURE:
- result = paymentApi.createCaptureWithPaymentControl(account, initialPayment.getId(), amount, currency, pendingTransaction.getExternalKey(),
- pluginProperties, paymentOptions, callContext);
- break;
- case PURCHASE:
- result = paymentApi.createPurchaseWithPaymentControl(account, initialPayment.getPaymentMethodId(), initialPayment.getId(), amount, currency,
- initialPayment.getExternalKey(), pendingTransaction.getExternalKey(),
- pluginProperties, paymentOptions, callContext);
- break;
- case CREDIT:
- result = paymentApi.createCreditWithPaymentControl(account, initialPayment.getPaymentMethodId(), initialPayment.getId(), amount, currency,
- initialPayment.getExternalKey(), pendingTransaction.getExternalKey(),
- pluginProperties, paymentOptions, callContext);
- break;
- case REFUND:
- result = paymentApi.createRefundWithPaymentControl(account, initialPayment.getId(), amount, currency,
- pendingTransaction.getExternalKey(), pluginProperties, paymentOptions, callContext);
- break;
- default:
- return Response.status(Status.PRECONDITION_FAILED).entity("TransactionType " + pendingTransaction.getTransactionType() + " cannot be completed").build();
- }
- return createPaymentResponse(uriInfo, result, pendingTransaction.getTransactionType(), pendingTransaction.getExternalKey(), request);
- }
@TimedResource(name = "captureAuthorization")
@POST
@@ -422,7 +356,9 @@ public class PaymentResource extends ComboPaymentResource {
verifyNonNullOrEmpty(json, "PaymentTransactionJson body should be specified");
verifyNonNullOrEmpty(json.getAmount(), "PaymentTransactionJson amount needs to be set");
- final Iterable<PluginProperty> pluginProperties = extractPluginProperties(pluginPropertiesString);
+ final Iterable<PluginProperty> pluginPropertiesFromBody = extractPluginProperties(json.getProperties());
+ final Iterable<PluginProperty> pluginPropertiesFromQuery = extractPluginProperties(pluginPropertiesString);
+ final Iterable<PluginProperty> pluginProperties = Iterables.concat(pluginPropertiesFromQuery, pluginPropertiesFromBody);
final CallContext callContext = context.createCallContextNoAccountId(createdBy, reason, comment, request);
final Payment initialPayment = getPaymentByIdOrKey(paymentIdStr, json.getPaymentExternalKey(), pluginProperties, callContext);
@@ -431,7 +367,7 @@ public class PaymentResource extends ComboPaymentResource {
final PaymentOptions paymentOptions = createControlPluginApiPaymentOptions(paymentControlPluginNames);
- final Payment payment = paymentApi.createCaptureWithPaymentControl(account, initialPayment.getId(), json.getAmount(), currency,
+ final Payment payment = paymentApi.createCaptureWithPaymentControl(account, initialPayment.getId(), json.getAmount(), currency, json.getEffectiveDate(),
json.getTransactionExternalKey(), pluginProperties, paymentOptions, callContext);
return createPaymentResponse(uriInfo, payment, TransactionType.CAPTURE, json.getTransactionExternalKey(), request);
}
@@ -500,8 +436,11 @@ public class PaymentResource extends ComboPaymentResource {
verifyNonNullOrEmpty(json, "PaymentTransactionJson body should be specified");
verifyNonNullOrEmpty(json.getAmount(), "PaymentTransactionJson amount needs to be set");
- final Iterable<PluginProperty> pluginProperties = extractPluginProperties(pluginPropertiesString);
+ final Iterable<PluginProperty> pluginPropertiesFromBody = extractPluginProperties(json.getProperties());
+ final Iterable<PluginProperty> pluginPropertiesFromQuery = extractPluginProperties(pluginPropertiesString);
+ final Iterable<PluginProperty> pluginProperties = Iterables.concat(pluginPropertiesFromQuery, pluginPropertiesFromBody);
final CallContext callContext = context.createCallContextNoAccountId(createdBy, reason, comment, request);
+
final Payment initialPayment = getPaymentByIdOrKey(paymentIdStr, json.getPaymentExternalKey(), pluginProperties, callContext);
final Account account = accountUserApi.getAccountById(initialPayment.getAccountId(), callContext);
@@ -509,7 +448,7 @@ public class PaymentResource extends ComboPaymentResource {
final PaymentOptions paymentOptions = createControlPluginApiPaymentOptions(paymentControlPluginNames);
- final Payment payment = paymentApi.createRefundWithPaymentControl(account, initialPayment.getId(), json.getAmount(), currency,
+ final Payment payment = paymentApi.createRefundWithPaymentControl(account, initialPayment.getId(), json.getAmount(), currency, json.getEffectiveDate(),
json.getTransactionExternalKey(), pluginProperties, paymentOptions, callContext);
return createPaymentResponse(uriInfo, payment, TransactionType.REFUND, json.getTransactionExternalKey(), request);
@@ -573,8 +512,11 @@ public class PaymentResource extends ComboPaymentResource {
final String comment,
final UriInfo uriInfo,
final HttpServletRequest request) throws PaymentApiException, AccountApiException {
- final Iterable<PluginProperty> pluginProperties = extractPluginProperties(pluginPropertiesString);
+ final Iterable<PluginProperty> pluginPropertiesFromBody = extractPluginProperties(json != null ? json.getProperties() : null);
+ final Iterable<PluginProperty> pluginPropertiesFromQuery = extractPluginProperties(pluginPropertiesString);
+ final Iterable<PluginProperty> pluginProperties = Iterables.concat(pluginPropertiesFromQuery, pluginPropertiesFromBody);
final CallContext callContext = context.createCallContextNoAccountId(createdBy, reason, comment, request);
+
final Payment initialPayment = getPaymentByIdOrKey(paymentIdStr, json.getPaymentExternalKey(), pluginProperties, callContext);
final Account account = accountUserApi.getAccountById(initialPayment.getAccountId(), callContext);
@@ -582,7 +524,7 @@ public class PaymentResource extends ComboPaymentResource {
final String transactionExternalKey = json != null ? json.getTransactionExternalKey() : null;
final PaymentOptions paymentOptions = createControlPluginApiPaymentOptions(paymentControlPluginNames);
- final Payment payment = paymentApi.createVoidWithPaymentControl(account, initialPayment.getId(), transactionExternalKey,
+ final Payment payment = paymentApi.createVoidWithPaymentControl(account, initialPayment.getId(), json.getEffectiveDate(), transactionExternalKey,
pluginProperties, paymentOptions, callContext);
return createPaymentResponse(uriInfo, payment, TransactionType.VOID, json.getTransactionExternalKey(), request);
}
@@ -649,8 +591,11 @@ public class PaymentResource extends ComboPaymentResource {
verifyNonNullOrEmpty(json, "PaymentTransactionJson body should be specified");
verifyNonNullOrEmpty(json.getAmount(), "PaymentTransactionJson amount needs to be set");
- final Iterable<PluginProperty> pluginProperties = extractPluginProperties(pluginPropertiesString);
+ final Iterable<PluginProperty> pluginPropertiesFromBody = extractPluginProperties(json.getProperties());
+ final Iterable<PluginProperty> pluginPropertiesFromQuery = extractPluginProperties(pluginPropertiesString);
+ final Iterable<PluginProperty> pluginProperties = Iterables.concat(pluginPropertiesFromQuery, pluginPropertiesFromBody);
final CallContext callContext = context.createCallContextNoAccountId(createdBy, reason, comment, request);
+
final Payment initialPayment = getPaymentByIdOrKey(paymentIdStr, json.getPaymentExternalKey(), pluginProperties, callContext);
final Account account = accountUserApi.getAccountById(initialPayment.getAccountId(), callContext);
@@ -658,7 +603,7 @@ public class PaymentResource extends ComboPaymentResource {
final PaymentOptions paymentOptions = createControlPluginApiPaymentOptions(paymentControlPluginNames);
- final Payment payment = paymentApi.createChargebackWithPaymentControl(account, initialPayment.getId(), json.getAmount(), currency,
+ final Payment payment = paymentApi.createChargebackWithPaymentControl(account, initialPayment.getId(), json.getAmount(), currency, json.getEffectiveDate(),
json.getTransactionExternalKey(), paymentOptions, callContext);
return createPaymentResponse(uriInfo, payment, TransactionType.CHARGEBACK, json.getTransactionExternalKey(), request);
}
@@ -733,7 +678,7 @@ public class PaymentResource extends ComboPaymentResource {
final PaymentOptions paymentOptions = createControlPluginApiPaymentOptions(paymentControlPluginNames);
- final Payment payment = paymentApi.createChargebackReversalWithPaymentControl(account, initialPayment.getId(), json.getTransactionExternalKey(), paymentOptions, callContext);
+ final Payment payment = paymentApi.createChargebackReversalWithPaymentControl(account, initialPayment.getId(), json.getEffectiveDate(), json.getTransactionExternalKey(), paymentOptions, callContext);
return createPaymentResponse(uriInfo, payment, TransactionType.CHARGEBACK, json.getTransactionExternalKey(), request);
}
@@ -777,17 +722,17 @@ public class PaymentResource extends ComboPaymentResource {
final UUID paymentId = null; // If we need to specify a paymentId (e.g 3DS authorization, we can use regular API, no need for combo call)
switch (transactionType) {
case AUTHORIZE:
- result = paymentApi.createAuthorizationWithPaymentControl(account, paymentMethodId, paymentId, paymentTransactionJson.getAmount(), currency,
+ result = paymentApi.createAuthorizationWithPaymentControl(account, paymentMethodId, paymentId, paymentTransactionJson.getAmount(), currency, paymentTransactionJson.getEffectiveDate(),
paymentTransactionJson.getPaymentExternalKey(), paymentTransactionJson.getTransactionExternalKey(),
transactionPluginProperties, paymentOptions, callContext);
break;
case PURCHASE:
- result = paymentApi.createPurchaseWithPaymentControl(account, paymentMethodId, paymentId, paymentTransactionJson.getAmount(), currency,
+ result = paymentApi.createPurchaseWithPaymentControl(account, paymentMethodId, paymentId, paymentTransactionJson.getAmount(), currency, paymentTransactionJson.getEffectiveDate(),
paymentTransactionJson.getPaymentExternalKey(), paymentTransactionJson.getTransactionExternalKey(),
transactionPluginProperties, paymentOptions, callContext);
break;
case CREDIT:
- result = paymentApi.createCreditWithPaymentControl(account, paymentMethodId, paymentId, paymentTransactionJson.getAmount(), currency,
+ result = paymentApi.createCreditWithPaymentControl(account, paymentMethodId, paymentId, paymentTransactionJson.getAmount(), currency, paymentTransactionJson.getEffectiveDate(),
paymentTransactionJson.getPaymentExternalKey(), paymentTransactionJson.getTransactionExternalKey(),
transactionPluginProperties, paymentOptions, callContext);
break;
@@ -867,6 +812,23 @@ public class PaymentResource extends ComboPaymentResource {
}
@TimedResource
+ @PUT
+ @Path("/{paymentId:" + UUID_PATTERN + "}/" + CUSTOM_FIELDS)
+ @Consumes(APPLICATION_JSON)
+ @Produces(APPLICATION_JSON)
+ @ApiOperation(value = "Modify custom fields to payment")
+ @ApiResponses(value = {@ApiResponse(code = 400, message = "Invalid payment id supplied")})
+ public Response modifyCustomFields(@PathParam(ID_PARAM_NAME) final String id,
+ final List<CustomFieldJson> customFields,
+ @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 CustomFieldApiException {
+ return super.modifyCustomFields(UUID.fromString(id), customFields,
+ context.createCallContextNoAccountId(createdBy, reason, comment, request));
+ }
+
+ @TimedResource
@DELETE
@Path("/{paymentId:" + UUID_PATTERN + "}/" + CUSTOM_FIELDS)
@Consumes(APPLICATION_JSON)
@@ -939,4 +901,27 @@ public class PaymentResource extends ComboPaymentResource {
protected ObjectType getObjectType() {
return ObjectType.PAYMENT;
}
+
+ private Response completeTransactionInternalWithoutPayment(final PaymentTransactionJson json,
+ @Nullable final String paymentIdStr,
+ final List<String> paymentControlPluginNames,
+ final Iterable<String> pluginPropertiesString,
+ final String createdBy,
+ final String reason,
+ final String comment,
+ final UriInfo uriInfo,
+ final HttpServletRequest request) throws PaymentApiException, AccountApiException {
+
+ final Iterable<PluginProperty> pluginPropertiesFromBody = extractPluginProperties(json.getProperties());
+
+ final Iterable<PluginProperty> pluginPropertiesFromQuery = extractPluginProperties(pluginPropertiesString);
+
+ final Iterable<PluginProperty> pluginProperties = Iterables.concat(pluginPropertiesFromQuery, pluginPropertiesFromBody);
+
+ final CallContext callContextNoAccountId = context.createCallContextNoAccountId(createdBy, reason, comment, request);
+ final Payment initialPayment = getPaymentByIdOrKey(paymentIdStr, json == null ? null : json.getPaymentExternalKey(), pluginProperties, callContextNoAccountId);
+
+ return completeTransactionInternal(json, initialPayment, paymentControlPluginNames, pluginProperties, callContextNoAccountId, createdBy, reason, comment, uriInfo, request);
+ }
+
}
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 c537f6b..60e55b5 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
@@ -180,17 +180,15 @@ public class SecurityResource extends JaxRsResourceBase {
@Path("/users/{username:" + ANYTHING_PATTERN + "}")
@ApiOperation(value = "Invalidate an existing user")
public Response invalidateUser(@PathParam("username") final String username,
- @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,
- @javax.ws.rs.core.Context final UriInfo uriInfo) throws SecurityApiException {
+ @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,
+ @javax.ws.rs.core.Context final UriInfo uriInfo) throws SecurityApiException {
securityApi.invalidateUser(username, context.createCallContextNoAccountId(createdBy, reason, comment, request));
return Response.status(Status.NO_CONTENT).build();
}
-
-
@TimedResource
@POST
@Path("/roles")
@@ -206,4 +204,22 @@ public class SecurityResource extends JaxRsResourceBase {
securityApi.addRoleDefinition(json.getRole(), json.getPermissions(), context.createCallContextNoAccountId(createdBy, reason, comment, request));
return Response.status(Status.CREATED).build();
}
+
+
+ @TimedResource
+ @PUT
+ @Consumes(APPLICATION_JSON)
+ @Produces(APPLICATION_JSON)
+ @Path("/roles")
+ @ApiOperation(value = "Update a new role definition)")
+ public Response updateRoleDefinition(final RoleDefinitionJson json,
+ @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,
+ @javax.ws.rs.core.Context final UriInfo uriInfo) throws SecurityApiException {
+ securityApi.updateRoleDefinition(json.getRole(), json.getPermissions(), context.createCallContextNoAccountId(createdBy, reason, comment, request));
+ return Response.status(Status.OK).build();
+ }
+
}
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 0d0f00c..ae71972 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
@@ -54,7 +54,6 @@ import org.killbill.billing.catalog.api.Currency;
import org.killbill.billing.catalog.api.PhaseType;
import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
-import org.killbill.billing.catalog.api.PlanSpecifier;
import org.killbill.billing.catalog.api.ProductCategory;
import org.killbill.billing.entitlement.api.BaseEntitlementWithAddOnsSpecifier;
import org.killbill.billing.entitlement.api.BlockingStateType;
@@ -173,6 +172,7 @@ public class SubscriptionResource extends JaxRsResourceBase {
@QueryParam(QUERY_REQUESTED_DT) final String requestedDate, /* This is deprecated, only used for backward compatibility */
@QueryParam(QUERY_ENTITLEMENT_REQUESTED_DT) final String entitlementDate,
@QueryParam(QUERY_BILLING_REQUESTED_DT) final String billingDate,
+ @QueryParam(QUERY_BUNDLES_RENAME_KEY_IF_EXIST_UNUSED) @DefaultValue("true") final Boolean renameKeyIfExistsAndUnused,
@QueryParam(QUERY_MIGRATED) @DefaultValue("false") final Boolean isMigrated,
@QueryParam(QUERY_BCD) final Integer newBCD,
@QueryParam(QUERY_CALL_COMPLETION) @DefaultValue("false") final Boolean callCompletion,
@@ -218,7 +218,7 @@ public class SubscriptionResource extends JaxRsResourceBase {
final List<PlanPhasePriceOverride> overrides = PhasePriceOverrideJson.toPlanPhasePriceOverrides(entitlement.getPriceOverrides(), spec, account.getCurrency());
final Entitlement result = createAddOnEntitlement ?
entitlementApi.addEntitlement(getBundleIdForAddOnCreation(entitlement), spec, overrides, resolvedEntitlementDate, resolvedBillingDate, isMigrated, pluginProperties, callContext) :
- entitlementApi.createBaseEntitlement(account.getId(), spec, entitlement.getExternalKey(), overrides, resolvedEntitlementDate, resolvedBillingDate, isMigrated, pluginProperties, callContext);
+ entitlementApi.createBaseEntitlement(account.getId(), spec, entitlement.getExternalKey(), overrides, resolvedEntitlementDate, resolvedBillingDate, isMigrated, renameKeyIfExistsAndUnused, pluginProperties, callContext);
if (newBCD != null) {
result.updateBCD(newBCD, null, callContext);
}
@@ -262,6 +262,7 @@ public class SubscriptionResource extends JaxRsResourceBase {
@QueryParam(QUERY_ENTITLEMENT_REQUESTED_DT) final String entitlementDate,
@QueryParam(QUERY_BILLING_REQUESTED_DT) final String billingDate,
@QueryParam(QUERY_MIGRATED) @DefaultValue("false") final Boolean isMigrated,
+ @QueryParam(QUERY_BUNDLES_RENAME_KEY_IF_EXIST_UNUSED) @DefaultValue("true") final Boolean renameKeyIfExistsAndUnused,
@QueryParam(QUERY_CALL_COMPLETION) @DefaultValue("false") final Boolean callCompletion,
@QueryParam(QUERY_CALL_TIMEOUT) @DefaultValue("3") final long timeoutSec,
@QueryParam(QUERY_PLUGIN_PROPERTY) final List<String> pluginPropertiesString,
@@ -271,7 +272,7 @@ public class SubscriptionResource extends JaxRsResourceBase {
@javax.ws.rs.core.Context final HttpServletRequest request,
@javax.ws.rs.core.Context final UriInfo uriInfo) throws EntitlementApiException, AccountApiException, SubscriptionApiException {
final List<BulkBaseSubscriptionAndAddOnsJson> entitlementsWithAddOns = ImmutableList.of(new BulkBaseSubscriptionAndAddOnsJson(entitlements));
- return createEntitlementsWithAddOnsInternal(entitlementsWithAddOns, requestedDate, entitlementDate, billingDate, isMigrated, callCompletion, timeoutSec, pluginPropertiesString, createdBy, reason, comment, request, uriInfo, ObjectType.BUNDLE);
+ return createEntitlementsWithAddOnsInternal(entitlementsWithAddOns, requestedDate, entitlementDate, billingDate, isMigrated, renameKeyIfExistsAndUnused, callCompletion, timeoutSec, pluginPropertiesString, createdBy, reason, comment, request, uriInfo, ObjectType.BUNDLE);
}
@TimedResource
@@ -285,7 +286,8 @@ public class SubscriptionResource extends JaxRsResourceBase {
@QueryParam(QUERY_REQUESTED_DT) final String requestedDate, /* This is deprecated, only used for backward compatibility */
@QueryParam(QUERY_ENTITLEMENT_REQUESTED_DT) final String entitlementDate,
@QueryParam(QUERY_BILLING_REQUESTED_DT) final String billingDate,
- @QueryParam(QUERY_MIGRATED) @DefaultValue("false") final Boolean isMigrated,
+ @QueryParam(QUERY_BUNDLES_RENAME_KEY_IF_EXIST_UNUSED) @DefaultValue("true") final Boolean renameKeyIfExistsAndUnused,
+ @QueryParam(QUERY_MIGRATED) @DefaultValue("false") final Boolean isMigrated,
@QueryParam(QUERY_CALL_COMPLETION) @DefaultValue("false") final Boolean callCompletion,
@QueryParam(QUERY_CALL_TIMEOUT) @DefaultValue("3") final long timeoutSec,
@QueryParam(QUERY_PLUGIN_PROPERTY) final List<String> pluginPropertiesString,
@@ -294,7 +296,7 @@ public class SubscriptionResource extends JaxRsResourceBase {
@HeaderParam(HDR_COMMENT) final String comment,
@javax.ws.rs.core.Context final HttpServletRequest request,
@javax.ws.rs.core.Context final UriInfo uriInfo) throws EntitlementApiException, AccountApiException, SubscriptionApiException {
- return createEntitlementsWithAddOnsInternal(entitlementsWithAddOns, requestedDate, entitlementDate, billingDate, isMigrated, callCompletion, timeoutSec, pluginPropertiesString, createdBy, reason, comment, request, uriInfo, ObjectType.ACCOUNT);
+ return createEntitlementsWithAddOnsInternal(entitlementsWithAddOns, requestedDate, entitlementDate, billingDate, isMigrated, renameKeyIfExistsAndUnused, callCompletion, timeoutSec, pluginPropertiesString, createdBy, reason, comment, request, uriInfo, ObjectType.ACCOUNT);
}
@@ -302,7 +304,9 @@ public class SubscriptionResource extends JaxRsResourceBase {
final String requestedDate,
final String entitlementDate,
final String billingDate,
- final Boolean isMigrated, final Boolean callCompletion,
+ final Boolean isMigrated,
+ final Boolean renameKeyIfExistsAndUnused,
+ final Boolean callCompletion,
final long timeoutSec,
final List<String> pluginPropertiesString,
final String createdBy,
@@ -357,7 +361,7 @@ public class SubscriptionResource extends JaxRsResourceBase {
final EntitlementCallCompletionCallback<List<Entitlement>> callback = new EntitlementCallCompletionCallback<List<Entitlement>>() {
@Override
public List<Entitlement> doOperation(final CallContext ctx) throws InterruptedException, TimeoutException, EntitlementApiException, SubscriptionApiException, AccountApiException {
- return entitlementApi.createBaseEntitlementsWithAddOns(account.getId(), baseEntitlementWithAddOnsSpecifierList, pluginProperties, callContext);
+ return entitlementApi.createBaseEntitlementsWithAddOns(account.getId(), baseEntitlementWithAddOnsSpecifierList, renameKeyIfExistsAndUnused, pluginProperties, callContext);
}
@Override
public boolean isImmOperation() {
@@ -494,7 +498,7 @@ public class SubscriptionResource extends JaxRsResourceBase {
@TimedResource
@PUT
- @Path("/{subscriptionId:" + UUID_PATTERN + "}/uncancel")
+ @Path("/{subscriptionId:" + UUID_PATTERN + "}/" + UNDO_CANCEL)
@Produces(APPLICATION_JSON)
@ApiOperation(value = "Un-cancel an entitlement")
@ApiResponses(value = {@ApiResponse(code = 400, message = "Invalid subscription id supplied"),
@@ -514,6 +518,26 @@ public class SubscriptionResource extends JaxRsResourceBase {
@TimedResource
@PUT
+ @Path("/{subscriptionId:" + UUID_PATTERN + "}/" + UNDO_CHANGE_PLAN)
+ @Produces(APPLICATION_JSON)
+ @ApiOperation(value = "Undo a pending change plan on an entitlement")
+ @ApiResponses(value = {@ApiResponse(code = 400, message = "Invalid subscription id supplied"),
+ @ApiResponse(code = 404, message = "Entitlement not found")})
+ public Response undoChangeEntitlementPlan(@PathParam("subscriptionId") final String subscriptionId,
+ @QueryParam(QUERY_PLUGIN_PROPERTY) final List<String> pluginPropertiesString,
+ @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 EntitlementApiException {
+ final Iterable<PluginProperty> pluginProperties = extractPluginProperties(pluginPropertiesString);
+ final UUID uuid = UUID.fromString(subscriptionId);
+ final Entitlement current = entitlementApi.getEntitlementForId(uuid, context.createCallContextNoAccountId(createdBy, reason, comment, request));
+ current.undoChangePlan(pluginProperties, context.createCallContextNoAccountId(createdBy, reason, comment, request));
+ return Response.status(Status.OK).build();
+ }
+
+ @TimedResource
+ @PUT
@Produces(APPLICATION_JSON)
@Consumes(APPLICATION_JSON)
@Path("/{subscriptionId:" + UUID_PATTERN + "}")
@@ -555,10 +579,11 @@ public class SubscriptionResource extends JaxRsResourceBase {
final Entitlement newEntitlement;
final Account account = accountUserApi.getAccountById(current.getAccountId(), callContext);
- final PlanSpecifier planSpec = entitlement.getPlanName() != null ?
- new PlanSpecifier(entitlement.getPlanName()) :
- new PlanSpecifier(entitlement.getProductName(),
- BillingPeriod.valueOf(entitlement.getBillingPeriod()), entitlement.getPriceList());
+ final PhaseType phaseType = entitlement.getPhaseType() != null ? PhaseType.valueOf(entitlement.getPhaseType()) : null;
+ final PlanPhaseSpecifier planSpec = entitlement.getPlanName() != null ?
+ new PlanPhaseSpecifier(entitlement.getPlanName(), phaseType) :
+ new PlanPhaseSpecifier(entitlement.getProductName(),
+ BillingPeriod.valueOf(entitlement.getBillingPeriod()), entitlement.getPriceList(), phaseType);
final List<PlanPhasePriceOverride> overrides = PhasePriceOverrideJson.toPlanPhasePriceOverrides(entitlement.getPriceOverrides(), planSpec, account.getCurrency());
if (requestedDate == null && policyString == null) {
@@ -865,6 +890,22 @@ public class SubscriptionResource extends JaxRsResourceBase {
context.createCallContextNoAccountId(createdBy, reason, comment, request), uriInfo, request);
}
+ @PUT
+ @Path("/{subscriptionId:" + UUID_PATTERN + "}/" + CUSTOM_FIELDS)
+ @Consumes(APPLICATION_JSON)
+ @Produces(APPLICATION_JSON)
+ @ApiOperation(value = "Modify custom fields to subscription")
+ @ApiResponses(value = {@ApiResponse(code = 400, message = "Invalid subscription id supplied")})
+ public Response modifyCustomFields(@PathParam(ID_PARAM_NAME) final String id,
+ final List<CustomFieldJson> customFields,
+ @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 CustomFieldApiException {
+ return super.modifyCustomFields(UUID.fromString(id), customFields,
+ context.createCallContextNoAccountId(createdBy, reason, comment, request));
+ }
+
@DELETE
@Path("/{subscriptionId:" + UUID_PATTERN + "}/" + CUSTOM_FIELDS)
@Consumes(APPLICATION_JSON)
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 d5cfaa0..efa5c24 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
@@ -52,6 +52,7 @@ import org.killbill.billing.util.tag.TagDefinition;
import org.killbill.clock.Clock;
import org.killbill.commons.metrics.TimedResource;
+import com.google.common.base.Preconditions;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import io.swagger.annotations.Api;
@@ -129,8 +130,11 @@ public class TagDefinitionResource extends JaxRsResourceBase {
verifyNonNullOrEmpty(json, "TagDefinitionJson body should be specified");
verifyNonNullOrEmpty(json.getName(), "TagDefinition name needs to be set",
json.getDescription(), "TagDefinition description needs to be set");
+ Preconditions.checkArgument(json.getApplicableObjectTypes() != null &&
+ !json.getApplicableObjectTypes().isEmpty(), "Applicable object types must be set");
- final TagDefinition createdTagDef = tagUserApi.createTagDefinition(json.getName(), json.getDescription(), context.createCallContextNoAccountId(createdBy, reason, comment, request));
+
+ final TagDefinition createdTagDef = tagUserApi.createTagDefinition(json.getName(), json.getDescription(), TagDefinitionJson.toObjectType(json.getApplicableObjectTypes()), context.createCallContextNoAccountId(createdBy, reason, comment, request));
return uriBuilder.buildResponse(uriInfo, TagDefinitionResource.class, "getTagDefinition", createdTagDef.getId(), request);
}
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 17ab9cd..664fcea 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
@@ -37,6 +37,7 @@ import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.UriInfo;
+import org.joda.time.DateTime;
import org.killbill.billing.ObjectType;
import org.killbill.billing.account.api.AccountUserApi;
import org.killbill.billing.callcontext.DefaultCallContext;
@@ -125,7 +126,7 @@ public class TenantResource extends JaxRsResourceBase {
@ApiOperation(value = "Create a tenant")
@ApiResponses(value = {@ApiResponse(code = 500, message = "Tenant already exists")})
public Response createTenant(final TenantJson json,
- @QueryParam(QUERY_TENANT_USE_GLOBAL_DEFAULT) @DefaultValue("true") final Boolean useGlobalDefault,
+ @QueryParam(QUERY_TENANT_USE_GLOBAL_DEFAULT) @DefaultValue("false") final Boolean useGlobalDefault,
@HeaderParam(HDR_CREATED_BY) final String createdBy,
@HeaderParam(HDR_REASON) final String reason,
@HeaderParam(HDR_COMMENT) final String comment,
@@ -140,11 +141,13 @@ public class TenantResource extends JaxRsResourceBase {
if (!useGlobalDefault) {
final CallContext callContext = new DefaultCallContext(null, tenant.getId(), createdBy, CallOrigin.EXTERNAL,
UserType.CUSTOMER, Context.getOrCreateUserToken(), clock);
- catalogUserApi.createDefaultEmptyCatalog(clock.getUTCNow(),callContext);
+
+ catalogUserApi.createDefaultEmptyCatalog(null, callContext);
}
return uriBuilder.buildResponse(uriInfo, TenantResource.class, "getTenant", tenant.getId(), request);
}
+
@TimedResource
@POST
@Path("/" + REGISTER_NOTIFICATION_CALLBACK)
@@ -420,4 +423,5 @@ public class TenantResource extends JaxRsResourceBase {
protected ObjectType getObjectType() {
return ObjectType.TENANT;
}
+
}
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TestResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TestResource.java
index 54bcfa6..a73a9b9 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TestResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TestResource.java
@@ -21,8 +21,10 @@ package org.killbill.billing.jaxrs.resources;
import javax.inject.Inject;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
+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;
import javax.ws.rs.Path;
@@ -30,19 +32,28 @@ import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
+import javax.ws.rs.core.UriInfo;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.LocalDate;
import org.killbill.billing.ObjectType;
import org.killbill.billing.account.api.AccountUserApi;
+import org.killbill.billing.catalog.api.CatalogApiException;
+import org.killbill.billing.catalog.api.CatalogUserApi;
import org.killbill.billing.jaxrs.util.Context;
import org.killbill.billing.jaxrs.util.JaxrsUriBuilder;
import org.killbill.billing.payment.api.PaymentApi;
+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.api.AuditUserApi;
import org.killbill.billing.util.api.CustomFieldUserApi;
import org.killbill.billing.util.api.RecordIdApi;
import org.killbill.billing.util.api.TagUserApi;
+import org.killbill.billing.util.cache.Cachable.CacheType;
+import org.killbill.billing.util.cache.CacheControllerDispatcher;
+import org.killbill.billing.util.callcontext.CallContext;
import org.killbill.billing.util.callcontext.TenantContext;
import org.killbill.bus.api.BusEvent;
import org.killbill.bus.api.BusEventWithMetadata;
@@ -85,16 +96,23 @@ public class TestResource extends JaxRsResourceBase {
private final PersistentBus persistentBus;
private final NotificationQueueService notificationQueueService;
private final RecordIdApi recordIdApi;
+ private final TenantUserApi tenantApi;
+ private final CatalogUserApi catalogUserApi;
+ private final CacheControllerDispatcher cacheControllerDispatcher;
@Inject
public TestResource(final JaxrsUriBuilder uriBuilder, final TagUserApi tagUserApi, final CustomFieldUserApi customFieldUserApi,
final AuditUserApi auditUserApi, final AccountUserApi accountUserApi, final RecordIdApi recordIdApi,
final PersistentBus persistentBus, final NotificationQueueService notificationQueueService, final PaymentApi paymentApi,
- final Clock clock, final Context context) {
+ final TenantUserApi tenantApi, final CatalogUserApi catalogUserApi,
+ final Clock clock, final CacheControllerDispatcher cacheControllerDispatcher, final Context context) {
super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, null, clock, context);
this.persistentBus = persistentBus;
this.notificationQueueService = notificationQueueService;
this.recordIdApi = recordIdApi;
+ this.catalogUserApi = catalogUserApi;
+ this.tenantApi = tenantApi;
+ this.cacheControllerDispatcher = cacheControllerDispatcher;
}
public final class ClockResource {
@@ -201,6 +219,8 @@ public class TestResource extends JaxRsResourceBase {
return getCurrentTime(timeZoneStr);
}
+
+
private boolean waitForNotificationToComplete(final ServletRequest request, final Long timeoutSec) {
final TenantContext tenantContext = context.createTenantContextNoAccountId(request);
final Long tenantRecordId = recordIdApi.getRecordId(tenantContext.getTenantId(), ObjectType.TENANT, tenantContext);
@@ -248,7 +268,7 @@ public class TestResource extends JaxRsResourceBase {
private boolean areAllBusEventsProcessed(final Long tenantRecordId) {
final Iterable<BusEventWithMetadata<BusEvent>> availableBusEventForSearchKey2 = persistentBus.getAvailableOrInProcessingBusEventsForSearchKey2(null, tenantRecordId);
- final int nbBusEvents = Iterables.<BusEventWithMetadata<BusEvent>>size(availableBusEventForSearchKey2);
+ final int nbBusEvents = Iterables.size(availableBusEventForSearchKey2);
if (nbBusEvents != 0) {
log.info("TestResource: at least {} more bus event(s) to process", nbBusEvents);
}
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 7972a37..414b01d 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
@@ -28,6 +28,7 @@ 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;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
@@ -48,6 +49,7 @@ 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.PaymentOptions;
import org.killbill.billing.payment.api.PluginProperty;
import org.killbill.billing.payment.api.TransactionStatus;
import org.killbill.billing.util.api.AuditUserApi;
@@ -119,6 +121,7 @@ public class TransactionResource extends JaxRsResourceBase {
@ApiResponse(code = 404, message = "Account or Payment not found")})
public Response notifyStateChanged(final PaymentTransactionJson json,
@PathParam("transactionId") final String transactionIdStr,
+ @QueryParam(QUERY_PAYMENT_CONTROL_PLUGIN_NAME) final List<String> paymentControlPluginNames,
@HeaderParam(HDR_CREATED_BY) final String createdBy,
@HeaderParam(HDR_REASON) final String reason,
@HeaderParam(HDR_COMMENT) final String comment,
@@ -128,6 +131,7 @@ public class TransactionResource extends JaxRsResourceBase {
verifyNonNullOrEmpty(json.getPaymentId(), "PaymentTransactionJson paymentId needs to be set",
json.getStatus(), "PaymentTransactionJson status needs to be set");
+ final PaymentOptions paymentOptions = createControlPluginApiPaymentOptions(paymentControlPluginNames);
final CallContext callContext = context.createCallContextNoAccountId(createdBy, reason, comment, request);
final UUID paymentId = UUID.fromString(json.getPaymentId());
@@ -135,7 +139,8 @@ public class TransactionResource extends JaxRsResourceBase {
final Account account = accountUserApi.getAccountById(payment.getAccountId(), callContext);
final boolean success = TransactionStatus.SUCCESS.name().equals(json.getStatus());
- final Payment result = paymentApi.notifyPendingTransactionOfStateChanged(account, UUID.fromString(transactionIdStr), success, callContext);
+ final Payment result = paymentApi.notifyPendingTransactionOfStateChangedWithPaymentControl(account, UUID.fromString(transactionIdStr), success, paymentOptions, callContext);
+
return uriBuilder.buildResponse(uriInfo, PaymentResource.class, "getPayment", result.getId(), request);
}
@@ -170,6 +175,25 @@ public class TransactionResource extends JaxRsResourceBase {
context.createCallContextNoAccountId(createdBy, reason, comment, request), uriInfo, request);
}
+
+ @TimedResource
+ @PUT
+ @Path("/{transactionId:" + UUID_PATTERN + "}/" + CUSTOM_FIELDS)
+ @Consumes(APPLICATION_JSON)
+ @Produces(APPLICATION_JSON)
+ @ApiOperation(value = "Modify custom fields to payment transaction")
+ @ApiResponses(value = {@ApiResponse(code = 400, message = "Invalid transaction id supplied")})
+ public Response modifyCustomFields(@PathParam(ID_PARAM_NAME) final String id,
+ final List<CustomFieldJson> customFields,
+ @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 CustomFieldApiException {
+ return super.modifyCustomFields(UUID.fromString(id), customFields,
+ context.createCallContextNoAccountId(createdBy, reason, comment, request));
+ }
+
+
@TimedResource
@DELETE
@Path("/{transactionId:" + UUID_PATTERN + "}/" + CUSTOM_FIELDS)
diff --git a/jaxrs/src/test/java/org/killbill/billing/jaxrs/json/TestAccountJson.java b/jaxrs/src/test/java/org/killbill/billing/jaxrs/json/TestAccountJson.java
index b601e7c..08b1186 100644
--- a/jaxrs/src/test/java/org/killbill/billing/jaxrs/json/TestAccountJson.java
+++ b/jaxrs/src/test/java/org/killbill/billing/jaxrs/json/TestAccountJson.java
@@ -18,6 +18,7 @@ package org.killbill.billing.jaxrs.json;
import java.util.UUID;
+import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.testng.Assert;
import org.testng.annotations.Test;
@@ -39,6 +40,7 @@ public class TestAccountJson extends JaxrsTestSuiteNoDB {
final Integer billCycleDayLocal = 6;
final String currency = UUID.randomUUID().toString();
final String paymentMethodId = UUID.randomUUID().toString();
+ final DateTime referenceTime = new DateTime();
final String timeZone = UUID.randomUUID().toString();
final String address1 = UUID.randomUUID().toString();
final String address2 = UUID.randomUUID().toString();
@@ -56,7 +58,7 @@ public class TestAccountJson extends JaxrsTestSuiteNoDB {
final AccountJson accountJson = new AccountJson(accountId, name, length, externalKey,
email, billCycleDayLocal, currency, parentAccountId, true, paymentMethodId,
- timeZone, address1, address2, postalCode, company, city, state,
+ referenceTime, timeZone, address1, address2, postalCode, company, city, state,
country, locale, phone, notes, isMigrated, isNotifiedForInvoice, null, null, null);
Assert.assertEquals(accountJson.getAccountId(), accountId);
Assert.assertEquals(accountJson.getName(), name);
diff --git a/jaxrs/src/test/java/org/killbill/billing/jaxrs/json/TestInvoiceJsonWithBundleKeys.java b/jaxrs/src/test/java/org/killbill/billing/jaxrs/json/TestInvoiceJsonWithBundleKeys.java
index 6b9a29e..5dc7791 100644
--- a/jaxrs/src/test/java/org/killbill/billing/jaxrs/json/TestInvoiceJsonWithBundleKeys.java
+++ b/jaxrs/src/test/java/org/killbill/billing/jaxrs/json/TestInvoiceJsonWithBundleKeys.java
@@ -54,7 +54,7 @@ public class TestInvoiceJsonWithBundleKeys extends JaxrsTestSuiteNoDB {
final InvoiceJson invoiceJsonSimple = new InvoiceJson(amount, Currency.USD.toString(), InvoiceStatus.COMMITTED.toString(),
creditAdj, refundAdj, invoiceId, invoiceDate,
targetDate, invoiceNumber, balance, accountId, bundleKeys,
- credits, null, false, auditLogs);
+ credits, null, false, null, null, auditLogs);
Assert.assertEquals(invoiceJsonSimple.getAmount(), amount);
Assert.assertEquals(invoiceJsonSimple.getCreditAdj(), creditAdj);
Assert.assertEquals(invoiceJsonSimple.getRefundAdj(), refundAdj);
diff --git a/jaxrs/src/test/java/org/killbill/billing/jaxrs/json/TestTagDefinitionJson.java b/jaxrs/src/test/java/org/killbill/billing/jaxrs/json/TestTagDefinitionJson.java
index 93721d6..2a6da5e 100644
--- a/jaxrs/src/test/java/org/killbill/billing/jaxrs/json/TestTagDefinitionJson.java
+++ b/jaxrs/src/test/java/org/killbill/billing/jaxrs/json/TestTagDefinitionJson.java
@@ -24,6 +24,7 @@ import org.testng.annotations.Test;
import org.killbill.billing.jaxrs.JaxrsTestSuiteNoDB;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
public class TestTagDefinitionJson extends JaxrsTestSuiteNoDB {
@@ -33,7 +34,7 @@ public class TestTagDefinitionJson extends JaxrsTestSuiteNoDB {
final Boolean isControlTag = true;
final String name = UUID.randomUUID().toString();
final String description = UUID.randomUUID().toString();
- final ImmutableList<String> applicableObjectTypes = ImmutableList.<String>of(UUID.randomUUID().toString());
+ final ImmutableSet<String> applicableObjectTypes = ImmutableSet.<String>of(UUID.randomUUID().toString());
final TagDefinitionJson tagDefinitionJson = new TagDefinitionJson(id, isControlTag, name, description, applicableObjectTypes, null);
Assert.assertEquals(tagDefinitionJson.getId(), id);
Assert.assertEquals(tagDefinitionJson.isControlTag(), isControlTag);
junction/pom.xml 2(+1 -1)
diff --git a/junction/pom.xml b/junction/pom.xml
index a11f142..b3f3200 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.19.0-SNAPSHOT</version>
+ <version>0.19.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-junction</artifactId>
diff --git a/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/BlockingCalculator.java b/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/BlockingCalculator.java
index 3ff89c4..6eb4976 100644
--- a/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/BlockingCalculator.java
+++ b/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/BlockingCalculator.java
@@ -33,7 +33,6 @@ import java.util.concurrent.atomic.AtomicLong;
import javax.annotation.Nullable;
import org.joda.time.DateTime;
-import org.joda.time.Days;
import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.catalog.api.BillingPeriod;
import org.killbill.billing.catalog.api.Catalog;
@@ -51,10 +50,13 @@ import org.killbill.billing.subscription.api.SubscriptionBaseTransitionType;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
+import com.google.common.base.Function;
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.Lists;
+import com.google.common.collect.Ordering;
import com.google.inject.Inject;
public class BlockingCalculator {
@@ -62,35 +64,10 @@ public class BlockingCalculator {
private static final AtomicLong globaltotalOrder = new AtomicLong();
private final BlockingInternalApi blockingApi;
- private final CatalogInternalApi catalogInternalApi;
-
- protected static class DisabledDuration {
-
- private final DateTime start;
- private DateTime end;
-
- public DisabledDuration(final DateTime start, final DateTime end) {
- this.start = start;
- this.end = end;
- }
-
- public DateTime getStart() {
- return start;
- }
-
- public DateTime getEnd() {
- return end;
- }
-
- public void setEnd(final DateTime end) {
- this.end = end;
- }
- }
@Inject
- public BlockingCalculator(final BlockingInternalApi blockingApi, final CatalogInternalApi catalogInternalApi) {
+ public BlockingCalculator(final BlockingInternalApi blockingApi) {
this.blockingApi = blockingApi;
- this.catalogInternalApi = catalogInternalApi;
}
/**
@@ -98,7 +75,7 @@ public class BlockingCalculator {
*
* @param billingEvents the original list of billing events to update (without overdue events)
*/
- public boolean insertBlockingEvents(final SortedSet<BillingEvent> billingEvents, final Set<UUID> skippedSubscriptions, final InternalTenantContext context) throws CatalogApiException {
+ public boolean insertBlockingEvents(final SortedSet<BillingEvent> billingEvents, final Set<UUID> skippedSubscriptions, final Catalog catalog, final InternalTenantContext context) throws CatalogApiException {
if (billingEvents.size() <= 0) {
return false;
}
@@ -134,8 +111,13 @@ public class BlockingCalculator {
final List<BlockingState> aggregateSubscriptionBlockingEvents = getAggregateBlockingEventsPerSubscription(subscription.getEndDate(), subscriptionBlockingEvents, bundleBlockingEvents, accountBlockingEvents);
final List<DisabledDuration> accountBlockingDurations = createBlockingDurations(aggregateSubscriptionBlockingEvents);
- billingEventsToAdd.addAll(createNewEvents(accountBlockingDurations, billingEvents, subscription, context));
- billingEventsToRemove.addAll(eventsToRemove(accountBlockingDurations, billingEvents, subscription));
+ final SortedSet<BillingEvent> subscriptionBillingEvents = filter(billingEvents, subscription);
+
+ final SortedSet<BillingEvent> newEvents = createNewEvents(accountBlockingDurations, subscriptionBillingEvents, catalog, context);
+ billingEventsToAdd.addAll(newEvents);
+
+ final SortedSet<BillingEvent> removedEvents = eventsToRemove(accountBlockingDurations, subscriptionBillingEvents);
+ billingEventsToRemove.addAll(removedEvents);
}
}
@@ -183,14 +165,13 @@ public class BlockingCalculator {
}
protected SortedSet<BillingEvent> eventsToRemove(final List<DisabledDuration> disabledDuration,
- final SortedSet<BillingEvent> billingEvents, final SubscriptionBase subscription) {
+ final SortedSet<BillingEvent> subscriptionBillingEvents) {
final SortedSet<BillingEvent> result = new TreeSet<BillingEvent>();
- final SortedSet<BillingEvent> filteredBillingEvents = filter(billingEvents, subscription);
for (final DisabledDuration duration : disabledDuration) {
- for (final BillingEvent event : filteredBillingEvents) {
+ for (final BillingEvent event : subscriptionBillingEvents) {
if (duration.getEnd() == null || event.getEffectiveDate().isBefore(duration.getEnd())) {
- if (event.getEffectiveDate().isAfter(duration.getStart())) { //between the pair
+ if (!event.getEffectiveDate().isBefore(duration.getStart())) {
result.add(event);
}
} else { //after the last event of the pair no need to keep checking
@@ -201,21 +182,20 @@ public class BlockingCalculator {
return result;
}
- protected SortedSet<BillingEvent> createNewEvents(final List<DisabledDuration> disabledDuration, final SortedSet<BillingEvent> billingEvents, final SubscriptionBase subscription, final InternalTenantContext context) throws CatalogApiException {
+ protected SortedSet<BillingEvent> createNewEvents(final List<DisabledDuration> disabledDuration, final SortedSet<BillingEvent> subscriptionBillingEvents, final Catalog catalog, final InternalTenantContext context) throws CatalogApiException {
Preconditions.checkState(context.getAccountRecordId() != null);
final SortedSet<BillingEvent> result = new TreeSet<BillingEvent>();
- final Catalog catalog = catalogInternalApi.getFullCatalog(true, true, context);
for (final DisabledDuration duration : disabledDuration) {
// The first one before the blocked duration
- final BillingEvent precedingInitialEvent = precedingBillingEventForSubscription(duration.getStart(), billingEvents, subscription);
+ final BillingEvent precedingInitialEvent = precedingBillingEventForSubscription(duration.getStart(), subscriptionBillingEvents);
// The last one during of before the duration
- final BillingEvent precedingFinalEvent = precedingBillingEventForSubscription(duration.getEnd(), billingEvents, subscription);
+ final BillingEvent precedingFinalEvent = precedingBillingEventForSubscription(duration.getEnd(), subscriptionBillingEvents);
if (precedingInitialEvent != null) { // there is a preceding billing event
- result.add(createNewDisableEvent(duration.getStart(), precedingInitialEvent, catalog, context));
+ result.add(createNewDisableEvent(duration.getStart(), precedingInitialEvent, catalog));
if (duration.getEnd() != null) { // no second event in the pair means they are still disabled (no re-enable)
result.add(createNewReenableEvent(duration.getEnd(), precedingFinalEvent, catalog, context));
}
@@ -227,43 +207,40 @@ public class BlockingCalculator {
return result;
}
- protected BillingEvent precedingBillingEventForSubscription(final DateTime datetime, final SortedSet<BillingEvent> billingEvents, final SubscriptionBase subscription) {
- if (datetime == null) { //second of a pair can be null if there's no re-enabling
- return null;
- }
-
- final SortedSet<BillingEvent> filteredBillingEvents = filter(billingEvents, subscription);
- BillingEvent result = filteredBillingEvents.first();
- if (datetime.isBefore(result.getEffectiveDate())) {
- //This case can happen, for example, if we have an add on and the bundle goes into disabled before the add on is created
+ protected BillingEvent precedingBillingEventForSubscription(final DateTime disabledDurationStart, final SortedSet<BillingEvent> subscriptionBillingEvents) {
+ if (disabledDurationStart == null) {
return null;
}
- for (final BillingEvent event : filteredBillingEvents) {
- if (!event.getEffectiveDate().isBefore(datetime)) { // found it its the previous event
- return result;
- } else { // still looking
- result = event;
+ // We look for the first billingEvent strictly prior our disabledDurationStart or null if none
+ BillingEvent prev = null;
+ for (final BillingEvent event : subscriptionBillingEvents) {
+ if (!event.getEffectiveDate().isBefore(disabledDurationStart)) {
+ return prev;
+ } else {
+ prev = event;
}
}
- return result;
+ return prev;
}
+
protected SortedSet<BillingEvent> filter(final SortedSet<BillingEvent> billingEvents, final SubscriptionBase subscription) {
final SortedSet<BillingEvent> result = new TreeSet<BillingEvent>();
for (final BillingEvent event : billingEvents) {
- if (event.getSubscription() == subscription) {
+ if (event.getSubscription().getId().equals(subscription.getId())) {
result.add(event);
}
}
return result;
}
- protected BillingEvent createNewDisableEvent(final DateTime odEventTime, final BillingEvent previousEvent, final Catalog catalog, final InternalTenantContext context) throws CatalogApiException {
+ protected BillingEvent createNewDisableEvent(final DateTime disabledDurationStart, final BillingEvent previousEvent, final Catalog catalog) throws CatalogApiException {
+
final int billCycleDay = previousEvent.getBillCycleDayLocal();
final SubscriptionBase subscription = previousEvent.getSubscription();
- final DateTime effectiveDate = odEventTime;
+ final DateTime effectiveDate = disabledDurationStart;
final PlanPhase planPhase = previousEvent.getPlanPhase();
final Plan plan = previousEvent.getPlan();
@@ -320,55 +297,54 @@ public class BlockingCalculator {
}
// In ascending order
- protected List<DisabledDuration> createBlockingDurations(final Iterable<BlockingState> overdueBundleEvents) {
- final List<DisabledDuration> result = new ArrayList<BlockingCalculator.DisabledDuration>();
- // Earliest blocking event
- BlockingState first = null;
-
- int blockedNesting = 0;
- BlockingState lastOne = null;
- for (final BlockingState e : overdueBundleEvents) {
- lastOne = e;
- if (e.isBlockBilling() && blockedNesting == 0) {
- // First blocking event of contiguous series of blocking events
- first = e;
- blockedNesting++;
- } else if (e.isBlockBilling() && blockedNesting > 0) {
- // Nest blocking states
- blockedNesting++;
- } else if (!e.isBlockBilling() && blockedNesting > 0) {
- blockedNesting--;
- if (blockedNesting == 0) {
- // End of the interval
- addDisabledDuration(result, first, e);
- first = null;
- }
+ protected List<DisabledDuration> createBlockingDurations(final Iterable<BlockingState> inputBundleEvents) {
+
+ final List<DisabledDuration> result = new ArrayList<DisabledDuration>();
+
+ final Set<String> services = ImmutableSet.copyOf(Iterables.transform(inputBundleEvents, new Function<BlockingState, String>() {
+ @Override
+ public String apply(final BlockingState input) {
+ return input.getService();
}
- }
+ }));
- if (first != null) { // found a transition to disabled with no terminating event
- addDisabledDuration(result, first, lastOne.isBlockBilling() ? null : lastOne);
+ final Map<String, BlockingStateService> svcBlockedMap = new HashMap<String, BlockingStateService>();
+ for (String svc : services) {
+ svcBlockedMap.put(svc, new BlockingStateService());
}
- return result;
- }
-
- private void addDisabledDuration(final List<DisabledDuration> result, final BlockingState firstBlocking, @Nullable final BlockingState firstNonBlocking) {
- final DisabledDuration lastOne;
- if (!result.isEmpty()) {
- lastOne = result.get(result.size() - 1);
- } else {
- lastOne = null;
+ for (final BlockingState e : inputBundleEvents) {
+ svcBlockedMap.get(e.getService()).addBlockingState(e);
}
- final DateTime startDate = firstBlocking.getEffectiveDate();
- final DateTime endDate = firstNonBlocking == null ? null : firstNonBlocking.getEffectiveDate();
- if (lastOne != null && lastOne.getEnd().compareTo(startDate) == 0) {
- lastOne.setEnd(endDate);
- } else if (endDate == null || Days.daysBetween(startDate, endDate).getDays() >= 1) {
- // Don't disable for periods less than a day (see https://github.com/killbill/killbill/issues/267)
- result.add(new DisabledDuration(startDate, endDate));
+ final Iterable<DisabledDuration> unorderedDisabledDuration = Iterables.concat(Iterables.transform(svcBlockedMap.values(), new Function<BlockingStateService, List<DisabledDuration>>() {
+ @Override
+ public List<DisabledDuration> apply(final BlockingStateService input) {
+ return input.build();
+ }
+ }));
+
+ final List<DisabledDuration> sortedDisabledDuration = Ordering.natural().sortedCopy(unorderedDisabledDuration);
+
+ DisabledDuration prevDuration = null;
+ for (DisabledDuration d : sortedDisabledDuration) {
+ // isDisjoint
+ if (prevDuration == null) {
+ prevDuration = d;
+ } else {
+ if (prevDuration.isDisjoint(d)) {
+ result.add(prevDuration);
+ prevDuration = d;
+ } else {
+ prevDuration = DisabledDuration.mergeDuration(prevDuration, d);
+ }
+ }
+ }
+ if (prevDuration != null) {
+ result.add(prevDuration);
}
+
+ return result;
}
@VisibleForTesting
diff --git a/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/BlockingStateService.java b/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/BlockingStateService.java
new file mode 100644
index 0000000..2e2dc56
--- /dev/null
+++ b/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/BlockingStateService.java
@@ -0,0 +1,69 @@
+/*
+ * 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.junction.plumbing.billing;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import javax.annotation.Nullable;
+
+import org.joda.time.DateTime;
+import org.joda.time.Days;
+import org.killbill.billing.entitlement.api.BlockingState;
+
+public class BlockingStateService {
+
+ final Map<UUID, BlockingState> perObjectTypeFirstBlockingState = new HashMap<UUID, BlockingState>();
+
+ private final List<DisabledDuration> result;
+
+ public BlockingStateService() {
+ this.result = new ArrayList<DisabledDuration>();
+ }
+
+ public List<DisabledDuration> build() {
+ for (BlockingState cur : perObjectTypeFirstBlockingState.values()) {
+ if (cur != null) {
+ addDisabledDuration(cur, null);
+ }
+ }
+ return result;
+ }
+
+ public void addBlockingState(final BlockingState currentBlockingState) {
+
+ final BlockingState firstBlockingState = perObjectTypeFirstBlockingState.get(currentBlockingState.getBlockedId());
+ if (currentBlockingState.isBlockBilling() && firstBlockingState == null) {
+ perObjectTypeFirstBlockingState.put(currentBlockingState.getBlockedId(), currentBlockingState);
+ } else if (!currentBlockingState.isBlockBilling() && firstBlockingState != null) {
+ addDisabledDuration(firstBlockingState, currentBlockingState.getEffectiveDate());
+ perObjectTypeFirstBlockingState.put(currentBlockingState.getBlockedId(), null);
+ }
+ }
+
+ private void addDisabledDuration(final BlockingState firstBlockingState, @Nullable final DateTime disableDurationEndDate) {
+
+ if (disableDurationEndDate == null || Days.daysBetween(firstBlockingState.getEffectiveDate(), disableDurationEndDate).getDays() >= 1) {
+ // Don't disable for periods less than a day (see https://github.com/killbill/killbill/issues/267)
+ result.add(new DisabledDuration(firstBlockingState.getEffectiveDate(), disableDurationEndDate));
+ }
+ }
+}
diff --git a/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultBillingEventSet.java b/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultBillingEventSet.java
index c971588..0522fb1 100644
--- a/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultBillingEventSet.java
+++ b/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultBillingEventSet.java
@@ -41,12 +41,15 @@ public class DefaultBillingEventSet extends TreeSet<BillingEvent> implements Sor
private static final long serialVersionUID = 1L;
private final boolean accountAutoInvoiceOff;
+ private final boolean accountAutoInvoiceDraft;
+ private final boolean accountAutoInvoiceReuseDraft;
private final List<UUID> subscriptionIdsWithAutoInvoiceOff;
- private final BillingMode recurringBillingMode;
- public DefaultBillingEventSet(final boolean accountAutoInvoiceOff, final BillingMode recurringBillingMode) {
+
+ public DefaultBillingEventSet(final boolean accountAutoInvoiceOff, final boolean accountAutoInvoiceDraft, final boolean accountAutoInvoiceReuseDraft) {
this.accountAutoInvoiceOff = accountAutoInvoiceOff;
- this.recurringBillingMode = recurringBillingMode;
+ this.accountAutoInvoiceDraft = accountAutoInvoiceDraft;
+ this.accountAutoInvoiceReuseDraft = accountAutoInvoiceReuseDraft;
this.subscriptionIdsWithAutoInvoiceOff = new ArrayList<UUID>();
}
@@ -56,8 +59,13 @@ public class DefaultBillingEventSet extends TreeSet<BillingEvent> implements Sor
}
@Override
- public BillingMode getRecurringBillingMode() {
- return recurringBillingMode;
+ public boolean isAccountAutoInvoiceDraft() {
+ return accountAutoInvoiceDraft;
+ }
+
+ @Override
+ public boolean isAccountAutoInvoiceReuseDraft() {
+ return accountAutoInvoiceReuseDraft;
}
@Override
diff --git a/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultInternalBillingApi.java b/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultInternalBillingApi.java
index 9674c01..a3499b5 100644
--- a/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultInternalBillingApi.java
+++ b/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultInternalBillingApi.java
@@ -26,8 +26,6 @@ import java.util.Set;
import java.util.SortedSet;
import java.util.UUID;
-import javax.annotation.Nullable;
-
import org.killbill.billing.ObjectType;
import org.killbill.billing.account.api.AccountApiException;
import org.killbill.billing.account.api.AccountInternalApi;
@@ -62,8 +60,9 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Function;
-import com.google.common.base.Preconditions;
+import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
+import com.google.common.collect.Iterables;
import com.google.inject.Inject;
public class DefaultInternalBillingApi implements BillingInternalApi {
@@ -91,23 +90,25 @@ public class DefaultInternalBillingApi implements BillingInternalApi {
@Override
public BillingEventSet getBillingEventsForAccountAndUpdateAccountBCD(final UUID accountId, final DryRunArguments dryRunArguments, final InternalCallContext context) throws CatalogApiException, AccountApiException, SubscriptionBaseApiException {
- final StaticCatalog currentCatalog = catalogInternalApi.getCurrentCatalog(true, true, context);
+ final Catalog currentCatalog = catalogInternalApi.getFullCatalog(true, true, context);
// Check to see if billing is off for the account
final List<Tag> accountTags = tagApi.getTags(accountId, ObjectType.ACCOUNT, context);
final boolean found_AUTO_INVOICING_OFF = is_AUTO_INVOICING_OFF(accountTags);
+ final boolean found_INVOICING_DRAFT = is_AUTO_INVOICING_DRAFT(accountTags);
+ final boolean found_INVOICING_REUSE_DRAFT = is_AUTO_INVOICING_REUSE_DRAFT(accountTags);
final Set<UUID> skippedSubscriptions = new HashSet<UUID>();
final DefaultBillingEventSet result;
if (found_AUTO_INVOICING_OFF) {
- result = new DefaultBillingEventSet(true, currentCatalog.getRecurringBillingMode()); // billing is off, we are done
+ result = new DefaultBillingEventSet(true, found_INVOICING_DRAFT, found_INVOICING_REUSE_DRAFT); // billing is off, we are done
} else {
final List<SubscriptionBaseBundle> bundles = subscriptionApi.getBundlesForAccount(accountId, context);
final ImmutableAccountData account = accountApi.getImmutableAccountDataById(accountId, context);
- result = new DefaultBillingEventSet(false, currentCatalog.getRecurringBillingMode());
- addBillingEventsForBundles(bundles, account, dryRunArguments, context, result, skippedSubscriptions);
+ result = new DefaultBillingEventSet(false, found_INVOICING_DRAFT, found_INVOICING_REUSE_DRAFT);
+ addBillingEventsForBundles(bundles, account, dryRunArguments, context, result, skippedSubscriptions, currentCatalog);
}
if (result.isEmpty()) {
@@ -118,7 +119,7 @@ public class DefaultInternalBillingApi implements BillingInternalApi {
// Pretty-print the events, before and after the blocking calculator does its magic
final StringBuilder logStringBuilder = new StringBuilder("Computed billing events for accountId='").append(accountId).append("'");
eventsToString(logStringBuilder, result);
- if (blockCalculator.insertBlockingEvents(result, skippedSubscriptions, context)) {
+ if (blockCalculator.insertBlockingEvents(result, skippedSubscriptions, currentCatalog, context)) {
logStringBuilder.append("\nBilling Events After Blocking");
eventsToString(logStringBuilder, result);
}
@@ -134,7 +135,7 @@ public class DefaultInternalBillingApi implements BillingInternalApi {
}
private void addBillingEventsForBundles(final List<SubscriptionBaseBundle> bundles, final ImmutableAccountData account, final DryRunArguments dryRunArguments, final InternalCallContext context,
- final DefaultBillingEventSet result, final Set<UUID> skipSubscriptionsSet) throws AccountApiException, CatalogApiException, SubscriptionBaseApiException {
+ final DefaultBillingEventSet result, final Set<UUID> skipSubscriptionsSet, final Catalog catalog) throws AccountApiException, CatalogApiException, SubscriptionBaseApiException {
final boolean dryRunMode = dryRunArguments != null;
@@ -146,7 +147,7 @@ public class DefaultInternalBillingApi implements BillingInternalApi {
final UUID fakeBundleId = UUIDs.randomUUID();
final List<SubscriptionBase> subscriptions = subscriptionApi.getSubscriptionsForBundle(fakeBundleId, dryRunArguments, context);
- addBillingEventsForSubscription(account, subscriptions, null, dryRunMode, context, result, skipSubscriptionsSet);
+ addBillingEventsForSubscription(account, subscriptions, null, dryRunMode, context, result, skipSubscriptionsSet, catalog);
}
@@ -166,7 +167,7 @@ public class DefaultInternalBillingApi implements BillingInternalApi {
}
} else { // billing is not off
final SubscriptionBase baseSubscription = !subscriptions.isEmpty() ? subscriptions.get(0) : null;
- addBillingEventsForSubscription(account, subscriptions, baseSubscription, dryRunMode, context, result, skipSubscriptionsSet);
+ addBillingEventsForSubscription(account, subscriptions, baseSubscription, dryRunMode, context, result, skipSubscriptionsSet, catalog);
}
}
}
@@ -177,7 +178,8 @@ public class DefaultInternalBillingApi implements BillingInternalApi {
final boolean dryRunMode,
final InternalCallContext context,
final DefaultBillingEventSet result,
- final Set<UUID> skipSubscriptionsSet) throws AccountApiException, CatalogApiException, SubscriptionBaseApiException {
+ final Set<UUID> skipSubscriptionsSet,
+ final Catalog catalog) throws AccountApiException, CatalogApiException, SubscriptionBaseApiException {
// If dryRun is specified, we don't want to to update the account BCD value, so we initialize the flag updatedAccountBCD to true
boolean updatedAccountBCD = dryRunMode;
@@ -197,8 +199,6 @@ public class DefaultInternalBillingApi implements BillingInternalApi {
return;
}
- final Catalog catalog = catalogInternalApi.getFullCatalog(true, true, context);
-
Integer overridenBCD = null;
for (final EffectiveSubscriptionInternalEvent transition : billingTransitions) {
//
@@ -245,11 +245,30 @@ public class DefaultInternalBillingApi implements BillingInternalApi {
private boolean is_AUTO_INVOICING_OFF(final List<Tag> tags) {
return ControlTagType.isAutoInvoicingOff(Collections2.transform(tags, new Function<Tag, UUID>() {
- @Nullable
@Override
- public UUID apply(@Nullable final Tag tag) {
+ public UUID apply(final Tag tag) {
return tag.getTagDefinitionId();
}
}));
}
+
+ private boolean is_AUTO_INVOICING_DRAFT(final List<Tag> tags) {
+ return Iterables.any(tags, new Predicate<Tag>() {
+ @Override
+ public boolean apply(final Tag input) {
+ return input.getTagDefinitionId().equals(ControlTagType.AUTO_INVOICING_DRAFT.getId());
+ }
+ });
+ }
+
+ private boolean is_AUTO_INVOICING_REUSE_DRAFT(final List<Tag> tags) {
+ return Iterables.any(tags, new Predicate<Tag>() {
+ @Override
+ public boolean apply(final Tag input) {
+ return input.getTagDefinitionId().equals(ControlTagType.AUTO_INVOICING_REUSE_DRAFT.getId());
+ }
+ });
+ }
+
+
}
diff --git a/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DisabledDuration.java b/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DisabledDuration.java
new file mode 100644
index 0000000..d8f7870
--- /dev/null
+++ b/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DisabledDuration.java
@@ -0,0 +1,124 @@
+/*
+ * 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.junction.plumbing.billing;
+
+import org.joda.time.DateTime;
+
+import com.google.common.base.Preconditions;
+
+class DisabledDuration implements Comparable<DisabledDuration> {
+
+ private final DateTime start;
+ private DateTime end;
+
+ public DisabledDuration(final DateTime start, final DateTime end) {
+ this.start = start;
+ this.end = end;
+ }
+
+ public DateTime getStart() {
+ return start;
+ }
+
+ public DateTime getEnd() {
+ return end;
+ }
+
+ public void setEnd(final DateTime end) {
+ this.end = end;
+ }
+
+ // Order by start date first and then end date
+ @Override
+ public int compareTo(final DisabledDuration o) {
+ int result = start.compareTo(o.getStart());
+ if (result == 0) {
+ if (end == null && o.getEnd() == null) {
+ result = 0;
+ } else if (end != null && o.getEnd() != null) {
+ result = end.compareTo(o.getEnd());
+ } else if (o.getEnd() == null) {
+ return -1;
+ } else {
+ return 1;
+ }
+ }
+ return result;
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof DisabledDuration)) {
+ return false;
+ }
+
+ final DisabledDuration that = (DisabledDuration) o;
+
+ return compareTo(that) == 0;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = start != null ? start.hashCode() : 0;
+ result = 31 * result + (end != null ? end.hashCode() : 0);
+ return result;
+ }
+
+ //
+ //
+ // Assumptions (based on ordering):
+ // * this.start <= o.start
+ // * this.end <= o.end when this.start == o.start
+ //
+ // Case 1: this contained into o => false
+ // |---------| this
+ // |--------------| o
+ //
+ // Case 2: this overlaps with o => false
+ // |---------| this
+ // |--------------| o
+ //
+ // Case 3: o contains into this => false
+ // |---------| this
+ // |---| o
+ //
+ // Case 4: this and o are adjacent => false
+ // |---------| this
+ // |---| o
+ // Case 5: this and o are disjoint => true
+ // |---------| this
+ // |---| o
+ public boolean isDisjoint(final DisabledDuration o) {
+ return end!= null && end.compareTo(o.getStart()) < 0;
+ }
+
+ public static DisabledDuration mergeDuration(DisabledDuration d1, DisabledDuration d2) {
+ Preconditions.checkState(d1.getStart().compareTo(d2.getStart()) <=0 );
+ Preconditions.checkState(!d1.isDisjoint(d2));
+
+ final DateTime endDate = (d1.getEnd() != null && d2.getEnd() != null) ?
+ d1.getEnd().compareTo(d2.getEnd()) < 0 ? d2.getEnd() : d1.getEnd() :
+ null;
+
+ return new DisabledDuration(d1.getStart(), endDate);
+ }
+
+}
diff --git a/junction/src/test/java/org/killbill/billing/junction/JunctionTestSuiteWithEmbeddedDB.java b/junction/src/test/java/org/killbill/billing/junction/JunctionTestSuiteWithEmbeddedDB.java
index 4eced64..859d024 100644
--- a/junction/src/test/java/org/killbill/billing/junction/JunctionTestSuiteWithEmbeddedDB.java
+++ b/junction/src/test/java/org/killbill/billing/junction/JunctionTestSuiteWithEmbeddedDB.java
@@ -108,16 +108,10 @@ public abstract class JunctionTestSuiteWithEmbeddedDB extends GuicyKillbillTestS
super.beforeMethod();
startTestFamework();
this.catalog = initCatalog(catalogService);
-
- // Make sure we start with a clean state
- assertListenerStatus();
}
@AfterMethod(groups = "slow")
public void afterMethod() throws Exception {
- // Make sure we finish in a clean state
- assertListenerStatus();
-
stopTestFramework();
}
@@ -219,6 +213,7 @@ public abstract class JunctionTestSuiteWithEmbeddedDB extends GuicyKillbillTestS
return account;
}
+ @Override
protected void assertListenerStatus() {
testListener.assertListenerStatus();
}
diff --git a/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBlockingCalculator.java b/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBlockingCalculator.java
index 2d17bdc..9436623 100644
--- a/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBlockingCalculator.java
+++ b/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBlockingCalculator.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2016 Groupon, Inc
- * Copyright 2014-2016 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
@@ -47,7 +47,6 @@ import org.killbill.billing.entitlement.dao.MockBlockingStateDao;
import org.killbill.billing.junction.BillingEvent;
import org.killbill.billing.junction.DefaultBlockingState;
import org.killbill.billing.junction.JunctionTestSuiteNoDB;
-import org.killbill.billing.junction.plumbing.billing.BlockingCalculator.DisabledDuration;
import org.killbill.billing.subscription.api.SubscriptionBase;
import org.killbill.billing.subscription.api.SubscriptionBaseTransitionType;
import org.mockito.Mockito;
@@ -128,7 +127,7 @@ public class TestBlockingCalculator extends JunctionTestSuiteNoDB {
blockingState2, Optional.<UUID>absent()),
internalCallContext);
- blockingCalculator.insertBlockingEvents(billingEvents, new HashSet<UUID>(), internalCallContext);
+ blockingCalculator.insertBlockingEvents(billingEvents, new HashSet<UUID>(), catalogInternalApi.getFullCatalog(true, true, internalCallContext), internalCallContext);
assertEquals(billingEvents.size(), 7);
@@ -154,13 +153,13 @@ public class TestBlockingCalculator extends JunctionTestSuiteNoDB {
@Test(groups = "fast")
public void testEventsToRemoveOpenPrev() {
final DateTime now = clock.getUTCNow();
- final List<DisabledDuration> disabledDuration = new ArrayList<BlockingCalculator.DisabledDuration>();
+ final List<DisabledDuration> disabledDuration = new ArrayList<DisabledDuration>();
final SortedSet<BillingEvent> billingEvents = new TreeSet<BillingEvent>();
disabledDuration.add(new DisabledDuration(now, null));
billingEvents.add(createRealEvent(now.minusDays(1), subscription1));
- final SortedSet<BillingEvent> results = blockingCalculator.eventsToRemove(disabledDuration, billingEvents, subscription1);
+ final SortedSet<BillingEvent> results = blockingCalculator.eventsToRemove(disabledDuration, billingEvents);
assertEquals(results.size(), 0);
}
@@ -170,7 +169,7 @@ public class TestBlockingCalculator extends JunctionTestSuiteNoDB {
@Test(groups = "fast")
public void testEventsToRemoveOpenPrevFollow() {
final DateTime now = clock.getUTCNow();
- final List<DisabledDuration> disabledDuration = new ArrayList<BlockingCalculator.DisabledDuration>();
+ final List<DisabledDuration> disabledDuration = new ArrayList<DisabledDuration>();
final SortedSet<BillingEvent> billingEvents = new TreeSet<BillingEvent>();
disabledDuration.add(new DisabledDuration(now, null));
@@ -179,7 +178,7 @@ public class TestBlockingCalculator extends JunctionTestSuiteNoDB {
billingEvents.add(e1);
billingEvents.add(e2);
- final SortedSet<BillingEvent> results = blockingCalculator.eventsToRemove(disabledDuration, billingEvents, subscription1);
+ final SortedSet<BillingEvent> results = blockingCalculator.eventsToRemove(disabledDuration, billingEvents);
assertEquals(results.size(), 1);
assertEquals(results.first(), e2);
@@ -190,14 +189,32 @@ public class TestBlockingCalculator extends JunctionTestSuiteNoDB {
@Test(groups = "fast")
public void testEventsToRemoveOpenFollow() {
final DateTime now = clock.getUTCNow();
- final List<DisabledDuration> disabledDuration = new ArrayList<BlockingCalculator.DisabledDuration>();
+ final List<DisabledDuration> disabledDuration = new ArrayList<DisabledDuration>();
final SortedSet<BillingEvent> billingEvents = new TreeSet<BillingEvent>();
disabledDuration.add(new DisabledDuration(now, null));
final BillingEvent e1 = createRealEvent(now.plusDays(1), subscription1);
billingEvents.add(e1);
- final SortedSet<BillingEvent> results = blockingCalculator.eventsToRemove(disabledDuration, billingEvents, subscription1);
+ final SortedSet<BillingEvent> results = blockingCalculator.eventsToRemove(disabledDuration, billingEvents);
+
+ assertEquals(results.size(), 1);
+ assertEquals(results.first(), e1);
+ }
+
+ // Open with no previous event (only at the same time)
+ // -----[X-----------------------------
+ @Test(groups = "fast")
+ public void testEventsToRemoveOpenSameTime() {
+ final DateTime now = clock.getUTCNow();
+ final List<DisabledDuration> disabledDuration = new ArrayList<DisabledDuration>();
+ final SortedSet<BillingEvent> billingEvents = new TreeSet<BillingEvent>();
+
+ disabledDuration.add(new DisabledDuration(now, null));
+ final BillingEvent e1 = createRealEvent(now, subscription1);
+ billingEvents.add(e1);
+
+ final SortedSet<BillingEvent> results = blockingCalculator.eventsToRemove(disabledDuration, billingEvents);
assertEquals(results.size(), 1);
assertEquals(results.first(), e1);
@@ -208,14 +225,14 @@ public class TestBlockingCalculator extends JunctionTestSuiteNoDB {
@Test(groups = "fast")
public void testEventsToRemoveClosedPrev() {
final DateTime now = clock.getUTCNow();
- final List<DisabledDuration> disabledDuration = new ArrayList<BlockingCalculator.DisabledDuration>();
+ final List<DisabledDuration> disabledDuration = new ArrayList<DisabledDuration>();
final SortedSet<BillingEvent> billingEvents = new TreeSet<BillingEvent>();
disabledDuration.add(new DisabledDuration(now, now.plusDays(2)));
final BillingEvent e1 = createRealEvent(now.minusDays(1), subscription1);
billingEvents.add(e1);
- final SortedSet<BillingEvent> results = blockingCalculator.eventsToRemove(disabledDuration, billingEvents, subscription1);
+ final SortedSet<BillingEvent> results = blockingCalculator.eventsToRemove(disabledDuration, billingEvents);
assertEquals(results.size(), 0);
}
@@ -225,7 +242,7 @@ public class TestBlockingCalculator extends JunctionTestSuiteNoDB {
@Test(groups = "fast")
public void testEventsToRemoveClosedPrevBetw() {
final DateTime now = clock.getUTCNow();
- final List<DisabledDuration> disabledDuration = new ArrayList<BlockingCalculator.DisabledDuration>();
+ final List<DisabledDuration> disabledDuration = new ArrayList<DisabledDuration>();
final SortedSet<BillingEvent> billingEvents = new TreeSet<BillingEvent>();
disabledDuration.add(new DisabledDuration(now, now.plusDays(2)));
@@ -234,7 +251,7 @@ public class TestBlockingCalculator extends JunctionTestSuiteNoDB {
billingEvents.add(e1);
billingEvents.add(e2);
- final SortedSet<BillingEvent> results = blockingCalculator.eventsToRemove(disabledDuration, billingEvents, subscription1);
+ final SortedSet<BillingEvent> results = blockingCalculator.eventsToRemove(disabledDuration, billingEvents);
assertEquals(results.size(), 1);
assertEquals(results.first(), e2);
@@ -245,7 +262,7 @@ public class TestBlockingCalculator extends JunctionTestSuiteNoDB {
@Test(groups = "fast")
public void testEventsToRemoveClosedPrevBetwNext() {
final DateTime now = clock.getUTCNow();
- final List<DisabledDuration> disabledDuration = new ArrayList<BlockingCalculator.DisabledDuration>();
+ final List<DisabledDuration> disabledDuration = new ArrayList<DisabledDuration>();
final SortedSet<BillingEvent> billingEvents = new TreeSet<BillingEvent>();
disabledDuration.add(new DisabledDuration(now, now.plusDays(2)));
@@ -256,7 +273,7 @@ public class TestBlockingCalculator extends JunctionTestSuiteNoDB {
billingEvents.add(e2);
billingEvents.add(e3);
- final SortedSet<BillingEvent> results = blockingCalculator.eventsToRemove(disabledDuration, billingEvents, subscription1);
+ final SortedSet<BillingEvent> results = blockingCalculator.eventsToRemove(disabledDuration, billingEvents);
assertEquals(results.size(), 1);
assertEquals(results.first(), e2);
@@ -267,14 +284,14 @@ public class TestBlockingCalculator extends JunctionTestSuiteNoDB {
@Test(groups = "fast")
public void testEventsToRemoveClosedBetwn() {
final DateTime now = clock.getUTCNow();
- final List<DisabledDuration> disabledDuration = new ArrayList<BlockingCalculator.DisabledDuration>();
+ final List<DisabledDuration> disabledDuration = new ArrayList<DisabledDuration>();
final SortedSet<BillingEvent> billingEvents = new TreeSet<BillingEvent>();
disabledDuration.add(new DisabledDuration(now, now.plusDays(2)));
final BillingEvent e2 = createRealEvent(now.plusDays(1), subscription1);
billingEvents.add(e2);
- final SortedSet<BillingEvent> results = blockingCalculator.eventsToRemove(disabledDuration, billingEvents, subscription1);
+ final SortedSet<BillingEvent> results = blockingCalculator.eventsToRemove(disabledDuration, billingEvents);
assertEquals(results.size(), 1);
assertEquals(results.first(), e2);
@@ -285,7 +302,7 @@ public class TestBlockingCalculator extends JunctionTestSuiteNoDB {
@Test(groups = "fast")
public void testEventsToRemoveClosedBetweenFollow() {
final DateTime now = clock.getUTCNow();
- final List<DisabledDuration> disabledDuration = new ArrayList<BlockingCalculator.DisabledDuration>();
+ final List<DisabledDuration> disabledDuration = new ArrayList<DisabledDuration>();
final SortedSet<BillingEvent> billingEvents = new TreeSet<BillingEvent>();
disabledDuration.add(new DisabledDuration(now, now.plusDays(2)));
@@ -295,7 +312,7 @@ public class TestBlockingCalculator extends JunctionTestSuiteNoDB {
billingEvents.add(e2);
billingEvents.add(e3);
- final SortedSet<BillingEvent> results = blockingCalculator.eventsToRemove(disabledDuration, billingEvents, subscription1);
+ final SortedSet<BillingEvent> results = blockingCalculator.eventsToRemove(disabledDuration, billingEvents);
assertEquals(results.size(), 1);
assertEquals(results.first(), e2);
@@ -306,7 +323,7 @@ public class TestBlockingCalculator extends JunctionTestSuiteNoDB {
@Test(groups = "fast")
public void testEventsToRemoveClosedFollow() {
final DateTime now = clock.getUTCNow();
- final List<DisabledDuration> disabledDuration = new ArrayList<BlockingCalculator.DisabledDuration>();
+ final List<DisabledDuration> disabledDuration = new ArrayList<DisabledDuration>();
final SortedSet<BillingEvent> billingEvents = new TreeSet<BillingEvent>();
disabledDuration.add(new DisabledDuration(now, now.plusDays(2)));
@@ -315,7 +332,7 @@ public class TestBlockingCalculator extends JunctionTestSuiteNoDB {
billingEvents.add(e3);
- final SortedSet<BillingEvent> results = blockingCalculator.eventsToRemove(disabledDuration, billingEvents, subscription1);
+ final SortedSet<BillingEvent> results = blockingCalculator.eventsToRemove(disabledDuration, billingEvents);
assertEquals(results.size(), 0);
}
@@ -325,13 +342,13 @@ public class TestBlockingCalculator extends JunctionTestSuiteNoDB {
@Test(groups = "fast")
public void testCreateNewEventsOpenPrev() throws CatalogApiException {
final DateTime now = clock.getUTCNow();
- final List<DisabledDuration> disabledDuration = new ArrayList<BlockingCalculator.DisabledDuration>();
+ final List<DisabledDuration> disabledDuration = new ArrayList<DisabledDuration>();
final SortedSet<BillingEvent> billingEvents = new TreeSet<BillingEvent>();
disabledDuration.add(new DisabledDuration(now, null));
billingEvents.add(createRealEvent(now.minusDays(1), subscription1));
- final SortedSet<BillingEvent> results = blockingCalculator.createNewEvents(disabledDuration, billingEvents, subscription1, internalCallContext);
+ final SortedSet<BillingEvent> results = blockingCalculator.createNewEvents(disabledDuration, billingEvents, catalogInternalApi.getFullCatalog(true, true, internalCallContext), internalCallContext);
assertEquals(results.size(), 1);
assertEquals(results.first().getEffectiveDate(), now);
@@ -346,14 +363,14 @@ public class TestBlockingCalculator extends JunctionTestSuiteNoDB {
@Test(groups = "fast")
public void testCreateNewEventsOpenPrevFollow() throws CatalogApiException {
final DateTime now = clock.getUTCNow();
- final List<DisabledDuration> disabledDuration = new ArrayList<BlockingCalculator.DisabledDuration>();
+ final List<DisabledDuration> disabledDuration = new ArrayList<DisabledDuration>();
final SortedSet<BillingEvent> billingEvents = new TreeSet<BillingEvent>();
disabledDuration.add(new DisabledDuration(now, null));
billingEvents.add(createRealEvent(now.minusDays(1), subscription1));
billingEvents.add(createRealEvent(now.plusDays(1), subscription1));
- final SortedSet<BillingEvent> results = blockingCalculator.createNewEvents(disabledDuration, billingEvents, subscription1, internalCallContext);
+ final SortedSet<BillingEvent> results = blockingCalculator.createNewEvents(disabledDuration, billingEvents, catalogInternalApi.getFullCatalog(true, true, internalCallContext), internalCallContext);
assertEquals(results.size(), 1);
assertEquals(results.first().getEffectiveDate(), now);
@@ -368,13 +385,29 @@ public class TestBlockingCalculator extends JunctionTestSuiteNoDB {
@Test(groups = "fast")
public void testCreateNewEventsOpenFollow() throws CatalogApiException {
final DateTime now = clock.getUTCNow();
- final List<DisabledDuration> disabledDuration = new ArrayList<BlockingCalculator.DisabledDuration>();
+ final List<DisabledDuration> disabledDuration = new ArrayList<DisabledDuration>();
final SortedSet<BillingEvent> billingEvents = new TreeSet<BillingEvent>();
disabledDuration.add(new DisabledDuration(now, null));
billingEvents.add(createRealEvent(now.plusDays(1), subscription1));
- final SortedSet<BillingEvent> results = blockingCalculator.createNewEvents(disabledDuration, billingEvents, subscription1, internalCallContext);
+ final SortedSet<BillingEvent> results = blockingCalculator.createNewEvents(disabledDuration, billingEvents, catalogInternalApi.getFullCatalog(true, true, internalCallContext), internalCallContext);
+
+ assertEquals(results.size(), 0);
+ }
+
+ // Open with no previous event (only at the same time)
+ // -----[X-----------------------------
+ @Test(groups = "fast")
+ public void testCreateNewEventsOpenSameTime() throws CatalogApiException {
+ final DateTime now = clock.getUTCNow();
+ final List<DisabledDuration> disabledDuration = new ArrayList<DisabledDuration>();
+ final SortedSet<BillingEvent> billingEvents = new TreeSet<BillingEvent>();
+
+ disabledDuration.add(new DisabledDuration(now, null));
+ billingEvents.add(createRealEvent(now, subscription1));
+
+ final SortedSet<BillingEvent> results = blockingCalculator.createNewEvents(disabledDuration, billingEvents, catalogInternalApi.getFullCatalog(true, true, internalCallContext), internalCallContext);
assertEquals(results.size(), 0);
}
@@ -384,13 +417,13 @@ public class TestBlockingCalculator extends JunctionTestSuiteNoDB {
@Test(groups = "fast")
public void testCreateNewEventsClosedPrev() throws CatalogApiException {
final DateTime now = clock.getUTCNow();
- final List<DisabledDuration> disabledDuration = new ArrayList<BlockingCalculator.DisabledDuration>();
+ final List<DisabledDuration> disabledDuration = new ArrayList<DisabledDuration>();
final SortedSet<BillingEvent> billingEvents = new TreeSet<BillingEvent>();
disabledDuration.add(new DisabledDuration(now, now.plusDays(2)));
billingEvents.add(createRealEvent(now.minusDays(1), subscription1));
- final SortedSet<BillingEvent> results = blockingCalculator.createNewEvents(disabledDuration, billingEvents, subscription1, internalCallContext);
+ final SortedSet<BillingEvent> results = blockingCalculator.createNewEvents(disabledDuration, billingEvents, catalogInternalApi.getFullCatalog(true, true, internalCallContext), internalCallContext);
assertEquals(results.size(), 2);
@@ -409,14 +442,14 @@ public class TestBlockingCalculator extends JunctionTestSuiteNoDB {
@Test(groups = "fast")
public void testCreateNewEventsClosedPrevBetw() throws CatalogApiException {
final DateTime now = clock.getUTCNow();
- final List<DisabledDuration> disabledDuration = new ArrayList<BlockingCalculator.DisabledDuration>();
+ final List<DisabledDuration> disabledDuration = new ArrayList<DisabledDuration>();
final SortedSet<BillingEvent> billingEvents = new TreeSet<BillingEvent>();
disabledDuration.add(new DisabledDuration(now, now.plusDays(2)));
billingEvents.add(createRealEvent(now.minusDays(1), subscription1));
billingEvents.add(createRealEvent(now.plusDays(1), subscription1));
- final SortedSet<BillingEvent> results = blockingCalculator.createNewEvents(disabledDuration, billingEvents, subscription1, internalCallContext);
+ final SortedSet<BillingEvent> results = blockingCalculator.createNewEvents(disabledDuration, billingEvents, catalogInternalApi.getFullCatalog(true, true, internalCallContext), internalCallContext);
assertEquals(results.size(), 2);
assertEquals(results.first().getEffectiveDate(), now);
@@ -434,7 +467,7 @@ public class TestBlockingCalculator extends JunctionTestSuiteNoDB {
@Test(groups = "fast")
public void testCreateNewEventsClosedPrevBetwNext() throws CatalogApiException {
final DateTime now = clock.getUTCNow();
- final List<DisabledDuration> disabledDuration = new ArrayList<BlockingCalculator.DisabledDuration>();
+ final List<DisabledDuration> disabledDuration = new ArrayList<DisabledDuration>();
final SortedSet<BillingEvent> billingEvents = new TreeSet<BillingEvent>();
disabledDuration.add(new DisabledDuration(now, now.plusDays(2)));
@@ -442,7 +475,7 @@ public class TestBlockingCalculator extends JunctionTestSuiteNoDB {
billingEvents.add(createRealEvent(now.plusDays(1), subscription1));
billingEvents.add(createRealEvent(now.plusDays(3), subscription1));
- final SortedSet<BillingEvent> results = blockingCalculator.createNewEvents(disabledDuration, billingEvents, subscription1, internalCallContext);
+ final SortedSet<BillingEvent> results = blockingCalculator.createNewEvents(disabledDuration, billingEvents, catalogInternalApi.getFullCatalog(true, true, internalCallContext), internalCallContext);
assertEquals(results.size(), 2);
assertEquals(results.first().getEffectiveDate(), now);
@@ -460,13 +493,13 @@ public class TestBlockingCalculator extends JunctionTestSuiteNoDB {
@Test(groups = "fast")
public void testCreateNewEventsClosedBetwn() throws CatalogApiException {
final DateTime now = clock.getUTCNow();
- final List<DisabledDuration> disabledDuration = new ArrayList<BlockingCalculator.DisabledDuration>();
+ final List<DisabledDuration> disabledDuration = new ArrayList<DisabledDuration>();
final SortedSet<BillingEvent> billingEvents = new TreeSet<BillingEvent>();
disabledDuration.add(new DisabledDuration(now, now.plusDays(2)));
billingEvents.add(createRealEvent(now.plusDays(1), subscription1));
- final SortedSet<BillingEvent> results = blockingCalculator.createNewEvents(disabledDuration, billingEvents, subscription1, internalCallContext);
+ final SortedSet<BillingEvent> results = blockingCalculator.createNewEvents(disabledDuration, billingEvents, catalogInternalApi.getFullCatalog(true, true, internalCallContext), internalCallContext);
assertEquals(results.size(), 1);
assertEquals(results.last().getEffectiveDate(), now.plusDays(2));
@@ -479,13 +512,13 @@ public class TestBlockingCalculator extends JunctionTestSuiteNoDB {
@Test(groups = "fast")
public void testCreateNewEventsClosedBetweenFollow() throws CatalogApiException {
final DateTime now = clock.getUTCNow();
- final List<DisabledDuration> disabledDuration = new ArrayList<BlockingCalculator.DisabledDuration>();
+ final List<DisabledDuration> disabledDuration = new ArrayList<DisabledDuration>();
final SortedSet<BillingEvent> billingEvents = new TreeSet<BillingEvent>();
disabledDuration.add(new DisabledDuration(now, now.plusDays(2)));
billingEvents.add(createRealEvent(now.plusDays(1), subscription1));
- final SortedSet<BillingEvent> results = blockingCalculator.createNewEvents(disabledDuration, billingEvents, subscription1, internalCallContext);
+ final SortedSet<BillingEvent> results = blockingCalculator.createNewEvents(disabledDuration, billingEvents, catalogInternalApi.getFullCatalog(true, true, internalCallContext), internalCallContext);
assertEquals(results.size(), 1);
assertEquals(results.last().getEffectiveDate(), now.plusDays(2));
@@ -498,13 +531,13 @@ public class TestBlockingCalculator extends JunctionTestSuiteNoDB {
@Test(groups = "fast")
public void testCreateNewEventsClosedFollow() throws CatalogApiException {
final DateTime now = clock.getUTCNow();
- final List<DisabledDuration> disabledDuration = new ArrayList<BlockingCalculator.DisabledDuration>();
+ final List<DisabledDuration> disabledDuration = new ArrayList<DisabledDuration>();
final SortedSet<BillingEvent> billingEvents = new TreeSet<BillingEvent>();
disabledDuration.add(new DisabledDuration(now, now.plusDays(2)));
billingEvents.add(createRealEvent(now.plusDays(3), subscription1));
- final SortedSet<BillingEvent> results = blockingCalculator.createNewEvents(disabledDuration, billingEvents, subscription1, internalCallContext);
+ final SortedSet<BillingEvent> results = blockingCalculator.createNewEvents(disabledDuration, billingEvents, catalogInternalApi.getFullCatalog(true, true, internalCallContext), internalCallContext);
assertEquals(results.size(), 0);
}
@@ -520,10 +553,10 @@ public class TestBlockingCalculator extends JunctionTestSuiteNoDB {
events.add(createRealEvent(now.minusDays(5), subscription1));
events.add(createRealEvent(now.minusDays(1), subscription1));
- final BillingEvent minus11 = blockingCalculator.precedingBillingEventForSubscription(now.minusDays(11), events, subscription1);
+ final BillingEvent minus11 = blockingCalculator.precedingBillingEventForSubscription(now.minusDays(11), events);
assertNull(minus11);
- final BillingEvent minus5andAHalf = blockingCalculator.precedingBillingEventForSubscription(now.minusDays(5).minusHours(12), events, subscription1);
+ final BillingEvent minus5andAHalf = blockingCalculator.precedingBillingEventForSubscription(now.minusDays(5).minusHours(12), events);
assertNotNull(minus5andAHalf);
assertEquals(minus5andAHalf.getEffectiveDate(), now.minusDays(6));
@@ -594,7 +627,7 @@ public class TestBlockingCalculator extends JunctionTestSuiteNoDB {
final DateTime now = clock.getUTCNow();
final BillingEvent event = new MockBillingEvent();
- final BillingEvent result = blockingCalculator.createNewDisableEvent(now, event, null, internalCallContext);
+ final BillingEvent result = blockingCalculator.createNewDisableEvent(now, event, null);
assertEquals(result.getBillCycleDayLocal(), event.getBillCycleDayLocal());
assertEquals(result.getEffectiveDate(), now);
assertEquals(result.getPlanPhase(), event.getPlanPhase());
@@ -759,6 +792,41 @@ public class TestBlockingCalculator extends JunctionTestSuiteNoDB {
}
@Test(groups = "fast")
+ public void testCreateAndMergeDisablePairs() {
+ final List<BlockingState> blockingEvents = new ArrayList<BlockingState>();
+ final UUID ovdId = UUID.randomUUID();
+ final DateTime entitlementStartDate = clock.getUTCNow();
+ final DateTime blockEffectiveDate = entitlementStartDate.plusSeconds(1);
+ final DateTime unblockEffectiveDate = blockEffectiveDate.plusDays(2);
+
+ // Similar to an entitlement start event
+ blockingEvents.add(new DefaultBlockingState(ovdId, BlockingStateType.SUBSCRIPTION_BUNDLE, CLEAR_BUNDLE, "test", false, false, false, entitlementStartDate));
+ List<DisabledDuration> pairs = blockingCalculator.createBlockingDurations(blockingEvents);
+ assertEquals(pairs.size(), 0);
+
+ blockingEvents.add(new DefaultBlockingState(ovdId, BlockingStateType.SUBSCRIPTION_BUNDLE, DISABLED_BUNDLE, "test", true, true, true, blockEffectiveDate));
+
+ pairs = blockingCalculator.createBlockingDurations(blockingEvents);
+ assertEquals(pairs.size(), 1);
+ assertEquals(pairs.get(0).getStart().compareTo(blockEffectiveDate), 0);
+ assertNull(pairs.get(0).getEnd());
+
+ blockingEvents.add(new DefaultBlockingState(ovdId, BlockingStateType.SUBSCRIPTION_BUNDLE, CLEAR_BUNDLE, "test", false, false, false, unblockEffectiveDate));
+
+ pairs = blockingCalculator.createBlockingDurations(blockingEvents);
+ assertEquals(pairs.size(), 1);
+ assertEquals(pairs.get(0).getStart().compareTo(blockEffectiveDate), 0);
+ assertEquals(pairs.get(0).getEnd().compareTo(unblockEffectiveDate), 0);
+
+ blockingEvents.add(new DefaultBlockingState(ovdId, BlockingStateType.SUBSCRIPTION_BUNDLE, DISABLED_BUNDLE, "test", true, true, true, unblockEffectiveDate));
+
+ pairs = blockingCalculator.createBlockingDurations(blockingEvents);
+ assertEquals(pairs.size(), 1);
+ assertEquals(pairs.get(0).getStart().compareTo(blockEffectiveDate), 0);
+ assertNull(pairs.get(0).getEnd());
+ }
+
+ @Test(groups = "fast")
public void testSimpleWithClearBlockingDuration() throws Exception {
final BillingEvent trial = createRealEvent(new LocalDate(2012, 5, 1).toDateTimeAtStartOfDay(DateTimeZone.UTC), subscription1, SubscriptionBaseTransitionType.CREATE);
@@ -780,7 +848,7 @@ public class TestBlockingCalculator extends JunctionTestSuiteNoDB {
blockingState4, Optional.<UUID>absent()),
internalCallContext);
- blockingCalculator.insertBlockingEvents(billingEvents, new HashSet<UUID>(), internalCallContext);
+ blockingCalculator.insertBlockingEvents(billingEvents, new HashSet<UUID>(), catalogInternalApi.getFullCatalog(true, true, internalCallContext), internalCallContext);
assertEquals(billingEvents.size(), 5);
final List<BillingEvent> events = new ArrayList<BillingEvent>(billingEvents);
@@ -793,6 +861,5 @@ public class TestBlockingCalculator extends JunctionTestSuiteNoDB {
assertEquals(events.get(3).getEffectiveDate(), new LocalDate(2012, 7, 25).toDateTimeAtStartOfDay(DateTimeZone.UTC));
assertEquals(events.get(3).getTransitionType(), SubscriptionBaseTransitionType.END_BILLING_DISABLED);
assertEquals(events.get(4).getEffectiveDate(), new LocalDate(2012, 7, 25).toDateTimeAtStartOfDay(DateTimeZone.UTC));
- assertEquals(events.get(4).getTransitionType(), SubscriptionBaseTransitionType.CHANGE);
- }
+ assertEquals(events.get(4).getTransitionType(), SubscriptionBaseTransitionType.CHANGE); }
}
diff --git a/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBlockingStateService.java b/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBlockingStateService.java
new file mode 100644
index 0000000..0013ab8
--- /dev/null
+++ b/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBlockingStateService.java
@@ -0,0 +1,399 @@
+/*
+ * 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.junction.plumbing.billing;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.killbill.billing.entitlement.api.BlockingState;
+import org.killbill.billing.entitlement.api.BlockingStateType;
+import org.killbill.billing.junction.DefaultBlockingState;
+import org.killbill.billing.junction.JunctionTestSuiteNoDB;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+public class TestBlockingStateService extends JunctionTestSuiteNoDB {
+
+
+ private UUID accountId;
+ private UUID bundleId;
+ private UUID subscriptionId;
+
+ @BeforeMethod(groups = "fast")
+ public void beforeMethod() throws Exception {
+ super.beforeMethod();
+ this.accountId = UUID.randomUUID();
+ this.bundleId = UUID.randomUUID();
+ this.subscriptionId = UUID.randomUUID();
+ }
+
+ //
+ // In all tests:
+ // * Events are B(locked) or U(nblocked)
+ // * Types are (A(ccount), B(undle), S(ubscription))
+
+ // B B U U
+ // |----|-----|-----|
+ // A B A B
+ //
+ // Expected: B----------------U
+ //
+ @Test(groups = "fast")
+ public void testInterlaceTypes() throws Exception {
+
+ final List<BlockingState> input = new ArrayList<BlockingState>();
+
+ final DateTimeZone tz = DateTimeZone.forID("America/Los_Angeles");
+ final DateTime testInit = new DateTime(2017, 04, 29, 14, 15, 53, tz);
+ clock.setTime(testInit);
+ input.add(createBillingBlockingState(BlockingStateType.ACCOUNT, true, testInit));
+ input.add(createBillingBlockingState(BlockingStateType.SUBSCRIPTION_BUNDLE, true, testInit.plusDays(1)));
+ input.add(createBillingBlockingState(BlockingStateType.ACCOUNT, false, testInit.plusDays(2)));
+ input.add(createBillingBlockingState(BlockingStateType.SUBSCRIPTION_BUNDLE, false, testInit.plusDays(3)));
+
+ final BlockingStateService test = new BlockingStateService();
+ for (BlockingState cur : input) {
+ test.addBlockingState(cur);
+ }
+ final List<DisabledDuration> result = test.build();
+
+ final List<DisabledDuration> expected = ImmutableList.of(new DisabledDuration(testInit, testInit.plusDays(2)),
+ new DisabledDuration(testInit.plusDays(1), testInit.plusDays(3)));
+
+ verify(result, expected);
+ }
+
+
+ // B B U
+ // |----|-----|-----
+ // A B A
+ //
+ // Expected: B-------------------
+ //
+ @Test(groups = "fast")
+ public void testInterlaceTypesWithNoEnd() throws Exception {
+
+ final List<BlockingState> input = new ArrayList<BlockingState>();
+
+ final DateTimeZone tz = DateTimeZone.forID("America/Los_Angeles");
+ final DateTime testInit = new DateTime(2017, 04, 29, 14, 15, 53, tz);
+ clock.setTime(testInit);
+ input.add(createBillingBlockingState(BlockingStateType.ACCOUNT, true, testInit));
+ input.add(createBillingBlockingState(BlockingStateType.SUBSCRIPTION_BUNDLE, true, testInit.plusDays(1)));
+ input.add(createBillingBlockingState(BlockingStateType.ACCOUNT, false, testInit.plusDays(2)));
+
+ final BlockingStateService test = new BlockingStateService();
+ for (BlockingState cur : input) {
+ test.addBlockingState(cur);
+ }
+ final List<DisabledDuration> result = test.build();
+
+ final List<DisabledDuration> expected = ImmutableList.of(new DisabledDuration(testInit, testInit.plusDays(2)),
+ new DisabledDuration(testInit.plusDays(1), null));
+
+ verify(result, expected);
+ }
+
+ // B U B U
+ // |----|-----|-----|
+ // A A A A
+ //
+ // Expected: B----------------U
+ //
+ @Test(groups = "fast")
+ public void testMultipleDisabledDurations() throws Exception {
+
+ final List<BlockingState> input = new ArrayList<BlockingState>();
+
+ final DateTimeZone tz = DateTimeZone.forID("America/Los_Angeles");
+ final DateTime testInit = new DateTime(2017, 04, 29, 14, 15, 53, tz);
+ clock.setTime(testInit);
+ input.add(createBillingBlockingState(BlockingStateType.ACCOUNT, true, testInit));
+ input.add(createBillingBlockingState(BlockingStateType.ACCOUNT, false, testInit.plusDays(1)));
+ input.add(createBillingBlockingState(BlockingStateType.ACCOUNT, true, testInit.plusDays(2)));
+ input.add(createBillingBlockingState(BlockingStateType.ACCOUNT, false, testInit.plusDays(3)));
+
+ final BlockingStateService test = new BlockingStateService();
+ for (BlockingState cur : input) {
+ test.addBlockingState(cur);
+ }
+ final List<DisabledDuration> result = test.build();
+
+ final List<DisabledDuration> expected = ImmutableList.of(new DisabledDuration(testInit, testInit.plusDays(1)),
+ new DisabledDuration(testInit.plusDays(2), testInit.plusDays(3)));
+
+ verify(result, expected);
+ }
+
+
+ // B U U
+ // |----|-----|
+ // AB B A
+ //
+ // Expected: B----------U
+ //
+ @Test(groups = "fast")
+ public void testSameBlockingDates() throws Exception {
+
+ final List<BlockingState> input = new ArrayList<BlockingState>();
+
+ final DateTimeZone tz = DateTimeZone.forID("America/Los_Angeles");
+ final DateTime testInit = new DateTime(2017, 04, 29, 14, 15, 53, tz);
+ clock.setTime(testInit);
+ input.add(createBillingBlockingState(BlockingStateType.ACCOUNT, true, testInit));
+ input.add(createBillingBlockingState(BlockingStateType.SUBSCRIPTION_BUNDLE, true, testInit));
+ input.add(createBillingBlockingState(BlockingStateType.SUBSCRIPTION_BUNDLE, false, testInit.plusDays(1)));
+ input.add(createBillingBlockingState(BlockingStateType.ACCOUNT, false, testInit.plusDays(2)));
+
+ final BlockingStateService test = new BlockingStateService();
+ for (BlockingState cur : input) {
+ test.addBlockingState(cur);
+ }
+ final List<DisabledDuration> result = test.build();
+
+ final List<DisabledDuration> expected = ImmutableList.of(new DisabledDuration(testInit, testInit.plusDays(1)),
+ new DisabledDuration(testInit, testInit.plusDays(2)));
+
+ verify(result, expected);
+ }
+
+
+ // BU
+ // |
+ // AA
+ //
+ // Expected: None
+ //
+ @Test(groups = "fast")
+ public void testSameBlockingUnblockingDates() throws Exception {
+
+ final List<BlockingState> input = new ArrayList<BlockingState>();
+
+ final DateTimeZone tz = DateTimeZone.forID("America/Los_Angeles");
+ final DateTime testInit = new DateTime(2017, 04, 29, 14, 15, 53, tz);
+ clock.setTime(testInit);
+ input.add(createBillingBlockingState(BlockingStateType.ACCOUNT, true, testInit));
+ input.add(createBillingBlockingState(BlockingStateType.ACCOUNT, false, testInit));
+
+ final BlockingStateService test = new BlockingStateService();
+ for (BlockingState cur : input) {
+ test.addBlockingState(cur);
+ }
+ final List<DisabledDuration> result = test.build();
+
+ final List<DisabledDuration> expected = ImmutableList.of();
+
+ verify(result, expected);
+ }
+
+
+ // B U
+ // |-|
+ // A A
+ //
+ // Expected: None
+ //
+ @Test(groups = "fast")
+ public void testBlockingUnblockingDatesLessThanADay1() throws Exception {
+
+ final List<BlockingState> input = new ArrayList<BlockingState>();
+
+ final DateTimeZone tz = DateTimeZone.forID("America/Los_Angeles");
+ final DateTime testInit = new DateTime(2017, 04, 29, 14, 15, 53, tz);
+ clock.setTime(testInit);
+ input.add(createBillingBlockingState(BlockingStateType.ACCOUNT, true, testInit));
+ input.add(createBillingBlockingState(BlockingStateType.ACCOUNT, false, testInit.plusHours(10)));
+
+ final BlockingStateService test = new BlockingStateService();
+ for (BlockingState cur : input) {
+ test.addBlockingState(cur);
+ }
+ final List<DisabledDuration> result = test.build();
+
+ final List<DisabledDuration> expected = ImmutableList.of();
+
+ verify(result, expected);
+ }
+
+
+ // B BU
+ // |-------|
+ // A AA
+ //
+ // Expected: B--------
+ //
+ @Test(groups = "fast")
+ public void testBlockingUnblockingDatesLessThanADay2() throws Exception {
+
+ final List<BlockingState> input = new ArrayList<BlockingState>();
+
+ final DateTimeZone tz = DateTimeZone.forID("America/Los_Angeles");
+ final DateTime testInit = new DateTime(2017, 04, 29, 14, 15, 53, tz);
+ clock.setTime(testInit);
+ input.add(createBillingBlockingState(BlockingStateType.ACCOUNT, true, testInit));
+ input.add(createBillingBlockingState(BlockingStateType.ACCOUNT, false, testInit.plusDays(1)));
+ input.add(createBillingBlockingState(BlockingStateType.ACCOUNT, true, testInit.plusDays(1)));
+
+ final BlockingStateService test = new BlockingStateService();
+ for (BlockingState cur : input) {
+ test.addBlockingState(cur);
+ }
+ final List<DisabledDuration> result = test.build();
+
+ final List<DisabledDuration> expected = ImmutableList.of(new DisabledDuration(testInit, testInit.plusDays(1)),
+ new DisabledDuration(testInit.plusDays(1), null));
+
+ verify(result, expected);
+ }
+
+
+ // B BU
+ // |-------|
+ // A AA
+ //
+ // Expected: B--------
+ //
+ @Test(groups = "fast")
+ public void testBlockingUnblockingDatesLessThanADay3() throws Exception {
+
+ final List<BlockingState> input = new ArrayList<BlockingState>();
+
+ final DateTimeZone tz = DateTimeZone.forID("America/Los_Angeles");
+ final DateTime testInit = new DateTime(2017, 04, 29, 14, 15, 53, tz);
+ clock.setTime(testInit);
+ input.add(createBillingBlockingState(BlockingStateType.ACCOUNT, true, testInit));
+ input.add(createBillingBlockingState(BlockingStateType.ACCOUNT, true, testInit.plusDays(1)));
+ input.add(createBillingBlockingState(BlockingStateType.ACCOUNT, false, testInit.plusDays(1)));
+
+ final BlockingStateService test = new BlockingStateService();
+ for (BlockingState cur : input) {
+ test.addBlockingState(cur);
+ }
+ final List<DisabledDuration> result = test.build();
+
+ final List<DisabledDuration> expected = ImmutableList.of(new DisabledDuration(testInit, testInit.plusDays(1)));
+
+ verify(result, expected);
+ }
+
+
+ // B UB
+ // |-------|
+ // A AA
+ //
+ // Expected: B--------
+ //
+ @Test(groups = "fast")
+ public void testBlockingUnblockingDatesLessThanADay4() throws Exception {
+
+ final List<BlockingState> input = new ArrayList<BlockingState>();
+
+ final DateTimeZone tz = DateTimeZone.forID("America/Los_Angeles");
+ final DateTime testInit = new DateTime(2017, 04, 29, 14, 15, 53, tz);
+ clock.setTime(testInit);
+ input.add(createBillingBlockingState(BlockingStateType.ACCOUNT, true, testInit));
+ input.add(createBillingBlockingState(BlockingStateType.ACCOUNT, false, testInit.plusDays(1)));
+ input.add(createBillingBlockingState(BlockingStateType.ACCOUNT, true, testInit.plusDays(1)));
+
+ final BlockingStateService test = new BlockingStateService();
+ for (BlockingState cur : input) {
+ test.addBlockingState(cur);
+ }
+ final List<DisabledDuration> result = test.build();
+
+ final List<DisabledDuration> expected = ImmutableList.of(new DisabledDuration(testInit, testInit.plusDays(1)),
+ new DisabledDuration(testInit.plusDays(1), null));
+
+ verify(result, expected);
+ }
+
+
+ // U B B
+ // |-------|----|
+ // B A B
+ //
+ // Expected: B--------
+ //
+ @Test(groups = "fast")
+ public void testStartingWithUnblock() throws Exception {
+
+ final List<BlockingState> input = new ArrayList<BlockingState>();
+
+ final DateTimeZone tz = DateTimeZone.forID("America/Los_Angeles");
+ final DateTime testInit = new DateTime(2017, 04, 29, 14, 15, 53, tz);
+ clock.setTime(testInit);
+ input.add(createBillingBlockingState(BlockingStateType.SUBSCRIPTION_BUNDLE, false, testInit));
+ input.add(createBillingBlockingState(BlockingStateType.ACCOUNT, true, testInit.plusDays(1)));
+ input.add(createBillingBlockingState(BlockingStateType.SUBSCRIPTION_BUNDLE, true, testInit.plusDays(2)));
+
+ final BlockingStateService test = new BlockingStateService();
+ for (BlockingState cur : input) {
+ test.addBlockingState(cur);
+ }
+ final List<DisabledDuration> result = test.build();
+
+ final List<DisabledDuration> expected = ImmutableList.of(new DisabledDuration(testInit.plusDays(2), null),
+ new DisabledDuration(testInit.plusDays(1), null));
+
+ verify(result, expected);
+ }
+
+
+
+
+ private void verify(final List<DisabledDuration> actual, final List<DisabledDuration> expected) {
+ assertEquals(expected.size(), actual.size());
+ for (int i = 0; i < actual.size(); i++) {
+ boolean found = false;
+ for (int j = 0; j < expected.size(); j++) {
+ if (actual.get(i).equals(expected.get(j))) {
+ found = true;
+ break;
+ }
+ }
+ assertTrue(found);
+ }
+ }
+
+ private BlockingState createBillingBlockingState(final BlockingStateType type, final boolean blockBilling, final DateTime effectiveDate) {
+ final UUID blockedId;
+ switch(type) {
+ case ACCOUNT:
+ blockedId = accountId;
+ break;
+ case SUBSCRIPTION_BUNDLE:
+ blockedId = bundleId;
+ break;
+ case SUBSCRIPTION:
+ blockedId = subscriptionId;
+ break;
+ default:
+ throw new IllegalStateException("Unexpexted type");
+ }
+ return new DefaultBlockingState(blockedId, type, UUID.randomUUID().toString(), "SVC", false, false, blockBilling, effectiveDate);
+ }
+
+}
\ No newline at end of file
diff --git a/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestDefaultInternalBillingApi.java b/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestDefaultInternalBillingApi.java
index f72fb2f..f4a34ed 100644
--- a/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestDefaultInternalBillingApi.java
+++ b/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestDefaultInternalBillingApi.java
@@ -1,7 +1,9 @@
/*
* Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
*
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
@@ -20,17 +22,14 @@ import java.util.List;
import org.joda.time.DateTime;
import org.joda.time.LocalDate;
-import org.killbill.billing.payment.api.PluginProperty;
-import org.testng.Assert;
-import org.testng.annotations.Test;
-
+import org.joda.time.Period;
+import org.joda.time.ReadablePeriod;
+import org.joda.time.Seconds;
import org.killbill.billing.account.api.Account;
import org.killbill.billing.api.TestApiListener.NextEvent;
-import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.catalog.api.BillingPeriod;
import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
import org.killbill.billing.catalog.api.PriceListSet;
-import org.killbill.billing.catalog.api.ProductCategory;
import org.killbill.billing.entitlement.EntitlementService;
import org.killbill.billing.entitlement.api.BlockingStateType;
import org.killbill.billing.entitlement.api.DefaultEntitlementApi;
@@ -38,8 +37,11 @@ import org.killbill.billing.entitlement.api.Entitlement;
import org.killbill.billing.junction.BillingEvent;
import org.killbill.billing.junction.DefaultBlockingState;
import org.killbill.billing.junction.JunctionTestSuiteWithEmbeddedDB;
+import org.killbill.billing.payment.api.PluginProperty;
import org.killbill.billing.subscription.api.SubscriptionBase;
import org.killbill.billing.subscription.api.SubscriptionBaseTransitionType;
+import org.testng.Assert;
+import org.testng.annotations.Test;
import com.google.common.collect.ImmutableList;
@@ -49,7 +51,7 @@ public class TestDefaultInternalBillingApi extends JunctionTestSuiteWithEmbedded
// The invocationCount > 0 was to trigger an issue where events would come out-of-order randomly.
// While the bug shouldn't occur anymore, we're keeping it just in case (the test will also try to insert the events out-of-order manually).
// This test also checks we don't generate billing events for blocking durations less than a day (https://github.com/killbill/killbill/issues/267).
- @Test(groups = "slow", description = "Check blocking states with same effective date are correctly handled", invocationCount = 10, enabled = false)
+ @Test(groups = "slow", description = "Check blocking states with same effective date are correctly handled", invocationCount = 10)
public void testBlockingStatesWithSameEffectiveDate() throws Exception {
final LocalDate initialDate = new LocalDate(2013, 8, 7);
clock.setDay(initialDate);
@@ -58,7 +60,7 @@ public class TestDefaultInternalBillingApi extends JunctionTestSuiteWithEmbedded
testListener.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK);
final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Shotgun", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
- final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, null, null, false, ImmutableList.<PluginProperty>of(), callContext);
+ final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, null, null, false, true, ImmutableList.<PluginProperty>of(), callContext);
final SubscriptionBase subscription = subscriptionInternalApi.getSubscriptionFromId(entitlement.getId(), internalCallContext);
assertListenerStatus();
@@ -187,6 +189,15 @@ public class TestDefaultInternalBillingApi extends JunctionTestSuiteWithEmbedded
// See https://github.com/killbill/killbill/commit/92042843e38a67f75495b207385e4c1f9ca60990#commitcomment-4749967
@Test(groups = "slow", description = "Check unblock then block states with same effective date are correctly handled", invocationCount = 10)
public void testUnblockThenBlockBlockingStatesWithSameEffectiveDate() throws Exception {
+ testUnblockThenBlockBlockingStatesWithSimilarEffectiveDate(Seconds.ZERO);
+ }
+
+ @Test(groups = "slow", description = "Check unblock then block states with almost the same effective date are correctly handled", invocationCount = 10)
+ public void testUnblockThenBlockBlockingStatesWithAlmostSameEffectiveDate() throws Exception {
+ testUnblockThenBlockBlockingStatesWithSimilarEffectiveDate(Seconds.ONE);
+ }
+
+ private void testUnblockThenBlockBlockingStatesWithSimilarEffectiveDate(final ReadablePeriod delay) throws Exception {
final LocalDate initialDate = new LocalDate(2013, 8, 7);
clock.setDay(initialDate);
@@ -194,11 +205,14 @@ public class TestDefaultInternalBillingApi extends JunctionTestSuiteWithEmbedded
testListener.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK);
final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Shotgun", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
- final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, null, null, false, ImmutableList.<PluginProperty>of(), callContext);
+ final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, null, null, false, true, ImmutableList.<PluginProperty>of(), callContext);
final SubscriptionBase subscription = subscriptionInternalApi.getSubscriptionFromId(entitlement.getId(), internalCallContext);
assertListenerStatus();
- final DateTime block1Date = clock.getUTCNow();
+ final DateTime block1Date = subscription.getStartDate().plus(delay);
+ // Make sure to update the clock here, because we don't disable for periods less than a day
+ clock.setTime(block1Date);
+
testListener.pushExpectedEvents(NextEvent.BLOCK);
final DefaultBlockingState state1 = new DefaultBlockingState(account.getId(),
BlockingStateType.ACCOUNT,
@@ -209,6 +223,7 @@ public class TestDefaultInternalBillingApi extends JunctionTestSuiteWithEmbedded
true,
block1Date);
blockingInternalApi.setBlockingState(state1, internalCallContext);
+ assertListenerStatus();
clock.addDays(1);
@@ -239,13 +254,17 @@ public class TestDefaultInternalBillingApi extends JunctionTestSuiteWithEmbedded
clock.addDays(3);
assertListenerStatus();
- // Expected blocking duration:
- // * 2013-08-07 to now [2013-08-07 to 2013-08-08 then 2013-08-08 to now]
final List<BillingEvent> events = ImmutableList.<BillingEvent>copyOf(billingInternalApi.getBillingEventsForAccountAndUpdateAccountBCD(account.getId(), null, internalCallContext));
- Assert.assertEquals(events.size(), 2);
- Assert.assertEquals(events.get(0).getTransitionType(), SubscriptionBaseTransitionType.CREATE);
- Assert.assertEquals(events.get(0).getEffectiveDate(), subscription.getStartDate());
- Assert.assertEquals(events.get(1).getTransitionType(), SubscriptionBaseTransitionType.START_BILLING_DISABLED);
- Assert.assertEquals(events.get(1).getEffectiveDate(), block1Date);
+ if (delay.toPeriod().toStandardDuration().compareTo(Period.ZERO.toStandardDuration()) == 0) {
+ Assert.assertEquals(events.size(), 0);
+ } else {
+ // Expected blocking duration:
+ // * 2013-08-07 to now [2013-08-07 to 2013-08-08 then 2013-08-08 to now]
+ Assert.assertEquals(events.size(), 2);
+ Assert.assertEquals(events.get(0).getTransitionType(), SubscriptionBaseTransitionType.CREATE);
+ Assert.assertEquals(events.get(0).getEffectiveDate(), subscription.getStartDate());
+ Assert.assertEquals(events.get(1).getTransitionType(), SubscriptionBaseTransitionType.START_BILLING_DISABLED);
+ Assert.assertEquals(events.get(1).getEffectiveDate(), block1Date);
+ }
}
}
diff --git a/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestDisabledDuration.java b/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestDisabledDuration.java
new file mode 100644
index 0000000..64f323b
--- /dev/null
+++ b/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestDisabledDuration.java
@@ -0,0 +1,205 @@
+/*
+ * 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.junction.plumbing.billing;
+
+import org.joda.time.DateTime;
+import org.killbill.billing.junction.JunctionTestSuiteNoDB;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
+
+public class TestDisabledDuration extends JunctionTestSuiteNoDB {
+
+ @Test(groups = "fast")
+ public void testCompare0() throws Exception {
+ final DateTime now = clock.getUTCNow();
+ final DisabledDuration d1 = new DisabledDuration(now, now.plusHours(10));
+ final DisabledDuration d2 = new DisabledDuration(now, now.plusHours(10));
+ assertEquals(d1.compareTo(d2), 0);
+ assertTrue(d1.equals(d2));
+ }
+
+ @Test(groups = "fast")
+ public void testCompare1() throws Exception {
+ final DateTime now = clock.getUTCNow();
+ final DisabledDuration d1 = new DisabledDuration(now, now.plusHours(10));
+ final DisabledDuration d2 = new DisabledDuration(now.plusSeconds(1), now.plusHours(10));
+ assertEquals(d1.compareTo(d2), -1);
+ assertEquals(d2.compareTo(d1), 1);
+ }
+
+ @Test(groups = "fast")
+ public void testCompare2() throws Exception {
+ final DateTime now = clock.getUTCNow();
+ final DisabledDuration d1 = new DisabledDuration(now, now.plusHours(10));
+ final DisabledDuration d2 = new DisabledDuration(now.plusSeconds(1), now.plusHours(10));
+ assertEquals(d1.compareTo(d2), -1);
+ assertEquals(d2.compareTo(d1), 1);
+ }
+
+ @Test(groups = "fast")
+ public void testCompare3() throws Exception {
+ final DateTime now = clock.getUTCNow();
+ final DisabledDuration d1 = new DisabledDuration(now, now.plusDays(1));
+ final DisabledDuration d2 = new DisabledDuration(now, now.plusDays(2));
+ assertEquals(d1.compareTo(d2), -1);
+ assertEquals(d2.compareTo(d1), 1);
+ }
+
+ @Test(groups = "fast")
+ public void testCompare4() throws Exception {
+ final DateTime now = clock.getUTCNow();
+ final DisabledDuration d1 = new DisabledDuration(now, now.plusDays(1));
+ final DisabledDuration d2 = new DisabledDuration(now, null);
+ assertEquals(d1.compareTo(d2), -1);
+ assertEquals(d2.compareTo(d1), 1);
+ }
+
+ @Test(groups = "fast")
+ public void testCompare5() throws Exception {
+ final DateTime now = clock.getUTCNow();
+ final DisabledDuration d1 = new DisabledDuration(now, null);
+ final DisabledDuration d2 = new DisabledDuration(now, now.plusDays(1));
+ assertEquals(d1.compareTo(d2), 1);
+ assertEquals(d2.compareTo(d1), -1);
+ }
+
+
+
+ // Case 1: this contained into o => false
+ // |---------| this
+ // |--------------| o
+ @Test(groups = "fast")
+ public void testDisjoint1() throws Exception {
+ final DateTime now = clock.getUTCNow();
+ final DisabledDuration d1 = new DisabledDuration(now, now.plusDays(1));
+ final DisabledDuration d2 = new DisabledDuration(now, now.plusDays(2));
+ assertFalse(d1.isDisjoint(d2));
+ }
+
+ // Case 2: this overlaps with o => false
+ // |---------| this
+ // |--------------| o
+ @Test(groups = "fast")
+ public void testDisjoint2() throws Exception {
+ final DateTime now = clock.getUTCNow();
+ final DisabledDuration d1 = new DisabledDuration(now, now.plusDays(10));
+ final DisabledDuration d2 = new DisabledDuration(now.plusDays(1), now.plusDays(12));
+ assertFalse(d1.isDisjoint(d2));
+ }
+
+ // Case 3: o contains into this => false
+ // |---------| this
+ // |---| o
+ @Test(groups = "fast")
+ public void testDisjoint3() throws Exception {
+ final DateTime now = clock.getUTCNow();
+ final DisabledDuration d1 = new DisabledDuration(now, now.plusDays(10));
+ final DisabledDuration d2 = new DisabledDuration(now.plusDays(1), now.plusDays(4));
+ assertFalse(d1.isDisjoint(d2));
+ }
+
+ // Case 4: this and o are adjacent => false
+ // |---------| this
+ // |---| o
+ @Test(groups = "fast")
+ public void testDisjoint4() throws Exception {
+ final DateTime now = clock.getUTCNow();
+ final DisabledDuration d1 = new DisabledDuration(now, now.plusDays(10));
+ final DisabledDuration d2 = new DisabledDuration(now.plusDays(10), now.plusDays(15));
+ assertFalse(d1.isDisjoint(d2));
+ }
+
+ // Case 5: this and o are disjoint => true
+ // |---------| this
+ // |---| o
+ @Test(groups = "fast")
+ public void testDisjoint5() throws Exception {
+ final DateTime now = clock.getUTCNow();
+ final DisabledDuration d1 = new DisabledDuration(now, now.plusDays(10));
+ final DisabledDuration d2 = new DisabledDuration(now.plusDays(11), now.plusDays(15));
+ assertTrue(d1.isDisjoint(d2));
+ }
+
+
+ @Test(groups = "fast")
+ public void testMergeDuration1() throws Exception {
+ final DateTime now = clock.getUTCNow();
+ final DisabledDuration d1 = new DisabledDuration(now, now.plusDays(10));
+ final DisabledDuration d2 = new DisabledDuration(now.plusDays(1), now.plusDays(15));
+
+ final DisabledDuration result = DisabledDuration.mergeDuration(d1, d2);
+ assertEquals(result.getStart().compareTo(now), 0);
+ assertEquals(result.getEnd().compareTo(now.plusDays(15)), 0);
+ }
+
+ @Test(groups = "fast")
+ public void testMergeDuration2() throws Exception {
+ final DateTime now = clock.getUTCNow();
+ final DisabledDuration d1 = new DisabledDuration(now, now.plusDays(15));
+ final DisabledDuration d2 = new DisabledDuration(now.plusDays(1), now.plusDays(10));
+
+ final DisabledDuration result = DisabledDuration.mergeDuration(d1, d2);
+ assertEquals(result.getStart().compareTo(now), 0);
+ assertEquals(result.getEnd().compareTo(now.plusDays(15)), 0);
+ }
+
+ @Test(groups = "fast")
+ public void testMergeDuration3() throws Exception {
+ final DateTime now = clock.getUTCNow();
+ final DisabledDuration d1 = new DisabledDuration(now, null);
+ final DisabledDuration d2 = new DisabledDuration(now.plusDays(1), now.plusDays(10));
+
+ final DisabledDuration result = DisabledDuration.mergeDuration(d1, d2);
+ assertEquals(result.getStart().compareTo(now), 0);
+ assertNull(result.getEnd());
+ }
+
+ @Test(groups = "fast")
+ public void testMergeDuration4() throws Exception {
+ final DateTime now = clock.getUTCNow();
+ final DisabledDuration d1 = new DisabledDuration(now, now.plusDays(10));
+ final DisabledDuration d2 = new DisabledDuration(now.plusDays(1), null);
+
+ final DisabledDuration result = DisabledDuration.mergeDuration(d1, d2);
+ assertEquals(result.getStart().compareTo(now), 0);
+ assertNull(result.getEnd());
+ }
+
+ @Test(groups = "fast", expectedExceptions = IllegalStateException.class)
+ public void testMergeDurationInvalid1() throws Exception {
+ final DateTime now = clock.getUTCNow();
+ final DisabledDuration d1 = new DisabledDuration(now, now.plusDays(10));
+ final DisabledDuration d2 = new DisabledDuration(now.plusDays(11), null);
+
+ DisabledDuration.mergeDuration(d1, d2);
+ }
+
+ @Test(groups = "fast", expectedExceptions = IllegalStateException.class)
+ public void testMergeDurationInvalid2() throws Exception {
+ final DateTime now = clock.getUTCNow();
+ final DisabledDuration d1 = new DisabledDuration(now.plusDays(1), now.plusDays(10));
+ final DisabledDuration d2 = new DisabledDuration(now, null);
+
+ DisabledDuration.mergeDuration(d1, d2);
+ }
+
+}
\ No newline at end of file
NEWS 15(+15 -0)
diff --git a/NEWS b/NEWS
index 001a636..9b7028a 100644
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,18 @@
+0.18.14
+ See https://github.com/killbill/killbill/releases/tag/killbill-0.18.14
+
+0.18.13
+ See https://github.com/killbill/killbill/releases/tag/killbill-0.18.13
+
+0.18.12
+ See https://github.com/killbill/killbill/releases/tag/killbill-0.18.12
+
+0.18.11
+ See https://github.com/killbill/killbill/releases/tag/killbill-0.18.11
+
+0.18.10
+ See https://github.com/killbill/killbill/releases/tag/killbill-0.18.10
+
0.18.7
See https://github.com/killbill/killbill/releases/tag/killbill-0.18.7
overdue/pom.xml 6(+1 -5)
diff --git a/overdue/pom.xml b/overdue/pom.xml
index 27874c3..290cd8a 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.19.0-SNAPSHOT</version>
+ <version>0.19.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-overdue</artifactId>
@@ -83,10 +83,6 @@
<scope>test</scope>
</dependency>
<dependency>
- <groupId>org.jdbi</groupId>
- <artifactId>jdbi</artifactId>
- </dependency>
- <dependency>
<groupId>org.kill-bill.billing</groupId>
<artifactId>killbill-api</artifactId>
</dependency>
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/applicator/OverdueStateApplicator.java b/overdue/src/main/java/org/killbill/billing/overdue/applicator/OverdueStateApplicator.java
index bdab42c..b880b1d 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/applicator/OverdueStateApplicator.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/applicator/OverdueStateApplicator.java
@@ -18,7 +18,6 @@
package org.killbill.billing.overdue.applicator;
-import java.io.IOException;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
@@ -30,12 +29,10 @@ import org.joda.time.DateTime;
import org.joda.time.Period;
import org.killbill.billing.ErrorCode;
import org.killbill.billing.ObjectType;
-import org.killbill.billing.account.api.Account;
import org.killbill.billing.account.api.AccountApiException;
import org.killbill.billing.account.api.AccountInternalApi;
import org.killbill.billing.account.api.ImmutableAccountData;
import org.killbill.billing.callcontext.InternalCallContext;
-import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.catalog.api.BillingActionPolicy;
import org.killbill.billing.catalog.api.ProductCategory;
import org.killbill.billing.entitlement.EntitlementInternalApi;
@@ -63,23 +60,16 @@ import org.killbill.billing.tag.TagInternalApi;
import org.killbill.billing.util.api.TagApiException;
import org.killbill.billing.util.callcontext.CallContext;
import org.killbill.billing.util.callcontext.InternalCallContextFactory;
-import org.killbill.billing.util.email.DefaultEmailSender;
-import org.killbill.billing.util.email.EmailApiException;
-import org.killbill.billing.util.email.EmailConfig;
-import org.killbill.billing.util.email.EmailSender;
import org.killbill.billing.util.tag.ControlTagType;
import org.killbill.billing.util.tag.Tag;
import org.killbill.bus.api.PersistentBus;
-import org.killbill.clock.Clock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Predicate;
-import com.google.common.base.Strings;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableList;
import com.google.inject.Inject;
-import com.samskivert.mustache.MustacheException;
public class OverdueStateApplicator {
@@ -91,9 +81,7 @@ public class OverdueStateApplicator {
private final AccountInternalApi accountApi;
private final EntitlementApi entitlementApi;
private final EntitlementInternalApi entitlementInternalApi;
- private final OverdueEmailGenerator overdueEmailGenerator;
private final TagInternalApi tagApi;
- private final EmailSender emailSender;
private final InternalCallContextFactory internalCallContextFactory;
@Inject
@@ -102,8 +90,6 @@ public class OverdueStateApplicator {
final EntitlementApi entitlementApi,
final EntitlementInternalApi entitlementInternalApi,
@Named(DefaultOverdueModule.OVERDUE_NOTIFIER_CHECK_NAMED) final OverduePoster checkPoster,
- final OverdueEmailGenerator overdueEmailGenerator,
- final EmailConfig config,
final PersistentBus bus,
final TagInternalApi tagApi,
final InternalCallContextFactory internalCallContextFactory) {
@@ -113,60 +99,52 @@ public class OverdueStateApplicator {
this.entitlementApi = entitlementApi;
this.entitlementInternalApi = entitlementInternalApi;
this.checkPoster = checkPoster;
- this.overdueEmailGenerator = overdueEmailGenerator;
this.tagApi = tagApi;
this.internalCallContextFactory = internalCallContextFactory;
- this.emailSender = new DefaultEmailSender(config);
this.bus = bus;
}
public void apply(final DateTime effectiveDate, final OverdueStateSet overdueStateSet, final BillingState billingState,
final ImmutableAccountData account, final OverdueState previousOverdueState,
final OverdueState nextOverdueState, final InternalCallContext context) throws OverdueException, OverdueApiException {
- try {
- if (isAccountTaggedWith_OVERDUE_ENFORCEMENT_OFF(context)) {
- log.debug("OverdueStateApplicator: apply returns because account (recordId={}) is set with OVERDUE_ENFORCEMENT_OFF", context.getAccountRecordId());
- return;
- }
+ if (isAccountTaggedWith_OVERDUE_ENFORCEMENT_OFF(context)) {
+ log.debug("OverdueStateApplicator: apply returns because account (recordId={}) is set with OVERDUE_ENFORCEMENT_OFF", context.getAccountRecordId());
+ return;
+ }
- log.debug("OverdueStateApplicator: time={}, previousState={}, nextState={}, billingState={}", effectiveDate, previousOverdueState, nextOverdueState, billingState);
-
- final OverdueState firstOverdueState = overdueStateSet.getFirstState();
- final boolean conditionForNextNotfication = !nextOverdueState.isClearState() ||
- // We did not reach the first state yet but we have an unpaid invoice
- (firstOverdueState != null && billingState != null && billingState.getDateOfEarliestUnpaidInvoice() != null);
-
- if (conditionForNextNotfication) {
- final Period reevaluationInterval = getReevaluationInterval(overdueStateSet, nextOverdueState);
- // If there is no configuration in the config, we assume this is because the overdue conditions are not time based and so there is nothing to retry
- if (reevaluationInterval == null) {
- log.debug("OverdueStateApplicator <notificationQ>: missing InitialReevaluationInterval from config, NOT inserting notification for account {}", account.getId());
- } else {
- log.debug("OverdueStateApplicator <notificationQ>: inserting notification for account={}, time={}", account.getId(), effectiveDate.plus(reevaluationInterval));
- createFutureNotification(account, effectiveDate.plus(reevaluationInterval), context);
- }
- } else if (nextOverdueState.isClearState()) {
- clearFutureNotification(account, context);
- }
+ log.debug("OverdueStateApplicator: time={}, previousState={}, nextState={}, billingState={}", effectiveDate, previousOverdueState, nextOverdueState, billingState);
+
+ final OverdueState firstOverdueState = overdueStateSet.getFirstState();
+ final boolean conditionForNextNotfication = !nextOverdueState.isClearState() ||
+ // We did not reach the first state yet but we have an unpaid invoice
+ (firstOverdueState != null && billingState != null && billingState.getDateOfEarliestUnpaidInvoice() != null);
- if (previousOverdueState.getName().equals(nextOverdueState.getName())) {
- log.debug("OverdueStateApplicator is no-op: previousState={}, nextState={}", previousOverdueState, nextOverdueState);
- return;
+ if (conditionForNextNotfication) {
+ final Period reevaluationInterval = getReevaluationInterval(overdueStateSet, nextOverdueState);
+ // If there is no configuration in the config, we assume this is because the overdue conditions are not time based and so there is nothing to retry
+ if (reevaluationInterval == null) {
+ log.debug("OverdueStateApplicator <notificationQ>: missing InitialReevaluationInterval from config, NOT inserting notification for account {}", account.getId());
+ } else {
+ log.debug("OverdueStateApplicator <notificationQ>: inserting notification for account={}, time={}", account.getId(), effectiveDate.plus(reevaluationInterval));
+ createFutureNotification(account, effectiveDate.plus(reevaluationInterval), context);
}
+ } else if (nextOverdueState.isClearState()) {
+ clearFutureNotification(account, context);
+ }
- cancelSubscriptionsIfRequired(effectiveDate, account, nextOverdueState, context);
+ if (previousOverdueState.getName().equals(nextOverdueState.getName())) {
+ log.debug("OverdueStateApplicator is no-op: previousState={}, nextState={}", previousOverdueState, nextOverdueState);
+ return;
+ }
- sendEmailIfRequired(account.getId(), billingState, nextOverdueState, context);
+ cancelSubscriptionsIfRequired(effectiveDate, account, nextOverdueState, context);
- avoid_extra_credit_by_toggling_AUTO_INVOICE_OFF(account, previousOverdueState, nextOverdueState, context);
+ avoid_extra_credit_by_toggling_AUTO_INVOICE_OFF(account, previousOverdueState, nextOverdueState, context);
- // Make sure to store the new state last here: the entitlement DAO will send a BlockingTransitionInternalEvent
- // on the bus to which invoice will react. We need the latest state (including AUTO_INVOICE_OFF tag for example)
- // to be present in the database first.
- storeNewState(effectiveDate, account, nextOverdueState, context);
- } catch (final AccountApiException e) {
- throw new OverdueException(e);
- }
+ // Make sure to store the new state last here: the entitlement DAO will send a BlockingTransitionInternalEvent
+ // on the bus to which invoice will react. We need the latest state (including AUTO_INVOICE_OFF tag for example)
+ // to be present in the database first.
+ storeNewState(effectiveDate, account, nextOverdueState, context);
final OverdueChangeInternalEvent event;
try {
@@ -354,45 +332,6 @@ public class OverdueStateApplicator {
result.addAll(allEntitlementsButAddonsForAccountId);
}
- private void sendEmailIfRequired(final UUID accountId, final BillingState billingState,
- final OverdueState nextOverdueState, final InternalTenantContext context) throws AccountApiException {
- // Note: we don't want to fail the full refresh call because sending the email failed.
- // That's the reason why we catch all exceptions here.
- // The alternative would be to: throw new OverdueApiException(e, ErrorCode.EMAIL_SENDING_FAILED);
-
- // If sending is not configured, skip
- if (nextOverdueState.getEmailNotification() == null) {
- return;
- }
-
- final Account account = accountApi.getAccountById(accountId, context);
- if (Strings.emptyToNull(account.getEmail()) == null) {
- log.warn("Unable to send overdue notification email for account {} and overdueable {}: no email specified", account.getId(), account.getId());
- return;
- }
-
- final List<String> to = ImmutableList.<String>of(account.getEmail());
- // TODO - should we look at the account CC: list?
- final List<String> cc = ImmutableList.<String>of();
- final String subject = nextOverdueState.getEmailNotification().getSubject();
-
- try {
- // Generate and send the email
- final String emailBody = overdueEmailGenerator.generateEmail(account, billingState, account, nextOverdueState);
- if (nextOverdueState.getEmailNotification().isHTML()) {
- emailSender.sendHTMLEmail(to, cc, subject, emailBody);
- } else {
- emailSender.sendPlainTextEmail(to, cc, subject, emailBody);
- }
- } catch (final IOException e) {
- log.warn("Unable to generate or send overdue notification email for accountId='{}'", account.getId(), e);
- } catch (final EmailApiException e) {
- log.warn("Unable to send overdue notification email for accountId='{}'", account.getId(), e);
- } catch (final MustacheException e) {
- log.warn("Unable to generate overdue notification email for accountId='{}'", account.getId(), e);
- }
- }
-
//
// Uses callcontext information to retrieve account matching the Overduable object and check whether we should do any overdue processing
//
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultEmailNotification.java b/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultEmailNotification.java
index ea314cb..c7167b7 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultEmailNotification.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultEmailNotification.java
@@ -22,8 +22,9 @@ import javax.xml.bind.annotation.XmlElement;
import org.killbill.billing.overdue.api.EmailNotification;
+@Deprecated // Not used, just kept for config compatibility
@XmlAccessorType(XmlAccessType.NONE)
-public class DefaultEmailNotification implements EmailNotification {
+public class DefaultEmailNotification {
@XmlElement(required = true, name = "subject")
private String subject;
@@ -33,19 +34,4 @@ public class DefaultEmailNotification implements EmailNotification {
@XmlElement(required = false, name = "isHTML")
private Boolean isHTML = false;
-
- @Override
- public String getSubject() {
- return subject;
- }
-
- @Override
- public String getTemplateName() {
- return templateName;
- }
-
- @Override
- public Boolean isHTML() {
- return isHTML;
- }
}
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultOverdueState.java b/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultOverdueState.java
index 6c57ffd..233a0ca 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultOverdueState.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultOverdueState.java
@@ -18,8 +18,11 @@
package org.killbill.billing.overdue.config;
+import java.util.List;
+
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlAnyElement;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlID;
@@ -68,6 +71,7 @@ public class DefaultOverdueState extends ValidatingConfig<DefaultOverdueConfig>
@XmlElement(required = false, name = "autoReevaluationInterval")
private DefaultDuration autoReevaluationInterval;
+ @Deprecated // Not used, just kept for config compatibility
@XmlElement(required = false, name = "enterStateEmailNotification")
private DefaultEmailNotification enterStateEmailNotification;
@@ -174,10 +178,6 @@ public class DefaultOverdueState extends ValidatingConfig<DefaultOverdueConfig>
return errors;
}
- @Override
- public EmailNotification getEmailNotification() {
- return enterStateEmailNotification;
- }
@Override
public String toString() {
@@ -190,7 +190,6 @@ public class DefaultOverdueState extends ValidatingConfig<DefaultOverdueConfig>
sb.append(", subscriptionCancellationPolicy=").append(subscriptionCancellationPolicy);
sb.append(", isClearState=").append(isClearState);
sb.append(", autoReevaluationInterval=").append(autoReevaluationInterval);
- sb.append(", enterStateEmailNotification=").append(enterStateEmailNotification);
sb.append('}');
return sb.toString();
}
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultOverdueStatesAccount.java b/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultOverdueStatesAccount.java
index 004fd2d..c384444 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultOverdueStatesAccount.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultOverdueStatesAccount.java
@@ -16,6 +16,9 @@
package org.killbill.billing.overdue.config;
+import java.util.List;
+
+import javax.xml.bind.annotation.XmlAnyElement;
import javax.xml.bind.annotation.XmlElement;
import org.joda.time.Period;
@@ -32,6 +35,7 @@ public class DefaultOverdueStatesAccount extends DefaultOverdueStateSet implemen
@XmlElement(required = true, name = "state")
private DefaultOverdueState[] accountOverdueStates = new DefaultOverdueState[0];
+
@Override
public DefaultOverdueState[] getStates() {
return accountOverdueStates;
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/glue/DefaultOverdueModule.java b/overdue/src/main/java/org/killbill/billing/overdue/glue/DefaultOverdueModule.java
index 930c774..9a00618 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/glue/DefaultOverdueModule.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/glue/DefaultOverdueModule.java
@@ -23,9 +23,6 @@ import org.killbill.billing.overdue.OverdueProperties;
import org.killbill.billing.overdue.OverdueService;
import org.killbill.billing.overdue.api.DefaultOverdueApi;
import org.killbill.billing.overdue.api.OverdueApi;
-import org.killbill.billing.overdue.applicator.OverdueEmailGenerator;
-import org.killbill.billing.overdue.applicator.formatters.DefaultOverdueEmailFormatterFactory;
-import org.killbill.billing.overdue.applicator.formatters.OverdueEmailFormatterFactory;
import org.killbill.billing.overdue.caching.EhCacheOverdueConfigCache;
import org.killbill.billing.overdue.caching.OverdueCacheInvalidationCallback;
import org.killbill.billing.overdue.caching.OverdueConfigCache;
@@ -64,7 +61,6 @@ public class DefaultOverdueModule extends KillBillModule implements OverdueModul
// internal bindings
installOverdueService();
installOverdueWrapperFactory();
- installOverdueEmail();
final OverdueProperties config = new ConfigurationObjectFactory(skifeConfigSource).build(OverdueProperties.class);
bind(OverdueProperties.class).toInstance(config);
@@ -86,11 +82,6 @@ public class DefaultOverdueModule extends KillBillModule implements OverdueModul
bind(OverdueWrapperFactory.class).asEagerSingleton();
}
- protected void installOverdueEmail() {
- bind(OverdueEmailFormatterFactory.class).to(DefaultOverdueEmailFormatterFactory.class).asEagerSingleton();
- bind(OverdueEmailGenerator.class).asEagerSingleton();
- }
-
@Override
public void installOverdueUserApi() {
bind(OverdueApi.class).to(DefaultOverdueApi.class).asEagerSingleton();
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/notification/OverdueAsyncBusPoster.java b/overdue/src/main/java/org/killbill/billing/overdue/notification/OverdueAsyncBusPoster.java
index 124fe69..9876898 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/notification/OverdueAsyncBusPoster.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/notification/OverdueAsyncBusPoster.java
@@ -50,6 +50,6 @@ public class OverdueAsyncBusPoster extends DefaultOverduePosterBase {
// Note that this is slightly incorrect because we could for instance already have a REFRESH and insert a CLEAR, but if that were the case,
// if means overdue state would change very rapidly and the behavior would anyway be non deterministic
// Note: don't use isEmpty() to go through all results to close the connection
- return Iterables.<NotificationEventWithMetadata<T>>size(futureNotifications) == 0;
+ return Iterables.size(futureNotifications) == 0;
}
}
diff --git a/overdue/src/test/java/org/killbill/billing/overdue/config/TestOverdueConfig.java b/overdue/src/test/java/org/killbill/billing/overdue/config/TestOverdueConfig.java
index d4326db..f0209b9 100644
--- a/overdue/src/test/java/org/killbill/billing/overdue/config/TestOverdueConfig.java
+++ b/overdue/src/test/java/org/killbill/billing/overdue/config/TestOverdueConfig.java
@@ -70,16 +70,9 @@ public class TestOverdueConfig extends OverdueTestSuiteNoDB {
final DefaultOverdueConfig c = XMLLoader.getObjectFromStreamNoValidation(is, DefaultOverdueConfig.class);
Assert.assertEquals(c.getOverdueStatesAccount().size(), 2);
- Assert.assertNull(c.getOverdueStatesAccount().getStates()[1].getEmailNotification());
-
Assert.assertNotNull(c.getOverdueStatesAccount().getInitialReevaluationInterval());
Assert.assertEquals(c.getOverdueStatesAccount().getInitialReevaluationInterval().getDays(), 1);
- final EmailNotification secondNotification = c.getOverdueStatesAccount().getStates()[0].getEmailNotification();
- Assert.assertEquals(secondNotification.getSubject(), "ToTo");
- Assert.assertEquals(secondNotification.getTemplateName(), "Titi");
- Assert.assertFalse(secondNotification.isHTML());
-
Assert.assertEquals(c.getOverdueStatesAccount().getFirstState().getName(), "OD1");
}
}
diff --git a/overdue/src/test/java/org/killbill/billing/overdue/glue/TestOverdueModule.java b/overdue/src/test/java/org/killbill/billing/overdue/glue/TestOverdueModule.java
index 8d603af..21943cd 100644
--- a/overdue/src/test/java/org/killbill/billing/overdue/glue/TestOverdueModule.java
+++ b/overdue/src/test/java/org/killbill/billing/overdue/glue/TestOverdueModule.java
@@ -40,7 +40,6 @@ import org.killbill.billing.overdue.caching.OverdueConfigCache;
import org.killbill.billing.overdue.wrapper.OverdueWrapper;
import org.killbill.billing.platform.api.KillbillConfigSource;
import org.killbill.billing.tenant.api.TenantInternalApi.CacheInvalidationCallback;
-import org.killbill.billing.util.email.EmailModule;
import org.killbill.billing.util.email.templates.TemplateModule;
import org.killbill.billing.util.glue.AuditModule;
import org.killbill.billing.util.glue.CacheModule;
@@ -66,7 +65,6 @@ public class TestOverdueModule extends DefaultOverdueModule {
install(new ConfigModule(configSource));
install(new CallContextModule(configSource));
install(new CustomFieldModule(configSource));
- install(new EmailModule(configSource));
install(new MockAccountModule(configSource));
install(new MockEntitlementModule(configSource, new ApplicatorBlockingApi()));
install(new MockInvoiceModule(configSource));
diff --git a/overdue/src/test/java/org/killbill/billing/overdue/notification/TestDefaultOverdueCheckPoster.java b/overdue/src/test/java/org/killbill/billing/overdue/notification/TestDefaultOverdueCheckPoster.java
index 3f8cd00..1a7b3bd 100644
--- a/overdue/src/test/java/org/killbill/billing/overdue/notification/TestDefaultOverdueCheckPoster.java
+++ b/overdue/src/test/java/org/killbill/billing/overdue/notification/TestDefaultOverdueCheckPoster.java
@@ -72,7 +72,7 @@ public class TestDefaultOverdueCheckPoster extends OverdueTestSuiteWithEmbeddedD
insertOverdueCheckAndVerifyQueueContent(overdueable, 15, 5);
// Verify the final content of the queue
- Assert.assertEquals(Iterables.<NotificationEventWithMetadata>size(overdueQueue.getFutureNotificationForSearchKeys(internalCallContext.getAccountRecordId(), internalCallContext.getTenantRecordId())), 1);
+ Assert.assertEquals(Iterables.size(overdueQueue.getFutureNotificationForSearchKeys(internalCallContext.getAccountRecordId(), internalCallContext.getTenantRecordId())), 1);
}
private void insertOverdueCheckAndVerifyQueueContent(final Account account, final int nbDaysInFuture, final int expectedNbDaysInFuture) throws IOException {
diff --git a/overdue/src/test/java/org/killbill/billing/overdue/notification/TestOverdueCheckNotifier.java b/overdue/src/test/java/org/killbill/billing/overdue/notification/TestOverdueCheckNotifier.java
index 162d756..8856294 100644
--- a/overdue/src/test/java/org/killbill/billing/overdue/notification/TestOverdueCheckNotifier.java
+++ b/overdue/src/test/java/org/killbill/billing/overdue/notification/TestOverdueCheckNotifier.java
@@ -69,9 +69,10 @@ public class TestOverdueCheckNotifier extends OverdueTestSuiteWithEmbeddedDB {
@Override
@BeforeMethod(groups = "slow")
public void beforeMethod() throws Exception {
- //super.beforeMethod();
// We override the parent method on purpose, because we want to register a different OverdueCheckNotifier
+ cleanupAllTables();
+
mockDispatcher = new OverdueDispatcherMock(internalCallContextFactory);
notifierForMock = new OverdueCheckNotifier(notificationQueueService, overdueProperties, internalCallContextFactory, mockDispatcher);
@@ -91,7 +92,7 @@ public class TestOverdueCheckNotifier extends OverdueTestSuiteWithEmbeddedDB {
final UUID accountId = new UUID(0L, 1L);
final Account account = Mockito.mock(Account.class);
Mockito.when(account.getId()).thenReturn(accountId);
- Mockito.when(accountApi.getImmutableAccountDataByRecordId(Mockito.<UUID>eq(internalCallContext.getAccountRecordId()), Mockito.<InternalTenantContext>any())).thenReturn(account);
+ Mockito.when(accountApi.getImmutableAccountDataByRecordId(Mockito.eq(internalCallContext.getAccountRecordId()), Mockito.<InternalTenantContext>any())).thenReturn(account);
final DateTime now = clock.getUTCNow();
final DateTime readyTime = now.plusMillis(2000);
diff --git a/overdue/src/test/java/org/killbill/billing/overdue/TestOverdueHelper.java b/overdue/src/test/java/org/killbill/billing/overdue/TestOverdueHelper.java
index 135e86f..7ad5c4d 100644
--- a/overdue/src/test/java/org/killbill/billing/overdue/TestOverdueHelper.java
+++ b/overdue/src/test/java/org/killbill/billing/overdue/TestOverdueHelper.java
@@ -34,11 +34,13 @@ import org.killbill.billing.entitlement.api.BlockingState;
import org.killbill.billing.invoice.api.Invoice;
import org.killbill.billing.invoice.api.InvoiceInternalApi;
import org.killbill.billing.invoice.api.InvoiceItem;
+import org.killbill.billing.invoice.api.InvoiceStatus;
import org.killbill.billing.junction.BlockingInternalApi;
import org.killbill.billing.overdue.api.OverdueState;
import org.killbill.billing.overdue.glue.TestOverdueModule.ApplicatorBlockingApi;
import org.killbill.billing.subscription.api.user.SubscriptionBaseApiException;
import org.killbill.billing.tag.TagInternalApi;
+import org.killbill.billing.util.tag.ControlTagType;
import org.killbill.billing.util.tag.Tag;
import org.mockito.Mockito;
import org.testng.Assert;
@@ -131,6 +133,7 @@ public class TestOverdueHelper {
final Invoice invoice = Mockito.mock(Invoice.class);
Mockito.when(invoice.getInvoiceDate()).thenReturn(dateOfLastUnPaidInvoice);
Mockito.when(invoice.getBalance()).thenReturn(BigDecimal.TEN);
+ Mockito.when(invoice.getStatus()).thenReturn(InvoiceStatus.COMMITTED);
Mockito.when(invoice.getId()).thenReturn(UUID.randomUUID());
final InvoiceItem item = Mockito.mock(InvoiceItem.class);
@@ -146,7 +149,7 @@ public class TestOverdueHelper {
final Tag tag = Mockito.mock(Tag.class);
Mockito.when(tag.getObjectId()).thenReturn(accountId);
Mockito.when(tag.getObjectType()).thenReturn(ObjectType.ACCOUNT);
- Mockito.when(tag.getTagDefinitionId()).thenReturn(new UUID(0, 6));
+ Mockito.when(tag.getTagDefinitionId()).thenReturn(ControlTagType.TEST.getId());
final List<Tag> tags = new ArrayList<Tag>();
tags.add(tag);
Mockito.when(tagInternalApi.getTags(Mockito.eq(account.getId()), Mockito.eq(ObjectType.ACCOUNT), Mockito.<InternalTenantContext>any()))
payment/pom.xml 6(+1 -5)
diff --git a/payment/pom.xml b/payment/pom.xml
index 7874638..1a79089 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.19.0-SNAPSHOT</version>
+ <version>0.19.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-payment</artifactId>
@@ -101,10 +101,6 @@
<scope>test</scope>
</dependency>
<dependency>
- <groupId>org.jdbi</groupId>
- <artifactId>jdbi</artifactId>
- </dependency>
- <dependency>
<groupId>org.kill-bill.billing</groupId>
<artifactId>killbill-account</artifactId>
<scope>test</scope>
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 6174f20..57e2596 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
@@ -25,6 +25,7 @@ import java.util.UUID;
import javax.annotation.Nullable;
import javax.inject.Inject;
+import org.joda.time.DateTime;
import org.killbill.billing.ErrorCode;
import org.killbill.billing.ObjectType;
import org.killbill.billing.account.api.Account;
@@ -70,7 +71,7 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
}
@Override
- public Payment createAuthorization(final Account account, final UUID paymentMethodId, @Nullable final UUID paymentId, final BigDecimal amount, final Currency currency, @Nullable final String paymentExternalKey, @Nullable final String paymentTransactionExternalKey,
+ public Payment createAuthorization(final Account account, final UUID paymentMethodId, @Nullable final UUID paymentId, final BigDecimal amount, final Currency currency, @Nullable final DateTime effectiveDate, @Nullable final String paymentExternalKey, @Nullable final String paymentTransactionExternalKey,
final Iterable<PluginProperty> properties, final CallContext callContext) throws PaymentApiException {
checkNotNullParameter(account, "account");
checkNotNullParameter(paymentMethodId, "paymentMethodId");
@@ -89,7 +90,7 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
logEnterAPICall(log, transactionType, account, paymentMethodId, paymentId, null, amount, currency, paymentExternalKey, paymentTransactionExternalKey, null, null);
final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
- payment = paymentProcessor.createAuthorization(IS_API_PAYMENT, NULL_ATTEMPT_ID, account, paymentMethodId, paymentId, amount, currency, paymentExternalKey, paymentTransactionExternalKey,
+ payment = paymentProcessor.createAuthorization(IS_API_PAYMENT, NULL_ATTEMPT_ID, account, paymentMethodId, paymentId, amount, currency, effectiveDate, paymentExternalKey, paymentTransactionExternalKey,
null, null, SHOULD_LOCK_ACCOUNT, properties, callContext, internalCallContext);
paymentTransaction = findPaymentTransaction(payment, paymentTransactionExternalKey);
@@ -116,12 +117,12 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
}
@Override
- public Payment createAuthorizationWithPaymentControl(final Account account, final UUID paymentMethodId, @Nullable final UUID paymentId, final BigDecimal amount, final Currency currency,
+ public Payment createAuthorizationWithPaymentControl(final Account account, @Nullable final UUID paymentMethodId, @Nullable final UUID paymentId, final BigDecimal amount, final Currency currency, @Nullable final DateTime effectiveDate,
@Nullable final String paymentExternalKey, @Nullable final String paymentTransactionExternalKey,
final Iterable<PluginProperty> properties, final PaymentOptions paymentOptions, final CallContext callContext) throws PaymentApiException {
final List<String> paymentControlPluginNames = toPaymentControlPluginNames(paymentOptions, callContext);
if (paymentControlPluginNames.isEmpty()) {
- return createAuthorization(account, paymentMethodId, paymentId, amount, currency, paymentExternalKey, paymentTransactionExternalKey, properties, callContext);
+ return createAuthorization(account, paymentMethodId, paymentId, amount, currency, effectiveDate, paymentExternalKey, paymentTransactionExternalKey, properties, callContext);
}
checkNotNullParameter(account, "account");
@@ -141,7 +142,7 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
logEnterAPICall(log, transactionType, account, paymentMethodId, paymentId, null, amount, currency, paymentExternalKey, paymentTransactionExternalKey, null, paymentControlPluginNames);
final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
- payment = pluginControlPaymentProcessor.createAuthorization(IS_API_PAYMENT, account, paymentMethodId, paymentId, amount, currency, paymentExternalKey, paymentTransactionExternalKey,
+ payment = pluginControlPaymentProcessor.createAuthorization(IS_API_PAYMENT, account, paymentMethodId, paymentId, amount, currency, effectiveDate, paymentExternalKey, paymentTransactionExternalKey,
properties, paymentControlPluginNames, callContext, internalCallContext);
paymentTransaction = findPaymentTransaction(payment, paymentTransactionExternalKey);
@@ -168,7 +169,7 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
}
@Override
- public Payment createCapture(final Account account, final UUID paymentId, final BigDecimal amount, final Currency currency, @Nullable final String paymentTransactionExternalKey,
+ public Payment createCapture(final Account account, final UUID paymentId, final BigDecimal amount, final Currency currency, @Nullable final DateTime effectiveDate, @Nullable final String paymentTransactionExternalKey,
final Iterable<PluginProperty> properties, final CallContext callContext) throws PaymentApiException {
checkNotNullParameter(account, "account");
@@ -186,7 +187,7 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
logEnterAPICall(log, transactionType, account, null, paymentId, null, amount, currency, null, paymentTransactionExternalKey, null, null);
final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
- payment = paymentProcessor.createCapture(IS_API_PAYMENT, NULL_ATTEMPT_ID, account, paymentId, amount, currency, paymentTransactionExternalKey,
+ payment = paymentProcessor.createCapture(IS_API_PAYMENT, NULL_ATTEMPT_ID, account, paymentId, amount, currency, effectiveDate, paymentTransactionExternalKey,
null, SHOULD_LOCK_ACCOUNT, properties, callContext, internalCallContext);
paymentTransaction = findPaymentTransaction(payment, paymentTransactionExternalKey);
@@ -213,11 +214,11 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
}
@Override
- public Payment createCaptureWithPaymentControl(final Account account, final UUID paymentId, final BigDecimal amount, final Currency currency, @Nullable final String paymentTransactionExternalKey,
+ public Payment createCaptureWithPaymentControl(final Account account, final UUID paymentId, final BigDecimal amount, final Currency currency, @Nullable final DateTime effectiveDate, @Nullable final String paymentTransactionExternalKey,
final Iterable<PluginProperty> properties, final PaymentOptions paymentOptions, final CallContext callContext) throws PaymentApiException {
final List<String> paymentControlPluginNames = toPaymentControlPluginNames(paymentOptions, callContext);
if (paymentControlPluginNames.isEmpty()) {
- return createCapture(account, paymentId, amount, currency, paymentTransactionExternalKey, properties, callContext);
+ return createCapture(account, paymentId, amount, currency, effectiveDate, paymentTransactionExternalKey, properties, callContext);
}
checkNotNullParameter(account, "account");
@@ -235,7 +236,7 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
logEnterAPICall(log, transactionType, account, null, paymentId, null, amount, currency, null, paymentTransactionExternalKey, null, paymentControlPluginNames);
final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
- payment = pluginControlPaymentProcessor.createCapture(IS_API_PAYMENT, account, paymentId, amount, currency, paymentTransactionExternalKey,
+ payment = pluginControlPaymentProcessor.createCapture(IS_API_PAYMENT, account, paymentId, amount, currency, effectiveDate, paymentTransactionExternalKey,
properties, paymentControlPluginNames, callContext, internalCallContext);
paymentTransaction = findPaymentTransaction(payment, paymentTransactionExternalKey);
@@ -263,7 +264,7 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
}
@Override
- public Payment createPurchase(final Account account, final UUID paymentMethodId, @Nullable final UUID paymentId, final BigDecimal amount, final Currency currency, @Nullable final String paymentExternalKey, @Nullable final String paymentTransactionExternalKey,
+ public Payment createPurchase(final Account account, final UUID paymentMethodId, @Nullable final UUID paymentId, final BigDecimal amount, final Currency currency, @Nullable final DateTime effectiveDate, @Nullable final String paymentExternalKey, @Nullable final String paymentTransactionExternalKey,
final Iterable<PluginProperty> properties, final CallContext callContext) throws PaymentApiException {
checkNotNullParameter(account, "account");
checkNotNullParameter(paymentMethodId, "paymentMethodId");
@@ -282,7 +283,7 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
logEnterAPICall(log, transactionType, account, paymentMethodId, paymentId, null, amount, currency, paymentExternalKey, paymentTransactionExternalKey, null, null);
final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
- payment = paymentProcessor.createPurchase(IS_API_PAYMENT, NULL_ATTEMPT_ID, account, paymentMethodId, paymentId, amount, currency, paymentExternalKey, paymentTransactionExternalKey,
+ payment = paymentProcessor.createPurchase(IS_API_PAYMENT, NULL_ATTEMPT_ID, account, paymentMethodId, paymentId, amount, currency, effectiveDate, paymentExternalKey, paymentTransactionExternalKey,
null, null, SHOULD_LOCK_ACCOUNT, properties, callContext, internalCallContext);
paymentTransaction = findPaymentTransaction(payment, paymentTransactionExternalKey);
@@ -309,11 +310,11 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
}
@Override
- public Payment createPurchaseWithPaymentControl(final Account account, @Nullable final UUID paymentMethodId, @Nullable final UUID paymentId, final BigDecimal amount, final Currency currency, @Nullable final String paymentExternalKey, final String paymentTransactionExternalKey,
+ public Payment createPurchaseWithPaymentControl(final Account account, @Nullable final UUID paymentMethodId, @Nullable final UUID paymentId, final BigDecimal amount, final Currency currency, @Nullable final DateTime effectiveDate, @Nullable final String paymentExternalKey, final String paymentTransactionExternalKey,
final Iterable<PluginProperty> properties, final PaymentOptions paymentOptions, final CallContext callContext) throws PaymentApiException {
final List<String> paymentControlPluginNames = toPaymentControlPluginNames(paymentOptions, callContext);
if (paymentControlPluginNames.isEmpty()) {
- return createPurchase(account, paymentMethodId, paymentId, amount, currency, paymentExternalKey, paymentTransactionExternalKey, properties, callContext);
+ return createPurchase(account, paymentMethodId, paymentId, amount, currency, effectiveDate, paymentExternalKey, paymentTransactionExternalKey, properties, callContext);
}
checkNotNullParameter(account, "account");
@@ -325,17 +326,12 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
checkNotNullParameter(properties, "plugin properties");
checkExternalKeyLength(paymentTransactionExternalKey);
- if (paymentMethodId == null && !paymentOptions.isExternalPayment()) {
- throw new PaymentApiException(ErrorCode.PAYMENT_NO_DEFAULT_PAYMENT_METHOD, "paymentMethodId", "should not be null");
- }
final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
- // TODO validate if the code is located properly here
- // The code should understand that the external payment method needs to be created
- // if it doesn't exist yet and trigger a CREDIT using the (built-in) external payment plugin.
- final UUID nonNullPaymentMethodId = (paymentMethodId != null) ?
- paymentMethodId :
- paymentMethodProcessor.createOrGetExternalPaymentMethod(UUIDs.randomUUID().toString(), account, properties, callContext, internalCallContext);
+
+ final UUID resolvedPaymentMethodId = (paymentMethodId == null && paymentOptions.isExternalPayment()) ?
+ paymentMethodProcessor.createOrGetExternalPaymentMethod(UUIDs.randomUUID().toString(), account, properties, callContext, internalCallContext) :
+ paymentMethodId;
final String transactionType = TransactionType.PURCHASE.name();
Payment payment = null;
@@ -344,7 +340,7 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
try {
logEnterAPICall(log, transactionType, account, paymentMethodId, paymentId, null, amount, currency, paymentExternalKey, paymentTransactionExternalKey, null, paymentControlPluginNames);
- payment = pluginControlPaymentProcessor.createPurchase(IS_API_PAYMENT, account, nonNullPaymentMethodId, paymentId, amount, currency, paymentExternalKey, paymentTransactionExternalKey,
+ payment = pluginControlPaymentProcessor.createPurchase(IS_API_PAYMENT, account, resolvedPaymentMethodId, paymentId, amount, currency, effectiveDate, paymentExternalKey, paymentTransactionExternalKey,
properties, paymentControlPluginNames, callContext, internalCallContext);
paymentTransaction = findPaymentTransaction(payment, paymentTransactionExternalKey);
@@ -371,7 +367,7 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
}
@Override
- public Payment createVoid(final Account account, final UUID paymentId, @Nullable final String paymentTransactionExternalKey, final Iterable<PluginProperty> properties,
+ public Payment createVoid(final Account account, final UUID paymentId, @Nullable final DateTime effectiveDate, @Nullable final String paymentTransactionExternalKey, final Iterable<PluginProperty> properties,
final CallContext callContext) throws PaymentApiException {
checkNotNullParameter(account, "account");
@@ -387,7 +383,7 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
logEnterAPICall(log, transactionType, account, null, paymentId, null, null, null, null, paymentTransactionExternalKey, null, null);
final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
- payment = paymentProcessor.createVoid(IS_API_PAYMENT, NULL_ATTEMPT_ID, account, paymentId, paymentTransactionExternalKey,
+ payment = paymentProcessor.createVoid(IS_API_PAYMENT, NULL_ATTEMPT_ID, account, paymentId, effectiveDate, paymentTransactionExternalKey,
null, SHOULD_LOCK_ACCOUNT, properties, callContext, internalCallContext);
paymentTransaction = findPaymentTransaction(payment, paymentTransactionExternalKey);
@@ -415,10 +411,10 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
}
@Override
- public Payment createVoidWithPaymentControl(final Account account, final UUID paymentId, final String paymentTransactionExternalKey, final Iterable<PluginProperty> properties, final PaymentOptions paymentOptions, final CallContext callContext) throws PaymentApiException {
+ public Payment createVoidWithPaymentControl(final Account account, final UUID paymentId, @Nullable final DateTime effectiveDate, final String paymentTransactionExternalKey, final Iterable<PluginProperty> properties, final PaymentOptions paymentOptions, final CallContext callContext) throws PaymentApiException {
final List<String> paymentControlPluginNames = toPaymentControlPluginNames(paymentOptions, callContext);
if (paymentControlPluginNames.isEmpty()) {
- return createVoid(account, paymentId, paymentTransactionExternalKey, properties, callContext);
+ return createVoid(account, paymentId, effectiveDate, paymentTransactionExternalKey, properties, callContext);
}
checkNotNullParameter(account, "account");
@@ -434,7 +430,7 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
logEnterAPICall(log, transactionType, account, null, paymentId, null, null, null, null, paymentTransactionExternalKey, null, paymentControlPluginNames);
final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
- payment = pluginControlPaymentProcessor.createVoid(IS_API_PAYMENT, account, paymentId, paymentTransactionExternalKey,
+ payment = pluginControlPaymentProcessor.createVoid(IS_API_PAYMENT, account, paymentId, effectiveDate, paymentTransactionExternalKey,
properties, paymentControlPluginNames, callContext, internalCallContext);
paymentTransaction = findPaymentTransaction(payment, paymentTransactionExternalKey);
@@ -461,7 +457,7 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
}
@Override
- public Payment createRefund(final Account account, @Nullable final UUID paymentId, final BigDecimal amount, final Currency currency, @Nullable final String paymentTransactionExternalKey, final Iterable<PluginProperty> properties,
+ public Payment createRefund(final Account account, @Nullable final UUID paymentId, final BigDecimal amount, final Currency currency, @Nullable final DateTime effectiveDate, @Nullable final String paymentTransactionExternalKey, final Iterable<PluginProperty> properties,
final CallContext callContext) throws PaymentApiException {
checkNotNullParameter(account, "account");
if (paymentId == null) {
@@ -479,7 +475,7 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
logEnterAPICall(log, transactionType, account, null, paymentId, null, amount, currency, null, paymentTransactionExternalKey, null, null);
final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
- payment = paymentProcessor.createRefund(IS_API_PAYMENT, NULL_ATTEMPT_ID, account, paymentId, amount, currency, paymentTransactionExternalKey,
+ payment = paymentProcessor.createRefund(IS_API_PAYMENT, NULL_ATTEMPT_ID, account, paymentId, amount, currency, effectiveDate, paymentTransactionExternalKey,
null, SHOULD_LOCK_ACCOUNT, properties, callContext, internalCallContext);
paymentTransaction = findPaymentTransaction(payment, paymentTransactionExternalKey);
@@ -506,11 +502,11 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
}
@Override
- public Payment createRefundWithPaymentControl(final Account account, @Nullable final UUID paymentId, @Nullable final BigDecimal amount, final Currency currency, final String paymentTransactionExternalKey, final Iterable<PluginProperty> properties,
+ public Payment createRefundWithPaymentControl(final Account account, @Nullable final UUID paymentId, @Nullable final BigDecimal amount, final Currency currency, @Nullable final DateTime effectiveDate, final String paymentTransactionExternalKey, final Iterable<PluginProperty> properties,
final PaymentOptions paymentOptions, final CallContext callContext) throws PaymentApiException {
final List<String> paymentControlPluginNames = toPaymentControlPluginNames(paymentOptions, callContext);
if (paymentControlPluginNames.isEmpty()) {
- return createRefund(account, paymentId, amount, currency, paymentTransactionExternalKey, properties, callContext);
+ return createRefund(account, paymentId, amount, currency, effectiveDate, paymentTransactionExternalKey, properties, callContext);
}
checkNotNullParameter(account, "account");
@@ -529,7 +525,7 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
logEnterAPICall(log, transactionType, account, null, paymentId, null, amount, currency, null, paymentTransactionExternalKey, null, paymentControlPluginNames);
final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
- payment = pluginControlPaymentProcessor.createRefund(IS_API_PAYMENT, account, paymentId, amount, currency, paymentTransactionExternalKey,
+ payment = pluginControlPaymentProcessor.createRefund(IS_API_PAYMENT, account, paymentId, amount, currency, effectiveDate, paymentTransactionExternalKey,
properties, paymentControlPluginNames, callContext, internalCallContext);
paymentTransaction = findPaymentTransaction(payment, paymentTransactionExternalKey);
@@ -556,7 +552,7 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
}
@Override
- public Payment createCredit(final Account account, final UUID paymentMethodId, @Nullable final UUID paymentId, final BigDecimal amount, final Currency currency,
+ public Payment createCredit(final Account account, final UUID paymentMethodId, @Nullable final UUID paymentId, final BigDecimal amount, final Currency currency, @Nullable final DateTime effectiveDate,
@Nullable final String paymentExternalKey, @Nullable final String paymentTransactionExternalKey,
final Iterable<PluginProperty> properties, final CallContext callContext) throws PaymentApiException {
checkNotNullParameter(account, "account");
@@ -580,7 +576,7 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
paymentMethodId :
paymentMethodProcessor.createOrGetExternalPaymentMethod(UUIDs.randomUUID().toString(), account, properties, callContext, internalCallContext);
- payment = paymentProcessor.createCredit(IS_API_PAYMENT, NULL_ATTEMPT_ID, account, nonNullPaymentMethodId, paymentId, amount, currency, paymentExternalKey, paymentTransactionExternalKey,
+ payment = paymentProcessor.createCredit(IS_API_PAYMENT, NULL_ATTEMPT_ID, account, nonNullPaymentMethodId, paymentId, amount, currency, effectiveDate, paymentExternalKey, paymentTransactionExternalKey,
null, null, SHOULD_LOCK_ACCOUNT, properties, callContext, internalCallContext);
paymentTransaction = findPaymentTransaction(payment, paymentTransactionExternalKey);
@@ -607,12 +603,12 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
}
@Override
- public Payment createCreditWithPaymentControl(final Account account, final UUID paymentMethodId, @Nullable final UUID paymentId, final BigDecimal amount, final Currency currency,
+ public Payment createCreditWithPaymentControl(final Account account, @Nullable final UUID paymentMethodId, @Nullable final UUID paymentId, final BigDecimal amount, final Currency currency, @Nullable final DateTime effectiveDate,
@Nullable final String paymentExternalKey, @Nullable final String paymentTransactionExternalKey,
final Iterable<PluginProperty> properties, final PaymentOptions paymentOptions, final CallContext callContext) throws PaymentApiException {
final List<String> paymentControlPluginNames = toPaymentControlPluginNames(paymentOptions, callContext);
if (paymentControlPluginNames.isEmpty()) {
- return createCredit(account, paymentMethodId, paymentId, amount, currency, paymentExternalKey, paymentTransactionExternalKey, properties, callContext);
+ return createCredit(account, paymentMethodId, paymentId, amount, currency, effectiveDate, paymentExternalKey, paymentTransactionExternalKey, properties, callContext);
}
checkNotNullParameter(account, "account");
@@ -632,14 +628,11 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
- // TODO validate if the code is located properly here
- // The code should understand that the external payment method needs to be created
- // if it doesn't exist yet and trigger a CREDIT using the (built-in) external payment plugin.
- final UUID nonNullPaymentMethodId = (paymentMethodId != null) ?
- paymentMethodId :
- paymentMethodProcessor.createOrGetExternalPaymentMethod(UUIDs.randomUUID().toString(), account, properties, callContext, internalCallContext);
+ final UUID resolvedPaymentMethodId = (paymentMethodId == null && paymentOptions.isExternalPayment()) ?
+ paymentMethodProcessor.createOrGetExternalPaymentMethod(UUIDs.randomUUID().toString(), account, properties, callContext, internalCallContext) :
+ paymentMethodId;
- payment = pluginControlPaymentProcessor.createCredit(IS_API_PAYMENT, account, nonNullPaymentMethodId, paymentId, amount, currency, paymentExternalKey, paymentTransactionExternalKey,
+ payment = pluginControlPaymentProcessor.createCredit(IS_API_PAYMENT, account, resolvedPaymentMethodId, paymentId, amount, currency, effectiveDate, paymentExternalKey, paymentTransactionExternalKey,
properties, paymentControlPluginNames, callContext, internalCallContext);
paymentTransaction = findPaymentTransaction(payment, paymentTransactionExternalKey);
@@ -769,7 +762,7 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
}
@Override
- public Payment createChargeback(final Account account, final UUID paymentId, final BigDecimal amount, final Currency currency, final String paymentTransactionExternalKey, final CallContext callContext) throws PaymentApiException {
+ public Payment createChargeback(final Account account, final UUID paymentId, final BigDecimal amount, final Currency currency, @Nullable final DateTime effectiveDate, final String paymentTransactionExternalKey, final CallContext callContext) throws PaymentApiException {
checkNotNullParameter(account, "account");
checkNotNullParameter(amount, "amount");
checkNotNullParameter(currency, "currency");
@@ -784,7 +777,7 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
logEnterAPICall(log, transactionType, account, null, paymentId, null, amount, currency, null, paymentTransactionExternalKey, null, null);
final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
- payment = paymentProcessor.createChargeback(IS_API_PAYMENT, NULL_ATTEMPT_ID, account, paymentId, paymentTransactionExternalKey, amount, currency, null, true,
+ payment = paymentProcessor.createChargeback(IS_API_PAYMENT, NULL_ATTEMPT_ID, account, paymentId, paymentTransactionExternalKey, amount, currency, effectiveDate, null, true,
callContext, internalCallContext);
paymentTransaction = findPaymentTransaction(payment, paymentTransactionExternalKey);
@@ -811,10 +804,10 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
}
@Override
- public Payment createChargebackWithPaymentControl(final Account account, final UUID paymentId, final BigDecimal amount, final Currency currency, final String paymentTransactionExternalKey, final PaymentOptions paymentOptions, final CallContext callContext) throws PaymentApiException {
+ public Payment createChargebackWithPaymentControl(final Account account, final UUID paymentId, final BigDecimal amount, final Currency currency, @Nullable final DateTime effectiveDate, final String paymentTransactionExternalKey, final PaymentOptions paymentOptions, final CallContext callContext) throws PaymentApiException {
final List<String> paymentControlPluginNames = toPaymentControlPluginNames(paymentOptions, callContext);
if (paymentControlPluginNames.isEmpty()) {
- return createChargeback(account, paymentId, amount, currency, paymentTransactionExternalKey, callContext);
+ return createChargeback(account, paymentId, amount, currency, effectiveDate, paymentTransactionExternalKey, callContext);
}
checkNotNullParameter(account, "account");
@@ -830,7 +823,7 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
logEnterAPICall(log, transactionType, account, null, paymentId, null, amount, currency, null, paymentTransactionExternalKey, null, paymentControlPluginNames);
final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
- payment = pluginControlPaymentProcessor.createChargeback(IS_API_PAYMENT, account, paymentId, paymentTransactionExternalKey, amount, currency,
+ payment = pluginControlPaymentProcessor.createChargeback(IS_API_PAYMENT, account, paymentId, paymentTransactionExternalKey, amount, currency, effectiveDate,
paymentControlPluginNames, callContext, internalCallContext);
paymentTransaction = findPaymentTransaction(payment, paymentTransactionExternalKey);
@@ -857,7 +850,7 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
}
@Override
- public Payment createChargebackReversal(final Account account, final UUID paymentId, final String paymentTransactionExternalKey, final CallContext callContext) throws PaymentApiException {
+ public Payment createChargebackReversal(final Account account, final UUID paymentId, @Nullable final DateTime effectiveDate, final String paymentTransactionExternalKey, final CallContext callContext) throws PaymentApiException {
checkNotNullParameter(account, "account");
checkNotNullParameter(paymentId, "paymentId");
checkExternalKeyLength(paymentTransactionExternalKey);
@@ -870,7 +863,7 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
logEnterAPICall(log, transactionType, account, null, paymentId, null, null, null, null, paymentTransactionExternalKey, null, null);
final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
- payment = paymentProcessor.createChargebackReversal(IS_API_PAYMENT, NULL_ATTEMPT_ID, account, paymentId, paymentTransactionExternalKey, null, null, null, true, callContext, internalCallContext);
+ payment = paymentProcessor.createChargebackReversal(IS_API_PAYMENT, NULL_ATTEMPT_ID, account, paymentId, paymentTransactionExternalKey, null, null, effectiveDate, null, true, callContext, internalCallContext);
paymentTransaction = findPaymentTransaction(payment, paymentTransactionExternalKey);
@@ -896,10 +889,10 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
}
@Override
- public Payment createChargebackReversalWithPaymentControl(final Account account, final UUID paymentId, final String paymentTransactionExternalKey, final PaymentOptions paymentOptions, final CallContext callContext) throws PaymentApiException {
+ public Payment createChargebackReversalWithPaymentControl(final Account account, final UUID paymentId, @Nullable final DateTime effectiveDate, final String paymentTransactionExternalKey, final PaymentOptions paymentOptions, final CallContext callContext) throws PaymentApiException {
final List<String> paymentControlPluginNames = toPaymentControlPluginNames(paymentOptions, callContext);
if (paymentControlPluginNames.isEmpty()) {
- return createChargebackReversal(account, paymentId, paymentTransactionExternalKey, callContext);
+ return createChargebackReversal(account, paymentId, effectiveDate, paymentTransactionExternalKey, callContext);
}
checkNotNullParameter(account, "account");
@@ -914,7 +907,7 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
logEnterAPICall(log, transactionType, account, null, paymentId, null, null, null, null, paymentTransactionExternalKey, null, paymentControlPluginNames);
final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
- payment = pluginControlPaymentProcessor.createChargebackReversal(IS_API_PAYMENT, account, paymentId, paymentTransactionExternalKey, paymentControlPluginNames, callContext, internalCallContext);
+ payment = pluginControlPaymentProcessor.createChargebackReversal(IS_API_PAYMENT, account, paymentId, effectiveDate, paymentTransactionExternalKey, paymentControlPluginNames, callContext, internalCallContext);
// See https://github.com/killbill/killbill/issues/552
paymentTransaction = Iterables.<PaymentTransaction>find(Lists.<PaymentTransaction>reverse(payment.getTransactions()),
@@ -999,9 +992,20 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
}
@Override
- public List<PaymentMethod> getAccountPaymentMethods(final UUID accountId, final boolean withPluginInfo, final Iterable<PluginProperty> properties, final TenantContext context)
+ public UUID addPaymentMethodWithPaymentControl(final Account account, final String paymentMethodExternalKey, final String pluginName, final boolean setDefault, final PaymentMethodPlugin paymentMethodInfo, final Iterable<PluginProperty> properties, final PaymentOptions paymentOptions, final CallContext context) throws PaymentApiException {
+ final List<String> paymentControlPluginNames = toPaymentControlPluginNames(paymentOptions, context);
+ if (paymentControlPluginNames.isEmpty()) {
+ return addPaymentMethod(account, paymentMethodExternalKey, pluginName, setDefault, paymentMethodInfo, properties, context);
+ }
+
+ return paymentMethodProcessor.addPaymentMethodWithControl(paymentMethodExternalKey, pluginName, account, setDefault, paymentMethodInfo, properties,
+ paymentControlPluginNames, context, internalCallContextFactory.createInternalCallContext(account.getId(), context));
+ }
+
+ @Override
+ public List<PaymentMethod> getAccountPaymentMethods(final UUID accountId, final boolean includedInactive, final boolean withPluginInfo, final Iterable<PluginProperty> properties, final TenantContext context)
throws PaymentApiException {
- return paymentMethodProcessor.getPaymentMethods(withPluginInfo, properties, context, internalCallContextFactory.createInternalTenantContext(accountId, context));
+ return paymentMethodProcessor.getPaymentMethods(includedInactive, withPluginInfo, properties, context, internalCallContextFactory.createInternalTenantContext(accountId, context));
}
@Override
diff --git a/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentGatewayApi.java b/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentGatewayApi.java
index 26bce39..55cddc7 100644
--- a/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentGatewayApi.java
+++ b/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentGatewayApi.java
@@ -147,6 +147,7 @@ public class DefaultPaymentGatewayApi extends DefaultApiBase implements PaymentG
null,
null,
null,
+ null,
PaymentApiType.HPP,
null,
HPPType.BUILD_FORM_DESCRIPTOR,
@@ -174,6 +175,7 @@ public class DefaultPaymentGatewayApi extends DefaultApiBase implements PaymentG
null,
null,
null,
+ null,
PaymentApiType.HPP,
null,
HPPType.BUILD_FORM_DESCRIPTOR,
@@ -194,6 +196,7 @@ public class DefaultPaymentGatewayApi extends DefaultApiBase implements PaymentG
null,
null,
null,
+ null,
PaymentApiType.HPP,
null,
HPPType.BUILD_FORM_DESCRIPTOR,
diff --git a/payment/src/main/java/org/killbill/billing/payment/bus/PaymentBusEventHandler.java b/payment/src/main/java/org/killbill/billing/payment/bus/PaymentBusEventHandler.java
index 30f5c13..99973ac 100644
--- a/payment/src/main/java/org/killbill/billing/payment/bus/PaymentBusEventHandler.java
+++ b/payment/src/main/java/org/killbill/billing/payment/bus/PaymentBusEventHandler.java
@@ -123,7 +123,7 @@ public class PaymentBusEventHandler {
null,
paymentControlPluginNames);
- payment = pluginControlPaymentProcessor.createPurchase(false, account, account.getPaymentMethodId(), null, amountToBePaid, account.getCurrency(), null, null, properties, paymentControlPluginNames, callContext, internalContext);
+ payment = pluginControlPaymentProcessor.createPurchase(false, account, account.getPaymentMethodId(), null, amountToBePaid, account.getCurrency(), null, null, null, properties, paymentControlPluginNames, callContext, internalContext);
paymentTransaction = payment.getTransactions().get(payment.getTransactions().size() - 1);
} catch (final AccountApiException e) {
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/janitor/CompletionTaskBase.java b/payment/src/main/java/org/killbill/billing/payment/core/janitor/CompletionTaskBase.java
index 409c770..29893e5 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/janitor/CompletionTaskBase.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/janitor/CompletionTaskBase.java
@@ -18,6 +18,7 @@
package org.killbill.billing.payment.core.janitor;
import java.io.IOException;
+import java.util.Iterator;
import org.killbill.billing.account.api.AccountApiException;
import org.killbill.billing.account.api.AccountInternalApi;
@@ -86,16 +87,25 @@ abstract class CompletionTaskBase<T> implements Runnable {
log.info("Janitor was requested to stop");
return;
}
- final Iterable<T> items = getItemsForIteration();
- for (final T item : items) {
- if (isStopped) {
- log.info("Janitor was requested to stop");
- return;
+
+ final Iterator<T> iterator = getItemsForIteration().iterator();
+ try {
+ while (iterator.hasNext()) {
+ final T item = iterator.next();
+ if (isStopped) {
+ log.info("Janitor was requested to stop");
+ return;
+ }
+ try {
+ doIteration(item);
+ } catch (final Exception e) {
+ log.warn(e.getMessage());
+ }
}
- try {
- doIteration(item);
- } catch (final IllegalStateException e) {
- log.warn(e.getMessage());
+ } finally {
+ // In case the loop stops early, make sure to close the underlying DB connection
+ while (iterator.hasNext()) {
+ iterator.next();
}
}
}
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/janitor/IncompletePaymentAttemptTask.java b/payment/src/main/java/org/killbill/billing/payment/core/janitor/IncompletePaymentAttemptTask.java
index 400b332..0467bed 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/janitor/IncompletePaymentAttemptTask.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/janitor/IncompletePaymentAttemptTask.java
@@ -143,6 +143,7 @@ public class IncompletePaymentAttemptTask extends CompletionTaskBase<PaymentAtte
attempt.getPaymentMethodId(),
transaction.getAmount(),
transaction.getCurrency(),
+ null,
PluginPropertySerializer.deserialize(attempt.getPluginProperties()),
internalCallContext,
callContext);
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/janitor/IncompletePaymentTransactionTask.java b/payment/src/main/java/org/killbill/billing/payment/core/janitor/IncompletePaymentTransactionTask.java
index 88fd895..6887cc5 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/janitor/IncompletePaymentTransactionTask.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/janitor/IncompletePaymentTransactionTask.java
@@ -175,7 +175,8 @@ public class IncompletePaymentTransactionTask extends CompletionTaskBase<Payment
final Boolean result = doJanitorOperationWithAccountLock(new JanitorIterationCallback() {
@Override
public Boolean doIteration() {
- return updatePaymentAndTransactionInternal(payment, null, null, paymentTransaction, paymentTransactionInfoPlugin, internalTenantContext);
+ final PaymentTransactionModelDao refreshedPaymentTransaction = paymentDao.getPaymentTransaction(paymentTransaction.getId(), internalTenantContext);
+ return updatePaymentAndTransactionInternal(payment, null, null, refreshedPaymentTransaction, paymentTransactionInfoPlugin, internalTenantContext);
}
}, internalTenantContext);
return result != null && result;
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/PaymentMethodProcessor.java b/payment/src/main/java/org/killbill/billing/payment/core/PaymentMethodProcessor.java
index a0203d1..b8bcf61 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/PaymentMethodProcessor.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/PaymentMethodProcessor.java
@@ -23,6 +23,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
+import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nullable;
@@ -33,12 +34,17 @@ import org.killbill.billing.account.api.AccountApiException;
import org.killbill.billing.account.api.AccountInternalApi;
import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.control.plugin.api.PaymentApiType;
+import org.killbill.billing.control.plugin.api.PaymentControlApiException;
+import org.killbill.billing.control.plugin.api.PriorPaymentControlResult;
import org.killbill.billing.invoice.api.InvoiceInternalApi;
import org.killbill.billing.payment.api.DefaultPaymentMethod;
import org.killbill.billing.payment.api.PaymentApiException;
import org.killbill.billing.payment.api.PaymentMethod;
import org.killbill.billing.payment.api.PaymentMethodPlugin;
import org.killbill.billing.payment.api.PluginProperty;
+import org.killbill.billing.payment.core.sm.control.ControlPluginRunner;
+import org.killbill.billing.payment.core.sm.control.PaymentControlApiAbortException;
import org.killbill.billing.payment.dao.PaymentDao;
import org.killbill.billing.payment.dao.PaymentMethodModelDao;
import org.killbill.billing.payment.dispatcher.PluginDispatcher;
@@ -50,6 +56,7 @@ import org.killbill.billing.payment.provider.DefaultNoOpPaymentMethodPlugin;
import org.killbill.billing.payment.provider.DefaultPaymentMethodInfoPlugin;
import org.killbill.billing.payment.provider.ExternalPaymentProviderPlugin;
import org.killbill.billing.tag.TagInternalApi;
+import org.killbill.billing.util.PluginProperties;
import org.killbill.billing.util.UUIDs;
import org.killbill.billing.util.callcontext.CallContext;
import org.killbill.billing.util.callcontext.InternalCallContextFactory;
@@ -65,6 +72,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Function;
+import com.google.common.base.Joiner;
import com.google.common.base.MoreObjects;
import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
@@ -80,9 +88,12 @@ import static org.killbill.billing.util.entity.dao.DefaultPaginationHelper.getEn
public class PaymentMethodProcessor extends ProcessorBase {
private static final Logger log = LoggerFactory.getLogger(PaymentMethodProcessor.class);
+ private static final Joiner JOINER = Joiner.on(", ");
private final PluginDispatcher<UUID> uuidPluginNotificationDispatcher;
+ private final ControlPluginRunner controlPluginRunner;
+
private final PaymentConfig paymentConfig;
@Inject
@@ -94,11 +105,13 @@ public class PaymentMethodProcessor extends ProcessorBase {
final GlobalLocker locker,
final PaymentConfig paymentConfig,
final PaymentExecutors executors,
+ final ControlPluginRunner controlPluginRunner,
final InternalCallContextFactory internalCallContextFactory,
final Clock clock) {
super(paymentPluginServiceRegistration, accountInternalApi, paymentDao, tagUserApi, locker, internalCallContextFactory, invoiceApi, clock);
final long paymentPluginTimeoutSec = TimeUnit.SECONDS.convert(paymentConfig.getPaymentPluginTimeout().getPeriod(), paymentConfig.getPaymentPluginTimeout().getUnit());
this.paymentConfig = paymentConfig;
+ this.controlPluginRunner = controlPluginRunner;
this.uuidPluginNotificationDispatcher = new PluginDispatcher<UUID>(paymentPluginTimeoutSec, executors);
}
@@ -145,7 +158,6 @@ public class PaymentMethodProcessor extends ProcessorBase {
return PluginDispatcher.createPluginDispatcherReturnType(pm.getId());
}
-
private void validateUniqueExternalPaymentMethod(final UUID accountId, final String pluginName) throws PaymentApiException {
if (ExternalPaymentProviderPlugin.PLUGIN_NAME.equals(pluginName)) {
final List<PaymentMethodModelDao> accountPaymentMethods = paymentDao.getPaymentMethods(context);
@@ -163,6 +175,119 @@ public class PaymentMethodProcessor extends ProcessorBase {
uuidPluginNotificationDispatcher);
}
+ public UUID addPaymentMethodWithControl(final String paymentMethodExternalKey, final String paymentPluginServiceName, final Account account,
+ final boolean setDefault, final PaymentMethodPlugin paymentMethodProps, final Iterable<PluginProperty> properties,
+ final List<String> paymentControlPluginNames, final CallContext callContext, final InternalCallContext context) throws PaymentApiException {
+ final Iterable<PluginProperty> mergedProperties = PluginProperties.merge(paymentMethodProps.getProperties(), properties);
+ return executeWithPaymentMethodControl(paymentPluginServiceName, account, mergedProperties, paymentControlPluginNames, callContext, uuidPluginNotificationDispatcher, new WithPaymentMethodControlCallback<UUID>() {
+ @Override
+ public UUID doPaymentMethodApiOperation(final String adjustedPaymentPluginServiceName, final Iterable<PluginProperty> adjustedPluginProperties) throws PaymentApiException {
+ if (adjustedPaymentPluginServiceName == null) {
+ return addPaymentMethod(paymentMethodExternalKey, paymentPluginServiceName, account, setDefault, paymentMethodProps, properties, callContext, context);
+ } else {
+ return addPaymentMethod(paymentMethodExternalKey, adjustedPaymentPluginServiceName, account, setDefault, paymentMethodProps, properties, callContext, context);
+ }
+ }
+ });
+ }
+
+
+ private interface WithPaymentMethodControlCallback<T> {
+ T doPaymentMethodApiOperation(final String adjustedPluginName, final Iterable<PluginProperty> adjustedPluginProperties) throws PaymentApiException;
+ }
+
+ private <T> T executeWithPaymentMethodControl(final String paymentPluginServiceName,
+ final Account account,
+ final Iterable<PluginProperty> properties,
+ final List<String> paymentControlPluginNames,
+ final CallContext callContext,
+ final PluginDispatcher<T> pluginDispatcher,
+ final WithPaymentMethodControlCallback<T> callback) throws PaymentApiException {
+
+ return dispatchWithExceptionHandling(account,
+ JOINER.join(paymentControlPluginNames),
+ new Callable<PluginDispatcherReturnType<T>>() {
+ @Override
+ public PluginDispatcherReturnType<T> call() throws Exception {
+ final PriorPaymentControlResult priorCallResult;
+ try {
+ priorCallResult = controlPluginRunner.executePluginPriorCalls(account,
+ null,
+ paymentPluginServiceName,
+ null,
+ null,
+ null,
+ null,
+ null,
+ PaymentApiType.PAYMENT_METHOD,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ true,
+ paymentControlPluginNames,
+ properties,
+ callContext);
+
+ } catch (final PaymentControlApiAbortException e) {
+ throw new PaymentApiException(ErrorCode.PAYMENT_PLUGIN_API_ABORTED, e.getPluginName());
+ } catch (final PaymentControlApiException e) {
+ throw new PaymentApiException(e, ErrorCode.PAYMENT_PLUGIN_EXCEPTION, e);
+ }
+
+ try {
+ final T result = callback.doPaymentMethodApiOperation(priorCallResult.getAdjustedPluginName(), priorCallResult.getAdjustedPluginProperties());
+ controlPluginRunner.executePluginOnSuccessCalls(account,
+ null,
+ priorCallResult.getAdjustedPluginName(),
+ null,
+ null,
+ null,
+ null,
+ null,
+ PaymentApiType.PAYMENT_METHOD,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ true,
+ paymentControlPluginNames,
+ priorCallResult.getAdjustedPluginProperties(),
+ callContext);
+ return PluginDispatcher.createPluginDispatcherReturnType(result);
+ } catch (final PaymentApiException e) {
+ controlPluginRunner.executePluginOnFailureCalls(account,
+ null,
+ priorCallResult.getAdjustedPluginName(),
+ null,
+ null,
+ null,
+ null,
+ null,
+ PaymentApiType.PAYMENT_METHOD,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ true,
+ paymentControlPluginNames,
+ priorCallResult.getAdjustedPluginProperties(),
+ callContext);
+ throw e;
+ }
+ }
+ },
+ pluginDispatcher);
+ }
+
+
+
private String retrieveActualPaymentMethodExternalKey(final Account account, final PaymentMethod pm, final PaymentPluginApi pluginApi, final Iterable<PluginProperty> properties, final TenantContext callContext, final InternalCallContext context) {
// If the user specified an external key, use it
if (pm.getExternalKey() != null) {
@@ -197,12 +322,14 @@ public class PaymentMethodProcessor extends ProcessorBase {
}
}
- public List<PaymentMethod> getPaymentMethods(final boolean withPluginInfo, final Iterable<PluginProperty> properties, final InternalTenantContext context) throws PaymentApiException {
- return getPaymentMethods(withPluginInfo, properties, buildTenantContext(context), context);
+ public List<PaymentMethod> getPaymentMethods(final boolean includedInactive, final boolean withPluginInfo, final Iterable<PluginProperty> properties, final InternalTenantContext context) throws PaymentApiException {
+ return getPaymentMethods(includedInactive, withPluginInfo, properties, buildTenantContext(context), context);
}
- public List<PaymentMethod> getPaymentMethods(final boolean withPluginInfo, final Iterable<PluginProperty> properties, final TenantContext tenantContext, final InternalTenantContext context) throws PaymentApiException {
- final List<PaymentMethodModelDao> paymentMethodModels = paymentDao.getPaymentMethods(context);
+ public List<PaymentMethod> getPaymentMethods(final boolean includedInactive, final boolean withPluginInfo, final Iterable<PluginProperty> properties, final TenantContext tenantContext, final InternalTenantContext context) throws PaymentApiException {
+ final List<PaymentMethodModelDao> paymentMethodModels = includedInactive ?
+ paymentDao.getPaymentMethodsIncludedDeleted(context) :
+ paymentDao.getPaymentMethods(context);
if (paymentMethodModels.isEmpty()) {
return Collections.emptyList();
}
@@ -228,7 +355,7 @@ public class PaymentMethodProcessor extends ProcessorBase {
final PaymentPluginApi pluginApi = getPaymentPluginApi(paymentMethodModelDao.getPluginName());
paymentMethodPlugin = pluginApi.getPaymentMethodDetail(paymentMethodModelDao.getAccountId(), paymentMethodModelDao.getId(), properties, tenantContext);
} catch (final PaymentPluginApiException e) {
- throw new PaymentApiException(ErrorCode.PAYMENT_GET_PAYMENT_METHODS, paymentMethodModelDao.getAccountId(), paymentMethodModelDao.getId());
+ throw new PaymentApiException(e, ErrorCode.PAYMENT_GET_PAYMENT_METHODS, paymentMethodModelDao.getAccountId(), paymentMethodModelDao.getId());
}
} else {
paymentMethodPlugin = null;
@@ -357,8 +484,8 @@ public class PaymentMethodProcessor extends ProcessorBase {
);
}
- public PaymentMethod getExternalPaymentMethod(final Iterable<PluginProperty> properties, final TenantContext tenantContext, final InternalTenantContext context) throws PaymentApiException {
- final List<PaymentMethod> paymentMethods = getPaymentMethods(false, properties, tenantContext, context);
+ private PaymentMethod getExternalPaymentMethod(final Iterable<PluginProperty> properties, final TenantContext tenantContext, final InternalTenantContext context) throws PaymentApiException {
+ final List<PaymentMethod> paymentMethods = getPaymentMethods(false, false, properties, tenantContext, context);
for (final PaymentMethod paymentMethod : paymentMethods) {
if (ExternalPaymentProviderPlugin.PLUGIN_NAME.equals(paymentMethod.getPluginName())) {
return paymentMethod;
@@ -506,8 +633,8 @@ public class PaymentMethodProcessor extends ProcessorBase {
for (final PaymentMethodInfoPlugin cur : pluginPms) {
// If the kbPaymentId is NULL, the plugin does not know about it, so we create a new UUID
final UUID paymentMethodId = cur.getPaymentMethodId() != null ? cur.getPaymentMethodId() : UUIDs.randomUUID();
- // TODO paymentMethod externalKey seems broken here.
- final PaymentMethod input = new DefaultPaymentMethod(paymentMethodId, paymentMethodId.toString(), account.getId(), pluginName);
+ final String externalKey = cur.getExternalPaymentMethodId() != null ? cur.getExternalPaymentMethodId() : paymentMethodId.toString();
+ final PaymentMethod input = new DefaultPaymentMethod(paymentMethodId, externalKey, account.getId(), pluginName);
final PaymentMethodModelDao pmModel = new PaymentMethodModelDao(input.getId(), input.getExternalKey(), input.getCreatedDate(), input.getUpdatedDate(),
input.getAccountId(), input.getPluginName(), input.isActive());
finalPaymentMethods.add(pmModel);
@@ -553,18 +680,13 @@ public class PaymentMethodProcessor extends ProcessorBase {
private void updateDefaultPaymentMethodIfNeeded(final String pluginName, final Account account, @Nullable final UUID defaultPluginPaymentMethodId, final InternalCallContext context) throws PaymentApiException, AccountApiException {
- // If the plugin does not have a default payment gateway, we keep the current default payment method in KB account as it is.
- if (defaultPluginPaymentMethodId == null) {
- return;
- }
-
// Some gateways have the concept of default payment methods. Kill Bill has also its own default payment method
// and is authoritative on this matter. However, if the default payment method is associated with a given plugin,
// and if the default payment method in that plugin has changed, we will reflect this change in Kill Bill as well.
boolean shouldUpdateDefaultPaymentMethod = true;
if (account.getPaymentMethodId() != null) {
- final PaymentMethodModelDao currentDefaultPaymentMethod = getPaymentMethodById(account.getPaymentMethodId(), false, context);
+ final PaymentMethodModelDao currentDefaultPaymentMethod = getPaymentMethodById(account.getPaymentMethodId(), true, context);
shouldUpdateDefaultPaymentMethod = pluginName.equals(currentDefaultPaymentMethod.getPluginName());
}
if (shouldUpdateDefaultPaymentMethod) {
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/PaymentPluginServiceRegistration.java b/payment/src/main/java/org/killbill/billing/payment/core/PaymentPluginServiceRegistration.java
index 016be05..f64e772 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/PaymentPluginServiceRegistration.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/PaymentPluginServiceRegistration.java
@@ -20,6 +20,7 @@ package org.killbill.billing.payment.core;
import java.util.Set;
import java.util.UUID;
+import javax.annotation.Nullable;
import javax.inject.Inject;
import org.killbill.billing.ErrorCode;
@@ -45,9 +46,11 @@ public class PaymentPluginServiceRegistration {
return pluginRegistry.getAllServices();
}
- public PaymentMethodModelDao getPaymentMethodById(final UUID paymentMethodId, final boolean includedDeleted, final InternalTenantContext context) throws PaymentApiException {
+ public PaymentMethodModelDao getPaymentMethodById(@Nullable final UUID paymentMethodId, final boolean includedDeleted, final InternalTenantContext context) throws PaymentApiException {
final PaymentMethodModelDao paymentMethodModel;
- if (includedDeleted) {
+ if (paymentMethodId == null) {
+ paymentMethodModel = null;
+ } else if (includedDeleted) {
paymentMethodModel = paymentDao.getPaymentMethodIncludedDeleted(paymentMethodId, context);
} else {
paymentMethodModel = paymentDao.getPaymentMethod(paymentMethodId, context);
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 8cc235e..4bd018a 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
@@ -32,6 +32,7 @@ import java.util.UUID;
import javax.annotation.Nullable;
import javax.inject.Inject;
+import org.joda.time.DateTime;
import org.killbill.automaton.OperationResult;
import org.killbill.billing.ErrorCode;
import org.killbill.billing.account.api.Account;
@@ -129,49 +130,49 @@ public class PaymentProcessor extends ProcessorBase {
this.notificationQueueService = notificationQueueService;
}
- public Payment createAuthorization(final boolean isApiPayment, @Nullable final UUID attemptId, final Account account, @Nullable final UUID paymentMethodId, @Nullable final UUID paymentId, final BigDecimal amount, final Currency currency,
+ public Payment createAuthorization(final boolean isApiPayment, @Nullable final UUID attemptId, final Account account, @Nullable final UUID paymentMethodId, @Nullable final UUID paymentId, final BigDecimal amount, final Currency currency, @Nullable final DateTime effectiveDate,
@Nullable final String paymentExternalKey, @Nullable final String paymentTransactionExternalKey, @Nullable final UUID paymentIdForNewPayment, @Nullable final UUID paymentTransactionIdForNewPaymentTransaction, final boolean shouldLockAccountAndDispatch,
final Iterable<PluginProperty> properties, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
- return performOperation(isApiPayment, attemptId, TransactionType.AUTHORIZE, account, paymentMethodId, paymentId, null, amount, currency, paymentExternalKey, paymentTransactionExternalKey, paymentIdForNewPayment, paymentTransactionIdForNewPaymentTransaction, shouldLockAccountAndDispatch, null, properties, callContext, internalCallContext);
+ return performOperation(isApiPayment, attemptId, TransactionType.AUTHORIZE, account, paymentMethodId, paymentId, null, amount, currency, effectiveDate, paymentExternalKey, paymentTransactionExternalKey, paymentIdForNewPayment, paymentTransactionIdForNewPaymentTransaction, shouldLockAccountAndDispatch, null, properties, callContext, internalCallContext);
}
- public Payment createCapture(final boolean isApiPayment, @Nullable final UUID attemptId, final Account account, final UUID paymentId, final BigDecimal amount, final Currency currency,
+ public Payment createCapture(final boolean isApiPayment, @Nullable final UUID attemptId, final Account account, final UUID paymentId, final BigDecimal amount, final Currency currency, @Nullable final DateTime effectiveDate,
@Nullable final String paymentTransactionExternalKey, @Nullable final UUID paymentTransactionIdForNewPaymentTransaction, final boolean shouldLockAccountAndDispatch,
final Iterable<PluginProperty> properties, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
- return performOperation(isApiPayment, attemptId, TransactionType.CAPTURE, account, null, paymentId, null, amount, currency, null, paymentTransactionExternalKey, null, paymentTransactionIdForNewPaymentTransaction, shouldLockAccountAndDispatch, null, properties, callContext, internalCallContext);
+ return performOperation(isApiPayment, attemptId, TransactionType.CAPTURE, account, null, paymentId, null, amount, currency, effectiveDate, null, paymentTransactionExternalKey, null, paymentTransactionIdForNewPaymentTransaction, shouldLockAccountAndDispatch, null, properties, callContext, internalCallContext);
}
- public Payment createPurchase(final boolean isApiPayment, @Nullable final UUID attemptId, final Account account, @Nullable final UUID paymentMethodId, @Nullable final UUID paymentId, final BigDecimal amount, final Currency currency,
+ public Payment createPurchase(final boolean isApiPayment, @Nullable final UUID attemptId, final Account account, @Nullable final UUID paymentMethodId, @Nullable final UUID paymentId, final BigDecimal amount, final Currency currency, @Nullable final DateTime effectiveDate,
@Nullable final String paymentExternalKey, @Nullable final String paymentTransactionExternalKey, @Nullable final UUID paymentIdForNewPayment, @Nullable final UUID paymentTransactionIdForNewPaymentTransaction, final boolean shouldLockAccountAndDispatch,
final Iterable<PluginProperty> properties, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
- return performOperation(isApiPayment, attemptId, TransactionType.PURCHASE, account, paymentMethodId, paymentId, null, amount, currency, paymentExternalKey, paymentTransactionExternalKey, paymentIdForNewPayment, paymentTransactionIdForNewPaymentTransaction, shouldLockAccountAndDispatch, null, properties, callContext, internalCallContext);
+ return performOperation(isApiPayment, attemptId, TransactionType.PURCHASE, account, paymentMethodId, paymentId, null, amount, currency, effectiveDate, paymentExternalKey, paymentTransactionExternalKey, paymentIdForNewPayment, paymentTransactionIdForNewPaymentTransaction, shouldLockAccountAndDispatch, null, properties, callContext, internalCallContext);
}
- public Payment createVoid(final boolean isApiPayment, @Nullable final UUID attemptId, final Account account, final UUID paymentId, @Nullable final String paymentTransactionExternalKey, @Nullable final UUID paymentTransactionIdForNewPaymentTransaction, final boolean shouldLockAccountAndDispatch,
+ public Payment createVoid(final boolean isApiPayment, @Nullable final UUID attemptId, final Account account, final UUID paymentId, @Nullable final DateTime effectiveDate, @Nullable final String paymentTransactionExternalKey, @Nullable final UUID paymentTransactionIdForNewPaymentTransaction, final boolean shouldLockAccountAndDispatch,
final Iterable<PluginProperty> properties, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
- return performOperation(isApiPayment, attemptId, TransactionType.VOID, account, null, paymentId, null, null, null, null, paymentTransactionExternalKey, null, paymentTransactionIdForNewPaymentTransaction, shouldLockAccountAndDispatch, null, properties, callContext, internalCallContext);
+ return performOperation(isApiPayment, attemptId, TransactionType.VOID, account, null, paymentId, null, null, null, effectiveDate, null, paymentTransactionExternalKey, null, paymentTransactionIdForNewPaymentTransaction, shouldLockAccountAndDispatch, null, properties, callContext, internalCallContext);
}
- public Payment createRefund(final boolean isApiPayment, @Nullable final UUID attemptId, final Account account, final UUID paymentId, final BigDecimal amount, final Currency currency,
+ public Payment createRefund(final boolean isApiPayment, @Nullable final UUID attemptId, final Account account, final UUID paymentId, final BigDecimal amount, final Currency currency, @Nullable final DateTime effectiveDate,
final String paymentTransactionExternalKey, @Nullable final UUID paymentTransactionIdForNewPaymentTransaction, final boolean shouldLockAccountAndDispatch,
final Iterable<PluginProperty> properties, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
- return performOperation(isApiPayment, attemptId, TransactionType.REFUND, account, null, paymentId, null, amount, currency, null, paymentTransactionExternalKey, null, paymentTransactionIdForNewPaymentTransaction, shouldLockAccountAndDispatch, null, properties, callContext, internalCallContext);
+ return performOperation(isApiPayment, attemptId, TransactionType.REFUND, account, null, paymentId, null, amount, currency, effectiveDate, null, paymentTransactionExternalKey, null, paymentTransactionIdForNewPaymentTransaction, shouldLockAccountAndDispatch, null, properties, callContext, internalCallContext);
}
- public Payment createCredit(final boolean isApiPayment, @Nullable final UUID attemptId, final Account account, @Nullable final UUID paymentMethodId, @Nullable final UUID paymentId, final BigDecimal amount, final Currency currency,
+ public Payment createCredit(final boolean isApiPayment, @Nullable final UUID attemptId, final Account account, @Nullable final UUID paymentMethodId, @Nullable final UUID paymentId, final BigDecimal amount, final Currency currency, @Nullable final DateTime effectiveDate,
@Nullable final String paymentExternalKey, @Nullable final String paymentTransactionExternalKey, @Nullable final UUID paymentIdForNewPayment, @Nullable final UUID paymentTransactionIdForNewPaymentTransaction, final boolean shouldLockAccountAndDispatch,
final Iterable<PluginProperty> properties, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
- return performOperation(isApiPayment, attemptId, TransactionType.CREDIT, account, paymentMethodId, paymentId, null, amount, currency, paymentExternalKey, paymentTransactionExternalKey, paymentIdForNewPayment, paymentTransactionIdForNewPaymentTransaction, shouldLockAccountAndDispatch, null, properties, callContext, internalCallContext);
+ return performOperation(isApiPayment, attemptId, TransactionType.CREDIT, account, paymentMethodId, paymentId, null, amount, currency, effectiveDate, paymentExternalKey, paymentTransactionExternalKey, paymentIdForNewPayment, paymentTransactionIdForNewPaymentTransaction, shouldLockAccountAndDispatch, null, properties, callContext, internalCallContext);
}
- public Payment createChargeback(final boolean isApiPayment, @Nullable final UUID attemptId, final Account account, final UUID paymentId, @Nullable final String paymentTransactionExternalKey, final BigDecimal amount, final Currency currency, @Nullable final UUID paymentTransactionIdForNewPaymentTransaction, final boolean shouldLockAccountAndDispatch,
+ public Payment createChargeback(final boolean isApiPayment, @Nullable final UUID attemptId, final Account account, final UUID paymentId, @Nullable final String paymentTransactionExternalKey, final BigDecimal amount, final Currency currency, @Nullable final DateTime effectiveDate, @Nullable final UUID paymentTransactionIdForNewPaymentTransaction, final boolean shouldLockAccountAndDispatch,
final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
- return performOperation(isApiPayment, attemptId, TransactionType.CHARGEBACK, account, null, paymentId, null, amount, currency, null, paymentTransactionExternalKey, null, paymentTransactionIdForNewPaymentTransaction, shouldLockAccountAndDispatch, null, PLUGIN_PROPERTIES, callContext, internalCallContext);
+ return performOperation(isApiPayment, attemptId, TransactionType.CHARGEBACK, account, null, paymentId, null, amount, currency, effectiveDate, null, paymentTransactionExternalKey, null, paymentTransactionIdForNewPaymentTransaction, shouldLockAccountAndDispatch, null, PLUGIN_PROPERTIES, callContext, internalCallContext);
}
- public Payment createChargebackReversal(final boolean isApiPayment, @Nullable final UUID attemptId, final Account account, final UUID paymentId, @Nullable final String paymentTransactionExternalKey, final BigDecimal amount, final Currency currency, @Nullable final UUID paymentTransactionIdForNewPaymentTransaction, final boolean shouldLockAccountAndDispatch,
+ public Payment createChargebackReversal(final boolean isApiPayment, @Nullable final UUID attemptId, final Account account, final UUID paymentId, @Nullable final String paymentTransactionExternalKey, final BigDecimal amount, final Currency currency, @Nullable final DateTime effectiveDate, @Nullable final UUID paymentTransactionIdForNewPaymentTransaction, final boolean shouldLockAccountAndDispatch,
final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
- return performOperation(isApiPayment, attemptId, TransactionType.CHARGEBACK, account, null, paymentId, null, amount, currency, null, paymentTransactionExternalKey, null, paymentTransactionIdForNewPaymentTransaction, shouldLockAccountAndDispatch, OperationResult.FAILURE, PLUGIN_PROPERTIES, callContext, internalCallContext);
+ return performOperation(isApiPayment, attemptId, TransactionType.CHARGEBACK, account, null, paymentId, null, amount, currency, effectiveDate, null, paymentTransactionExternalKey, null, paymentTransactionIdForNewPaymentTransaction, shouldLockAccountAndDispatch, OperationResult.FAILURE, PLUGIN_PROPERTIES, callContext, internalCallContext);
}
public Payment notifyPendingPaymentOfStateChanged(final Account account, final UUID transactionId, final boolean isSuccess, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
@@ -184,7 +185,7 @@ public class PaymentProcessor extends ProcessorBase {
final boolean runJanitor = false;
return performOperation(true, runJanitor, null, transactionModelDao.getTransactionType(), account, null, transactionModelDao.getPaymentId(),
- transactionModelDao.getId(), transactionModelDao.getAmount(), transactionModelDao.getCurrency(), null, transactionModelDao.getTransactionExternalKey(), null, null, true,
+ transactionModelDao.getId(), transactionModelDao.getAmount(), transactionModelDao.getCurrency(), null, null, transactionModelDao.getTransactionExternalKey(), null, null, true,
overridePluginResult, PLUGIN_PROPERTIES, callContext, internalCallContext);
}
@@ -440,6 +441,7 @@ public class PaymentProcessor extends ProcessorBase {
@Nullable final UUID transactionId,
@Nullable final BigDecimal amount,
@Nullable final Currency currency,
+ @Nullable final DateTime effectiveDate,
@Nullable final String paymentExternalKey,
@Nullable final String paymentTransactionExternalKey,
@Nullable final UUID paymentIdForNewPayment,
@@ -451,7 +453,7 @@ public class PaymentProcessor extends ProcessorBase {
final InternalCallContext internalCallContext) throws PaymentApiException {
boolean runJanitor = true;
return performOperation(isApiPayment, runJanitor, attemptId, transactionType, account, paymentMethodId, paymentId,
- transactionId, amount, currency, paymentExternalKey, paymentTransactionExternalKey,
+ transactionId, amount, currency, effectiveDate, paymentExternalKey, paymentTransactionExternalKey,
paymentIdForNewPayment, paymentTransactionIdForNewPaymentTransaction, shouldLockAccountAndDispatch, overridePluginOperationResult, properties, callContext, internalCallContext);
}
@@ -465,6 +467,7 @@ public class PaymentProcessor extends ProcessorBase {
@Nullable final UUID transactionId,
@Nullable final BigDecimal amount,
@Nullable final Currency currency,
+ @Nullable final DateTime effectiveDate,
@Nullable final String paymentExternalKey,
@Nullable final String paymentTransactionExternalKey,
@Nullable final UUID paymentIdForNewPayment,
@@ -485,6 +488,7 @@ public class PaymentProcessor extends ProcessorBase {
paymentTransactionExternalKey,
amount,
currency,
+ effectiveDate,
paymentIdForNewPayment,
paymentTransactionIdForNewPaymentTransaction,
shouldLockAccountAndDispatch,
@@ -608,7 +612,7 @@ public class PaymentProcessor extends ProcessorBase {
}
}
- Preconditions.checkState(Iterables.<PaymentTransactionModelDao>size(completionCandidates) <= 1, "There should be at most one completion candidate");
+ Preconditions.checkState(Iterables.size(completionCandidates) <= 1, "There should be at most one completion candidate");
return Iterables.<PaymentTransactionModelDao>getLast(completionCandidates, null);
}
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/PluginControlPaymentProcessor.java b/payment/src/main/java/org/killbill/billing/payment/core/PluginControlPaymentProcessor.java
index 9491e51..651d2d1 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/PluginControlPaymentProcessor.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/PluginControlPaymentProcessor.java
@@ -25,6 +25,7 @@ import java.util.UUID;
import javax.annotation.Nullable;
import javax.inject.Inject;
+import org.joda.time.DateTime;
import org.killbill.automaton.MissingEntryException;
import org.killbill.automaton.State;
import org.killbill.billing.ErrorCode;
@@ -91,7 +92,7 @@ public class PluginControlPaymentProcessor extends ProcessorBase {
this.pluginControlledPaymentAutomatonRunner = pluginControlledPaymentAutomatonRunner;
}
- public Payment createAuthorization(final boolean isApiPayment, final Account account, final UUID paymentMethodId, @Nullable final UUID paymentId, final BigDecimal amount, final Currency currency, final String paymentExternalKey, final String transactionExternalKey,
+ public Payment createAuthorization(final boolean isApiPayment, final Account account, final UUID paymentMethodId, @Nullable final UUID paymentId, final BigDecimal amount, final Currency currency, final DateTime effectiveDate, final String paymentExternalKey, final String transactionExternalKey,
final Iterable<PluginProperty> properties, final List<String> paymentControlPluginNames, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
return pluginControlledPaymentAutomatonRunner.run(isApiPayment,
TransactionType.AUTHORIZE,
@@ -103,12 +104,13 @@ public class PluginControlPaymentProcessor extends ProcessorBase {
transactionExternalKey,
amount,
currency,
+ effectiveDate,
properties,
paymentControlPluginNames,
callContext, internalCallContext);
}
- public Payment createCapture(final boolean isApiPayment, final Account account, final UUID paymentId, final BigDecimal amount, final Currency currency,
+ public Payment createCapture(final boolean isApiPayment, final Account account, final UUID paymentId, final BigDecimal amount, final Currency currency, final DateTime effectiveDate,
final String transactionExternalKey,
final Iterable<PluginProperty> properties, final List<String> paymentControlPluginNames,
final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
@@ -122,12 +124,13 @@ public class PluginControlPaymentProcessor extends ProcessorBase {
transactionExternalKey,
amount,
currency,
+ effectiveDate,
properties,
paymentControlPluginNames,
callContext, internalCallContext);
}
- public Payment createPurchase(final boolean isApiPayment, final Account account, final UUID paymentMethodId, final UUID paymentId, final BigDecimal amount, final Currency currency,
+ public Payment createPurchase(final boolean isApiPayment, final Account account, final UUID paymentMethodId, final UUID paymentId, final BigDecimal amount, final Currency currency, final DateTime effectiveDate,
final String paymentExternalKey, final String transactionExternalKey, final Iterable<PluginProperty> properties,
final List<String> paymentControlPluginNames, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
return pluginControlledPaymentAutomatonRunner.run(isApiPayment,
@@ -140,12 +143,13 @@ public class PluginControlPaymentProcessor extends ProcessorBase {
transactionExternalKey,
amount,
currency,
+ effectiveDate,
properties,
paymentControlPluginNames,
callContext, internalCallContext);
}
- public Payment createVoid(final boolean isApiPayment, final Account account, final UUID paymentId, final String transactionExternalKey,
+ public Payment createVoid(final boolean isApiPayment, final Account account, final UUID paymentId, final DateTime effectiveDate, final String transactionExternalKey,
final Iterable<PluginProperty> properties, final List<String> paymentControlPluginNames, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
return pluginControlledPaymentAutomatonRunner.run(isApiPayment,
TransactionType.VOID,
@@ -157,12 +161,13 @@ public class PluginControlPaymentProcessor extends ProcessorBase {
transactionExternalKey,
null,
null,
+ effectiveDate,
properties,
paymentControlPluginNames,
callContext, internalCallContext);
}
- public Payment createRefund(final boolean isApiPayment, final Account account, final UUID paymentId, final BigDecimal amount, final Currency currency, final String transactionExternalKey,
+ public Payment createRefund(final boolean isApiPayment, final Account account, final UUID paymentId, final BigDecimal amount, final Currency currency, final DateTime effectiveDate, final String transactionExternalKey,
final Iterable<PluginProperty> properties, final List<String> paymentControlPluginNames, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
return pluginControlledPaymentAutomatonRunner.run(isApiPayment,
TransactionType.REFUND,
@@ -174,12 +179,13 @@ public class PluginControlPaymentProcessor extends ProcessorBase {
transactionExternalKey,
amount,
currency,
+ effectiveDate,
properties,
paymentControlPluginNames,
callContext, internalCallContext);
}
- public Payment createCredit(final boolean isApiPayment, final Account account, final UUID paymentMethodId, final UUID paymentId, final BigDecimal amount, final Currency currency, final String paymentExternalKey,
+ public Payment createCredit(final boolean isApiPayment, final Account account, final UUID paymentMethodId, final UUID paymentId, final BigDecimal amount, final Currency currency, final DateTime effectiveDate, final String paymentExternalKey,
final String transactionExternalKey, final Iterable<PluginProperty> properties, final List<String> paymentControlPluginNames, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
return pluginControlledPaymentAutomatonRunner.run(isApiPayment,
@@ -192,6 +198,7 @@ public class PluginControlPaymentProcessor extends ProcessorBase {
transactionExternalKey,
amount,
currency,
+ effectiveDate,
properties,
paymentControlPluginNames,
callContext, internalCallContext);
@@ -227,13 +234,14 @@ public class PluginControlPaymentProcessor extends ProcessorBase {
paymentTransactionModelDao.getTransactionExternalKey(),
paymentTransactionModelDao.getAmount(),
paymentTransactionModelDao.getCurrency(),
+ null,
pluginProperties,
paymentControlPluginNames,
callContext,
internalCallContext);
}
- public Payment createChargeback(final boolean isApiPayment, final Account account, final UUID paymentId, final String transactionExternalKey, final BigDecimal amount, final Currency currency,
+ public Payment createChargeback(final boolean isApiPayment, final Account account, final UUID paymentId, final String transactionExternalKey, final BigDecimal amount, final Currency currency, final DateTime effectiveDate,
final List<String> paymentControlPluginNames, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
return pluginControlledPaymentAutomatonRunner.run(isApiPayment,
TransactionType.CHARGEBACK,
@@ -245,12 +253,13 @@ public class PluginControlPaymentProcessor extends ProcessorBase {
transactionExternalKey,
amount,
currency,
+ effectiveDate,
ImmutableList.<PluginProperty>of(),
paymentControlPluginNames,
callContext, internalCallContext);
}
- public Payment createChargebackReversal(final boolean isApiPayment, final Account account, final UUID paymentId, final String transactionExternalKey,
+ public Payment createChargebackReversal(final boolean isApiPayment, final Account account, final UUID paymentId, final DateTime effectiveDate, final String transactionExternalKey,
final List<String> paymentControlPluginNames, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
return pluginControlledPaymentAutomatonRunner.run(isApiPayment,
TransactionType.CHARGEBACK,
@@ -262,6 +271,7 @@ public class PluginControlPaymentProcessor extends ProcessorBase {
transactionExternalKey,
null,
null,
+ effectiveDate,
ImmutableList.<PluginProperty>of(),
paymentControlPluginNames,
callContext,
@@ -311,6 +321,7 @@ public class PluginControlPaymentProcessor extends ProcessorBase {
attempt.getTransactionExternalKey(),
attempt.getAmount(),
attempt.getCurrency(),
+ null,
pluginProperties,
paymentControlPluginNames,
callContext,
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/AuthorizeControlOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/AuthorizeControlOperation.java
index 93ce8b7..f4870d8 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/AuthorizeControlOperation.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/AuthorizeControlOperation.java
@@ -44,6 +44,7 @@ public class AuthorizeControlOperation extends OperationControlCallback {
paymentStateControlContext.getPaymentId(),
paymentStateControlContext.getAmount(),
paymentStateControlContext.getCurrency(),
+ paymentStateControlContext.getEffectiveDate(),
paymentStateControlContext.getPaymentExternalKey(),
paymentStateControlContext.getPaymentTransactionExternalKey(),
paymentStateControlContext.getPaymentIdForNewPayment(),
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CaptureControlOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CaptureControlOperation.java
index af79455..4a12b23 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CaptureControlOperation.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CaptureControlOperation.java
@@ -43,6 +43,7 @@ public class CaptureControlOperation extends OperationControlCallback {
paymentStateControlContext.getPaymentId(),
paymentStateControlContext.getAmount(),
paymentStateControlContext.getCurrency(),
+ paymentStateControlContext.getEffectiveDate(),
paymentStateControlContext.getPaymentTransactionExternalKey(),
paymentStateControlContext.getPaymentTransactionIdForNewPaymentTransaction(),
false,
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/ChargebackControlOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/ChargebackControlOperation.java
index d53481f..739ad24 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/ChargebackControlOperation.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/ChargebackControlOperation.java
@@ -45,6 +45,7 @@ public class ChargebackControlOperation extends OperationControlCallback {
paymentStateControlContext.getPaymentTransactionExternalKey(),
paymentStateControlContext.getAmount(),
paymentStateControlContext.getCurrency(),
+ paymentStateControlContext.getEffectiveDate(),
paymentStateControlContext.getPaymentTransactionIdForNewPaymentTransaction(),
false,
paymentStateControlContext.getCallContext(),
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/ChargebackReversalControlOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/ChargebackReversalControlOperation.java
index d258b1e..5753809 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/ChargebackReversalControlOperation.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/ChargebackReversalControlOperation.java
@@ -45,6 +45,7 @@ public class ChargebackReversalControlOperation extends OperationControlCallback
paymentStateControlContext.getPaymentTransactionExternalKey(),
paymentStateControlContext.getAmount(),
paymentStateControlContext.getCurrency(),
+ paymentStateControlContext.getEffectiveDate(),
paymentStateControlContext.getPaymentTransactionIdForNewPaymentTransaction(),
false,
paymentStateControlContext.getCallContext(),
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CompletionControlOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CompletionControlOperation.java
index 9ba0ab2..7c503ea 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CompletionControlOperation.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CompletionControlOperation.java
@@ -67,6 +67,7 @@ public class CompletionControlOperation extends OperationControlCallback {
final PaymentTransactionModelDao transaction = paymentStateContext.getPaymentTransactionModelDao();
final PaymentControlContext updatedPaymentControlContext = new DefaultPaymentControlContext(paymentStateContext.getAccount(),
paymentStateContext.getPaymentMethodId(),
+ null,
paymentStateControlContext.getAttemptId(),
transaction.getPaymentId(),
paymentStateContext.getPaymentExternalKey(),
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/ControlPluginRunner.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/ControlPluginRunner.java
index c132bea..15da0e2 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/ControlPluginRunner.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/ControlPluginRunner.java
@@ -59,6 +59,7 @@ public class ControlPluginRunner {
public PriorPaymentControlResult executePluginPriorCalls(final Account account,
final UUID paymentMethodId,
+ final String pluginName,
final UUID paymentAttemptId,
final UUID paymentId,
final String paymentExternalKey,
@@ -76,15 +77,17 @@ public class ControlPluginRunner {
final Iterable<PluginProperty> pluginProperties,
final CallContext callContext) throws PaymentControlApiException {
// Return as soon as the first plugin aborts, or the last result for the last plugin
- PriorPaymentControlResult prevResult = new DefaultPriorPaymentControlResult(false, amount, currency, paymentMethodId, pluginProperties);
+ PriorPaymentControlResult prevResult = new DefaultPriorPaymentControlResult(false, amount, currency, paymentMethodId, null, pluginProperties);
// Those values are adjusted prior each call with the result of what previous call to plugin returned
UUID inputPaymentMethodId = paymentMethodId;
+ String inputPaymentMethodName = pluginName;
BigDecimal inputAmount = amount;
Currency inputCurrency = currency;
Iterable<PluginProperty> inputPluginProperties = pluginProperties;
PaymentControlContext inputPaymentControlContext = new DefaultPaymentControlContext(account,
paymentMethodId,
+ pluginName,
paymentAttemptId,
paymentId,
paymentExternalKey,
@@ -100,16 +103,16 @@ public class ControlPluginRunner {
isApiPayment,
callContext);
- for (final String pluginName : paymentControlPluginNames) {
- final PaymentControlPluginApi plugin = paymentControlPluginRegistry.getServiceForName(pluginName);
+ for (final String controlPluginName : paymentControlPluginNames) {
+ final PaymentControlPluginApi plugin = paymentControlPluginRegistry.getServiceForName(controlPluginName);
if (plugin == null) {
// First call to plugin, we log warn, if plugin is not registered
- log.warn("Skipping unknown payment control plugin {} when fetching results", pluginName);
+ log.warn("Skipping unknown payment control plugin {} when fetching results", controlPluginName);
continue;
}
- log.debug("Calling priorCall of plugin {}", pluginName);
+ log.debug("Calling priorCall of plugin {}", controlPluginName);
prevResult = plugin.priorCall(inputPaymentControlContext, inputPluginProperties);
- log.debug("Successful executed priorCall of plugin {}", pluginName);
+ log.debug("Successful executed priorCall of plugin {}", controlPluginName);
if (prevResult == null) {
// Nothing returned by the plugin
continue;
@@ -118,6 +121,9 @@ public class ControlPluginRunner {
if (prevResult.getAdjustedPaymentMethodId() != null) {
inputPaymentMethodId = prevResult.getAdjustedPaymentMethodId();
}
+ if (prevResult.getAdjustedPluginName() != null) {
+ inputPaymentMethodName = prevResult.getAdjustedPluginName();
+ }
if (prevResult.getAdjustedAmount() != null) {
inputAmount = prevResult.getAdjustedAmount();
}
@@ -128,10 +134,11 @@ public class ControlPluginRunner {
inputPluginProperties = prevResult.getAdjustedPluginProperties();
}
if (prevResult.isAborted()) {
- throw new PaymentControlApiAbortException(pluginName);
+ throw new PaymentControlApiAbortException(controlPluginName);
}
inputPaymentControlContext = new DefaultPaymentControlContext(account,
inputPaymentMethodId,
+ controlPluginName,
paymentAttemptId,
paymentId,
paymentExternalKey,
@@ -148,12 +155,13 @@ public class ControlPluginRunner {
callContext);
}
// Rebuild latest result to include inputPluginProperties
- prevResult = new DefaultPriorPaymentControlResult(prevResult != null && prevResult.isAborted(), inputPaymentMethodId, inputAmount, inputCurrency, inputPluginProperties);
+ prevResult = new DefaultPriorPaymentControlResult(prevResult != null && prevResult.isAborted(), inputPaymentMethodId, inputPaymentMethodName, inputAmount, inputCurrency, inputPluginProperties);
return prevResult;
}
public OnSuccessPaymentControlResult executePluginOnSuccessCalls(final Account account,
final UUID paymentMethodId,
+ final String pluginName,
final UUID paymentAttemptId,
final UUID paymentId,
final String paymentExternalKey,
@@ -173,6 +181,7 @@ public class ControlPluginRunner {
final PaymentControlContext inputPaymentControlContext = new DefaultPaymentControlContext(account,
paymentMethodId,
+ pluginName,
paymentAttemptId,
paymentId,
paymentExternalKey,
@@ -189,13 +198,13 @@ public class ControlPluginRunner {
callContext);
Iterable<PluginProperty> inputPluginProperties = pluginProperties;
- for (final String pluginName : paymentControlPluginNames) {
- final PaymentControlPluginApi plugin = paymentControlPluginRegistry.getServiceForName(pluginName);
+ for (final String controlPluginName : paymentControlPluginNames) {
+ final PaymentControlPluginApi plugin = paymentControlPluginRegistry.getServiceForName(controlPluginName);
if (plugin != null) {
try {
- log.debug("Calling onSuccessCall of plugin {}", pluginName);
+ log.debug("Calling onSuccessCall of plugin {}", controlPluginName);
final OnSuccessPaymentControlResult result = plugin.onSuccessCall(inputPaymentControlContext, inputPluginProperties);
- log.debug("Successful executed onSuccessCall of plugin {}", pluginName);
+ log.debug("Successful executed onSuccessCall of plugin {}", controlPluginName);
if (result == null) {
// Nothing returned by the plugin
continue;
@@ -206,9 +215,9 @@ public class ControlPluginRunner {
}
// Exceptions from the control plugins are ignored (and logged) because the semantics on what to do are undefined.
} catch (final PaymentControlApiException e) {
- log.warn("Error during onSuccessCall for plugin='{}', paymentExternalKey='{}'", pluginName, inputPaymentControlContext.getPaymentExternalKey(), e);
+ log.warn("Error during onSuccessCall for plugin='{}', paymentExternalKey='{}'", controlPluginName, inputPaymentControlContext.getPaymentExternalKey(), e);
} catch (final RuntimeException e) {
- log.warn("Error during onSuccessCall for plugin='{}', paymentExternalKey='{}'", pluginName, inputPaymentControlContext.getPaymentExternalKey(), e);
+ log.warn("Error during onSuccessCall for plugin='{}', paymentExternalKey='{}'", controlPluginName, inputPaymentControlContext.getPaymentExternalKey(), e);
}
}
}
@@ -217,6 +226,7 @@ public class ControlPluginRunner {
public OnFailurePaymentControlResult executePluginOnFailureCalls(final Account account,
final UUID paymentMethodId,
+ final String pluginName,
final UUID paymentAttemptId,
final UUID paymentId,
final String paymentExternalKey,
@@ -236,6 +246,7 @@ public class ControlPluginRunner {
final PaymentControlContext inputPaymentControlContext = new DefaultPaymentControlContext(account,
paymentMethodId,
+ pluginName,
paymentAttemptId,
paymentId,
paymentExternalKey,
@@ -254,13 +265,13 @@ public class ControlPluginRunner {
DateTime candidate = null;
Iterable<PluginProperty> inputPluginProperties = pluginProperties;
- for (final String pluginName : paymentControlPluginNames) {
- final PaymentControlPluginApi plugin = paymentControlPluginRegistry.getServiceForName(pluginName);
+ for (final String controlPluginName : paymentControlPluginNames) {
+ final PaymentControlPluginApi plugin = paymentControlPluginRegistry.getServiceForName(controlPluginName);
if (plugin != null) {
try {
- log.debug("Calling onSuccessCall of plugin {}", pluginName);
+ log.debug("Calling onSuccessCall of plugin {}", controlPluginName);
final OnFailurePaymentControlResult result = plugin.onFailureCall(inputPaymentControlContext, inputPluginProperties);
- log.debug("Successful executed onSuccessCall of plugin {}", pluginName);
+ log.debug("Successful executed onSuccessCall of plugin {}", controlPluginName);
if (result == null) {
// Nothing returned by the plugin
continue;
@@ -277,7 +288,7 @@ public class ControlPluginRunner {
}
} catch (final PaymentControlApiException e) {
- log.warn("Error during onFailureCall for plugin='{}', paymentExternalKey='{}'", pluginName, inputPaymentControlContext.getPaymentExternalKey(), e);
+ log.warn("Error during onFailureCall for plugin='{}', paymentExternalKey='{}'", controlPluginName, inputPaymentControlContext.getPaymentExternalKey(), e);
return new DefaultFailureCallResult(candidate, inputPluginProperties);
}
}
@@ -289,6 +300,7 @@ public class ControlPluginRunner {
private final Account account;
private final UUID paymentMethodId;
+ private final String pluginName;
private final UUID attemptId;
private final UUID paymentId;
private final String paymentExternalKey;
@@ -305,6 +317,7 @@ public class ControlPluginRunner {
public DefaultPaymentControlContext(final Account account,
final UUID paymentMethodId,
+ @Nullable String pluginName,
final UUID attemptId,
@Nullable final UUID paymentId,
final String paymentExternalKey,
@@ -322,6 +335,7 @@ public class ControlPluginRunner {
super(account.getId(), callContext.getTenantId(), callContext.getUserName(), callContext.getCallOrigin(), callContext.getUserType(), callContext.getReasonCode(), callContext.getComments(), callContext.getUserToken(), callContext.getCreatedDate(), callContext.getUpdatedDate());
this.account = account;
this.paymentMethodId = paymentMethodId;
+ this.pluginName = pluginName;
this.attemptId = attemptId;
this.paymentId = paymentId;
this.paymentExternalKey = paymentExternalKey;
@@ -383,6 +397,11 @@ public class ControlPluginRunner {
}
@Override
+ public String getPaymentPluginName() {
+ return pluginName;
+ }
+
+ @Override
public UUID getPaymentId() {
return paymentId;
}
@@ -416,6 +435,7 @@ public class ControlPluginRunner {
return "DefaultPaymentControlContext{" +
"account=" + account +
", paymentMethodId=" + paymentMethodId +
+ ", pluginName=" + pluginName +
", attemptId=" + attemptId +
", paymentId=" + paymentId +
", paymentExternalKey='" + paymentExternalKey + '\'' +
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CreditControlOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CreditControlOperation.java
index e8c3e91..d55fe9a 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CreditControlOperation.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CreditControlOperation.java
@@ -44,6 +44,7 @@ public class CreditControlOperation extends OperationControlCallback {
paymentStateControlContext.getPaymentId(),
paymentStateControlContext.getAmount(),
paymentStateControlContext.getCurrency(),
+ paymentStateControlContext.getEffectiveDate(),
paymentStateControlContext.getPaymentExternalKey(),
paymentStateControlContext.getPaymentTransactionExternalKey(),
paymentStateControlContext.getPaymentIdForNewPayment(),
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/DefaultControlCompleted.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/DefaultControlCompleted.java
index 0d3f97f..4f67980 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/DefaultControlCompleted.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/DefaultControlCompleted.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
@@ -62,7 +62,12 @@ public class DefaultControlCompleted implements EnteringStateCallback {
null;
// At this stage we can update the paymentAttempt state AND serialize the plugin properties. Control plugins will have had the opportunity to erase sensitive data if required.
- retryablePaymentAutomatonRunner.getPaymentDao().updatePaymentAttemptWithProperties(attempt.getId(), transactionId, state.getName(), getSerializedProperties(), paymentStateContext.getInternalCallContext());
+ retryablePaymentAutomatonRunner.getPaymentDao().updatePaymentAttemptWithProperties(attempt.getId(),
+ paymentStateContext.getPaymentMethodId(),
+ transactionId,
+ state.getName(),
+ getSerializedProperties(),
+ paymentStateContext.getInternalCallContext());
if (retriedState.getName().equals(state.getName()) && !isUnknownTransaction()) {
retryServiceScheduler.scheduleRetry(ObjectType.PAYMENT_ATTEMPT, attempt.getId(), attempt.getId(), attempt.getTenantRecordId(),
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/OperationControlCallback.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/OperationControlCallback.java
index a562e32..65348af 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/OperationControlCallback.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/OperationControlCallback.java
@@ -88,6 +88,7 @@ public abstract class OperationControlCallback extends OperationCallbackBase<Pay
final PaymentControlContext paymentControlContext = new DefaultPaymentControlContext(paymentStateContext.getAccount(),
paymentStateContext.getPaymentMethodId(),
+ null,
paymentStateControlContext.getAttemptId(),
paymentStateContext.getPaymentId(),
paymentStateContext.getPaymentExternalKey(),
@@ -123,6 +124,7 @@ public abstract class OperationControlCallback extends OperationCallbackBase<Pay
success = transaction.getTransactionStatus() == TransactionStatus.SUCCESS || transaction.getTransactionStatus() == TransactionStatus.PENDING;
final PaymentControlContext updatedPaymentControlContext = new DefaultPaymentControlContext(paymentStateContext.getAccount(),
paymentStateContext.getPaymentMethodId(),
+ null,
paymentStateControlContext.getAttemptId(),
result.getId(),
result.getExternalKey(),
@@ -173,6 +175,7 @@ public abstract class OperationControlCallback extends OperationCallbackBase<Pay
final PriorPaymentControlResult result = controlPluginRunner.executePluginPriorCalls(paymentStateContext.getAccount(),
paymentControlContextArg.getPaymentMethodId(),
+ null,
paymentStateControlContext.getAttemptId(),
paymentStateContext.getPaymentId(),
paymentStateContext.getPaymentExternalKey(),
@@ -199,6 +202,7 @@ public abstract class OperationControlCallback extends OperationCallbackBase<Pay
// paymentId, paymentExternalKey, transactionAmount, transaction currency are extracted from paymentControlContext which was update from the operation result.
final OnSuccessPaymentControlResult result = controlPluginRunner.executePluginOnSuccessCalls(paymentStateContext.getAccount(),
paymentStateContext.getPaymentMethodId(),
+ null,
paymentStateControlContext.getAttemptId(),
paymentControlContext.getPaymentId(),
paymentControlContext.getPaymentExternalKey(),
@@ -230,6 +234,7 @@ public abstract class OperationControlCallback extends OperationCallbackBase<Pay
final OnFailurePaymentControlResult result = controlPluginRunner.executePluginOnFailureCalls(paymentStateContext.getAccount(),
paymentControlContext.getPaymentMethodId(),
+ null,
paymentStateControlContext.getAttemptId(),
paymentControlContext.getPaymentId(),
paymentControlContext.getPaymentExternalKey(),
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/PaymentStateControlContext.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/PaymentStateControlContext.java
index 599e653..7037d43 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/PaymentStateControlContext.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/PaymentStateControlContext.java
@@ -60,8 +60,9 @@ public class PaymentStateControlContext extends PaymentStateContext {
@Nullable final UUID paymentMethodId,
final BigDecimal amount,
final Currency currency,
+ final DateTime effectiveDate,
final Iterable<PluginProperty> properties, final InternalCallContext internalCallContext, final CallContext callContext) {
- super(isApiPayment, paymentId, transactionId, null, paymentExternalKey, paymentTransactionExternalKey, transactionType, account, paymentMethodId, amount, currency, null, null, true, null, properties, internalCallContext, callContext);
+ super(isApiPayment, paymentId, transactionId, null, paymentExternalKey, paymentTransactionExternalKey, transactionType, account, paymentMethodId, amount, currency, effectiveDate, null, null, true, null, properties, internalCallContext, callContext);
this.paymentControlPluginNames = paymentControlPluginNames;
this.isSuccess = isSuccess;
}
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/PurchaseControlOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/PurchaseControlOperation.java
index cacc797..0b0f193 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/PurchaseControlOperation.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/PurchaseControlOperation.java
@@ -44,6 +44,7 @@ public class PurchaseControlOperation extends OperationControlCallback {
paymentStateControlContext.getPaymentId(),
paymentStateControlContext.getAmount(),
paymentStateControlContext.getCurrency(),
+ paymentStateControlContext.getEffectiveDate(),
paymentStateControlContext.getPaymentExternalKey(),
paymentStateControlContext.getPaymentTransactionExternalKey(),
paymentStateControlContext.getPaymentIdForNewPayment(),
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/RefundControlOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/RefundControlOperation.java
index 8ed65db..65e030e 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/RefundControlOperation.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/RefundControlOperation.java
@@ -43,6 +43,7 @@ public class RefundControlOperation extends OperationControlCallback {
paymentStateControlContext.getPaymentId(),
paymentStateControlContext.getAmount(),
paymentStateControlContext.getCurrency(),
+ paymentStateControlContext.getEffectiveDate(),
paymentStateControlContext.getPaymentTransactionExternalKey(),
paymentStateControlContext.getPaymentTransactionIdForNewPaymentTransaction(),
false,
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/VoidControlOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/VoidControlOperation.java
index 9433a56..1e61b4b 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/VoidControlOperation.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/VoidControlOperation.java
@@ -41,6 +41,7 @@ public class VoidControlOperation extends OperationControlCallback {
paymentStateControlContext.getAttemptId(),
paymentStateControlContext.getAccount(),
paymentStateControlContext.getPaymentId(),
+ paymentStateControlContext.getEffectiveDate(),
paymentStateControlContext.getPaymentTransactionExternalKey(),
paymentStateControlContext.getPaymentTransactionIdForNewPaymentTransaction(),
false,
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonDAOHelper.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonDAOHelper.java
index 70c793a..c800413 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonDAOHelper.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonDAOHelper.java
@@ -215,7 +215,7 @@ public class PaymentAutomatonDAOHelper {
private PaymentTransactionModelDao buildNewPaymentTransactionModelDao(final UUID paymentId) {
final DateTime createdDate = utcNow;
final DateTime updatedDate = utcNow;
- final DateTime effectiveDate = utcNow;
+ final DateTime effectiveDate = paymentStateContext.getEffectiveDate() != null ? paymentStateContext.getEffectiveDate() : utcNow;
final String gatewayErrorCode = null;
final String gatewayErrorMsg = null;
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 6c2f77a..109d91e 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
@@ -122,6 +122,7 @@ public class PaymentAutomatonRunner {
final String paymentTransactionExternalKey,
@Nullable final BigDecimal amount,
@Nullable final Currency currency,
+ final DateTime effectiveDate,
@Nullable final UUID paymentIdForNewPayment,
@Nullable final UUID paymentTransactionIdForNewPaymentTransaction,
final boolean shouldLockAccount,
@@ -144,6 +145,7 @@ public class PaymentAutomatonRunner {
paymentMethodId,
amount,
currency,
+ effectiveDate,
paymentIdForNewPayment,
paymentTransactionIdForNewPaymentTransaction,
shouldLockAccount,
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/ChargebackInitiated.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/ChargebackInitiated.java
index 5e07f04..5f03f39 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/ChargebackInitiated.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/ChargebackInitiated.java
@@ -50,7 +50,7 @@ public class ChargebackInitiated extends PaymentLeavingStateCallback {
ImmutableList.<PaymentTransactionModelDao>of();
final Iterable<PaymentTransactionModelDao> existingPaymentTransactionsForTransactionIdOrKey = filterExistingPaymentTransactionsForTransactionIdOrKey(paymentTransactionsForCurrentPayment, paymentStateContext.getTransactionId(), paymentStateContext.getPaymentTransactionExternalKey());
- if (Iterables.<PaymentTransactionModelDao>isEmpty(existingPaymentTransactionsForTransactionIdOrKey)) {
+ if (Iterables.isEmpty(existingPaymentTransactionsForTransactionIdOrKey)) {
// Chargeback reversals can only happen after a successful chargeback
throw new OperationException(new PaymentApiException(ErrorCode.PAYMENT_NO_SUCH_SUCCESS_PAYMENT, paymentStateContext.getPaymentId()));
}
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentStateContext.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentStateContext.java
index c0d5709..f6444ac 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentStateContext.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentStateContext.java
@@ -23,6 +23,7 @@ import java.util.UUID;
import javax.annotation.Nullable;
+import org.joda.time.DateTime;
import org.killbill.automaton.OperationResult;
import org.killbill.billing.account.api.Account;
import org.killbill.billing.callcontext.InternalCallContext;
@@ -75,21 +76,23 @@ public class PaymentStateContext {
private final InternalCallContext internalCallContext;
private final CallContext callContext;
private final boolean isApiPayment;
+ private final DateTime effectiveDate;
@VisibleForTesting
public PaymentStateContext(final boolean isApiPayment, @Nullable final UUID paymentId, @Nullable final String paymentTransactionExternalKey, final TransactionType transactionType,
- final Account account, @Nullable final UUID paymentMethodId, final BigDecimal amount, final Currency currency,
+ final Account account, @Nullable final UUID paymentMethodId, final BigDecimal amount, final Currency currency, final DateTime effectiveDate,
final boolean shouldLockAccountAndDispatch, final Iterable<PluginProperty> properties,
final InternalCallContext internalCallContext, final CallContext callContext) {
this(isApiPayment, paymentId, null, null, null, paymentTransactionExternalKey, transactionType, account, paymentMethodId,
- amount, currency, null, null, shouldLockAccountAndDispatch, null, properties, internalCallContext, callContext);
+ amount, currency, effectiveDate, null, null, shouldLockAccountAndDispatch, null, properties, internalCallContext, callContext);
}
// Used to create new payment and transactions
public PaymentStateContext(final boolean isApiPayment, @Nullable final UUID paymentId, final UUID transactionId, @Nullable final UUID attemptId, @Nullable final String paymentExternalKey,
@Nullable final String paymentTransactionExternalKey, final TransactionType transactionType,
- final Account account, @Nullable final UUID paymentMethodId, final BigDecimal amount, final Currency currency,
- @Nullable final UUID paymentIdForNewPayment, @Nullable final UUID paymentTransactionIdForNewPaymentTransaction, final boolean shouldLockAccountAndDispatch, final OperationResult overridePluginOperationResult, final Iterable<PluginProperty> properties,
+ final Account account, @Nullable final UUID paymentMethodId, final BigDecimal amount, final Currency currency, final DateTime effectiveDate,
+ @Nullable final UUID paymentIdForNewPayment, @Nullable final UUID paymentTransactionIdForNewPaymentTransaction, final boolean shouldLockAccountAndDispatch,
+ final OperationResult overridePluginOperationResult, final Iterable<PluginProperty> properties,
final InternalCallContext internalCallContext, final CallContext callContext) {
this.isApiPayment = isApiPayment;
this.paymentId = paymentId;
@@ -102,6 +105,7 @@ public class PaymentStateContext {
this.paymentMethodId = paymentMethodId;
this.amount = amount;
this.currency = currency;
+ this.effectiveDate = effectiveDate;
this.paymentIdForNewPayment = paymentIdForNewPayment;
this.paymentTransactionIdForNewPaymentTransaction = paymentTransactionIdForNewPaymentTransaction;
this.shouldLockAccountAndDispatch = shouldLockAccountAndDispatch;
@@ -212,6 +216,10 @@ public class PaymentStateContext {
return currency;
}
+ public DateTime getEffectiveDate() {
+ return effectiveDate;
+ }
+
public TransactionType getTransactionType() {
return transactionType;
}
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/PluginControlPaymentAutomatonRunner.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/PluginControlPaymentAutomatonRunner.java
index 1ec0b0d..5be8359 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/PluginControlPaymentAutomatonRunner.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/PluginControlPaymentAutomatonRunner.java
@@ -25,6 +25,7 @@ import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.inject.Named;
+import org.joda.time.DateTime;
import org.killbill.automaton.MissingEntryException;
import org.killbill.automaton.Operation.OperationCallback;
import org.killbill.automaton.OperationException;
@@ -118,6 +119,7 @@ public class PluginControlPaymentAutomatonRunner extends PaymentAutomatonRunner
final String paymentTransactionExternalKey,
@Nullable final BigDecimal amount,
@Nullable final Currency currency,
+ @Nullable final DateTime effectiveDate,
final Iterable<PluginProperty> properties,
@Nullable final List<String> paymentControlPluginNames,
final CallContext callContext,
@@ -135,6 +137,7 @@ public class PluginControlPaymentAutomatonRunner extends PaymentAutomatonRunner
paymentTransactionExternalKey,
amount,
currency,
+ effectiveDate,
properties,
paymentControlPluginNames,
callContext,
@@ -153,6 +156,7 @@ public class PluginControlPaymentAutomatonRunner extends PaymentAutomatonRunner
final String paymentTransactionExternalKey,
@Nullable final BigDecimal amount,
@Nullable final Currency currency,
+ @Nullable final DateTime effectiveDate,
final Iterable<PluginProperty> properties,
@Nullable final List<String> paymentControlPluginNames,
final CallContext callContext,
@@ -170,6 +174,7 @@ public class PluginControlPaymentAutomatonRunner extends PaymentAutomatonRunner
paymentTransactionExternalKey,
amount,
currency,
+ effectiveDate,
properties,
paymentControlPluginNames,
callContext,
@@ -187,6 +192,7 @@ public class PluginControlPaymentAutomatonRunner extends PaymentAutomatonRunner
final String paymentTransactionExternalKey,
@Nullable final BigDecimal amount,
@Nullable final Currency currency,
+ @Nullable final DateTime effectiveDate,
final Iterable<PluginProperty> properties,
@Nullable final List<String> paymentControlPluginNames,
final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
@@ -203,6 +209,7 @@ public class PluginControlPaymentAutomatonRunner extends PaymentAutomatonRunner
paymentTransactionExternalKey,
amount,
currency,
+ effectiveDate,
properties,
paymentControlPluginNames,
callContext,
@@ -222,6 +229,7 @@ public class PluginControlPaymentAutomatonRunner extends PaymentAutomatonRunner
final String paymentTransactionExternalKey,
@Nullable final BigDecimal amount,
@Nullable final Currency currency,
+ @Nullable final DateTime effectiveDate,
final Iterable<PluginProperty> properties,
@Nullable final List<String> paymentControlPluginNames,
final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
@@ -236,6 +244,7 @@ public class PluginControlPaymentAutomatonRunner extends PaymentAutomatonRunner
paymentTransactionExternalKey,
amount,
currency,
+ effectiveDate,
properties,
paymentControlPluginNames,
callContext,
@@ -289,11 +298,11 @@ public class PluginControlPaymentAutomatonRunner extends PaymentAutomatonRunner
@VisibleForTesting
PaymentStateControlContext createContext(final boolean isApiPayment, final Boolean isSuccess, final TransactionType transactionType, final Account account, @Nullable final UUID paymentMethodId,
- @Nullable final UUID paymentId, @Nullable final String paymentExternalKey,@Nullable final UUID transactionId, final String paymentTransactionExternalKey,
- @Nullable final BigDecimal amount, @Nullable final Currency currency, final Iterable<PluginProperty> properties,
+ @Nullable final UUID paymentId, @Nullable final String paymentExternalKey, @Nullable final UUID transactionId, final String paymentTransactionExternalKey,
+ @Nullable final BigDecimal amount, @Nullable final Currency currency, @Nullable final DateTime effectiveDate, final Iterable<PluginProperty> properties,
final List<String> paymentControlPluginNames, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
return new PaymentStateControlContext(paymentControlPluginNames, isApiPayment, isSuccess, paymentId, paymentExternalKey, transactionId, paymentTransactionExternalKey, transactionType, account,
- paymentMethodId, amount, currency, properties, internalCallContext, callContext);
+ paymentMethodId, amount, currency, effectiveDate, properties, internalCallContext, callContext);
}
@VisibleForTesting
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 22baff9..516057c 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
@@ -126,14 +126,14 @@ public class DefaultPaymentDao extends EntityDaoBase<PaymentModelDao, Payment, P
}
@Override
- public void updatePaymentAttemptWithProperties(final UUID paymentAttemptId, final UUID transactionId, final String state, final byte[] pluginProperties, final InternalCallContext context) {
+ public void updatePaymentAttemptWithProperties(final UUID paymentAttemptId, final UUID paymentMethodId, final UUID transactionId, final String state, final byte[] pluginProperties, final InternalCallContext context) {
transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<Void>() {
@Override
public Void inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
final String transactionIdStr = transactionId != null ? transactionId.toString() : null;
final PaymentAttemptSqlDao transactional = entitySqlDaoWrapperFactory.become(PaymentAttemptSqlDao.class);
- transactional.updateAttemptWithProperties(paymentAttemptId.toString(), transactionIdStr, state, pluginProperties, contextWithUpdatedDate(context));
+ transactional.updateAttemptWithProperties(paymentAttemptId.toString(), paymentMethodId == null ? null : paymentMethodId.toString(), transactionIdStr, state, pluginProperties, contextWithUpdatedDate(context));
return null;
}
});
@@ -500,6 +500,16 @@ public class DefaultPaymentDao extends EntityDaoBase<PaymentModelDao, Payment, P
});
}
+ public List<PaymentMethodModelDao> getPaymentMethodsIncludedDeleted(final InternalTenantContext context) {
+ return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<List<PaymentMethodModelDao>>() {
+ @Override
+ public List<PaymentMethodModelDao> inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
+ return entitySqlDaoWrapperFactory.become(PaymentMethodSqlDao.class).getForAccountIncludedDelete(context);
+ }
+ });
+ }
+
+
@Override
public Pagination<PaymentMethodModelDao> searchPaymentMethods(final String searchKey, final Long offset, final Long limit, final InternalTenantContext context) {
return paginationHelper.getPagination(PaymentMethodSqlDao.class,
diff --git a/payment/src/main/java/org/killbill/billing/payment/dao/PaymentAttemptSqlDao.java b/payment/src/main/java/org/killbill/billing/payment/dao/PaymentAttemptSqlDao.java
index d9aef5d..5ef0bb6 100644
--- a/payment/src/main/java/org/killbill/billing/payment/dao/PaymentAttemptSqlDao.java
+++ b/payment/src/main/java/org/killbill/billing/payment/dao/PaymentAttemptSqlDao.java
@@ -1,7 +1,9 @@
/*
* Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
*
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
@@ -26,14 +28,14 @@ import org.killbill.billing.util.audit.ChangeType;
import org.killbill.billing.util.entity.Entity;
import org.killbill.billing.util.entity.dao.Audited;
import org.killbill.billing.util.entity.dao.EntitySqlDao;
-import org.killbill.billing.util.entity.dao.EntitySqlDaoStringTemplate;
+import org.killbill.commons.jdbi.template.KillBillSqlDaoStringTemplate;
import org.skife.jdbi.v2.sqlobject.Bind;
-import org.skife.jdbi.v2.sqlobject.BindBean;
+import org.killbill.commons.jdbi.binder.SmartBindBean;
import org.skife.jdbi.v2.sqlobject.SqlQuery;
import org.skife.jdbi.v2.sqlobject.SqlUpdate;
import org.skife.jdbi.v2.sqlobject.customizers.Define;
-@EntitySqlDaoStringTemplate
+@KillBillSqlDaoStringTemplate
public interface PaymentAttemptSqlDao extends EntitySqlDao<PaymentAttemptModelDao, Entity> {
@SqlUpdate
@@ -41,23 +43,24 @@ public interface PaymentAttemptSqlDao extends EntitySqlDao<PaymentAttemptModelDa
void updateAttempt(@Bind("id") final String attemptId,
@Bind("transactionId") final String transactionId,
@Bind("stateName") final String stateName,
- @BindBean final InternalCallContext context);
+ @SmartBindBean final InternalCallContext context);
@SqlUpdate
@Audited(ChangeType.UPDATE)
void updateAttemptWithProperties(@Bind("id") final String attemptId,
+ @Bind("paymentMethodId") final String paymentMethodId,
@Bind("transactionId") final String transactionId,
@Bind("stateName") final String stateName,
@Bind("pluginProperties") final byte[] pluginProperties,
- @BindBean final InternalCallContext context);
+ @SmartBindBean final InternalCallContext context);
@SqlQuery
List<PaymentAttemptModelDao> getByTransactionExternalKey(@Bind("transactionExternalKey") final String transactionExternalKey,
- @BindBean final InternalTenantContext context);
+ @SmartBindBean final InternalTenantContext context);
@SqlQuery
List<PaymentAttemptModelDao> getByPaymentExternalKey(@Bind("paymentExternalKey") final String paymentExternalKey,
- @BindBean final InternalTenantContext context);
+ @SmartBindBean final InternalTenantContext context);
@SqlQuery
Long getCountByStateNameAcrossTenants(@Bind("stateName") final String stateName,
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 b7180e8..5c67ff0 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
@@ -41,7 +41,7 @@ public interface PaymentDao extends EntityDao<PaymentModelDao, Payment, PaymentA
public void updatePaymentAttempt(UUID paymentAttemptId, UUID transactionId, String state, InternalCallContext context);
- public void updatePaymentAttemptWithProperties(UUID paymentAttemptId, UUID transactionId, String state, final byte[] pluginProperties, InternalCallContext context);
+ public void updatePaymentAttemptWithProperties(UUID paymentAttemptId, UUID paymentMethodId, UUID transactionId, String state, final byte[] pluginProperties, InternalCallContext context);
public Pagination<PaymentAttemptModelDao> getPaymentAttemptsByStateAcrossTenants(String stateName, DateTime createdBeforeDate, final Long offset, final Long limit);
@@ -91,6 +91,8 @@ public interface PaymentDao extends EntityDao<PaymentModelDao, Payment, PaymentA
public List<PaymentMethodModelDao> getPaymentMethods(InternalTenantContext context);
+ public List<PaymentMethodModelDao> getPaymentMethodsIncludedDeleted(InternalTenantContext context);
+
public Pagination<PaymentMethodModelDao> getPaymentMethods(String pluginName, Long offset, Long limit, InternalTenantContext context);
public Pagination<PaymentMethodModelDao> searchPaymentMethods(String searchKey, Long offset, Long limit, InternalTenantContext context);
diff --git a/payment/src/main/java/org/killbill/billing/payment/dao/PaymentMethodSqlDao.java b/payment/src/main/java/org/killbill/billing/payment/dao/PaymentMethodSqlDao.java
index da14f3e..5512781 100644
--- a/payment/src/main/java/org/killbill/billing/payment/dao/PaymentMethodSqlDao.java
+++ b/payment/src/main/java/org/killbill/billing/payment/dao/PaymentMethodSqlDao.java
@@ -22,7 +22,7 @@ import java.util.Iterator;
import java.util.List;
import org.skife.jdbi.v2.sqlobject.Bind;
-import org.skife.jdbi.v2.sqlobject.BindBean;
+import org.killbill.commons.jdbi.binder.SmartBindBean;
import org.skife.jdbi.v2.sqlobject.SqlQuery;
import org.skife.jdbi.v2.sqlobject.SqlUpdate;
@@ -33,37 +33,37 @@ import org.killbill.billing.payment.api.PaymentMethod;
import org.killbill.billing.util.audit.ChangeType;
import org.killbill.billing.util.entity.dao.Audited;
import org.killbill.billing.util.entity.dao.EntitySqlDao;
-import org.killbill.billing.util.entity.dao.EntitySqlDaoStringTemplate;
+import org.killbill.commons.jdbi.template.KillBillSqlDaoStringTemplate;
import org.skife.jdbi.v2.sqlobject.customizers.Define;
-@EntitySqlDaoStringTemplate
+@KillBillSqlDaoStringTemplate
public interface PaymentMethodSqlDao extends EntitySqlDao<PaymentMethodModelDao, PaymentMethod> {
@SqlUpdate
@Audited(ChangeType.UPDATE)
void markPaymentMethodAsDeleted(@Bind("id") final String paymentMethodId,
- @BindBean final InternalCallContext context);
+ @SmartBindBean final InternalCallContext context);
@SqlUpdate
@Audited(ChangeType.UPDATE)
void unmarkPaymentMethodAsDeleted(@Bind("id") final String paymentMethodId,
- @BindBean final InternalCallContext context);
+ @SmartBindBean final InternalCallContext context);
@SqlQuery
- PaymentMethodModelDao getByExternalKey(@Bind("externalKey") String paymentMethodExternalKey, @BindBean InternalTenantContext context);
+ PaymentMethodModelDao getByExternalKey(@Bind("externalKey") String paymentMethodExternalKey, @SmartBindBean InternalTenantContext context);
@SqlQuery
- PaymentMethodModelDao getPaymentMethodByExternalKeyIncludedDeleted(@Bind("externalKey") String paymentMethodExternalKey, @BindBean InternalTenantContext context);
+ PaymentMethodModelDao getPaymentMethodByExternalKeyIncludedDeleted(@Bind("externalKey") String paymentMethodExternalKey, @SmartBindBean InternalTenantContext context);
@SqlQuery
PaymentMethodModelDao getPaymentMethodIncludedDelete(@Bind("id") final String paymentMethodId,
- @BindBean final InternalTenantContext context);
+ @SmartBindBean final InternalTenantContext context);
@SqlQuery
- List<PaymentMethodModelDao> getForAccount(@BindBean final InternalTenantContext context);
+ List<PaymentMethodModelDao> getForAccount(@SmartBindBean final InternalTenantContext context);
@SqlQuery
- List<PaymentMethodModelDao> getForAccountIncludedDelete(@BindBean final InternalTenantContext context);
+ List<PaymentMethodModelDao> getForAccountIncludedDelete(@SmartBindBean final InternalTenantContext context);
@SqlQuery
@SmartFetchSize(shouldStream = true)
@@ -71,10 +71,10 @@ public interface PaymentMethodSqlDao extends EntitySqlDao<PaymentMethodModelDao,
@Bind("offset") final Long offset,
@Bind("rowCount") final Long rowCount,
@Define("ordering") final String ordering,
- @BindBean final InternalTenantContext context);
+ @SmartBindBean final InternalTenantContext context);
@SqlQuery
public Long getCountByPluginName(@Bind("pluginName") final String pluginName,
- @BindBean final InternalTenantContext context);
+ @SmartBindBean final InternalTenantContext context);
}
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 ee8ad0f..e932660 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
@@ -1,7 +1,8 @@
/*
- * Copyright 2014 Groupon, Inc
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
*
- * Groupon licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
@@ -27,56 +28,57 @@ import org.killbill.billing.payment.api.Payment;
import org.killbill.billing.util.audit.ChangeType;
import org.killbill.billing.util.entity.dao.Audited;
import org.killbill.billing.util.entity.dao.EntitySqlDao;
-import org.killbill.billing.util.entity.dao.EntitySqlDaoStringTemplate;
+import org.killbill.commons.jdbi.binder.SmartBindBean;
import org.killbill.commons.jdbi.statement.SmartFetchSize;
+import org.killbill.commons.jdbi.template.KillBillSqlDaoStringTemplate;
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;
+import org.skife.jdbi.v2.unstable.BindIn;
-@EntitySqlDaoStringTemplate
+@KillBillSqlDaoStringTemplate
public interface PaymentSqlDao extends EntitySqlDao<PaymentModelDao, Payment> {
@SqlUpdate
@Audited(ChangeType.UPDATE)
void updatePaymentForNewTransaction(@Bind("id") final String paymentId,
- @BindBean final InternalCallContext context);
+ @SmartBindBean final InternalCallContext context);
@SqlUpdate
@Audited(ChangeType.UPDATE)
Object updatePaymentStateName(@Bind("id") final String paymentId,
@Bind("stateName") final String stateName,
- @BindBean final InternalCallContext context);
+ @SmartBindBean final InternalCallContext context);
@SqlUpdate
@Audited(ChangeType.UPDATE)
Object updateLastSuccessPaymentStateName(@Bind("id") final String paymentId,
@Bind("stateName") final String stateName,
@Bind("lastSuccessStateName") final String lastSuccessStateName,
- @BindBean final InternalCallContext context);
+ @SmartBindBean final InternalCallContext context);
@SqlQuery
public PaymentModelDao getPaymentByExternalKey(@Bind("externalKey") final String externalKey,
- @BindBean final InternalTenantContext context);
+ @SmartBindBean final InternalTenantContext context);
@SqlQuery
- public List<PaymentModelDao> getPaymentsByStatesAcrossTenants(@StateCollectionBinder final Collection<String> states,
+ public List<PaymentModelDao> getPaymentsByStatesAcrossTenants(@BindIn("states") final Collection<String> states,
@Bind("createdBeforeDate") final Date createdBeforeDate,
@Bind("createdAfterDate") final Date createdAfterDate,
@Bind("limit") final int limit);
@SqlQuery
@SmartFetchSize(shouldStream = true)
- public Iterator<PaymentModelDao> searchByState(@PaymentStateCollectionBinder final Collection<String> paymentStates,
+ public Iterator<PaymentModelDao> searchByState(@BindIn("states") final Collection<String> paymentStates,
@Bind("offset") final Long offset,
@Bind("rowCount") final Long rowCount,
@Define("ordering") final String ordering,
- @BindBean final InternalTenantContext context);
+ @SmartBindBean final InternalTenantContext context);
@SqlQuery
- public Long getSearchByStateCount(@PaymentStateCollectionBinder final Collection<String> paymentStates,
- @BindBean final InternalTenantContext context);
+ public Long getSearchByStateCount(@BindIn("states") final Collection<String> paymentStates,
+ @SmartBindBean final InternalTenantContext context);
@SqlQuery
@SmartFetchSize(shouldStream = true)
@@ -84,9 +86,9 @@ public interface PaymentSqlDao extends EntitySqlDao<PaymentModelDao, Payment> {
@Bind("offset") final Long offset,
@Bind("rowCount") final Long rowCount,
@Define("ordering") final String ordering,
- @BindBean final InternalTenantContext context);
+ @SmartBindBean final InternalTenantContext context);
@SqlQuery
public Long getCountByPluginName(@Bind("pluginName") final String pluginName,
- @BindBean final InternalTenantContext context);
+ @SmartBindBean final InternalTenantContext context);
}
diff --git a/payment/src/main/java/org/killbill/billing/payment/dao/TransactionSqlDao.java b/payment/src/main/java/org/killbill/billing/payment/dao/TransactionSqlDao.java
index ba8b739..b6666b7 100644
--- a/payment/src/main/java/org/killbill/billing/payment/dao/TransactionSqlDao.java
+++ b/payment/src/main/java/org/killbill/billing/payment/dao/TransactionSqlDao.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2014-2016 Groupon, Inc
- * Copyright 2014-2016 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
@@ -30,14 +30,15 @@ import org.killbill.billing.payment.api.PaymentTransaction;
import org.killbill.billing.util.audit.ChangeType;
import org.killbill.billing.util.entity.dao.Audited;
import org.killbill.billing.util.entity.dao.EntitySqlDao;
-import org.killbill.billing.util.entity.dao.EntitySqlDaoStringTemplate;
+import org.killbill.commons.jdbi.binder.SmartBindBean;
+import org.killbill.commons.jdbi.template.KillBillSqlDaoStringTemplate;
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;
+import org.skife.jdbi.v2.unstable.BindIn;
-@EntitySqlDaoStringTemplate
+@KillBillSqlDaoStringTemplate
public interface TransactionSqlDao extends EntitySqlDao<PaymentTransactionModelDao, PaymentTransaction> {
@SqlUpdate
@@ -49,19 +50,19 @@ public interface TransactionSqlDao extends EntitySqlDao<PaymentTransactionModelD
@Bind("transactionStatus") final String transactionStatus,
@Bind("gatewayErrorCode") final String gatewayErrorCode,
@Bind("gatewayErrorMsg") final String gatewayErrorMsg,
- @BindBean final InternalCallContext context);
+ @SmartBindBean final InternalCallContext context);
@SqlQuery
List<PaymentTransactionModelDao> getPaymentTransactionsByExternalKey(@Bind("transactionExternalKey") final String transactionExternalKey,
- @BindBean final InternalTenantContext context);
+ @SmartBindBean final InternalTenantContext context);
@SqlQuery
- Long getCountByTransactionStatusPriorDateAcrossTenants(@TransactionStatusCollectionBinder final Collection<String> statuses,
+ Long getCountByTransactionStatusPriorDateAcrossTenants(@BindIn("statuses") final Collection<String> statuses,
@Bind("createdBeforeDate") final Date createdBeforeDate,
@Bind("createdAfterDate") final Date createdAfterDate);
@SqlQuery
- Iterator<PaymentTransactionModelDao> getByTransactionStatusPriorDateAcrossTenants(@TransactionStatusCollectionBinder final Collection<String> statuses,
+ Iterator<PaymentTransactionModelDao> getByTransactionStatusPriorDateAcrossTenants(@BindIn("statuses") final Collection<String> statuses,
@Bind("createdBeforeDate") final Date createdBeforeDate,
@Bind("createdAfterDate") final Date createdAfterDate,
@Bind("offset") final Long offset,
@@ -70,7 +71,7 @@ public interface TransactionSqlDao extends EntitySqlDao<PaymentTransactionModelD
@SqlQuery
public List<PaymentTransactionModelDao> getByPaymentId(@Bind("paymentId") final UUID paymentId,
- @BindBean final InternalTenantContext context);
+ @SmartBindBean final InternalTenantContext context);
}
diff --git a/payment/src/main/java/org/killbill/billing/payment/invoice/InvoicePaymentControlPluginApi.java b/payment/src/main/java/org/killbill/billing/payment/invoice/InvoicePaymentControlPluginApi.java
index f03d42f..dced1af 100644
--- a/payment/src/main/java/org/killbill/billing/payment/invoice/InvoicePaymentControlPluginApi.java
+++ b/payment/src/main/java/org/killbill/billing/payment/invoice/InvoicePaymentControlPluginApi.java
@@ -321,16 +321,25 @@ public final class InvoicePaymentControlPluginApi implements PaymentControlPlugi
return new DefaultPriorPaymentControlResult(true);
}
+
// Get account and check if it is child and payment is delegated to parent => abort
+
final AccountData accountData = accountApi.getAccountById(invoice.getAccountId(), internalContext);
- if ((accountData != null) && (accountData.getParentAccountId() != null) && accountData.isPaymentDelegatedToParent()) {
+ if (((accountData != null) && (accountData.getParentAccountId() != null) && accountData.isPaymentDelegatedToParent()) || // Valid when we initially create the child invoice (even if parent invoice does not exist yet)
+ (invoice.getParentAccountId() != null)) { // Valid after we have unparented the child
return new DefaultPriorPaymentControlResult(true);
}
+
+
+ // Is remaining amount > 0 ?
final BigDecimal requestedAmount = validateAndComputePaymentAmount(invoice, paymentControlPluginContext.getAmount(), paymentControlPluginContext.isApiPayment());
+ if (requestedAmount.compareTo(BigDecimal.ZERO) <= 0) {
+ return new DefaultPriorPaymentControlResult(true);
+ }
- final boolean isAborted = requestedAmount.compareTo(BigDecimal.ZERO) == 0;
- if (!isAborted && paymentControlPluginContext.getPaymentMethodId() == null) {
+ // Do we have a paymentMethod ?
+ if (paymentControlPluginContext.getPaymentMethodId() == null) {
log.warn("Payment for invoiceId='{}' was not triggered, accountId='{}' doesn't have a default payment method", getInvoiceId(pluginProperties), paymentControlPluginContext.getAccountId());
invoiceApi.recordPaymentAttemptCompletion(invoiceId,
paymentControlPluginContext.getAmount(),
@@ -344,35 +353,31 @@ public final class InvoicePaymentControlPluginApi implements PaymentControlPlugi
return new DefaultPriorPaymentControlResult(true);
}
- if (!isAborted && insert_AUTO_PAY_OFF_ifRequired(paymentControlPluginContext, requestedAmount)) {
+
+ // Are we in auto-payoff ?
+ if (insert_AUTO_PAY_OFF_ifRequired(paymentControlPluginContext, requestedAmount)) {
return new DefaultPriorPaymentControlResult(true);
}
- if (paymentControlPluginContext.isApiPayment() && isAborted) {
- throw new PaymentControlApiException("Abort purchase call: ", new PaymentApiException(ErrorCode.PAYMENT_PLUGIN_EXCEPTION,
- String.format("Aborted Payment for invoice %s : invoice balance is = %s, requested payment amount is = %s",
- invoice.getId(),
- invoice.getBalance(),
- paymentControlPluginContext.getAmount())));
- } else {
-
- //
- // Insert attempt row with a success = false status to implement a two-phase commit strategy and guard against scenario where payment would go through
- // but onSuccessCall callback never gets called (leaving the place for a double payment if user retries the operation)
- //
- invoiceApi.recordPaymentAttemptInit(invoice.getId(),
- MoreObjects.firstNonNull(paymentControlPluginContext.getAmount(), BigDecimal.ZERO),
- paymentControlPluginContext.getCurrency(),
- paymentControlPluginContext.getCurrency(),
- // Likely to be null, but we don't care as we use the transactionExternalKey
- // to match the operation in the checkForIncompleteInvoicePaymentAndRepair logic below
- paymentControlPluginContext.getPaymentId(),
- paymentControlPluginContext.getTransactionExternalKey(),
- paymentControlPluginContext.getCreatedDate(),
- internalContext);
-
- return new DefaultPriorPaymentControlResult(isAborted, requestedAmount);
- }
+
+ //
+ // Insert attempt row with a success = false status to implement a two-phase commit strategy and guard against scenario where payment would go through
+ // but onSuccessCall callback never gets called (leaving the place for a double payment if user retries the operation)
+ //
+ invoiceApi.recordPaymentAttemptInit(invoice.getId(),
+ MoreObjects.firstNonNull(paymentControlPluginContext.getAmount(), BigDecimal.ZERO),
+ paymentControlPluginContext.getCurrency(),
+ paymentControlPluginContext.getCurrency(),
+ // Likely to be null, but we don't care as we use the transactionExternalKey
+ // to match the operation in the checkForIncompleteInvoicePaymentAndRepair logic below
+ paymentControlPluginContext.getPaymentId(),
+ paymentControlPluginContext.getTransactionExternalKey(),
+ paymentControlPluginContext.getCreatedDate(),
+ internalContext);
+
+ return new DefaultPriorPaymentControlResult(false, requestedAmount);
+
+
} catch (final InvoiceApiException e) {
throw new PaymentControlApiException(e);
} catch (final IllegalArgumentException e) {
@@ -641,23 +646,25 @@ public final class InvoicePaymentControlPluginApi implements PaymentControlPlugi
return false;
}
- private BigDecimal validateAndComputePaymentAmount(final Invoice invoice, @Nullable final BigDecimal inputAmount, final boolean isApiPayment) {
+ private BigDecimal validateAndComputePaymentAmount(final Invoice invoice, @Nullable final BigDecimal inputAmount, final boolean isApiPayment) throws PaymentControlApiException {
if (invoice.getBalance().compareTo(BigDecimal.ZERO) <= 0) {
log.info("invoiceId='{}' has already been paid", invoice.getId());
return BigDecimal.ZERO;
}
+
if (isApiPayment &&
inputAmount != null &&
invoice.getBalance().compareTo(inputAmount) < 0) {
- log.info("invoiceId='{}' has a balance='{}' < retry paymentAmount='{}'", invoice.getId(), invoice.getBalance().floatValue(), inputAmount.floatValue());
- return BigDecimal.ZERO;
- }
- if (inputAmount == null) {
- return invoice.getBalance();
- } else {
- return invoice.getBalance().compareTo(inputAmount) < 0 ? invoice.getBalance() : inputAmount;
+ log.info("invoiceId='{}' has a balance='{}' < paymentAmount='{}'", invoice.getId(), invoice.getBalance().floatValue(), inputAmount.floatValue());
+ throw new PaymentControlApiException("Abort purchase call: ", new PaymentApiException(ErrorCode.PAYMENT_PLUGIN_EXCEPTION,
+ String.format("Invalid amount '%s' for invoice '%s': invoice balance is = '%s'",
+ inputAmount,
+ invoice.getId(),
+ invoice.getBalance())));
}
+
+ return (inputAmount == null || invoice.getBalance().compareTo(inputAmount) < 0) ? invoice.getBalance() : inputAmount;
}
private boolean insert_AUTO_PAY_OFF_ifRequired(final PaymentControlContext paymentControlContext, final BigDecimal computedAmount) {
diff --git a/payment/src/main/java/org/killbill/billing/payment/retry/DefaultPriorPaymentControlResult.java b/payment/src/main/java/org/killbill/billing/payment/retry/DefaultPriorPaymentControlResult.java
index 27ce65b..4ab3d4b 100644
--- a/payment/src/main/java/org/killbill/billing/payment/retry/DefaultPriorPaymentControlResult.java
+++ b/payment/src/main/java/org/killbill/billing/payment/retry/DefaultPriorPaymentControlResult.java
@@ -30,22 +30,25 @@ public class DefaultPriorPaymentControlResult implements PriorPaymentControlResu
private final BigDecimal adjustedRetryAmount;
private final Currency adjustedCurrency;
private final UUID adjustedPaymentMethodId;
+ private final String adjustedPaymentPluginName;
private final Iterable<PluginProperty> adjustedPluginProperties;
public DefaultPriorPaymentControlResult(final boolean isAborted,
final BigDecimal adjustedRetryAmount,
final Currency adjustedCurrency,
final UUID adjustedPaymentMethodId,
+ final String adjustedPaymentPluginName,
final Iterable<PluginProperty> adjustedPluginProperties) {
this.isAborted = isAborted;
this.adjustedRetryAmount = adjustedRetryAmount;
this.adjustedCurrency = adjustedCurrency;
this.adjustedPaymentMethodId = adjustedPaymentMethodId;
this.adjustedPluginProperties = adjustedPluginProperties;
+ this.adjustedPaymentPluginName = adjustedPaymentPluginName;
}
public DefaultPriorPaymentControlResult(final boolean isAborted, final BigDecimal adjustedRetryAmount) {
- this(isAborted, adjustedRetryAmount, null, null, null);
+ this(isAborted, adjustedRetryAmount, null, null, null, null);
}
public DefaultPriorPaymentControlResult(final boolean isAborted) {
@@ -53,8 +56,8 @@ public class DefaultPriorPaymentControlResult implements PriorPaymentControlResu
}
- public DefaultPriorPaymentControlResult(final boolean isAborted, final UUID adjustedPaymentMethodId, final BigDecimal adjustedAmount, final Currency adjustedCurrency, final Iterable<PluginProperty> adjustedPluginProperties) {
- this(isAborted, adjustedAmount, adjustedCurrency, adjustedPaymentMethodId, adjustedPluginProperties);
+ public DefaultPriorPaymentControlResult(final boolean isAborted, final UUID adjustedPaymentMethodId, final String adjustedPaymentPluginName, final BigDecimal adjustedAmount, final Currency adjustedCurrency, final Iterable<PluginProperty> adjustedPluginProperties) {
+ this(isAborted, adjustedAmount, adjustedCurrency, adjustedPaymentMethodId, adjustedPaymentPluginName, adjustedPluginProperties);
}
@Override
@@ -78,6 +81,11 @@ public class DefaultPriorPaymentControlResult implements PriorPaymentControlResu
}
@Override
+ public String getAdjustedPluginName() {
+ return adjustedPaymentPluginName;
+ }
+
+ @Override
public Iterable<PluginProperty> getAdjustedPluginProperties() {
return adjustedPluginProperties;
}
diff --git a/payment/src/main/resources/org/killbill/billing/payment/dao/PaymentAttemptSqlDao.sql.stg b/payment/src/main/resources/org/killbill/billing/payment/dao/PaymentAttemptSqlDao.sql.stg
index d8699fe..39dad12 100644
--- a/payment/src/main/resources/org/killbill/billing/payment/dao/PaymentAttemptSqlDao.sql.stg
+++ b/payment/src/main/resources/org/killbill/billing/payment/dao/PaymentAttemptSqlDao.sql.stg
@@ -1,4 +1,4 @@
-group PaymentAttemptSqlDao: EntitySqlDao;
+import "org/killbill/billing/util/entity/dao/EntitySqlDao.sql.stg"
tableName() ::= "payment_attempts"
@@ -49,7 +49,7 @@ from <tableName()>
where transaction_external_key = :transactionExternalKey
<andCheckSoftDeletionWithComma("")>
<AND_CHECK_TENANT("")>
-<defaultOrderBy()>
+<defaultOrderBy("")>
;
>>
@@ -59,8 +59,8 @@ select
from <tableName()>
where payment_external_key = :paymentExternalKey
<andCheckSoftDeletionWithComma("")>
-<AND_CHECK_TENANT()>
-<defaultOrderBy()>
+<AND_CHECK_TENANT("")>
+<defaultOrderBy("")>
;
>>
@@ -72,7 +72,7 @@ from <tableName()>
where state_name = :stateName
and created_date \< :createdBeforeDate
<andCheckSoftDeletionWithComma("")>
-order by <recordIdField()> <ordering>
+order by <recordIdField("")> <ordering>
limit :rowCount offset :offset
;
>>
@@ -95,19 +95,20 @@ set state_name = :stateName
, updated_by = :updatedBy
, updated_date = :updatedDate
where id = :id
-<AND_CHECK_TENANT()>
+<AND_CHECK_TENANT("")>
;
>>
updateAttemptWithProperties() ::= <<
update <tableName()>
set state_name = :stateName
+, payment_method_id = :paymentMethodId
, transaction_id = :transactionId
, plugin_properties = :pluginProperties
, updated_by = :updatedBy
, updated_date = :updatedDate
where id = :id
-<AND_CHECK_TENANT()>
+<AND_CHECK_TENANT("")>
;
>>
diff --git a/payment/src/main/resources/org/killbill/billing/payment/dao/PaymentMethodSqlDao.sql.stg b/payment/src/main/resources/org/killbill/billing/payment/dao/PaymentMethodSqlDao.sql.stg
index 67c65f1..a26b7dd 100644
--- a/payment/src/main/resources/org/killbill/billing/payment/dao/PaymentMethodSqlDao.sql.stg
+++ b/payment/src/main/resources/org/killbill/billing/payment/dao/PaymentMethodSqlDao.sql.stg
@@ -1,6 +1,4 @@
-group PaymentMethodSqlDao: EntitySqlDao;
-
-
+import "org/killbill/billing/util/entity/dao/EntitySqlDao.sql.stg"
tableName() ::= "payment_methods"
@@ -36,7 +34,7 @@ set is_active = false
, updated_by = :updatedBy
, updated_date = :updatedDate
where id = :id
-<AND_CHECK_TENANT()>
+<AND_CHECK_TENANT("")>
;
>>
@@ -46,51 +44,51 @@ set is_active = true
, updated_by = :updatedBy
, updated_date = :updatedDate
where id = :id
-<AND_CHECK_TENANT()>
+<AND_CHECK_TENANT("")>
;
>>
getByExternalKey() ::= <<
-select <allTableFields()>
+select <allTableFields("")>
from <tableName()>
where external_key = :externalKey
-<andCheckSoftDeletionWithComma()>
-<AND_CHECK_TENANT()>
+<andCheckSoftDeletionWithComma("")>
+<AND_CHECK_TENANT("")>
;
>>
getPaymentMethodByExternalKeyIncludedDeleted() ::= <<
-select <allTableFields()>
+select <allTableFields("")>
from <tableName()>
where external_key = :externalKey
-<AND_CHECK_TENANT()>
+<AND_CHECK_TENANT("")>
;
>>
getPaymentMethodIncludedDelete(accountId) ::= <<
-select <allTableFields()>
+select <allTableFields("")>
from <tableName()>
where id = :id
-<AND_CHECK_TENANT()>
+<AND_CHECK_TENANT("")>
;
>>
getForAccount() ::= <<
select
-<allTableFields()>
+<allTableFields("")>
from <tableName()>
-where <accountRecordIdField()> = :accountRecordId
-<andCheckSoftDeletionWithComma()>
-<AND_CHECK_TENANT()>
+where <accountRecordIdField("")> = :accountRecordId
+<andCheckSoftDeletionWithComma("")>
+<AND_CHECK_TENANT("")>
;
>>
getForAccountIncludedDelete() ::= <<
select
-<allTableFields()>
+<allTableFields("")>
from <tableName()>
-where <accountRecordIdField()> = :accountRecordId
-<AND_CHECK_TENANT()>
+where <accountRecordIdField("")> = :accountRecordId
+<AND_CHECK_TENANT("")>
;
>>
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 b15a04b..395dfae 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
@@ -1,4 +1,4 @@
-group PaymentSqlDao: EntitySqlDao;
+import "org/killbill/billing/util/entity/dao/EntitySqlDao.sql.stg"
tableName() ::= "payments"
@@ -41,7 +41,7 @@ update <tableName()>
set updated_by = :updatedBy
, updated_date = :updatedDate
where id = :id
-<AND_CHECK_TENANT()>
+<AND_CHECK_TENANT("")>
;
>>
@@ -51,7 +51,7 @@ set state_name = :stateName
, updated_by = :updatedBy
, updated_date = :updatedDate
where id = :id
-<AND_CHECK_TENANT()>
+<AND_CHECK_TENANT("")>
;
>>
@@ -62,7 +62,7 @@ set state_name = :stateName
, updated_by = :updatedBy
, updated_date = :updatedDate
where id = :id
-<AND_CHECK_TENANT()>
+<AND_CHECK_TENANT("")>
;
>>
@@ -71,7 +71,7 @@ select
<allTableFields("")>
from <tableName()>
where external_key = :externalKey
-<AND_CHECK_TENANT()>
+<AND_CHECK_TENANT("")>
;
>>
@@ -87,12 +87,12 @@ select
<allTableFields("t.")>
from <tableName()> t
join (
- select <recordIdField()>
+ select <recordIdField("")>
from <tableName()>
- where state_name in (<states: {state | :state_<i0>}; separator="," >)
- <AND_CHECK_TENANT()>
- <andCheckSoftDeletionWithComma()>
- order by <recordIdField()> <ordering>
+ where state_name in (<states>)
+ <AND_CHECK_TENANT("")>
+ <andCheckSoftDeletionWithComma("")>
+ order by <recordIdField("")> <ordering>
limit :rowCount offset :offset
) optimization on <recordIdField("optimization.")> = <recordIdField("t.")>
order by <recordIdField("t.")> <ordering>
@@ -103,7 +103,7 @@ getSearchByStateCount(states) ::= <<
select
count(1) as count
from <tableName()> t
-where t.state_name in (<states: {state | :state_<i0>}; separator="," >)
+where t.state_name in (<states>)
<andCheckSoftDeletionWithComma("t.")>
<AND_CHECK_TENANT("t.")>
;
@@ -138,7 +138,7 @@ from <tableName()> t
where
created_date >= :createdAfterDate
and created_date \< :createdBeforeDate
-and state_name in (<states: {state | :state_<i0>}; separator="," >)
+and state_name in (<states>)
limit :limit
;
>>
diff --git a/payment/src/main/resources/org/killbill/billing/payment/dao/TransactionSqlDao.sql.stg b/payment/src/main/resources/org/killbill/billing/payment/dao/TransactionSqlDao.sql.stg
index 1f19f0b..cbdc740 100644
--- a/payment/src/main/resources/org/killbill/billing/payment/dao/TransactionSqlDao.sql.stg
+++ b/payment/src/main/resources/org/killbill/billing/payment/dao/TransactionSqlDao.sql.stg
@@ -1,4 +1,4 @@
-group TransactionSqlDao: EntitySqlDao;
+import "org/killbill/billing/util/entity/dao/EntitySqlDao.sql.stg"
tableName() ::= "payment_transactions"
@@ -51,8 +51,8 @@ select
<allTableFields("")>
from <tableName()>
where transaction_external_key = :transactionExternalKey
-<AND_CHECK_TENANT()>
-<defaultOrderBy()>
+<AND_CHECK_TENANT("")>
+<defaultOrderBy("")>
;
>>
@@ -68,29 +68,29 @@ set transaction_status = :transactionStatus
, updated_by = :updatedBy
, updated_date = :updatedDate
where id = :id
-<AND_CHECK_TENANT()>
+<AND_CHECK_TENANT("")>
;
>>
getByPaymentId() ::= <<
-select <allTableFields()>
+select <allTableFields("")>
from <tableName()>
where payment_id = :paymentId
-<AND_CHECK_TENANT()>
-<defaultOrderBy()>
+<AND_CHECK_TENANT("")>
+<defaultOrderBy("")>
;
>>
/* Does not include AND_CHECK_TENANT() since this is a global operation */
getByTransactionStatusPriorDateAcrossTenants(statuses, ordering) ::= <<
-select <allTableFields()>
+select <allTableFields("")>
from <tableName()>
where
created_date >= :createdAfterDate
and created_date \< :createdBeforeDate
-and transaction_status in (<statuses: {status | :status_<i0>}; separator="," >)
-order by <recordIdField()> <ordering>
+and transaction_status in (<statuses>)
+order by <recordIdField("")> <ordering>
limit :rowCount offset :offset
;
>>
@@ -102,7 +102,7 @@ from <tableName()>
where
created_date >= :createdAfterDate
and created_date \< :createdBeforeDate
-and transaction_status in (<statuses: {status | :status_<i0>}; separator="," >)
+and transaction_status in (<statuses>)
;
>>
diff --git a/payment/src/test/java/org/killbill/billing/payment/api/TestDefaultAdminPaymentApi.java b/payment/src/test/java/org/killbill/billing/payment/api/TestDefaultAdminPaymentApi.java
index e710d8b..0ba11c3 100644
--- a/payment/src/test/java/org/killbill/billing/payment/api/TestDefaultAdminPaymentApi.java
+++ b/payment/src/test/java/org/killbill/billing/payment/api/TestDefaultAdminPaymentApi.java
@@ -86,6 +86,7 @@ public class TestDefaultAdminPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
null,
BigDecimal.TEN,
Currency.EUR,
+ null,
UUID.randomUUID().toString(),
UUID.randomUUID().toString(),
ImmutableList.<PluginProperty>of(),
@@ -122,6 +123,7 @@ public class TestDefaultAdminPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
null,
BigDecimal.TEN,
Currency.EUR,
+ null,
UUID.randomUUID().toString(),
UUID.randomUUID().toString(),
ImmutableList.<PluginProperty>of(),
@@ -152,6 +154,7 @@ public class TestDefaultAdminPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
null,
BigDecimal.TEN,
Currency.EUR,
+ null,
UUID.randomUUID().toString(),
UUID.randomUUID().toString(),
ImmutableList.<PluginProperty>of(),
@@ -214,6 +217,7 @@ public class TestDefaultAdminPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
null,
BigDecimal.TEN,
Currency.EUR,
+ null,
UUID.randomUUID().toString(),
UUID.randomUUID().toString(),
ImmutableList.<PluginProperty>of(),
@@ -229,6 +233,7 @@ public class TestDefaultAdminPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
payment.getId(),
payment.getPurchasedAmount(),
payment.getCurrency(),
+ null,
UUID.randomUUID().toString(),
ImmutableList.<PluginProperty>of(),
callContext);
@@ -261,6 +266,7 @@ public class TestDefaultAdminPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
null,
BigDecimal.TEN,
Currency.EUR,
+ null,
UUID.randomUUID().toString(),
UUID.randomUUID().toString(),
ImmutableList.<PluginProperty>of(),
@@ -276,6 +282,7 @@ public class TestDefaultAdminPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
payment.getId(),
payment.getPurchasedAmount(),
payment.getCurrency(),
+ null,
UUID.randomUUID().toString(),
ImmutableList.<PluginProperty>of(new PluginProperty(MockPaymentProviderPlugin.PLUGIN_PROPERTY_PAYMENT_PLUGIN_STATUS_OVERRIDE, PaymentPluginStatus.ERROR.toString(), false)),
callContext);
diff --git a/payment/src/test/java/org/killbill/billing/payment/api/TestExternalPaymentPlugin.java b/payment/src/test/java/org/killbill/billing/payment/api/TestExternalPaymentPlugin.java
index 589ec38..48a7419 100644
--- a/payment/src/test/java/org/killbill/billing/payment/api/TestExternalPaymentPlugin.java
+++ b/payment/src/test/java/org/killbill/billing/payment/api/TestExternalPaymentPlugin.java
@@ -57,7 +57,7 @@ public class TestExternalPaymentPlugin extends PaymentTestSuiteWithEmbeddedDB {
final String paymentExternalKey = "externalKey";
final String transactionExternalKey = "transactionKey";
- final Payment payment = paymentApi.createPurchase(account, account.getPaymentMethodId(), null, requestedAmount, Currency.AED, paymentExternalKey, transactionExternalKey,
+ final Payment payment = paymentApi.createPurchase(account, account.getPaymentMethodId(), null, requestedAmount, Currency.AED, null, paymentExternalKey, transactionExternalKey,
ImmutableList.<PluginProperty>of(), callContext);
final Payment paymentPluginInfoFalse = paymentApi.getPayment(payment.getId(), false, false, ImmutableList.<PluginProperty>of(), callContext);
@@ -97,7 +97,7 @@ public class TestExternalPaymentPlugin extends PaymentTestSuiteWithEmbeddedDB {
final String transactionExternalKey = "transactionKey";
final Payment payment = paymentApi.createPurchase(account, account.getPaymentMethodId(), null, requestedAmount,
- Currency.AED, paymentExternalKey, transactionExternalKey,
+ Currency.AED, null, paymentExternalKey, transactionExternalKey,
ImmutableList.<PluginProperty>of(), callContext);
final Pagination<PaymentMethod> paymentMethods = paymentApi.getPaymentMethods(0L, 10L, false, null, callContext);
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 d31b5ca..1e98dff 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
@@ -151,6 +151,13 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
paymentApi.deletePaymentMethod(account, paymentMethodId, true, false, ImmutableList.<PluginProperty>of(), callContext);
+ List<PaymentMethod> paymentMethods = paymentApi.getAccountPaymentMethods(account.getId(), false, false, ImmutableList.<PluginProperty>of(), callContext);
+ assertEquals(paymentMethods.size(), 0);
+
+
+ paymentMethods = paymentApi.getAccountPaymentMethods(account.getId(), true, false, ImmutableList.<PluginProperty>of(), callContext);
+ assertEquals(paymentMethods.size(), 1);
+
checkPaymentMethodPagination(paymentMethodId, baseNbRecords, true);
}
@@ -189,12 +196,12 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
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(),
+ final Payment payment = paymentApi.createPurchase(account, account.getPaymentMethodId(), null, requestedAmount, Currency.EUR, null, 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);
+ final Payment newPayment = paymentApi.createRefund(account, payment.getId(),requestedAmount, Currency.EUR, null, UUID.randomUUID().toString(), ImmutableList.<PluginProperty>of(), callContext);
Assert.assertEquals(newPayment.getTransactions().size(), 2);
}
@@ -206,7 +213,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
final String paymentExternalKey = "bwwrr";
final String transactionExternalKey = "krapaut";
- final Payment payment = paymentApi.createPurchase(account, account.getPaymentMethodId(), null, requestedAmount, Currency.AED, paymentExternalKey, transactionExternalKey,
+ final Payment payment = paymentApi.createPurchase(account, account.getPaymentMethodId(), null, requestedAmount, Currency.AED, null, paymentExternalKey, transactionExternalKey,
ImmutableList.<PluginProperty>of(), callContext);
assertEquals(payment.getExternalKey(), paymentExternalKey);
@@ -242,7 +249,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
mockPaymentProviderPlugin.makeNextPaymentFailWithError();
- final Payment payment = paymentApi.createPurchase(account, account.getPaymentMethodId(), null, requestedAmount, Currency.AED, paymentExternalKey, transactionExternalKey,
+ final Payment payment = paymentApi.createPurchase(account, account.getPaymentMethodId(), null, requestedAmount, Currency.AED, null, paymentExternalKey, transactionExternalKey,
ImmutableList.<PluginProperty>of(), callContext);
assertEquals(payment.getExternalKey(), paymentExternalKey);
@@ -267,7 +274,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
assertNotNull(payment.getTransactions().get(0).getGatewayErrorMsg());
assertNotNull(payment.getTransactions().get(0).getGatewayErrorCode());
- final Payment payment2 = paymentApi.createPurchase(account, account.getPaymentMethodId(), null, requestedAmount, Currency.AED, paymentExternalKey, transactionExternalKey,
+ final Payment payment2 = paymentApi.createPurchase(account, account.getPaymentMethodId(), null, requestedAmount, Currency.AED, null, paymentExternalKey, transactionExternalKey,
ImmutableList.<PluginProperty>of(), callContext);
assertEquals(payment2.getExternalKey(), paymentExternalKey);
@@ -292,7 +299,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
mockPaymentProviderPlugin.makeNextPaymentFailWithCancellation();
- final Payment payment = paymentApi.createPurchase(account, account.getPaymentMethodId(), null, requestedAmount, Currency.AED, paymentExternalKey, transactionExternalKey,
+ final Payment payment = paymentApi.createPurchase(account, account.getPaymentMethodId(), null, requestedAmount, Currency.AED, null, paymentExternalKey, transactionExternalKey,
ImmutableList.<PluginProperty>of(), callContext);
assertEquals(payment.getExternalKey(), paymentExternalKey);
@@ -317,7 +324,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
assertNotNull(payment.getTransactions().get(0).getGatewayErrorMsg());
assertNotNull(payment.getTransactions().get(0).getGatewayErrorCode());
- final Payment payment2 = paymentApi.createPurchase(account, account.getPaymentMethodId(), null, requestedAmount, Currency.AED, paymentExternalKey, transactionExternalKey,
+ final Payment payment2 = paymentApi.createPurchase(account, account.getPaymentMethodId(), null, requestedAmount, Currency.AED, null, paymentExternalKey, transactionExternalKey,
ImmutableList.<PluginProperty>of(), callContext);
assertEquals(payment2.getExternalKey(), paymentExternalKey);
@@ -339,7 +346,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
final String paymentExternalKey = "pay external key";
final String transactionExternalKey = "txn external key";
try {
- paymentApi.createPurchase(account, account.getPaymentMethodId(), null, requestedAmount, Currency.AED,
+ paymentApi.createPurchase(account, account.getPaymentMethodId(), null, requestedAmount, Currency.AED, null,
paymentExternalKey, transactionExternalKey, ImmutableList.<PluginProperty>of(), callContext);
fail();
} catch (PaymentApiException e) {
@@ -356,7 +363,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
final String transactionExternalKey = "txn control external key";
try {
paymentApi.createPurchaseWithPaymentControl(
- account, account.getPaymentMethodId(), null, requestedAmount, Currency.AED,
+ account, account.getPaymentMethodId(), null, requestedAmount, Currency.AED, null,
paymentExternalKey, transactionExternalKey, ImmutableList.<PluginProperty>of(), CONTROL_PLUGIN_OPTIONS, callContext);
fail();
} catch (PaymentApiException e) {
@@ -373,7 +380,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
final String transactionExternalKey = "txn control external key";
try {
paymentApi.createPurchaseWithPaymentControl(
- account, account.getPaymentMethodId(), null, requestedAmount, Currency.AED,
+ account, account.getPaymentMethodId(), null, requestedAmount, Currency.AED, null,
paymentExternalKey, transactionExternalKey, ImmutableList.<PluginProperty>of(), CONTROL_PLUGIN_OPTIONS, callContext);
fail();
} catch (PaymentApiException e) {
@@ -390,7 +397,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
final String transactionExternalKey = "txn control external key";
try {
paymentApi.createPurchaseWithPaymentControl(
- account, account.getPaymentMethodId(), null, requestedAmount, Currency.AED,
+ account, account.getPaymentMethodId(), null, requestedAmount, Currency.AED, null,
paymentExternalKey, transactionExternalKey, ImmutableList.<PluginProperty>of(), CONTROL_PLUGIN_OPTIONS, callContext);
fail();
} catch (PaymentApiException e) {
@@ -406,7 +413,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
final String transactionExternalKey = UUID.randomUUID().toString();
final String transactionExternalKey2 = UUID.randomUUID().toString();
- final Payment payment = paymentApi.createAuthorization(account, account.getPaymentMethodId(), null, authAmount, Currency.AED,
+ final Payment payment = paymentApi.createAuthorization(account, account.getPaymentMethodId(), null, authAmount, Currency.AED, null,
paymentExternalKey, transactionExternalKey,
ImmutableList.<PluginProperty>of(), callContext);
@@ -434,7 +441,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
assertNotNull(payment.getTransactions().get(0).getGatewayErrorCode());
// Void the authorization
- final Payment payment2 = paymentApi.createVoid(account, payment.getId(), transactionExternalKey2, ImmutableList.<PluginProperty>of(), callContext);
+ final Payment payment2 = paymentApi.createVoid(account, payment.getId(), null, transactionExternalKey2, ImmutableList.<PluginProperty>of(), callContext);
assertEquals(payment2.getExternalKey(), paymentExternalKey);
assertEquals(payment2.getPaymentMethodId(), account.getPaymentMethodId());
@@ -461,7 +468,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
try {
// Verify further VOIDs are prohibited (see https://github.com/killbill/killbill/issues/514)
- paymentApi.createVoid(account, payment.getId(), UUID.randomUUID().toString(), ImmutableList.<PluginProperty>of(), callContext);
+ paymentApi.createVoid(account, payment.getId(), null, UUID.randomUUID().toString(), ImmutableList.<PluginProperty>of(), callContext);
Assert.fail();
} catch (final PaymentApiException e) {
Assert.assertEquals(e.getCode(), ErrorCode.PAYMENT_INVALID_OPERATION.getCode());
@@ -482,7 +489,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
final String transactionExternalKey3 = UUID.randomUUID().toString();
final String transactionExternalKey4 = UUID.randomUUID().toString();
- final Payment payment = paymentApi.createAuthorization(account, account.getPaymentMethodId(), null, authAmount, Currency.AED,
+ final Payment payment = paymentApi.createAuthorization(account, account.getPaymentMethodId(), null, authAmount, Currency.AED, null,
paymentExternalKey, transactionExternalKey,
ImmutableList.<PluginProperty>of(), callContext);
@@ -509,7 +516,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
assertNotNull(payment.getTransactions().get(0).getGatewayErrorMsg());
assertNotNull(payment.getTransactions().get(0).getGatewayErrorCode());
- final Payment payment2 = paymentApi.createCapture(account, payment.getId(), captureAmount, Currency.AED, transactionExternalKey2,
+ final Payment payment2 = paymentApi.createCapture(account, payment.getId(), captureAmount, Currency.AED, null, transactionExternalKey2,
ImmutableList.<PluginProperty>of(), callContext);
assertEquals(payment2.getExternalKey(), paymentExternalKey);
@@ -536,7 +543,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
assertNotNull(payment2.getTransactions().get(1).getGatewayErrorCode());
// Void the capture
- final Payment payment3 = paymentApi.createVoid(account, payment.getId(), transactionExternalKey3, ImmutableList.<PluginProperty>of(), callContext);
+ final Payment payment3 = paymentApi.createVoid(account, payment.getId(), null, transactionExternalKey3, ImmutableList.<PluginProperty>of(), callContext);
assertEquals(payment3.getExternalKey(), paymentExternalKey);
assertEquals(payment3.getPaymentMethodId(), account.getPaymentMethodId());
@@ -562,7 +569,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
assertNotNull(payment3.getTransactions().get(2).getGatewayErrorCode());
// Capture again
- final Payment payment4 = paymentApi.createCapture(account, payment.getId(), captureAmount, Currency.AED, transactionExternalKey4,
+ final Payment payment4 = paymentApi.createCapture(account, payment.getId(), captureAmount, Currency.AED, null, transactionExternalKey4,
ImmutableList.<PluginProperty>of(), callContext);
assertEquals(payment4.getExternalKey(), paymentExternalKey);
@@ -599,7 +606,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
final String transactionExternalKey2 = UUID.randomUUID().toString();
final String transactionExternalKey3 = UUID.randomUUID().toString();
- final Payment payment = paymentApi.createAuthorization(account, account.getPaymentMethodId(), null, authAmount, Currency.AED,
+ final Payment payment = paymentApi.createAuthorization(account, account.getPaymentMethodId(), null, authAmount, Currency.AED, null,
paymentExternalKey, transactionExternalKey,
ImmutableList.<PluginProperty>of(), callContext);
@@ -626,7 +633,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
assertNotNull(payment.getTransactions().get(0).getGatewayErrorMsg());
assertNotNull(payment.getTransactions().get(0).getGatewayErrorCode());
- final Payment payment2 = paymentApi.createCapture(account, payment.getId(), captureAmount, Currency.AED, transactionExternalKey2,
+ final Payment payment2 = paymentApi.createCapture(account, payment.getId(), captureAmount, Currency.AED, null, transactionExternalKey2,
ImmutableList.<PluginProperty>of(), callContext);
assertEquals(payment2.getExternalKey(), paymentExternalKey);
@@ -654,7 +661,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
try {
// Voiding a capture is prohibited by default
- paymentApi.createVoid(account, payment.getId(), transactionExternalKey3, ImmutableList.<PluginProperty>of(), callContext);
+ paymentApi.createVoid(account, payment.getId(), null, transactionExternalKey3, ImmutableList.<PluginProperty>of(), callContext);
Assert.fail();
} catch (final PaymentApiException e) {
Assert.assertEquals(e.getCode(), ErrorCode.PAYMENT_INVALID_OPERATION.getCode());
@@ -675,7 +682,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
final String transactionExternalKey3 = UUID.randomUUID().toString();
final String transactionExternalKey4 = UUID.randomUUID().toString();
- final Payment payment = paymentApi.createAuthorization(account, account.getPaymentMethodId(), null, authAmount, Currency.AED,
+ final Payment payment = paymentApi.createAuthorization(account, account.getPaymentMethodId(), null, authAmount, Currency.AED, null,
paymentExternalKey, transactionExternalKey,
ImmutableList.<PluginProperty>of(), callContext);
@@ -702,7 +709,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
assertNotNull(payment.getTransactions().get(0).getGatewayErrorMsg());
assertNotNull(payment.getTransactions().get(0).getGatewayErrorCode());
- final Payment payment2 = paymentApi.createCapture(account, payment.getId(), captureAmount, Currency.AED, transactionExternalKey2,
+ final Payment payment2 = paymentApi.createCapture(account, payment.getId(), captureAmount, Currency.AED, null, transactionExternalKey2,
ImmutableList.<PluginProperty>of(), callContext);
assertEquals(payment2.getExternalKey(), paymentExternalKey);
@@ -729,7 +736,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
assertNotNull(payment2.getTransactions().get(1).getGatewayErrorCode());
// Void the capture
- final Payment payment3 = paymentApi.createVoid(account, payment.getId(), transactionExternalKey3, ImmutableList.<PluginProperty>of(), callContext);
+ final Payment payment3 = paymentApi.createVoid(account, payment.getId(), null, transactionExternalKey3, ImmutableList.<PluginProperty>of(), callContext);
assertEquals(payment3.getExternalKey(), paymentExternalKey);
assertEquals(payment3.getPaymentMethodId(), account.getPaymentMethodId());
@@ -755,7 +762,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
assertNotNull(payment3.getTransactions().get(2).getGatewayErrorCode());
// Void the authorization
- final Payment payment4 = paymentApi.createVoid(account, payment.getId(), transactionExternalKey4, ImmutableList.<PluginProperty>of(), callContext);
+ final Payment payment4 = paymentApi.createVoid(account, payment.getId(), null, transactionExternalKey4, ImmutableList.<PluginProperty>of(), callContext);
assertEquals(payment4.getExternalKey(), paymentExternalKey);
assertEquals(payment4.getPaymentMethodId(), account.getPaymentMethodId());
@@ -793,13 +800,13 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
final String transactionExternalKey3 = "sioux3";
final String transactionExternalKey4 = "sioux4";
- final Payment payment = paymentApi.createAuthorization(account, account.getPaymentMethodId(), null, authAmount, Currency.USD, paymentExternalKey, transactionExternalKey,
+ final Payment payment = paymentApi.createAuthorization(account, account.getPaymentMethodId(), null, authAmount, Currency.USD, null, paymentExternalKey, transactionExternalKey,
ImmutableList.<PluginProperty>of(), callContext);
- paymentApi.createCapture(account, payment.getId(), captureAmount, Currency.USD, transactionExternalKey2,
+ paymentApi.createCapture(account, payment.getId(), captureAmount, Currency.USD, null, transactionExternalKey2,
ImmutableList.<PluginProperty>of(), callContext);
- final Payment payment3 = paymentApi.createCapture(account, payment.getId(), captureAmount, Currency.USD, transactionExternalKey3,
+ final Payment payment3 = paymentApi.createCapture(account, payment.getId(), captureAmount, Currency.USD, null, transactionExternalKey3,
ImmutableList.<PluginProperty>of(), callContext);
assertEquals(payment3.getExternalKey(), paymentExternalKey);
@@ -812,7 +819,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
assertEquals(payment3.getCurrency(), Currency.USD);
assertEquals(payment3.getTransactions().size(), 3);
- final Payment payment4 = paymentApi.createRefund(account, payment3.getId(), payment3.getCapturedAmount(), Currency.USD, transactionExternalKey4, ImmutableList.<PluginProperty>of(), callContext);
+ final Payment payment4 = paymentApi.createRefund(account, payment3.getId(), payment3.getCapturedAmount(), Currency.USD, null, transactionExternalKey4, ImmutableList.<PluginProperty>of(), callContext);
assertEquals(payment4.getAuthAmount().compareTo(authAmount), 0);
assertEquals(payment4.getCapturedAmount().compareTo(captureAmount.add(captureAmount)), 0);
assertEquals(payment4.getPurchasedAmount().compareTo(BigDecimal.ZERO), 0);
@@ -853,7 +860,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
requestedAmount,
new BigDecimal("1.0"),
Currency.USD));
- final Payment payment = paymentApi.createPurchaseWithPaymentControl(account, account.getPaymentMethodId(), null, requestedAmount, Currency.USD, paymentExternalKey, transactionExternalKey,
+ final Payment payment = paymentApi.createPurchaseWithPaymentControl(account, account.getPaymentMethodId(), null, requestedAmount, Currency.USD, null, paymentExternalKey, transactionExternalKey,
createPropertiesForInvoice(invoice), INVOICE_PAYMENT, callContext);
assertEquals(payment.getExternalKey(), paymentExternalKey);
@@ -894,7 +901,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
final String transactionExternalKey = "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis,.";
try {
- paymentApi.createPurchaseWithPaymentControl(account, account.getPaymentMethodId(), null, requestedAmount, Currency.USD, paymentExternalKey, transactionExternalKey,
+ paymentApi.createPurchaseWithPaymentControl(account, account.getPaymentMethodId(), null, requestedAmount, Currency.USD, null, paymentExternalKey, transactionExternalKey,
createPropertiesForInvoice(invoice), INVOICE_PAYMENT, callContext);
Assert.fail();
} catch (final PaymentApiException e) {
@@ -928,7 +935,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
new BigDecimal("1.0"),
Currency.USD));
try {
- paymentApi.createPurchaseWithPaymentControl(account, account.getPaymentMethodId(), null, requestedAmount, Currency.USD, paymentExternalKey, transactionExternalKey,
+ paymentApi.createPurchaseWithPaymentControl(account, account.getPaymentMethodId(), null, requestedAmount, Currency.USD, null, paymentExternalKey, transactionExternalKey,
createPropertiesForInvoice(invoice), INVOICE_PAYMENT, callContext);
} catch (final PaymentApiException expected) {
assertTrue(true);
@@ -985,7 +992,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
new BigDecimal("1.0"),
Currency.USD));
try {
- paymentApi.createPurchaseWithPaymentControl(account, account.getPaymentMethodId(), null, requestedAmount, Currency.USD, paymentExternalKey, transactionExternalKey,
+ paymentApi.createPurchaseWithPaymentControl(account, account.getPaymentMethodId(), null, requestedAmount, Currency.USD, null, paymentExternalKey, transactionExternalKey,
createPropertiesForInvoice(invoice), INVOICE_PAYMENT, callContext);
} catch (final PaymentApiException expected) {
assertTrue(true);
@@ -1016,7 +1023,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
assertEquals(payment.getTransactions().get(0).getTransactionType(), TransactionType.PURCHASE);
// Make sure we can retry and that works
- paymentApi.createPurchaseWithPaymentControl(account, account.getPaymentMethodId(), null, requestedAmount, Currency.USD, paymentExternalKey, transactionExternalKey,
+ paymentApi.createPurchaseWithPaymentControl(account, account.getPaymentMethodId(), null, requestedAmount, Currency.USD, null, paymentExternalKey, transactionExternalKey,
createPropertiesForInvoice(invoice), INVOICE_PAYMENT, callContext);
@@ -1060,7 +1067,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
Currency.USD));
try {
- paymentApi.createPurchaseWithPaymentControl(account, account.getPaymentMethodId(), null, requestedAmount, Currency.USD, paymentExternalKey, transactionExternalKey,
+ paymentApi.createPurchaseWithPaymentControl(account, account.getPaymentMethodId(), null, requestedAmount, Currency.USD, null, paymentExternalKey, transactionExternalKey,
createPropertiesForInvoice(invoice), INVOICE_PAYMENT, callContext);
Assert.fail("Unexpected success");
} catch (final PaymentApiException e) {
@@ -1092,11 +1099,11 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
Currency.USD);
invoice.addInvoiceItem(invoiceItem);
- final Payment payment = paymentApi.createPurchaseWithPaymentControl(account, account.getPaymentMethodId(), null, requestedAmount, Currency.USD, paymentExternalKey, transactionExternalKey,
+ final Payment payment = paymentApi.createPurchaseWithPaymentControl(account, account.getPaymentMethodId(), null, requestedAmount, Currency.USD, null, paymentExternalKey, transactionExternalKey,
createPropertiesForInvoice(invoice), INVOICE_PAYMENT, callContext);
final List<PluginProperty> refundProperties = ImmutableList.<PluginProperty>of();
- final Payment payment2 = paymentApi.createRefundWithPaymentControl(account, payment.getId(), requestedAmount, Currency.USD, transactionExternalKey2,
+ final Payment payment2 = paymentApi.createRefundWithPaymentControl(account, payment.getId(), requestedAmount, Currency.USD, null, transactionExternalKey2,
refundProperties, INVOICE_PAYMENT, callContext);
assertEquals(payment2.getTransactions().size(), 2);
@@ -1135,13 +1142,13 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
Currency.USD);
invoice.addInvoiceItem(invoiceItem);
- final Payment payment = paymentApi.createPurchaseWithPaymentControl(account, account.getPaymentMethodId(), null, requestedAmount, Currency.USD, paymentExternalKey, transactionExternalKey,
+ final Payment payment = paymentApi.createPurchaseWithPaymentControl(account, account.getPaymentMethodId(), null, requestedAmount, Currency.USD, null, paymentExternalKey, transactionExternalKey,
createPropertiesForInvoice(invoice), INVOICE_PAYMENT, callContext);
final List<PluginProperty> refundProperties = ImmutableList.<PluginProperty>of();
try {
- paymentApi.createRefundWithPaymentControl(account, payment.getId(), BigDecimal.TEN, Currency.USD, transactionExternalKey2,
+ paymentApi.createRefundWithPaymentControl(account, payment.getId(), BigDecimal.TEN, Currency.USD, null,transactionExternalKey2,
refundProperties, INVOICE_PAYMENT, callContext);
} catch (final PaymentApiException e) {
assertTrue(e.getCause() instanceof PaymentControlApiException);
@@ -1173,7 +1180,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
Currency.USD);
invoice.addInvoiceItem(invoiceItem);
- final Payment payment = paymentApi.createPurchaseWithPaymentControl(account, account.getPaymentMethodId(), null, requestedAmount, Currency.USD, paymentExternalKey, transactionExternalKey,
+ final Payment payment = paymentApi.createPurchaseWithPaymentControl(account, account.getPaymentMethodId(), null, requestedAmount, Currency.USD, null, paymentExternalKey, transactionExternalKey,
createPropertiesForInvoice(invoice), INVOICE_PAYMENT, callContext);
final List<PluginProperty> refundProperties = new ArrayList<PluginProperty>();
@@ -1182,7 +1189,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
final PluginProperty refundIdsProp = new PluginProperty(InvoicePaymentControlPluginApi.PROP_IPCD_REFUND_IDS_WITH_AMOUNT_KEY, uuidBigDecimalHashMap, false);
refundProperties.add(refundIdsProp);
- final Payment payment2 = paymentApi.createRefundWithPaymentControl(account, payment.getId(), null, Currency.USD, transactionExternalKey2,
+ final Payment payment2 = paymentApi.createRefundWithPaymentControl(account, payment.getId(), null, Currency.USD, null, transactionExternalKey2,
refundProperties, INVOICE_PAYMENT, callContext);
assertEquals(payment2.getTransactions().size(), 2);
@@ -1210,6 +1217,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
null,
requestedAmount,
currency,
+ null,
paymentExternalKey,
purchaseTransactionExternalKey,
properties,
@@ -1244,6 +1252,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
payment.getId(),
requestedAmount,
currency,
+ null,
chargebackTransactionExternalKey,
callContext);
@@ -1277,6 +1286,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
payment.getId(),
requestedAmount,
currency,
+ null,
UUID.randomUUID().toString(),
properties,
callContext);
@@ -1288,6 +1298,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
// First reversal
final Payment payment3 = paymentApi.createChargebackReversal(account,
payment.getId(),
+ null,
chargebackTransactionExternalKey,
callContext);
@@ -1323,6 +1334,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
payment.getId(),
refundAmount,
currency,
+ null,
refundTransactionExternalKey,
properties,
callContext);
@@ -1358,6 +1370,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
payment.getId(),
secondChargebackAmount,
currency,
+ null,
chargebackTransactionExternalKey,
callContext);
@@ -1391,6 +1404,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
payment.getId(),
refundAmount,
currency,
+ null,
UUID.randomUUID().toString(),
properties,
callContext);
@@ -1402,6 +1416,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
// Second reversal
final Payment payment6 = paymentApi.createChargebackReversal(account,
payment.getId(),
+ null,
chargebackTransactionExternalKey,
callContext);
@@ -1445,6 +1460,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
null,
requestedAmount,
currency,
+ null,
paymentExternalKey,
purchaseTransactionExternalKey,
properties,
@@ -1477,6 +1493,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
try {
paymentApi.createChargebackReversal(account,
payment.getId(),
+ null,
chargebackTransactionExternalKey,
callContext);
Assert.fail("Chargeback reversals are not permitted before a chargeback");
@@ -1565,10 +1582,10 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
public void testSimpleAuthCaptureWithInvalidPaymentId() throws Exception {
final BigDecimal requestedAmount = new BigDecimal("80.0091");
- final Payment initialPayment = paymentApi.createAuthorization(account, account.getPaymentMethodId(), null, requestedAmount, account.getCurrency(),
+ final Payment initialPayment = paymentApi.createAuthorization(account, account.getPaymentMethodId(), null, requestedAmount, account.getCurrency(), null,
UUID.randomUUID().toString(), UUID.randomUUID().toString(), ImmutableList.<PluginProperty>of(), callContext);
try {
- paymentApi.createCapture(account, UUID.randomUUID(), requestedAmount, account.getCurrency(), UUID.randomUUID().toString(), ImmutableList.<PluginProperty>of(), callContext);
+ paymentApi.createCapture(account, UUID.randomUUID(), requestedAmount, account.getCurrency(), null, UUID.randomUUID().toString(), ImmutableList.<PluginProperty>of(), callContext);
Assert.fail("Expected capture to fail...");
} catch (final PaymentApiException e) {
Assert.assertEquals(e.getCode(), ErrorCode.PAYMENT_NO_SUCH_PAYMENT.getCode());
@@ -1582,11 +1599,11 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
public void testSimpleAuthCaptureWithInvalidCurrency() throws Exception {
final BigDecimal requestedAmount = new BigDecimal("80.0091");
- final Payment initialPayment = paymentApi.createAuthorization(account, account.getPaymentMethodId(), null, requestedAmount, account.getCurrency(),
+ final Payment initialPayment = paymentApi.createAuthorization(account, account.getPaymentMethodId(), null, requestedAmount, account.getCurrency(), null,
UUID.randomUUID().toString(), UUID.randomUUID().toString(), ImmutableList.<PluginProperty>of(), callContext);
try {
- paymentApi.createCapture(account, initialPayment.getId(), requestedAmount, Currency.AMD, UUID.randomUUID().toString(), ImmutableList.<PluginProperty>of(), callContext);
+ paymentApi.createCapture(account, initialPayment.getId(), requestedAmount, Currency.AMD, null, UUID.randomUUID().toString(), ImmutableList.<PluginProperty>of(), callContext);
Assert.fail("Expected capture to fail...");
} catch (final PaymentApiException e) {
Assert.assertEquals(e.getCode(), ErrorCode.PAYMENT_INVALID_PARAMETER.getCode());
@@ -1604,7 +1621,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
final String paymentExternalKey = "krapo";
final String transactionExternalKey = "grenouye";
- final Payment payment = paymentApi.createAuthorization(account, account.getPaymentMethodId(), null, requestedAmount, Currency.EUR, paymentExternalKey, transactionExternalKey,
+ final Payment payment = paymentApi.createAuthorization(account, account.getPaymentMethodId(), null, requestedAmount, Currency.EUR, null,paymentExternalKey, transactionExternalKey,
ImmutableList.<PluginProperty>of(), callContext);
// Hack the Database to make it look like it was a failure
@@ -1614,7 +1631,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
paymentSqlDao.updateLastSuccessPaymentStateName(payment.getId().toString(), "AUTH_ERRORED", null, internalCallContext);
try {
- paymentApi.createCapture(account, payment.getId(), requestedAmount, Currency.EUR, "tetard", ImmutableList.<PluginProperty>of(), callContext);
+ paymentApi.createCapture(account, payment.getId(), requestedAmount, Currency.EUR, null, "tetard", ImmutableList.<PluginProperty>of(), callContext);
Assert.fail("Unexpected success");
} catch (final PaymentApiException e) {
Assert.assertEquals(e.getCode(), ErrorCode.PAYMENT_INVALID_OPERATION.getCode());
@@ -1654,6 +1671,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
payment.getId(),
requestedAmount,
account.getCurrency(),
+ null,
refundTransactionExternalKey,
pendingPluginProperties,
callContext);
@@ -1667,6 +1685,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
payment.getId(),
null,
null,
+ null,
refundTransactionExternalKey,
pendingPluginProperties,
callContext);
@@ -1679,6 +1698,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
payment.getId(),
refundAmount,
account.getCurrency(),
+ null,
refundTransactionExternalKey,
pendingPluginProperties,
callContext);
@@ -1691,6 +1711,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
payment.getId(),
null,
null,
+ null,
refundTransactionExternalKey,
ImmutableList.<PluginProperty>of(),
callContext);
@@ -1908,7 +1929,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
mockPaymentProviderPlugin.makePluginWaitSomeMilliseconds((int) (paymentConfig.getPaymentPluginTimeout().getMillis() + 100));
try {
- paymentApi.createPurchase(account, account.getPaymentMethodId(), null, requestedAmount, Currency.AED,
+ paymentApi.createPurchase(account, account.getPaymentMethodId(), null, requestedAmount, Currency.AED, null,
paymentExternalKey, transactionExternalKey, ImmutableList.<PluginProperty>of(), callContext);
fail();
} catch (PaymentApiException e) {
@@ -1925,7 +1946,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
mockPaymentProviderPlugin.makePluginWaitSomeMilliseconds((int) (paymentConfig.getPaymentPluginTimeout().getMillis() + 100));
try {
paymentApi.createPurchaseWithPaymentControl(
- account, account.getPaymentMethodId(), null, requestedAmount, Currency.AED, paymentExternalKey,
+ account, account.getPaymentMethodId(), null, requestedAmount, Currency.AED, null, paymentExternalKey,
transactionExternalKey, ImmutableList.<PluginProperty>of(), CONTROL_PLUGIN_OPTIONS, callContext);
fail();
} catch (PaymentApiException e) {
@@ -2496,6 +2517,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
paymentId,
amount,
amount == null ? null : account.getCurrency(),
+ null,
paymentExternalKey,
paymentTransactionExternalKey,
pluginProperties,
@@ -2506,6 +2528,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
paymentId,
amount,
amount == null ? null : account.getCurrency(),
+ null,
paymentExternalKey,
paymentTransactionExternalKey,
pluginProperties,
@@ -2516,6 +2539,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
paymentId,
amount,
amount == null ? null : account.getCurrency(),
+ null,
paymentExternalKey,
paymentTransactionExternalKey,
pluginProperties,
@@ -2525,6 +2549,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
paymentId,
amount,
amount == null ? null : account.getCurrency(),
+ null,
paymentTransactionExternalKey,
pluginProperties,
callContext);
@@ -2533,6 +2558,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
paymentId,
amount,
amount == null ? null : account.getCurrency(),
+ null,
paymentTransactionExternalKey,
pluginProperties,
callContext);
diff --git a/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApiNoDB.java b/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApiNoDB.java
index bf1e53c..b71b66f 100644
--- a/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApiNoDB.java
+++ b/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApiNoDB.java
@@ -136,7 +136,7 @@ public class TestPaymentApiNoDB extends PaymentTestSuiteNoDB {
final PluginProperty prop1 = new PluginProperty(InvoicePaymentControlPluginApi.PROP_IPCD_INVOICE_ID, invoice.getId().toString(), false);
properties.add(prop1);
- final Payment paymentInfo = paymentApi.createPurchaseWithPaymentControl(account, account.getPaymentMethodId(), null, requestedAmount, account.getCurrency(),
+ final Payment paymentInfo = paymentApi.createPurchaseWithPaymentControl(account, account.getPaymentMethodId(), null, requestedAmount, account.getCurrency(), null,
invoice.getId().toString(), UUID.randomUUID().toString(), properties, PAYMENT_OPTIONS, callContext);
if (expectedAmount == null) {
fail("Expected to fail because requested amount > invoice amount");
@@ -163,7 +163,7 @@ public class TestPaymentApiNoDB extends PaymentTestSuiteNoDB {
@Test(groups = "fast")
public void testPaymentMethods() throws Exception {
- List<PaymentMethod> methods = paymentApi.getAccountPaymentMethods(account.getId(), false, PLUGIN_PROPERTIES, callContext);
+ List<PaymentMethod> methods = paymentApi.getAccountPaymentMethods(account.getId(), false, false, PLUGIN_PROPERTIES, callContext);
assertEquals(methods.size(), 1);
final PaymentMethod initDefaultMethod = methods.get(0);
@@ -172,7 +172,7 @@ public class TestPaymentApiNoDB extends PaymentTestSuiteNoDB {
final PaymentMethodPlugin newPaymentMethod = new DefaultNoOpPaymentMethodPlugin(UUID.randomUUID().toString(), true, null);
account = testHelper.addTestPaymentMethod(account, newPaymentMethod, PLUGIN_PROPERTIES);
- methods = paymentApi.getAccountPaymentMethods(account.getId(), false, PLUGIN_PROPERTIES, callContext);
+ methods = paymentApi.getAccountPaymentMethods(account.getId(), false, false, PLUGIN_PROPERTIES, callContext);
assertEquals(methods.size(), 2);
boolean failed = false;
@@ -184,13 +184,14 @@ public class TestPaymentApiNoDB extends PaymentTestSuiteNoDB {
assertTrue(failed);
paymentApi.deletePaymentMethod(account, initDefaultMethod.getId(), true, false, PLUGIN_PROPERTIES, callContext);
- methods = paymentApi.getAccountPaymentMethods(account.getId(), false, PLUGIN_PROPERTIES, callContext);
+ methods = paymentApi.getAccountPaymentMethods(account.getId(), false, false, PLUGIN_PROPERTIES, callContext);
assertEquals(methods.size(), 1);
// NOW retry with default payment method with special flag
paymentApi.deletePaymentMethod(account, account.getPaymentMethodId(), true, false, PLUGIN_PROPERTIES, callContext);
- methods = paymentApi.getAccountPaymentMethods(account.getId(), false, PLUGIN_PROPERTIES, callContext);
+ methods = paymentApi.getAccountPaymentMethods(account.getId(), false, false, PLUGIN_PROPERTIES, callContext);
assertEquals(methods.size(), 0);
+
}
}
diff --git a/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApiWithControl.java b/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApiWithControl.java
index 545fcc4..5d52fa5 100644
--- a/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApiWithControl.java
+++ b/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApiWithControl.java
@@ -97,8 +97,8 @@ public class TestPaymentApiWithControl extends PaymentTestSuiteWithEmbeddedDB {
final UUID newPaymentMethodId = paymentApi.addPaymentMethod(account, null, MockPaymentProviderPlugin.PLUGIN_NAME, false, paymentMethodInfo, ImmutableList.<PluginProperty>of(), callContext);
testPaymentControlPluginApi.setNewPaymentMethodId(newPaymentMethodId);
- final Payment payment = paymentApi.createAuthorizationWithPaymentControl(account, account.getPaymentMethodId(), null, BigDecimal.TEN, Currency.USD, UUID.randomUUID().toString(),
- UUID.randomUUID().toString(), ImmutableList.<PluginProperty>of(), PAYMENT_OPTIONS, callContext);
+ final Payment payment = paymentApi.createAuthorizationWithPaymentControl(account, account.getPaymentMethodId(), null, BigDecimal.TEN, Currency.USD, null,null, UUID.randomUUID().toString(),
+ ImmutableList.<PluginProperty>of(), PAYMENT_OPTIONS, callContext);
Assert.assertEquals(payment.getPaymentMethodId(), newPaymentMethodId);
verifyOnSuccess(payment.getId(),
@@ -115,7 +115,7 @@ public class TestPaymentApiWithControl extends PaymentTestSuiteWithEmbeddedDB {
final BigDecimal requestedAmount = BigDecimal.TEN;
final Iterable<PluginProperty> pendingPluginProperties = ImmutableList.<PluginProperty>of(new PluginProperty(MockPaymentProviderPlugin.PLUGIN_PROPERTY_PAYMENT_PLUGIN_STATUS_OVERRIDE, PaymentPluginStatus.PENDING, false));
- Payment payment = paymentApi.createAuthorizationWithPaymentControl(account, account.getPaymentMethodId(), null, requestedAmount, Currency.USD, UUID.randomUUID().toString(),
+ Payment payment = paymentApi.createAuthorizationWithPaymentControl(account, account.getPaymentMethodId(), null, requestedAmount, Currency.USD, null,UUID.randomUUID().toString(),
paymentTransactionExternalKey, pendingPluginProperties, PAYMENT_OPTIONS, callContext);
Assert.assertEquals(payment.getAuthAmount().compareTo(BigDecimal.ZERO), 0);
Assert.assertEquals(payment.getCapturedAmount().compareTo(BigDecimal.ZERO), 0);
@@ -129,7 +129,7 @@ public class TestPaymentApiWithControl extends PaymentTestSuiteWithEmbeddedDB {
requestedAmount,
Currency.USD);
- payment = paymentApi.createAuthorizationWithPaymentControl(account, payment.getPaymentMethodId(), payment.getId(), requestedAmount, payment.getCurrency(), payment.getExternalKey(),
+ payment = paymentApi.createAuthorizationWithPaymentControl(account, payment.getPaymentMethodId(), payment.getId(), requestedAmount, payment.getCurrency(), null,payment.getExternalKey(),
payment.getTransactions().get(0).getExternalKey(), ImmutableList.<PluginProperty>of(), PAYMENT_OPTIONS, callContext);
Assert.assertEquals(payment.getAuthAmount().compareTo(requestedAmount), 0);
Assert.assertEquals(payment.getCapturedAmount().compareTo(BigDecimal.ZERO), 0);
@@ -151,7 +151,7 @@ public class TestPaymentApiWithControl extends PaymentTestSuiteWithEmbeddedDB {
final BigDecimal requestedAmount = BigDecimal.TEN;
final Iterable<PluginProperty> pendingPluginProperties = ImmutableList.<PluginProperty>of(new PluginProperty(MockPaymentProviderPlugin.PLUGIN_PROPERTY_PAYMENT_PLUGIN_STATUS_OVERRIDE, PaymentPluginStatus.UNDEFINED, false));
- Payment payment = paymentApi.createAuthorizationWithPaymentControl(account, account.getPaymentMethodId(), null, requestedAmount, Currency.USD, UUID.randomUUID().toString(),
+ Payment payment = paymentApi.createAuthorizationWithPaymentControl(account, account.getPaymentMethodId(), null, requestedAmount, Currency.USD, null,UUID.randomUUID().toString(),
paymentTransactionExternalKey, pendingPluginProperties, PAYMENT_OPTIONS, callContext);
Assert.assertEquals(payment.getAuthAmount().compareTo(BigDecimal.ZERO), 0);
Assert.assertEquals(payment.getCapturedAmount().compareTo(BigDecimal.ZERO), 0);
@@ -166,7 +166,7 @@ public class TestPaymentApiWithControl extends PaymentTestSuiteWithEmbeddedDB {
Currency.USD);
try {
- payment = paymentApi.createAuthorizationWithPaymentControl(account, payment.getPaymentMethodId(), payment.getId(), requestedAmount, payment.getCurrency(), payment.getExternalKey(),
+ payment = paymentApi.createAuthorizationWithPaymentControl(account, payment.getPaymentMethodId(), payment.getId(), requestedAmount, payment.getCurrency(), null,payment.getExternalKey(),
payment.getTransactions().get(0).getExternalKey(), ImmutableList.<PluginProperty>of(), PAYMENT_OPTIONS, callContext);
Assert.fail();
} catch (final PaymentApiException e) {
@@ -191,7 +191,7 @@ public class TestPaymentApiWithControl extends PaymentTestSuiteWithEmbeddedDB {
public void testCreateAuthSuccessCapturePendingWithControlCompleteWithControl() throws PaymentApiException {
final BigDecimal requestedAmount = BigDecimal.TEN;
- Payment payment = paymentApi.createAuthorization(account, account.getPaymentMethodId(), null, requestedAmount, Currency.USD, UUID.randomUUID().toString(),
+ Payment payment = paymentApi.createAuthorization(account, account.getPaymentMethodId(), null, requestedAmount, Currency.USD, null,UUID.randomUUID().toString(),
UUID.randomUUID().toString(), ImmutableList.<PluginProperty>of(), callContext);
Assert.assertEquals(payment.getAuthAmount().compareTo(requestedAmount), 0);
Assert.assertEquals(payment.getCapturedAmount().compareTo(BigDecimal.ZERO), 0);
@@ -201,7 +201,7 @@ public class TestPaymentApiWithControl extends PaymentTestSuiteWithEmbeddedDB {
final String paymentTransactionExternalKey = UUID.randomUUID().toString();
final Iterable<PluginProperty> pendingPluginProperties = ImmutableList.<PluginProperty>of(new PluginProperty(MockPaymentProviderPlugin.PLUGIN_PROPERTY_PAYMENT_PLUGIN_STATUS_OVERRIDE, PaymentPluginStatus.PENDING, false));
- payment = paymentApi.createCaptureWithPaymentControl(account, payment.getId(), requestedAmount, payment.getCurrency(), paymentTransactionExternalKey,
+ payment = paymentApi.createCaptureWithPaymentControl(account, payment.getId(), requestedAmount, payment.getCurrency(), null,paymentTransactionExternalKey,
pendingPluginProperties, PAYMENT_OPTIONS, callContext);
Assert.assertEquals(payment.getAuthAmount().compareTo(requestedAmount), 0);
Assert.assertEquals(payment.getCapturedAmount().compareTo(BigDecimal.ZERO), 0);
@@ -218,7 +218,7 @@ public class TestPaymentApiWithControl extends PaymentTestSuiteWithEmbeddedDB {
requestedAmount,
Currency.USD);
- payment = paymentApi.createCaptureWithPaymentControl(account, payment.getId(), requestedAmount, payment.getCurrency(), paymentTransactionExternalKey,
+ payment = paymentApi.createCaptureWithPaymentControl(account, payment.getId(), requestedAmount, payment.getCurrency(), null,paymentTransactionExternalKey,
ImmutableList.<PluginProperty>of(), PAYMENT_OPTIONS, callContext);
Assert.assertEquals(payment.getAuthAmount().compareTo(requestedAmount), 0);
Assert.assertEquals(payment.getCapturedAmount().compareTo(requestedAmount), 0);
@@ -240,7 +240,7 @@ public class TestPaymentApiWithControl extends PaymentTestSuiteWithEmbeddedDB {
public void testCreateAuthSuccessCaptureUnknownWithControlCompleteWithControl() throws PaymentApiException {
final BigDecimal requestedAmount = BigDecimal.TEN;
- Payment payment = paymentApi.createAuthorization(account, account.getPaymentMethodId(), null, requestedAmount, Currency.USD, UUID.randomUUID().toString(),
+ Payment payment = paymentApi.createAuthorization(account, account.getPaymentMethodId(), null, requestedAmount, Currency.USD, null,UUID.randomUUID().toString(),
UUID.randomUUID().toString(), ImmutableList.<PluginProperty>of(), callContext);
Assert.assertEquals(payment.getAuthAmount().compareTo(requestedAmount), 0);
Assert.assertEquals(payment.getCapturedAmount().compareTo(BigDecimal.ZERO), 0);
@@ -250,7 +250,7 @@ public class TestPaymentApiWithControl extends PaymentTestSuiteWithEmbeddedDB {
final String paymentTransactionExternalKey = UUID.randomUUID().toString();
final Iterable<PluginProperty> pendingPluginProperties = ImmutableList.<PluginProperty>of(new PluginProperty(MockPaymentProviderPlugin.PLUGIN_PROPERTY_PAYMENT_PLUGIN_STATUS_OVERRIDE, PaymentPluginStatus.UNDEFINED, false));
- payment = paymentApi.createCaptureWithPaymentControl(account, payment.getId(), requestedAmount, payment.getCurrency(), paymentTransactionExternalKey,
+ payment = paymentApi.createCaptureWithPaymentControl(account, payment.getId(), requestedAmount, payment.getCurrency(), null,paymentTransactionExternalKey,
pendingPluginProperties, PAYMENT_OPTIONS, callContext);
Assert.assertEquals(payment.getAuthAmount().compareTo(requestedAmount), 0);
Assert.assertEquals(payment.getCapturedAmount().compareTo(BigDecimal.ZERO), 0);
@@ -268,7 +268,7 @@ public class TestPaymentApiWithControl extends PaymentTestSuiteWithEmbeddedDB {
Currency.USD);
try {
- payment = paymentApi.createCaptureWithPaymentControl(account, payment.getId(), requestedAmount, payment.getCurrency(), paymentTransactionExternalKey,
+ payment = paymentApi.createCaptureWithPaymentControl(account, payment.getId(), requestedAmount, payment.getCurrency(), null,paymentTransactionExternalKey,
pendingPluginProperties, PAYMENT_OPTIONS, callContext);
Assert.fail();
} catch (final PaymentApiException e) {
@@ -296,7 +296,7 @@ public class TestPaymentApiWithControl extends PaymentTestSuiteWithEmbeddedDB {
final BigDecimal requestedAmount = BigDecimal.TEN;
final Iterable<PluginProperty> pendingPluginProperties = ImmutableList.<PluginProperty>of(new PluginProperty(MockPaymentProviderPlugin.PLUGIN_PROPERTY_PAYMENT_PLUGIN_STATUS_OVERRIDE, PaymentPluginStatus.PENDING, false));
- Payment payment = paymentApi.createAuthorizationWithPaymentControl(account, account.getPaymentMethodId(), null, requestedAmount, Currency.USD, UUID.randomUUID().toString(),
+ Payment payment = paymentApi.createAuthorizationWithPaymentControl(account, account.getPaymentMethodId(), null, requestedAmount, Currency.USD, null,UUID.randomUUID().toString(),
paymentTransactionExternalKey, pendingPluginProperties, PAYMENT_OPTIONS, callContext);
Assert.assertEquals(payment.getAuthAmount().compareTo(BigDecimal.ZERO), 0);
Assert.assertEquals(payment.getCapturedAmount().compareTo(BigDecimal.ZERO), 0);
@@ -310,7 +310,7 @@ public class TestPaymentApiWithControl extends PaymentTestSuiteWithEmbeddedDB {
requestedAmount,
Currency.USD);
- payment = paymentApi.createAuthorization(account, account.getPaymentMethodId(), payment.getId(), requestedAmount, payment.getCurrency(), payment.getExternalKey(),
+ payment = paymentApi.createAuthorization(account, account.getPaymentMethodId(), payment.getId(), requestedAmount, payment.getCurrency(), null,payment.getExternalKey(),
payment.getTransactions().get(0).getExternalKey(), ImmutableList.<PluginProperty>of(), callContext);
Assert.assertEquals(payment.getAuthAmount().compareTo(requestedAmount), 0);
Assert.assertEquals(payment.getCapturedAmount().compareTo(BigDecimal.ZERO), 0);
@@ -325,7 +325,7 @@ public class TestPaymentApiWithControl extends PaymentTestSuiteWithEmbeddedDB {
final BigDecimal requestedAmount = BigDecimal.TEN;
final Iterable<PluginProperty> pendingPluginProperties = ImmutableList.<PluginProperty>of(new PluginProperty(MockPaymentProviderPlugin.PLUGIN_PROPERTY_PAYMENT_PLUGIN_STATUS_OVERRIDE, PaymentPluginStatus.UNDEFINED, false));
- Payment payment = paymentApi.createAuthorizationWithPaymentControl(account, account.getPaymentMethodId(), null, requestedAmount, Currency.USD, UUID.randomUUID().toString(),
+ Payment payment = paymentApi.createAuthorizationWithPaymentControl(account, account.getPaymentMethodId(), null, requestedAmount, Currency.USD, null,UUID.randomUUID().toString(),
paymentTransactionExternalKey, pendingPluginProperties, PAYMENT_OPTIONS, callContext);
Assert.assertEquals(payment.getAuthAmount().compareTo(BigDecimal.ZERO), 0);
Assert.assertEquals(payment.getCapturedAmount().compareTo(BigDecimal.ZERO), 0);
@@ -340,7 +340,7 @@ public class TestPaymentApiWithControl extends PaymentTestSuiteWithEmbeddedDB {
Currency.USD);
try {
- payment = paymentApi.createAuthorization(account, account.getPaymentMethodId(), payment.getId(), requestedAmount, payment.getCurrency(), payment.getExternalKey(),
+ payment = paymentApi.createAuthorization(account, account.getPaymentMethodId(), payment.getId(), requestedAmount, payment.getCurrency(), null,payment.getExternalKey(),
payment.getTransactions().get(0).getExternalKey(), ImmutableList.<PluginProperty>of(), callContext);
Assert.fail();
} catch (final PaymentApiException e) {
@@ -358,7 +358,7 @@ public class TestPaymentApiWithControl extends PaymentTestSuiteWithEmbeddedDB {
public void testCreateAuthSuccessCapturePendingWithControlCompleteNoControl() throws PaymentApiException {
final BigDecimal requestedAmount = BigDecimal.TEN;
- Payment payment = paymentApi.createAuthorization(account, account.getPaymentMethodId(), null, requestedAmount, Currency.USD, UUID.randomUUID().toString(),
+ Payment payment = paymentApi.createAuthorization(account, account.getPaymentMethodId(), null, requestedAmount, Currency.USD, null,UUID.randomUUID().toString(),
UUID.randomUUID().toString(), ImmutableList.<PluginProperty>of(), callContext);
Assert.assertEquals(payment.getAuthAmount().compareTo(requestedAmount), 0);
Assert.assertEquals(payment.getCapturedAmount().compareTo(BigDecimal.ZERO), 0);
@@ -368,7 +368,7 @@ public class TestPaymentApiWithControl extends PaymentTestSuiteWithEmbeddedDB {
final String paymentTransactionExternalKey = UUID.randomUUID().toString();
final Iterable<PluginProperty> pendingPluginProperties = ImmutableList.<PluginProperty>of(new PluginProperty(MockPaymentProviderPlugin.PLUGIN_PROPERTY_PAYMENT_PLUGIN_STATUS_OVERRIDE, PaymentPluginStatus.PENDING, false));
- payment = paymentApi.createCaptureWithPaymentControl(account, payment.getId(), requestedAmount, payment.getCurrency(), paymentTransactionExternalKey,
+ payment = paymentApi.createCaptureWithPaymentControl(account, payment.getId(), requestedAmount, payment.getCurrency(), null,paymentTransactionExternalKey,
pendingPluginProperties, PAYMENT_OPTIONS, callContext);
Assert.assertEquals(payment.getAuthAmount().compareTo(requestedAmount), 0);
Assert.assertEquals(payment.getCapturedAmount().compareTo(BigDecimal.ZERO), 0);
@@ -385,7 +385,7 @@ public class TestPaymentApiWithControl extends PaymentTestSuiteWithEmbeddedDB {
requestedAmount,
Currency.USD);
- payment = paymentApi.createCapture(account, payment.getId(), requestedAmount, payment.getCurrency(), paymentTransactionExternalKey, ImmutableList.<PluginProperty>of(), callContext);
+ payment = paymentApi.createCapture(account, payment.getId(), requestedAmount, payment.getCurrency(), null,paymentTransactionExternalKey, ImmutableList.<PluginProperty>of(), callContext);
Assert.assertEquals(payment.getAuthAmount().compareTo(requestedAmount), 0);
Assert.assertEquals(payment.getCapturedAmount().compareTo(requestedAmount), 0);
Assert.assertEquals(payment.getTransactions().size(), 2);
@@ -399,7 +399,7 @@ public class TestPaymentApiWithControl extends PaymentTestSuiteWithEmbeddedDB {
public void testCreateAuthSuccessCaptureUnknownWithControlCompleteNoControl() throws PaymentApiException {
final BigDecimal requestedAmount = BigDecimal.TEN;
- Payment payment = paymentApi.createAuthorization(account, account.getPaymentMethodId(), null, requestedAmount, Currency.USD, UUID.randomUUID().toString(),
+ Payment payment = paymentApi.createAuthorization(account, account.getPaymentMethodId(), null, requestedAmount, Currency.USD, null,UUID.randomUUID().toString(),
UUID.randomUUID().toString(), ImmutableList.<PluginProperty>of(), callContext);
Assert.assertEquals(payment.getAuthAmount().compareTo(requestedAmount), 0);
Assert.assertEquals(payment.getCapturedAmount().compareTo(BigDecimal.ZERO), 0);
@@ -409,7 +409,7 @@ public class TestPaymentApiWithControl extends PaymentTestSuiteWithEmbeddedDB {
final String paymentTransactionExternalKey = UUID.randomUUID().toString();
final Iterable<PluginProperty> pendingPluginProperties = ImmutableList.<PluginProperty>of(new PluginProperty(MockPaymentProviderPlugin.PLUGIN_PROPERTY_PAYMENT_PLUGIN_STATUS_OVERRIDE, PaymentPluginStatus.UNDEFINED, false));
- payment = paymentApi.createCaptureWithPaymentControl(account, payment.getId(), requestedAmount, payment.getCurrency(), paymentTransactionExternalKey,
+ payment = paymentApi.createCaptureWithPaymentControl(account, payment.getId(), requestedAmount, payment.getCurrency(), null,paymentTransactionExternalKey,
pendingPluginProperties, PAYMENT_OPTIONS, callContext);
Assert.assertEquals(payment.getAuthAmount().compareTo(requestedAmount), 0);
Assert.assertEquals(payment.getCapturedAmount().compareTo(BigDecimal.ZERO), 0);
@@ -427,7 +427,7 @@ public class TestPaymentApiWithControl extends PaymentTestSuiteWithEmbeddedDB {
Currency.USD);
try {
- payment = paymentApi.createCapture(account, payment.getId(), requestedAmount, payment.getCurrency(), paymentTransactionExternalKey, ImmutableList.<PluginProperty>of(), callContext);
+ payment = paymentApi.createCapture(account, payment.getId(), requestedAmount, payment.getCurrency(), null,paymentTransactionExternalKey, ImmutableList.<PluginProperty>of(), callContext);
Assert.fail();
} catch (final PaymentApiException e) {
Assert.assertEquals(e.getCode(), ErrorCode.PAYMENT_INVALID_OPERATION.getCode());
@@ -447,14 +447,14 @@ public class TestPaymentApiWithControl extends PaymentTestSuiteWithEmbeddedDB {
final BigDecimal requestedAmount = BigDecimal.TEN;
final Iterable<PluginProperty> pendingPluginProperties = ImmutableList.<PluginProperty>of(new PluginProperty(MockPaymentProviderPlugin.PLUGIN_PROPERTY_PAYMENT_PLUGIN_STATUS_OVERRIDE, PaymentPluginStatus.PENDING, false));
- Payment payment = paymentApi.createAuthorization(account, account.getPaymentMethodId(), null, requestedAmount, Currency.USD, UUID.randomUUID().toString(),
+ Payment payment = paymentApi.createAuthorization(account, account.getPaymentMethodId(), null, requestedAmount, Currency.USD, null,UUID.randomUUID().toString(),
paymentTransactionExternalKey, pendingPluginProperties, callContext);
Assert.assertEquals(payment.getAuthAmount().compareTo(BigDecimal.ZERO), 0);
Assert.assertEquals(payment.getCapturedAmount().compareTo(BigDecimal.ZERO), 0);
Assert.assertEquals(payment.getTransactions().size(), 1);
Assert.assertNull(((DefaultPaymentTransaction) payment.getTransactions().get(0)).getAttemptId());
- payment = paymentApi.createAuthorizationWithPaymentControl(account, payment.getPaymentMethodId(), payment.getId(), requestedAmount, payment.getCurrency(), payment.getExternalKey(),
+ payment = paymentApi.createAuthorizationWithPaymentControl(account, payment.getPaymentMethodId(), payment.getId(), requestedAmount, payment.getCurrency(), null,payment.getExternalKey(),
payment.getTransactions().get(0).getExternalKey(), ImmutableList.<PluginProperty>of(), PAYMENT_OPTIONS, callContext);
Assert.assertEquals(payment.getAuthAmount().compareTo(requestedAmount), 0);
Assert.assertEquals(payment.getCapturedAmount().compareTo(BigDecimal.ZERO), 0);
@@ -476,7 +476,7 @@ public class TestPaymentApiWithControl extends PaymentTestSuiteWithEmbeddedDB {
final BigDecimal requestedAmount = BigDecimal.TEN;
final Iterable<PluginProperty> pendingPluginProperties = ImmutableList.<PluginProperty>of(new PluginProperty(MockPaymentProviderPlugin.PLUGIN_PROPERTY_PAYMENT_PLUGIN_STATUS_OVERRIDE, PaymentPluginStatus.UNDEFINED, false));
- Payment payment = paymentApi.createAuthorization(account, account.getPaymentMethodId(), null, requestedAmount, Currency.USD, UUID.randomUUID().toString(),
+ Payment payment = paymentApi.createAuthorization(account, account.getPaymentMethodId(), null, requestedAmount, Currency.USD, null,UUID.randomUUID().toString(),
paymentTransactionExternalKey, pendingPluginProperties, callContext);
Assert.assertEquals(payment.getAuthAmount().compareTo(BigDecimal.ZERO), 0);
Assert.assertEquals(payment.getCapturedAmount().compareTo(BigDecimal.ZERO), 0);
@@ -484,7 +484,7 @@ public class TestPaymentApiWithControl extends PaymentTestSuiteWithEmbeddedDB {
Assert.assertNull(((DefaultPaymentTransaction) payment.getTransactions().get(0)).getAttemptId());
try {
- payment = paymentApi.createAuthorizationWithPaymentControl(account, payment.getPaymentMethodId(), payment.getId(), requestedAmount, payment.getCurrency(), payment.getExternalKey(),
+ payment = paymentApi.createAuthorizationWithPaymentControl(account, payment.getPaymentMethodId(), payment.getId(), requestedAmount, payment.getCurrency(), null,payment.getExternalKey(),
payment.getTransactions().get(0).getExternalKey(), ImmutableList.<PluginProperty>of(), PAYMENT_OPTIONS, callContext);
Assert.fail();
} catch (final PaymentApiException e) {
@@ -509,7 +509,7 @@ public class TestPaymentApiWithControl extends PaymentTestSuiteWithEmbeddedDB {
public void testCreateAuthSuccessCapturePendingNoControlCompleteWithControl() throws PaymentApiException {
final BigDecimal requestedAmount = BigDecimal.TEN;
- Payment payment = paymentApi.createAuthorization(account, account.getPaymentMethodId(), null, requestedAmount, Currency.USD, UUID.randomUUID().toString(),
+ Payment payment = paymentApi.createAuthorization(account, account.getPaymentMethodId(), null, requestedAmount, Currency.USD, null,UUID.randomUUID().toString(),
UUID.randomUUID().toString(), ImmutableList.<PluginProperty>of(), callContext);
Assert.assertEquals(payment.getAuthAmount().compareTo(requestedAmount), 0);
Assert.assertEquals(payment.getCapturedAmount().compareTo(BigDecimal.ZERO), 0);
@@ -519,7 +519,7 @@ public class TestPaymentApiWithControl extends PaymentTestSuiteWithEmbeddedDB {
final String paymentTransactionExternalKey = UUID.randomUUID().toString();
final Iterable<PluginProperty> pendingPluginProperties = ImmutableList.<PluginProperty>of(new PluginProperty(MockPaymentProviderPlugin.PLUGIN_PROPERTY_PAYMENT_PLUGIN_STATUS_OVERRIDE, PaymentPluginStatus.PENDING, false));
- payment = paymentApi.createCapture(account, payment.getId(), requestedAmount, payment.getCurrency(), paymentTransactionExternalKey, pendingPluginProperties, callContext);
+ payment = paymentApi.createCapture(account, payment.getId(), requestedAmount, payment.getCurrency(), null,paymentTransactionExternalKey, pendingPluginProperties, callContext);
Assert.assertEquals(payment.getAuthAmount().compareTo(requestedAmount), 0);
Assert.assertEquals(payment.getCapturedAmount().compareTo(BigDecimal.ZERO), 0);
Assert.assertEquals(payment.getTransactions().size(), 2);
@@ -528,7 +528,7 @@ public class TestPaymentApiWithControl extends PaymentTestSuiteWithEmbeddedDB {
Assert.assertEquals(payment.getTransactions().get(1).getTransactionStatus(), TransactionStatus.PENDING);
Assert.assertEquals(payment.getTransactions().get(1).getExternalKey(), paymentTransactionExternalKey);
- payment = paymentApi.createCaptureWithPaymentControl(account, payment.getId(), requestedAmount, payment.getCurrency(), paymentTransactionExternalKey,
+ payment = paymentApi.createCaptureWithPaymentControl(account, payment.getId(), requestedAmount, payment.getCurrency(), null,paymentTransactionExternalKey,
ImmutableList.<PluginProperty>of(), PAYMENT_OPTIONS, callContext);
Assert.assertEquals(payment.getAuthAmount().compareTo(requestedAmount), 0);
Assert.assertEquals(payment.getCapturedAmount().compareTo(requestedAmount), 0);
@@ -550,7 +550,7 @@ public class TestPaymentApiWithControl extends PaymentTestSuiteWithEmbeddedDB {
public void testCreateAuthSuccessCaptureUnknownNoControlCompleteWithControl() throws PaymentApiException {
final BigDecimal requestedAmount = BigDecimal.TEN;
- Payment payment = paymentApi.createAuthorization(account, account.getPaymentMethodId(), null, requestedAmount, Currency.USD, UUID.randomUUID().toString(),
+ Payment payment = paymentApi.createAuthorization(account, account.getPaymentMethodId(), null, requestedAmount, Currency.USD, null,UUID.randomUUID().toString(),
UUID.randomUUID().toString(), ImmutableList.<PluginProperty>of(), callContext);
Assert.assertEquals(payment.getAuthAmount().compareTo(requestedAmount), 0);
Assert.assertEquals(payment.getCapturedAmount().compareTo(BigDecimal.ZERO), 0);
@@ -560,7 +560,7 @@ public class TestPaymentApiWithControl extends PaymentTestSuiteWithEmbeddedDB {
final String paymentTransactionExternalKey = UUID.randomUUID().toString();
final Iterable<PluginProperty> pendingPluginProperties = ImmutableList.<PluginProperty>of(new PluginProperty(MockPaymentProviderPlugin.PLUGIN_PROPERTY_PAYMENT_PLUGIN_STATUS_OVERRIDE, PaymentPluginStatus.UNDEFINED, false));
- payment = paymentApi.createCapture(account, payment.getId(), requestedAmount, payment.getCurrency(), paymentTransactionExternalKey, pendingPluginProperties, callContext);
+ payment = paymentApi.createCapture(account, payment.getId(), requestedAmount, payment.getCurrency(), null,paymentTransactionExternalKey, pendingPluginProperties, callContext);
Assert.assertEquals(payment.getAuthAmount().compareTo(requestedAmount), 0);
Assert.assertEquals(payment.getCapturedAmount().compareTo(BigDecimal.ZERO), 0);
Assert.assertEquals(payment.getTransactions().size(), 2);
@@ -570,7 +570,7 @@ public class TestPaymentApiWithControl extends PaymentTestSuiteWithEmbeddedDB {
Assert.assertEquals(payment.getTransactions().get(1).getExternalKey(), paymentTransactionExternalKey);
try {
- payment = paymentApi.createCaptureWithPaymentControl(account, payment.getId(), requestedAmount, payment.getCurrency(), paymentTransactionExternalKey,
+ payment = paymentApi.createCaptureWithPaymentControl(account, payment.getId(), requestedAmount, payment.getCurrency(), null,paymentTransactionExternalKey,
ImmutableList.<PluginProperty>of(), PAYMENT_OPTIONS, callContext);
Assert.fail();
} catch (final PaymentApiException e) {
@@ -596,7 +596,7 @@ public class TestPaymentApiWithControl extends PaymentTestSuiteWithEmbeddedDB {
public void testCreateAuthWithControlCaptureNoControl() throws PaymentApiException {
final BigDecimal requestedAmount = BigDecimal.TEN;
- Payment payment = paymentApi.createAuthorizationWithPaymentControl(account, account.getPaymentMethodId(), null, requestedAmount, Currency.USD, UUID.randomUUID().toString(),
+ Payment payment = paymentApi.createAuthorizationWithPaymentControl(account, account.getPaymentMethodId(), null, requestedAmount, Currency.USD, null,UUID.randomUUID().toString(),
UUID.randomUUID().toString(), ImmutableList.<PluginProperty>of(), PAYMENT_OPTIONS, callContext);
Assert.assertEquals(payment.getAuthAmount().compareTo(requestedAmount), 0);
Assert.assertEquals(payment.getCapturedAmount().compareTo(BigDecimal.ZERO), 0);
@@ -610,7 +610,7 @@ public class TestPaymentApiWithControl extends PaymentTestSuiteWithEmbeddedDB {
requestedAmount,
Currency.USD);
- payment = paymentApi.createCapture(account, payment.getId(), payment.getAuthAmount(), payment.getCurrency(), UUID.randomUUID().toString(), ImmutableList.<PluginProperty>of(), callContext);
+ payment = paymentApi.createCapture(account, payment.getId(), payment.getAuthAmount(), payment.getCurrency(), null,UUID.randomUUID().toString(), ImmutableList.<PluginProperty>of(), callContext);
Assert.assertEquals(payment.getAuthAmount().compareTo(requestedAmount), 0);
Assert.assertEquals(payment.getCapturedAmount().compareTo(requestedAmount), 0);
Assert.assertEquals(payment.getTransactions().size(), 2);
@@ -622,14 +622,14 @@ public class TestPaymentApiWithControl extends PaymentTestSuiteWithEmbeddedDB {
public void testCreateAuthNoControlCaptureWithControl() throws PaymentApiException {
final BigDecimal requestedAmount = BigDecimal.TEN;
- Payment payment = paymentApi.createAuthorization(account, account.getPaymentMethodId(), null, requestedAmount, Currency.USD, UUID.randomUUID().toString(),
+ Payment payment = paymentApi.createAuthorization(account, account.getPaymentMethodId(), null, requestedAmount, Currency.USD, null,UUID.randomUUID().toString(),
UUID.randomUUID().toString(), ImmutableList.<PluginProperty>of(), callContext);
Assert.assertEquals(payment.getAuthAmount().compareTo(requestedAmount), 0);
Assert.assertEquals(payment.getCapturedAmount().compareTo(BigDecimal.ZERO), 0);
Assert.assertEquals(payment.getTransactions().size(), 1);
Assert.assertNull(((DefaultPaymentTransaction) payment.getTransactions().get(0)).getAttemptId());
- payment = paymentApi.createCaptureWithPaymentControl(account, payment.getId(), payment.getAuthAmount(), payment.getCurrency(), UUID.randomUUID().toString(),
+ payment = paymentApi.createCaptureWithPaymentControl(account, payment.getId(), payment.getAuthAmount(), payment.getCurrency(), null,UUID.randomUUID().toString(),
ImmutableList.<PluginProperty>of(), PAYMENT_OPTIONS, callContext);
Assert.assertEquals(payment.getAuthAmount().compareTo(requestedAmount), 0);
Assert.assertEquals(payment.getCapturedAmount().compareTo(requestedAmount), 0);
@@ -645,6 +645,28 @@ public class TestPaymentApiWithControl extends PaymentTestSuiteWithEmbeddedDB {
Currency.USD);
}
+ @Test(groups = "slow")
+ public void testAddPaymentMethodWithControl() throws PaymentApiException {
+ final PaymentMethodPlugin paymentMethodInfo = new DefaultNoOpPaymentMethodPlugin(UUID.randomUUID().toString(), false, null);
+ testPaymentControlPluginApi.setNewPaymentMethodName(MockPaymentProviderPlugin.PLUGIN_NAME);
+ final UUID newPaymentMethodId = paymentApi.addPaymentMethodWithPaymentControl(account, null, "SomeDummyValueToBeChanged", false, paymentMethodInfo, ImmutableList.<PluginProperty>of(), PAYMENT_OPTIONS, callContext);
+
+
+ final PaymentMethod paymentMethod = paymentApi.getPaymentMethodById(newPaymentMethodId, false, false, ImmutableList.<PluginProperty>of(), callContext);
+ Assert.assertEquals(paymentMethod.getPluginName(), MockPaymentProviderPlugin.PLUGIN_NAME);
+
+ final Payment payment = paymentApi.createAuthorizationWithPaymentControl(account, newPaymentMethodId, null, BigDecimal.TEN, Currency.USD, null,UUID.randomUUID().toString(),
+ UUID.randomUUID().toString(), ImmutableList.<PluginProperty>of(), PAYMENT_OPTIONS, callContext);
+ Assert.assertEquals(payment.getPaymentMethodId(), newPaymentMethodId);
+
+ verifyOnSuccess(payment.getId(),
+ payment.getExternalKey(),
+ payment.getTransactions().get(0).getId(),
+ payment.getTransactions().get(0).getExternalKey(),
+ BigDecimal.TEN,
+ Currency.USD);
+ }
+
private void verifyPriorAndOnSuccess(final UUID paymentId,
final String paymentExternalKey,
final UUID paymentTransactionId,
@@ -830,6 +852,7 @@ public class TestPaymentApiWithControl extends PaymentTestSuiteWithEmbeddedDB {
public static final String PLUGIN_NAME = "TEST_CONTROL_API_PLUGIN_NAME";
private UUID newPaymentMethodId;
+ private String newPaymentMethodName;
private UUID actualPriorCallPaymentId;
private String actualPriorCallPaymentExternalKey;
@@ -856,6 +879,10 @@ public class TestPaymentApiWithControl extends PaymentTestSuiteWithEmbeddedDB {
this.newPaymentMethodId = newPaymentMethodId;
}
+ public void setNewPaymentMethodName(final String newPaymentMethodName) {
+ this.newPaymentMethodName = newPaymentMethodName;
+ }
+
public UUID getActualPriorCallPaymentId() {
return actualPriorCallPaymentId;
}
@@ -959,6 +986,11 @@ public class TestPaymentApiWithControl extends PaymentTestSuiteWithEmbeddedDB {
}
@Override
+ public String getAdjustedPluginName() {
+ return newPaymentMethodName;
+ }
+
+ @Override
public Iterable<PluginProperty> getAdjustedPluginProperties() {
return null;
}
diff --git a/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentGatewayApiWithPaymentControl.java b/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentGatewayApiWithPaymentControl.java
index 3aa3554..4698e0d 100644
--- a/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentGatewayApiWithPaymentControl.java
+++ b/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentGatewayApiWithPaymentControl.java
@@ -257,7 +257,7 @@ public class TestPaymentGatewayApiWithPaymentControl extends PaymentTestSuiteNoD
@Override
public PriorPaymentControlResult priorCall(final PaymentControlContext paymentControlContext, final Iterable<PluginProperty> properties) throws PaymentControlApiException {
- return new DefaultPriorPaymentControlResult(aborted, account.getPaymentMethodId(), null, null, getAdjustedProperties(properties, newPriorCallProperties, removedPriorCallProperties));
+ return new DefaultPriorPaymentControlResult(aborted, account.getPaymentMethodId(), null, null, null, getAdjustedProperties(properties, newPriorCallProperties, removedPriorCallProperties));
}
@Override
diff --git a/payment/src/test/java/org/killbill/billing/payment/core/janitor/TestCompletionTaskBase.java b/payment/src/test/java/org/killbill/billing/payment/core/janitor/TestCompletionTaskBase.java
new file mode 100644
index 0000000..c32ea7f
--- /dev/null
+++ b/payment/src/test/java/org/killbill/billing/payment/core/janitor/TestCompletionTaskBase.java
@@ -0,0 +1,91 @@
+/*
+ * 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.core.janitor;
+
+import java.util.Iterator;
+import java.util.List;
+
+import org.killbill.billing.account.api.AccountInternalApi;
+import org.killbill.billing.payment.PaymentTestSuiteWithEmbeddedDB;
+import org.killbill.billing.payment.api.PaymentApiException;
+import org.killbill.billing.payment.core.sm.PaymentControlStateMachineHelper;
+import org.killbill.billing.payment.core.sm.PaymentStateMachineHelper;
+import org.killbill.billing.payment.core.sm.PluginControlPaymentAutomatonRunner;
+import org.killbill.billing.payment.dao.PaymentAttemptModelDao;
+import org.killbill.billing.payment.dao.PaymentDao;
+import org.killbill.billing.util.callcontext.InternalCallContextFactory;
+import org.killbill.billing.util.config.definition.PaymentConfig;
+import org.killbill.clock.Clock;
+import org.killbill.commons.locker.GlobalLocker;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+
+public class TestCompletionTaskBase extends PaymentTestSuiteWithEmbeddedDB {
+
+ @Test(groups = "slow", description = "https://github.com/killbill/killbill/issues/757")
+ public void testHandleRuntimeExceptions() throws PaymentApiException {
+ final List<PaymentAttemptModelDao> paymentAttemptModelDaos = ImmutableList.<PaymentAttemptModelDao>of(new PaymentAttemptModelDao(),
+ new PaymentAttemptModelDao());
+ final Iterator<PaymentAttemptModelDao> paymentAttemptModelDaoIterator = paymentAttemptModelDaos.iterator();
+ final Iterable<PaymentAttemptModelDao> itemsForIteration = new Iterable<PaymentAttemptModelDao>() {
+ @Override
+ public Iterator<PaymentAttemptModelDao> iterator() {
+ return paymentAttemptModelDaoIterator;
+ }
+ };
+ Assert.assertTrue(paymentAttemptModelDaoIterator.hasNext());
+
+ final Runnable incompletePaymentAttemptTaskWithException = new IncompletePaymentAttemptTaskWithException(itemsForIteration,
+ internalCallContextFactory,
+ paymentConfig,
+ paymentDao,
+ clock,
+ paymentSMHelper,
+ paymentControlStateMachineHelper,
+ accountApi,
+ pluginControlPaymentAutomatonRunner,
+ locker);
+
+ incompletePaymentAttemptTaskWithException.run();
+
+ // Make sure we cycled through all entries
+ Assert.assertFalse(paymentAttemptModelDaoIterator.hasNext());
+ }
+
+ private final class IncompletePaymentAttemptTaskWithException extends IncompletePaymentAttemptTask {
+
+ private final Iterable<PaymentAttemptModelDao> itemsForIteration;
+
+ public IncompletePaymentAttemptTaskWithException(final Iterable<PaymentAttemptModelDao> itemsForIteration, final InternalCallContextFactory internalCallContextFactory, final PaymentConfig paymentConfig, final PaymentDao paymentDao, final Clock clock, final PaymentStateMachineHelper paymentStateMachineHelper, final PaymentControlStateMachineHelper retrySMHelper, final AccountInternalApi accountInternalApi, final PluginControlPaymentAutomatonRunner pluginControlledPaymentAutomatonRunner, final GlobalLocker locker) {
+ super(internalCallContextFactory, paymentConfig, paymentDao, clock, paymentStateMachineHelper, retrySMHelper, accountInternalApi, pluginControlledPaymentAutomatonRunner, locker);
+ this.itemsForIteration = itemsForIteration;
+ }
+
+ @Override
+ public Iterable<PaymentAttemptModelDao> getItemsForIteration() {
+ return itemsForIteration;
+ }
+
+ @Override
+ public void doIteration(final PaymentAttemptModelDao attempt) {
+ throw new NullPointerException("NPE for tests");
+ }
+ }
+}
diff --git a/payment/src/test/java/org/killbill/billing/payment/core/janitor/TestIncompletePaymentTransactionTaskWithDB.java b/payment/src/test/java/org/killbill/billing/payment/core/janitor/TestIncompletePaymentTransactionTaskWithDB.java
index 8dc40d7..168e06e 100644
--- a/payment/src/test/java/org/killbill/billing/payment/core/janitor/TestIncompletePaymentTransactionTaskWithDB.java
+++ b/payment/src/test/java/org/killbill/billing/payment/core/janitor/TestIncompletePaymentTransactionTaskWithDB.java
@@ -18,7 +18,6 @@
package org.killbill.billing.payment.core.janitor;
import java.math.BigDecimal;
-import java.util.List;
import java.util.UUID;
import org.killbill.billing.account.api.Account;
@@ -27,7 +26,13 @@ import org.killbill.billing.payment.PaymentTestSuiteWithEmbeddedDB;
import org.killbill.billing.payment.api.Payment;
import org.killbill.billing.payment.api.PaymentApiException;
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.payment.dao.PaymentModelDao;
+import org.killbill.billing.payment.dao.PaymentTransactionModelDao;
import org.killbill.billing.payment.plugin.api.PaymentPluginStatus;
+import org.killbill.billing.payment.plugin.api.PaymentTransactionInfoPlugin;
+import org.killbill.billing.payment.provider.DefaultNoOpPaymentInfoPlugin;
import org.killbill.billing.payment.provider.MockPaymentProviderPlugin;
import org.killbill.billing.util.globallocker.LockerType;
import org.killbill.commons.locker.GlobalLock;
@@ -69,6 +74,7 @@ public class TestIncompletePaymentTransactionTaskWithDB extends PaymentTestSuite
null,
BigDecimal.TEN,
Currency.EUR,
+ null,
UUID.randomUUID().toString(),
UUID.randomUUID().toString(),
ImmutableList.<PluginProperty>of(new PluginProperty(MockPaymentProviderPlugin.PLUGIN_PROPERTY_PAYMENT_PLUGIN_STATUS_OVERRIDE, PaymentPluginStatus.PENDING.toString(), false)),
@@ -78,7 +84,7 @@ public class TestIncompletePaymentTransactionTaskWithDB extends PaymentTestSuite
final JanitorNotificationKey notificationKey = new JanitorNotificationKey(transactionId, incompletePaymentTransactionTask.getClass().toString(), 1);
final UUID userToken = UUID.randomUUID();
- Assert.assertTrue(Iterables.<NotificationEventWithMetadata<NotificationEvent>>isEmpty(incompletePaymentTransactionTask.janitorQueue.getFutureNotificationForSearchKeys(internalCallContext.getAccountRecordId(), internalCallContext.getTenantRecordId())));
+ Assert.assertTrue(Iterables.isEmpty(incompletePaymentTransactionTask.janitorQueue.getFutureNotificationForSearchKeys(internalCallContext.getAccountRecordId(), internalCallContext.getTenantRecordId())));
GlobalLock lock = null;
try {
@@ -87,7 +93,7 @@ public class TestIncompletePaymentTransactionTaskWithDB extends PaymentTestSuite
incompletePaymentTransactionTask.processNotification(notificationKey, userToken, internalCallContext.getAccountRecordId(), internalCallContext.getTenantRecordId());
final Iterable<NotificationEventWithMetadata<NotificationEvent>> futureNotifications = incompletePaymentTransactionTask.janitorQueue.getFutureNotificationForSearchKeys(internalCallContext.getAccountRecordId(), internalCallContext.getTenantRecordId());
- Assert.assertFalse(Iterables.<NotificationEventWithMetadata<NotificationEvent>>isEmpty(futureNotifications));
+ Assert.assertFalse(Iterables.isEmpty(futureNotifications));
final NotificationEventWithMetadata<NotificationEvent> notificationEventWithMetadata = ImmutableList.<NotificationEventWithMetadata<NotificationEvent>>copyOf(futureNotifications).get(0);
Assert.assertEquals(notificationEventWithMetadata.getUserToken(), userToken);
Assert.assertEquals(notificationEventWithMetadata.getEvent().getClass(), JanitorNotificationKey.class);
@@ -105,4 +111,71 @@ public class TestIncompletePaymentTransactionTaskWithDB extends PaymentTestSuite
}
}
}
+
+ @Test(groups = "slow", description = "https://github.com/killbill/killbill/issues/809")
+ public void testUpdateWithinLock() throws PaymentApiException {
+ final Payment payment = paymentApi.createAuthorization(account,
+ account.getPaymentMethodId(),
+ null,
+ BigDecimal.TEN,
+ Currency.EUR,
+ null,
+ UUID.randomUUID().toString(),
+ UUID.randomUUID().toString(),
+ ImmutableList.<PluginProperty>of(new PluginProperty(MockPaymentProviderPlugin.PLUGIN_PROPERTY_PAYMENT_PLUGIN_STATUS_OVERRIDE, PaymentPluginStatus.UNDEFINED.toString(), false)),
+ callContext);
+ final PaymentModelDao paymentModel = paymentDao.getPayment(payment.getId(), internalCallContext);
+ final UUID transactionId = payment.getTransactions().get(0).getId();
+ final PaymentTransactionModelDao transactionModel = paymentDao.getPaymentTransaction(transactionId, internalCallContext);
+
+ Assert.assertEquals(paymentModel.getStateName(), "AUTH_ERRORED");
+ Assert.assertEquals(transactionModel.getTransactionStatus().toString(), "UNKNOWN");
+
+ paymentDao.updatePaymentAndTransactionOnCompletion(
+ account.getId(),
+ null,
+ payment.getId(),
+ TransactionType.AUTHORIZE,
+ "AUTH_SUCCESS",
+ "AUTH_SUCCESS",
+ transactionId,
+ TransactionStatus.SUCCESS,
+ BigDecimal.TEN,
+ Currency.EUR,
+ "200",
+ "Ok",
+ internalCallContext);
+
+ paymentApi.createCapture(account,
+ payment.getId(),
+ BigDecimal.TEN,
+ Currency.EUR,
+ null,
+ UUID.randomUUID().toString(),
+ ImmutableList.<PluginProperty>of(new PluginProperty(MockPaymentProviderPlugin.PLUGIN_PROPERTY_PAYMENT_PLUGIN_STATUS_OVERRIDE, PaymentPluginStatus.PROCESSED.toString(), false)),
+ callContext);
+
+ final PaymentModelDao paymentAfterCapture = paymentDao.getPayment(payment.getId(), internalCallContext);
+ Assert.assertEquals(paymentAfterCapture.getStateName(), "CAPTURE_SUCCESS");
+
+ PaymentTransactionInfoPlugin paymentTransactionInfoPlugin = new DefaultNoOpPaymentInfoPlugin(
+ payment.getId(),
+ transactionId,
+ TransactionType.AUTHORIZE,
+ BigDecimal.TEN,
+ Currency.EUR,
+ transactionModel.getEffectiveDate(),
+ transactionModel.getCreatedDate(),
+ PaymentPluginStatus.PROCESSED,
+ "200",
+ "OK");
+ incompletePaymentTransactionTask.updatePaymentAndTransactionIfNeededWithAccountLock(
+ paymentModel,
+ transactionModel,
+ paymentTransactionInfoPlugin,
+ internalCallContext);
+
+ final PaymentModelDao paymentAfterJanitor = paymentDao.getPayment(payment.getId(), internalCallContext);
+ Assert.assertEquals(paymentAfterJanitor.getStateName(), "CAPTURE_SUCCESS");
+ }
}
diff --git a/payment/src/test/java/org/killbill/billing/payment/core/sm/control/TestControlPluginRunner.java b/payment/src/test/java/org/killbill/billing/payment/core/sm/control/TestControlPluginRunner.java
index 80cb640..1e114e4 100644
--- a/payment/src/test/java/org/killbill/billing/payment/core/sm/control/TestControlPluginRunner.java
+++ b/payment/src/test/java/org/killbill/billing/payment/core/sm/control/TestControlPluginRunner.java
@@ -54,6 +54,7 @@ public class TestControlPluginRunner extends PaymentTestSuiteNoDB {
final PriorPaymentControlResult paymentControlResult = controlPluginRunner.executePluginPriorCalls(account,
paymentMethodId,
null,
+ null,
paymentId,
paymentExternalKey,
paymentTransactionId,
diff --git a/payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryablePaymentAutomatonRunner.java b/payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryablePaymentAutomatonRunner.java
index 350bf9f..bfbde89 100644
--- a/payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryablePaymentAutomatonRunner.java
+++ b/payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryablePaymentAutomatonRunner.java
@@ -25,6 +25,7 @@ import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.inject.Named;
+import org.joda.time.DateTime;
import org.killbill.automaton.Operation.OperationCallback;
import org.killbill.automaton.OperationResult;
import org.killbill.billing.account.api.Account;
@@ -76,12 +77,12 @@ public class MockRetryablePaymentAutomatonRunner extends PluginControlPaymentAut
@Override
PaymentStateControlContext createContext(final boolean isApiPayment, final Boolean isSuccess, final TransactionType transactionType, final Account account, @Nullable final UUID paymentMethodId,
@Nullable final UUID paymentId, @Nullable final String paymentExternalKey, @Nullable final UUID transactionId, final String paymentTransactionExternalKey,
- @Nullable final BigDecimal amount, @Nullable final Currency currency,
+ @Nullable final BigDecimal amount, @Nullable final Currency currency, @Nullable DateTime effectiveDate,
final Iterable<PluginProperty> properties,
final List<String> pluginNames, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
if (context == null) {
return super.createContext(isApiPayment, isSuccess, transactionType, account, paymentMethodId, paymentId, paymentExternalKey, transactionId, paymentTransactionExternalKey,
- amount, currency, properties, pluginNames, callContext, internalCallContext);
+ amount, currency, effectiveDate, properties, pluginNames, callContext, internalCallContext);
} else {
return context;
}
diff --git a/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPaymentAutomatonDAOHelper.java b/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPaymentAutomatonDAOHelper.java
index 88655c2..6372ed2 100644
--- a/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPaymentAutomatonDAOHelper.java
+++ b/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPaymentAutomatonDAOHelper.java
@@ -127,6 +127,7 @@ public class TestPaymentAutomatonDAOHelper extends PaymentTestSuiteWithEmbeddedD
currency,
null,
null,
+ null,
false,
null,
ImmutableList.<PluginProperty>of(),
diff --git a/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPaymentEnteringStateCallback.java b/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPaymentEnteringStateCallback.java
index 287e297..2ffb21d 100644
--- a/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPaymentEnteringStateCallback.java
+++ b/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPaymentEnteringStateCallback.java
@@ -65,6 +65,7 @@ public class TestPaymentEnteringStateCallback extends PaymentTestSuiteWithEmbedd
UUID.randomUUID(),
new BigDecimal("192.3920111"),
Currency.BRL,
+ null,
false,
ImmutableList.<PluginProperty>of(),
internalCallContext,
diff --git a/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPaymentLeavingStateCallback.java b/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPaymentLeavingStateCallback.java
index 6b1f308..2f013f0 100644
--- a/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPaymentLeavingStateCallback.java
+++ b/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPaymentLeavingStateCallback.java
@@ -114,6 +114,7 @@ public class TestPaymentLeavingStateCallback extends PaymentTestSuiteWithEmbedde
Currency.BRL,
null,
null,
+ null,
false,
null, ImmutableList.<PluginProperty>of(),
internalCallContext,
diff --git a/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPaymentOperation.java b/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPaymentOperation.java
index 8ee5efb..2e74e94 100644
--- a/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPaymentOperation.java
+++ b/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPaymentOperation.java
@@ -118,6 +118,7 @@ public class TestPaymentOperation extends PaymentTestSuiteNoDB {
Currency.BRL,
null,
null,
+ null,
false,
null, ImmutableList.<PluginProperty>of(),
internalCallContext,
diff --git a/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPluginOperation.java b/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPluginOperation.java
index e55fa52..b70932f 100644
--- a/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPluginOperation.java
+++ b/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPluginOperation.java
@@ -219,6 +219,7 @@ public class TestPluginOperation extends PaymentTestSuiteNoDB {
Currency.BRL,
null,
null,
+ null,
shouldLockAccount,
null,
ImmutableList.<PluginProperty>of(),
diff --git a/payment/src/test/java/org/killbill/billing/payment/core/sm/TestRetryablePayment.java b/payment/src/test/java/org/killbill/billing/payment/core/sm/TestRetryablePayment.java
index 0be4a2f..5b121ee 100644
--- a/payment/src/test/java/org/killbill/billing/payment/core/sm/TestRetryablePayment.java
+++ b/payment/src/test/java/org/killbill/billing/payment/core/sm/TestRetryablePayment.java
@@ -186,6 +186,7 @@ public class TestRetryablePayment extends PaymentTestSuiteNoDB {
paymentMethodId,
amount,
currency,
+ null,
emptyProperties,
internalCallContext,
callContext);
@@ -239,6 +240,7 @@ public class TestRetryablePayment extends PaymentTestSuiteNoDB {
paymentTransactionExternalKey,
amount,
currency,
+ null,
emptyProperties,
null,
callContext,
@@ -280,6 +282,7 @@ public class TestRetryablePayment extends PaymentTestSuiteNoDB {
paymentTransactionExternalKey,
amount,
currency,
+ null,
emptyProperties,
null,
callContext,
@@ -315,6 +318,7 @@ public class TestRetryablePayment extends PaymentTestSuiteNoDB {
paymentTransactionExternalKey,
amount,
currency,
+ null,
emptyProperties,
null,
callContext, internalCallContext);
@@ -350,6 +354,7 @@ public class TestRetryablePayment extends PaymentTestSuiteNoDB {
paymentTransactionExternalKey,
amount,
currency,
+ null,
emptyProperties,
null,
callContext, internalCallContext);
@@ -389,6 +394,7 @@ public class TestRetryablePayment extends PaymentTestSuiteNoDB {
paymentTransactionExternalKey,
amount,
currency,
+ null,
emptyProperties,
null,
callContext, internalCallContext);
@@ -427,6 +433,7 @@ public class TestRetryablePayment extends PaymentTestSuiteNoDB {
paymentTransactionExternalKey,
amount,
currency,
+ null,
emptyProperties,
null,
callContext, internalCallContext);
@@ -465,6 +472,7 @@ public class TestRetryablePayment extends PaymentTestSuiteNoDB {
paymentTransactionExternalKey,
amount,
currency,
+ null,
emptyProperties,
null,
callContext, internalCallContext);
@@ -510,6 +518,7 @@ public class TestRetryablePayment extends PaymentTestSuiteNoDB {
paymentTransactionExternalKey,
amount,
currency,
+ null,
emptyProperties,
null,
callContext,
@@ -561,6 +570,7 @@ public class TestRetryablePayment extends PaymentTestSuiteNoDB {
paymentTransactionExternalKey,
amount,
currency,
+ null,
emptyProperties,
null,
callContext,
@@ -609,6 +619,7 @@ public class TestRetryablePayment extends PaymentTestSuiteNoDB {
paymentTransactionExternalKey,
amount,
currency,
+ null,
emptyProperties,
null,
callContext,
diff --git a/payment/src/test/java/org/killbill/billing/payment/core/TestPaymentMethodProcessorNoDB.java b/payment/src/test/java/org/killbill/billing/payment/core/TestPaymentMethodProcessorNoDB.java
index 226a34b..e2872d8 100644
--- a/payment/src/test/java/org/killbill/billing/payment/core/TestPaymentMethodProcessorNoDB.java
+++ b/payment/src/test/java/org/killbill/billing/payment/core/TestPaymentMethodProcessorNoDB.java
@@ -59,11 +59,11 @@ public class TestPaymentMethodProcessorNoDB extends PaymentTestSuiteNoDB {
Mockito.when(account.getId()).thenReturn(accountId);
Mockito.when(account.getExternalKey()).thenReturn(accountId.toString());
- Assert.assertEquals(paymentMethodProcessor.getPaymentMethods(false, properties, internalCallContext).size(), 0);
+ Assert.assertEquals(paymentMethodProcessor.getPaymentMethods(false, false, properties, internalCallContext).size(), 0);
// The first call should create the payment method
final ExternalPaymentProviderPlugin providerPlugin = paymentMethodProcessor.createPaymentMethodAndGetExternalPaymentProviderPlugin(UUID.randomUUID().toString(), account, properties, callContext, internalCallContext);
- final List<PaymentMethod> paymentMethods = paymentMethodProcessor.getPaymentMethods(false, properties, internalCallContext);
+ final List<PaymentMethod> paymentMethods = paymentMethodProcessor.getPaymentMethods(false, false, properties, internalCallContext);
Assert.assertEquals(paymentMethods.size(), 1);
Assert.assertEquals(paymentMethods.get(0).getPluginName(), ExternalPaymentProviderPlugin.PLUGIN_NAME);
Assert.assertEquals(paymentMethods.get(0).getAccountId(), account.getId());
@@ -74,7 +74,7 @@ public class TestPaymentMethodProcessorNoDB extends PaymentTestSuiteNoDB {
final ExternalPaymentProviderPlugin foundProviderPlugin = paymentMethodProcessor.createPaymentMethodAndGetExternalPaymentProviderPlugin(UUID.randomUUID().toString(), account, properties, callContext, internalCallContext);
Assert.assertNotNull(foundProviderPlugin);
- final List<PaymentMethod> foundPaymentMethods = paymentMethodProcessor.getPaymentMethods(false, properties, internalCallContext);
+ final List<PaymentMethod> foundPaymentMethods = paymentMethodProcessor.getPaymentMethods(false, false, properties, internalCallContext);
Assert.assertEquals(foundPaymentMethods.size(), 1);
Assert.assertEquals(foundPaymentMethods.get(0).getPluginName(), ExternalPaymentProviderPlugin.PLUGIN_NAME);
Assert.assertEquals(foundPaymentMethods.get(0).getAccountId(), account.getId());
diff --git a/payment/src/test/java/org/killbill/billing/payment/core/TestPaymentMethodProcessorRefreshWithDB.java b/payment/src/test/java/org/killbill/billing/payment/core/TestPaymentMethodProcessorRefreshWithDB.java
index 8a61a5e..def20b8 100644
--- a/payment/src/test/java/org/killbill/billing/payment/core/TestPaymentMethodProcessorRefreshWithDB.java
+++ b/payment/src/test/java/org/killbill/billing/payment/core/TestPaymentMethodProcessorRefreshWithDB.java
@@ -71,12 +71,12 @@ public class TestPaymentMethodProcessorRefreshWithDB extends PaymentTestSuiteWit
String secondPaymentMethodExternalKey = UUID.randomUUID().toString();
final UUID secondPmId = paymentApi.addPaymentMethod(account, secondPaymentMethodExternalKey, MockPaymentProviderPlugin.PLUGIN_NAME, true, new DefaultNoOpPaymentMethodPlugin(secondPaymentMethodExternalKey, false, null), PLUGIN_PROPERTIES, callContext);
Assert.assertEquals(getPluginApi().getPaymentMethods(account.getId(), true, PLUGIN_PROPERTIES, callContext).size(), 2);
- Assert.assertEquals(paymentApi.getAccountPaymentMethods(account.getId(), false, PLUGIN_PROPERTIES, callContext).size(), 2);
+ Assert.assertEquals(paymentApi.getAccountPaymentMethods(account.getId(), false, false, PLUGIN_PROPERTIES, callContext).size(), 2);
// Remove second PM from plugin
getPluginApi().deletePaymentMethod(account.getId(), secondPmId, PLUGIN_PROPERTIES, callContext);
Assert.assertEquals(getPluginApi().getPaymentMethods(account.getId(), true, PLUGIN_PROPERTIES, callContext).size(), 1);
- Assert.assertEquals(paymentApi.getAccountPaymentMethods(account.getId(), false, PLUGIN_PROPERTIES, callContext).size(), 2);
+ Assert.assertEquals(paymentApi.getAccountPaymentMethods(account.getId(), false, false, PLUGIN_PROPERTIES, callContext).size(), 2);
// Verify that the refresh sees that PM as being deleted now
final List<PaymentMethod> methods = paymentMethodProcessor.refreshPaymentMethods(MockPaymentProviderPlugin.PLUGIN_NAME, account, PLUGIN_PROPERTIES, callContext, internalCallContext);
diff --git a/payment/src/test/java/org/killbill/billing/payment/core/TestPaymentProcessor.java b/payment/src/test/java/org/killbill/billing/payment/core/TestPaymentProcessor.java
index 54d227b..d9e9b96 100644
--- a/payment/src/test/java/org/killbill/billing/payment/core/TestPaymentProcessor.java
+++ b/payment/src/test/java/org/killbill/billing/payment/core/TestPaymentProcessor.java
@@ -80,7 +80,7 @@ public class TestPaymentProcessor extends PaymentTestSuiteWithEmbeddedDB {
final Iterable<PluginProperty> pluginPropertiesToDriveTransationToUnknown = ImmutableList.<PluginProperty>of(new PluginProperty(MockPaymentProviderPlugin.PLUGIN_PROPERTY_PAYMENT_PLUGIN_STATUS_OVERRIDE, PaymentPluginStatus.UNDEFINED, false));
final String authorizationKey = UUID.randomUUID().toString();
- final Payment authorization = paymentProcessor.createAuthorization(true, null, account, null, null, TEN, CURRENCY, paymentExternalKey, authorizationKey,
+ final Payment authorization = paymentProcessor.createAuthorization(true, null, account, null, null, TEN, CURRENCY, null, paymentExternalKey, authorizationKey,
null, null, SHOULD_LOCK_ACCOUNT, pluginPropertiesToDriveTransationToUnknown, callContext, internalCallContext);
verifyPayment(authorization, paymentExternalKey, ZERO, ZERO, ZERO, 1);
final UUID paymentId = authorization.getId();
@@ -104,7 +104,7 @@ public class TestPaymentProcessor extends PaymentTestSuiteWithEmbeddedDB {
// AUTH pre-3DS
final String authorizationKey = UUID.randomUUID().toString();
- final Payment authorization = paymentProcessor.createAuthorization(true, null, account, null, null, TEN, CURRENCY, paymentExternalKey, authorizationKey,
+ final Payment authorization = paymentProcessor.createAuthorization(true, null, account, null, null, TEN, CURRENCY, null,paymentExternalKey, authorizationKey,
null, null, SHOULD_LOCK_ACCOUNT, pluginPropertiesToDriveTransationToPending, callContext, internalCallContext);
verifyPayment(authorization, paymentExternalKey, ZERO, ZERO, ZERO, 1);
final UUID paymentId = authorization.getId();
@@ -112,7 +112,7 @@ public class TestPaymentProcessor extends PaymentTestSuiteWithEmbeddedDB {
paymentBusListener.verify(1, account.getId(), paymentId, TEN, TransactionStatus.PENDING);
// AUTH post-3DS
- final Payment authorizationPost3DS = paymentProcessor.createAuthorization(true, null, account, null, paymentId, TEN, CURRENCY, paymentExternalKey, authorizationKey,
+ final Payment authorizationPost3DS = paymentProcessor.createAuthorization(true, null, account, null, paymentId, TEN, CURRENCY, null,paymentExternalKey, authorizationKey,
null, null, SHOULD_LOCK_ACCOUNT, PLUGIN_PROPERTIES, callContext, internalCallContext);
verifyPayment(authorizationPost3DS, paymentExternalKey, TEN, ZERO, ZERO, 1);
verifyPaymentTransaction(authorizationPost3DS.getTransactions().get(0), authorizationKey, TransactionType.AUTHORIZE, TEN, paymentId);
@@ -120,7 +120,7 @@ public class TestPaymentProcessor extends PaymentTestSuiteWithEmbeddedDB {
// CAPTURE
final String capture1Key = UUID.randomUUID().toString();
- final Payment partialCapture1 = paymentProcessor.createCapture(true, null, account, paymentId, FIVE, CURRENCY, capture1Key,
+ final Payment partialCapture1 = paymentProcessor.createCapture(true, null, account, paymentId, FIVE, CURRENCY, null,capture1Key,
null, SHOULD_LOCK_ACCOUNT, PLUGIN_PROPERTIES, callContext, internalCallContext);
verifyPayment(partialCapture1, paymentExternalKey, TEN, FIVE, ZERO, 2);
verifyPaymentTransaction(partialCapture1.getTransactions().get(1), capture1Key, TransactionType.CAPTURE, FIVE, paymentId);
@@ -128,7 +128,7 @@ public class TestPaymentProcessor extends PaymentTestSuiteWithEmbeddedDB {
// CAPTURE
final String capture2Key = UUID.randomUUID().toString();
- final Payment partialCapture2 = paymentProcessor.createCapture(true, null, account, paymentId, FIVE, CURRENCY, capture2Key,
+ final Payment partialCapture2 = paymentProcessor.createCapture(true, null, account, paymentId, FIVE, CURRENCY, null,capture2Key,
null, SHOULD_LOCK_ACCOUNT, PLUGIN_PROPERTIES, callContext, internalCallContext);
verifyPayment(partialCapture2, paymentExternalKey, TEN, TEN, ZERO, 3);
verifyPaymentTransaction(partialCapture2.getTransactions().get(2), capture2Key, TransactionType.CAPTURE, FIVE, paymentId);
@@ -136,7 +136,7 @@ public class TestPaymentProcessor extends PaymentTestSuiteWithEmbeddedDB {
// REFUND
final String refund1Key = UUID.randomUUID().toString();
- final Payment partialRefund1 = paymentProcessor.createRefund(true, null, account, paymentId, FIVE, CURRENCY, refund1Key,
+ final Payment partialRefund1 = paymentProcessor.createRefund(true, null, account, paymentId, FIVE, CURRENCY, null,refund1Key,
null, SHOULD_LOCK_ACCOUNT, PLUGIN_PROPERTIES, callContext, internalCallContext);
verifyPayment(partialRefund1, paymentExternalKey, TEN, TEN, FIVE, 4);
verifyPaymentTransaction(partialRefund1.getTransactions().get(3), refund1Key, TransactionType.REFUND, FIVE, paymentId);
@@ -144,7 +144,7 @@ public class TestPaymentProcessor extends PaymentTestSuiteWithEmbeddedDB {
// REFUND
final String refund2Key = UUID.randomUUID().toString();
- final Payment partialRefund2 = paymentProcessor.createRefund(true, null, account, paymentId, FIVE, CURRENCY, refund2Key,
+ final Payment partialRefund2 = paymentProcessor.createRefund(true, null, account, paymentId, FIVE, CURRENCY, null,refund2Key,
null, SHOULD_LOCK_ACCOUNT, PLUGIN_PROPERTIES, callContext, internalCallContext);
verifyPayment(partialRefund2, paymentExternalKey, TEN, TEN, TEN, 5);
verifyPaymentTransaction(partialRefund2.getTransactions().get(4), refund2Key, TransactionType.REFUND, FIVE, paymentId);
@@ -157,7 +157,7 @@ public class TestPaymentProcessor extends PaymentTestSuiteWithEmbeddedDB {
// AUTH
final String authorizationKey = UUID.randomUUID().toString();
- final Payment authorization = paymentProcessor.createAuthorization(true, null, account, null, null, TEN, CURRENCY, paymentExternalKey, authorizationKey,
+ final Payment authorization = paymentProcessor.createAuthorization(true, null, account, null, null, TEN, CURRENCY, null,paymentExternalKey, authorizationKey,
null, null, SHOULD_LOCK_ACCOUNT, PLUGIN_PROPERTIES, callContext, internalCallContext);
verifyPayment(authorization, paymentExternalKey, TEN, ZERO, ZERO, 1);
final UUID paymentId = authorization.getId();
@@ -166,7 +166,7 @@ public class TestPaymentProcessor extends PaymentTestSuiteWithEmbeddedDB {
// VOID
final String voidKey = UUID.randomUUID().toString();
- final Payment voidTransaction = paymentProcessor.createVoid(true, null, account, paymentId, voidKey,
+ final Payment voidTransaction = paymentProcessor.createVoid(true, null, account, paymentId, null, voidKey,
null, SHOULD_LOCK_ACCOUNT, PLUGIN_PROPERTIES, callContext, internalCallContext);
verifyPayment(voidTransaction, paymentExternalKey, ZERO, ZERO, ZERO, 2);
verifyPaymentTransaction(voidTransaction.getTransactions().get(1), voidKey, TransactionType.VOID, null, paymentId);
@@ -179,7 +179,7 @@ public class TestPaymentProcessor extends PaymentTestSuiteWithEmbeddedDB {
// PURCHASE
final String purchaseKey = UUID.randomUUID().toString();
- final Payment purchase = paymentProcessor.createPurchase(true, null, account, null, null, TEN, CURRENCY, paymentExternalKey, purchaseKey,
+ final Payment purchase = paymentProcessor.createPurchase(true, null, account, null, null, TEN, CURRENCY, null,paymentExternalKey, purchaseKey,
null, null, SHOULD_LOCK_ACCOUNT, PLUGIN_PROPERTIES, callContext, internalCallContext);
verifyPayment(purchase, paymentExternalKey, ZERO, ZERO, ZERO, 1);
final UUID paymentId = purchase.getId();
@@ -193,7 +193,7 @@ public class TestPaymentProcessor extends PaymentTestSuiteWithEmbeddedDB {
// CREDIT
final String creditKey = UUID.randomUUID().toString();
- final Payment purchase = paymentProcessor.createCredit(true, null, account, null, null, TEN, CURRENCY, paymentExternalKey, creditKey,
+ final Payment purchase = paymentProcessor.createCredit(true, null, account, null, null, TEN, CURRENCY, null,paymentExternalKey, creditKey,
null, null, SHOULD_LOCK_ACCOUNT, PLUGIN_PROPERTIES, callContext, internalCallContext);
verifyPayment(purchase, paymentExternalKey, ZERO, ZERO, ZERO, 1);
final UUID paymentId = purchase.getId();
@@ -208,7 +208,7 @@ public class TestPaymentProcessor extends PaymentTestSuiteWithEmbeddedDB {
// AUTH
final String authorizationKey = UUID.randomUUID().toString();
- final Payment authorization = paymentProcessor.createAuthorization(true, null, account, null, null, TEN, CURRENCY, paymentExternalKey, authorizationKey,
+ final Payment authorization = paymentProcessor.createAuthorization(true, null, account, null, null, TEN, CURRENCY, null,paymentExternalKey, authorizationKey,
null, null, SHOULD_LOCK_ACCOUNT, pluginPropertiesToDriveTransationToPending, callContext, internalCallContext);
verifyPayment(authorization, paymentExternalKey, ZERO, ZERO, ZERO, 1);
final UUID paymentId = authorization.getId();
@@ -218,7 +218,7 @@ public class TestPaymentProcessor extends PaymentTestSuiteWithEmbeddedDB {
// REFUND
final String refundKey = UUID.randomUUID().toString();
try {
- paymentProcessor.createRefund(true, null, account, paymentId, TEN, CURRENCY, refundKey,
+ paymentProcessor.createRefund(true, null, account, paymentId, TEN, CURRENCY, null,refundKey,
null, SHOULD_LOCK_ACCOUNT, PLUGIN_PROPERTIES, callContext, internalCallContext);
Assert.fail();
} catch (final PaymentApiException e) {
@@ -237,7 +237,7 @@ public class TestPaymentProcessor extends PaymentTestSuiteWithEmbeddedDB {
// Create Pending AUTH
final String authorizationKey = UUID.randomUUID().toString();
- final Payment authorization = paymentProcessor.createAuthorization(true, null, account, null, null, TEN, CURRENCY, paymentExternalKey, authorizationKey,
+ final Payment authorization = paymentProcessor.createAuthorization(true, null, account, null, null, TEN, CURRENCY, null,paymentExternalKey, authorizationKey,
null, null, SHOULD_LOCK_ACCOUNT, pluginPropertiesToDriveTransationToPending, callContext, internalCallContext);
final PaymentTransaction pendingTransaction = authorization.getTransactions().get(0);
Assert.assertEquals(pendingTransaction.getTransactionStatus(), TransactionStatus.PENDING);
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 f167975..8a0df98 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
@@ -27,6 +27,7 @@ import java.util.List;
import java.util.Map;
import java.util.UUID;
+import javax.annotation.Nullable;
import javax.inject.Inject;
import org.joda.time.DateTime;
@@ -100,15 +101,18 @@ public class MockPaymentDao extends MockEntityDaoBase<PaymentModelDao, Payment,
@Override
public void updatePaymentAttempt(final UUID paymentAttemptId, final UUID transactionId, final String state, final InternalCallContext context) {
- updatePaymentAttemptWithProperties(paymentAttemptId, transactionId, state, null, context);
+ updatePaymentAttemptWithProperties(paymentAttemptId, null, transactionId, state, null, context);
}
@Override
- public void updatePaymentAttemptWithProperties(final UUID paymentAttemptId, final UUID transactionId, final String state, final byte[] pluginProperties, final InternalCallContext context) {
+ public void updatePaymentAttemptWithProperties(final UUID paymentAttemptId, @Nullable final UUID paymentMethodId, final UUID transactionId, final String state, final byte[] pluginProperties, final InternalCallContext context) {
boolean success = false;
synchronized (this) {
for (PaymentAttemptModelDao cur : attempts.values()) {
if (cur.getId().equals(paymentAttemptId)) {
+ if (paymentMethodId != null) {
+ cur.setPaymentMethodId(paymentMethodId);
+ }
cur.setStateName(state);
cur.setTransactionId(transactionId);
if (pluginProperties != null) {
@@ -377,6 +381,11 @@ public class MockPaymentDao extends MockEntityDaoBase<PaymentModelDao, Payment,
}
@Override
+ public List<PaymentMethodModelDao> getPaymentMethodsIncludedDeleted(final InternalTenantContext context) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
public Pagination<PaymentMethodModelDao> getPaymentMethods(final String pluginName, final Long offset, final Long limit, final InternalTenantContext context) {
throw new UnsupportedOperationException();
}
diff --git a/payment/src/test/java/org/killbill/billing/payment/dao/TestPaymentDao.java b/payment/src/test/java/org/killbill/billing/payment/dao/TestPaymentDao.java
index 113c147..8e0f0fe 100644
--- a/payment/src/test/java/org/killbill/billing/payment/dao/TestPaymentDao.java
+++ b/payment/src/test/java/org/killbill/billing/payment/dao/TestPaymentDao.java
@@ -564,7 +564,7 @@ public class TestPaymentDao extends PaymentTestSuiteWithEmbeddedDB {
properties.add(new PluginProperty("prop2", "value2", false));
final byte [] serializedProperties = PluginPropertySerializer.serialize(properties);
- paymentDao.updatePaymentAttemptWithProperties(rehydratedAttempt.getId(), transactionId, newStateName, serializedProperties, internalCallContext);
+ paymentDao.updatePaymentAttemptWithProperties(rehydratedAttempt.getId(), rehydratedAttempt.getPaymentMethodId(), transactionId, newStateName, serializedProperties, internalCallContext);
final PaymentAttemptModelDao attempt2 = paymentDao.getPaymentAttempt(rehydratedAttempt.getId(), internalCallContext);
assertEquals(attempt2.getStateName(), newStateName);
assertEquals(attempt2.getTransactionId(), transactionId);
diff --git a/payment/src/test/java/org/killbill/billing/payment/MockInvoice.java b/payment/src/test/java/org/killbill/billing/payment/MockInvoice.java
index 0100b74..eca259b 100644
--- a/payment/src/test/java/org/killbill/billing/payment/MockInvoice.java
+++ b/payment/src/test/java/org/killbill/billing/payment/MockInvoice.java
@@ -222,5 +222,15 @@ public class MockInvoice extends EntityBase implements Invoice {
public boolean isParentInvoice() {
return parentInvoice;
}
+
+ @Override
+ public UUID getParentAccountId() {
+ return null;
+ }
+
+ @Override
+ public UUID getParentInvoiceId() {
+ return null;
+ }
}
diff --git a/payment/src/test/java/org/killbill/billing/payment/PaymentTestSuiteWithEmbeddedDB.java b/payment/src/test/java/org/killbill/billing/payment/PaymentTestSuiteWithEmbeddedDB.java
index 0210596..cf7454d 100644
--- a/payment/src/test/java/org/killbill/billing/payment/PaymentTestSuiteWithEmbeddedDB.java
+++ b/payment/src/test/java/org/killbill/billing/payment/PaymentTestSuiteWithEmbeddedDB.java
@@ -33,7 +33,9 @@ import org.killbill.billing.payment.core.PaymentPluginServiceRegistration;
import org.killbill.billing.payment.core.PaymentProcessor;
import org.killbill.billing.payment.core.janitor.IncompletePaymentTransactionTask;
import org.killbill.billing.payment.core.janitor.Janitor;
+import org.killbill.billing.payment.core.sm.PaymentControlStateMachineHelper;
import org.killbill.billing.payment.core.sm.PaymentStateMachineHelper;
+import org.killbill.billing.payment.core.sm.PluginControlPaymentAutomatonRunner;
import org.killbill.billing.payment.dao.PaymentDao;
import org.killbill.billing.payment.glue.PaymentModule;
import org.killbill.billing.payment.glue.TestPaymentModuleWithEmbeddedDB;
@@ -103,6 +105,10 @@ public abstract class PaymentTestSuiteWithEmbeddedDB extends GuicyKillbillTestSu
protected IncompletePaymentTransactionTask incompletePaymentTransactionTask;
@Inject
protected GlobalLocker locker;
+ @Inject
+ protected PluginControlPaymentAutomatonRunner pluginControlPaymentAutomatonRunner;
+ @Inject
+ protected PaymentControlStateMachineHelper paymentControlStateMachineHelper;
@Override
protected KillbillConfigSource getConfigSource() {
diff --git a/payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentControlProviderPlugin.java b/payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentControlProviderPlugin.java
index 3719daf..f7c949e 100644
--- a/payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentControlProviderPlugin.java
+++ b/payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentControlProviderPlugin.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
@@ -17,6 +17,8 @@
package org.killbill.billing.payment.provider;
+import java.util.UUID;
+
import org.joda.time.DateTime;
import org.killbill.billing.payment.api.PluginProperty;
import org.killbill.billing.payment.retry.DefaultFailureCallResult;
@@ -33,6 +35,7 @@ public class MockPaymentControlProviderPlugin implements PaymentControlPluginApi
public static final String PLUGIN_NAME = "MOCK_RETRY_PLUGIN";
+ private UUID adjustedPaymentMethodId;
private boolean isAborted;
private DateTime nextRetryDate;
private Exception exception;
@@ -41,6 +44,11 @@ public class MockPaymentControlProviderPlugin implements PaymentControlPluginApi
private boolean onSuccessCallExecuted;
private boolean onFailureCallExecuted;
+ public MockPaymentControlProviderPlugin setAdjustedPaymentMethodId(final UUID adjustedPaymentMethodId) {
+ this.adjustedPaymentMethodId = adjustedPaymentMethodId;
+ return this;
+ }
+
public MockPaymentControlProviderPlugin setAborted(final boolean isAborted) {
this.isAborted = isAborted;
return this;
@@ -69,7 +77,7 @@ public class MockPaymentControlProviderPlugin implements PaymentControlPluginApi
} else if (exception instanceof RuntimeException) {
throw (RuntimeException) exception;
}
- return new DefaultPriorPaymentControlResult(isAborted);
+ return new DefaultPriorPaymentControlResult(isAborted, adjustedPaymentMethodId, null, null, null, null);
}
@Override
diff --git a/payment/src/test/java/org/killbill/billing/payment/TestJanitor.java b/payment/src/test/java/org/killbill/billing/payment/TestJanitor.java
index 73d4bbd..d09c923 100644
--- a/payment/src/test/java/org/killbill/billing/payment/TestJanitor.java
+++ b/payment/src/test/java/org/killbill/billing/payment/TestJanitor.java
@@ -115,6 +115,11 @@ public class TestJanitor extends PaymentTestSuiteWithEmbeddedDB {
);
}
+ @Override
+ protected void assertListenerStatus() {
+ testListener.assertListenerStatus();
+ }
+
@BeforeClass(groups = "slow")
protected void beforeClass() throws Exception {
super.beforeClass();
@@ -133,16 +138,12 @@ public class TestJanitor extends PaymentTestSuiteWithEmbeddedDB {
eventBus.register(testListener);
mockPaymentProviderPlugin.clear();
account = testHelper.createTestAccount("bobo@gmail.com", true);
-
- testListener.assertListenerStatus();
}
@AfterMethod(groups = "slow")
public void afterMethod() throws Exception {
retryService.stop();
- testListener.assertListenerStatus();
-
eventBus.unregister(handler);
eventBus.unregister(testListener);
super.afterMethod();
@@ -175,7 +176,7 @@ public class TestJanitor extends PaymentTestSuiteWithEmbeddedDB {
Currency.USD));
testListener.pushExpectedEvent(NextEvent.PAYMENT);
- final Payment payment = paymentApi.createPurchaseWithPaymentControl(account, account.getPaymentMethodId(), null, requestedAmount, Currency.USD, paymentExternalKey, transactionExternalKey,
+ final Payment payment = paymentApi.createPurchaseWithPaymentControl(account, account.getPaymentMethodId(), null, requestedAmount, Currency.USD, null, paymentExternalKey, transactionExternalKey,
createPropertiesForInvoice(invoice), INVOICE_PAYMENT, callContext);
testListener.assertListenerStatus();
assertEquals(payment.getTransactions().size(), 1);
@@ -231,7 +232,7 @@ public class TestJanitor extends PaymentTestSuiteWithEmbeddedDB {
invoice.addInvoiceItem(invoiceItem);
testListener.pushExpectedEvent(NextEvent.PAYMENT);
- final Payment payment = paymentApi.createPurchaseWithPaymentControl(account, account.getPaymentMethodId(), null, requestedAmount, Currency.USD, paymentExternalKey, transactionExternalKey,
+ final Payment payment = paymentApi.createPurchaseWithPaymentControl(account, account.getPaymentMethodId(), null, requestedAmount, Currency.USD, null, paymentExternalKey, transactionExternalKey,
createPropertiesForInvoice(invoice), INVOICE_PAYMENT, callContext);
testListener.assertListenerStatus();
@@ -242,7 +243,7 @@ public class TestJanitor extends PaymentTestSuiteWithEmbeddedDB {
refundProperties.add(refundIdsProp);
testListener.pushExpectedEvent(NextEvent.PAYMENT);
- final Payment payment2 = paymentApi.createRefundWithPaymentControl(account, payment.getId(), null, Currency.USD, transactionExternalKey2,
+ final Payment payment2 = paymentApi.createRefundWithPaymentControl(account, payment.getId(), null, Currency.USD, null, transactionExternalKey2,
refundProperties, INVOICE_PAYMENT, callContext);
testListener.assertListenerStatus();
@@ -280,7 +281,7 @@ public class TestJanitor extends PaymentTestSuiteWithEmbeddedDB {
final String transactionExternalKey = "lkjdsf";
testListener.pushExpectedEvent(NextEvent.PAYMENT);
- final Payment payment = paymentApi.createAuthorization(account, account.getPaymentMethodId(), null, requestedAmount, account.getCurrency(), paymentExternalKey,
+ final Payment payment = paymentApi.createAuthorization(account, account.getPaymentMethodId(), null, requestedAmount, account.getCurrency(), null, paymentExternalKey,
transactionExternalKey, ImmutableList.<PluginProperty>of(), callContext);
testListener.assertListenerStatus();
@@ -312,7 +313,7 @@ public class TestJanitor extends PaymentTestSuiteWithEmbeddedDB {
// Make sure the state as seen by the plugin will be in PaymentPluginStatus.ERROR, which will be returned later to Janitor
mockPaymentProviderPlugin.makeNextPaymentFailWithError();
testListener.pushExpectedEvent(NextEvent.PAYMENT_ERROR);
- final Payment payment = paymentApi.createAuthorization(account, account.getPaymentMethodId(), null, requestedAmount, account.getCurrency(), paymentExternalKey,
+ final Payment payment = paymentApi.createAuthorization(account, account.getPaymentMethodId(), null, requestedAmount, account.getCurrency(), null, paymentExternalKey,
transactionExternalKey, ImmutableList.<PluginProperty>of(), callContext);
testListener.assertListenerStatus();
@@ -354,7 +355,7 @@ public class TestJanitor extends PaymentTestSuiteWithEmbeddedDB {
mockPaymentProviderPlugin.makeNextPaymentFailWithException();
try {
testListener.pushExpectedEvent(NextEvent.PAYMENT_PLUGIN_ERROR);
- paymentApi.createAuthorization(account, account.getPaymentMethodId(), null, requestedAmount, account.getCurrency(), paymentExternalKey,
+ paymentApi.createAuthorization(account, account.getPaymentMethodId(), null, requestedAmount, account.getCurrency(), null, paymentExternalKey,
transactionExternalKey, ImmutableList.<PluginProperty>of(), callContext);
} catch (PaymentApiException ignore) {
testListener.assertListenerStatus();
@@ -390,7 +391,7 @@ public class TestJanitor extends PaymentTestSuiteWithEmbeddedDB {
final String transactionExternalKey = "4jhjj2";
testListener.pushExpectedEvent(NextEvent.PAYMENT);
- final Payment payment = paymentApi.createAuthorization(account, account.getPaymentMethodId(), null, requestedAmount, account.getCurrency(), paymentExternalKey,
+ final Payment payment = paymentApi.createAuthorization(account, account.getPaymentMethodId(), null, requestedAmount, account.getCurrency(), null, paymentExternalKey,
transactionExternalKey, ImmutableList.<PluginProperty>of(), callContext);
testListener.assertListenerStatus();
@@ -421,7 +422,7 @@ public class TestJanitor extends PaymentTestSuiteWithEmbeddedDB {
final String transactionExternalKey = "hoho!";
testListener.pushExpectedEvent(NextEvent.PAYMENT);
- final Payment payment = paymentApi.createAuthorization(account, account.getPaymentMethodId(), null, requestedAmount, account.getCurrency(), paymentExternalKey,
+ final Payment payment = paymentApi.createAuthorization(account, account.getPaymentMethodId(), null, requestedAmount, account.getCurrency(), null, paymentExternalKey,
transactionExternalKey, ImmutableList.<PluginProperty>of(), callContext);
testListener.assertListenerStatus();
@@ -525,7 +526,7 @@ public class TestJanitor extends PaymentTestSuiteWithEmbeddedDB {
private int getPendingNotificationCnt(final InternalCallContext internalCallContext) {
try {
- return Iterables.<NotificationEventWithMetadata>size(notificationQueueService.getNotificationQueue(DefaultPaymentService.SERVICE_NAME, Janitor.QUEUE_NAME).getFutureOrInProcessingNotificationForSearchKeys(internalCallContext.getAccountRecordId(), internalCallContext.getTenantRecordId()));
+ return Iterables.size(notificationQueueService.getNotificationQueue(DefaultPaymentService.SERVICE_NAME, Janitor.QUEUE_NAME).getFutureOrInProcessingNotificationForSearchKeys(internalCallContext.getAccountRecordId(), internalCallContext.getTenantRecordId()));
} catch (final Exception e) {
fail("Test failed ", e);
}
diff --git a/payment/src/test/java/org/killbill/billing/payment/TestPaymentHelper.java b/payment/src/test/java/org/killbill/billing/payment/TestPaymentHelper.java
index 772235b..5114550 100644
--- a/payment/src/test/java/org/killbill/billing/payment/TestPaymentHelper.java
+++ b/payment/src/test/java/org/killbill/billing/payment/TestPaymentHelper.java
@@ -146,6 +146,7 @@ public class TestPaymentHelper {
Mockito.when(accountData.isNotifiedForInvoices()).thenReturn(false);
Mockito.when(accountData.getTimeZone()).thenReturn(DateTimeZone.UTC);
Mockito.when(accountData.getCreatedDate()).thenReturn(clock.getUTCNow());
+ Mockito.when(accountData.getReferenceTime()).thenReturn(clock.getUTCNow());
Account account;
if (isFastTest()) {
diff --git a/payment/src/test/java/org/killbill/billing/payment/TestRetryService.java b/payment/src/test/java/org/killbill/billing/payment/TestRetryService.java
index fcf1dc6..1ce8045 100644
--- a/payment/src/test/java/org/killbill/billing/payment/TestRetryService.java
+++ b/payment/src/test/java/org/killbill/billing/payment/TestRetryService.java
@@ -111,7 +111,7 @@ public class TestRetryService extends PaymentTestSuiteNoDB {
final String paymentExternalKey = UUID.randomUUID().toString();
final String transactionExternalKey = UUID.randomUUID().toString();
try {
- pluginControlPaymentProcessor.createPurchase(false, account, account.getPaymentMethodId(), null, amount, Currency.USD, paymentExternalKey, transactionExternalKey,
+ pluginControlPaymentProcessor.createPurchase(false, account, account.getPaymentMethodId(), null, amount, Currency.USD, null, paymentExternalKey, transactionExternalKey,
createPropertiesForInvoice(invoice), ImmutableList.<String>of(InvoicePaymentControlPluginApi.PLUGIN_NAME), callContext, internalCallContext);
} catch (final PaymentApiException e) {
failed = true;
@@ -156,7 +156,7 @@ public class TestRetryService extends PaymentTestSuiteNoDB {
final String paymentExternalKey = UUID.randomUUID().toString();
final String transactionExternalKey = UUID.randomUUID().toString();
- pluginControlPaymentProcessor.createPurchase(false, account, account.getPaymentMethodId(), null, amount, Currency.USD, paymentExternalKey, transactionExternalKey,
+ pluginControlPaymentProcessor.createPurchase(false, account, account.getPaymentMethodId(), null, amount, Currency.USD, null, paymentExternalKey, transactionExternalKey,
createPropertiesForInvoice(invoice), ImmutableList.<String>of(InvoicePaymentControlPluginApi.PLUGIN_NAME), callContext, internalCallContext);
Payment payment = getPaymentForExternalKey(paymentExternalKey);
@@ -229,7 +229,7 @@ public class TestRetryService extends PaymentTestSuiteNoDB {
final String paymentExternalKey = UUID.randomUUID().toString();
final String transactionExternalKey = UUID.randomUUID().toString();
- pluginControlPaymentProcessor.createPurchase(false, account, account.getPaymentMethodId(), null, amount, Currency.USD, paymentExternalKey, transactionExternalKey,
+ pluginControlPaymentProcessor.createPurchase(false, account, account.getPaymentMethodId(), null, amount, Currency.USD, null, paymentExternalKey, transactionExternalKey,
createPropertiesForInvoice(invoice), ImmutableList.<String>of(InvoicePaymentControlPluginApi.PLUGIN_NAME), callContext, internalCallContext);
Payment payment = getPaymentForExternalKey(paymentExternalKey);
@@ -312,7 +312,7 @@ public class TestRetryService extends PaymentTestSuiteNoDB {
final String paymentExternalKey = UUID.randomUUID().toString();
final String transactionExternalKey = UUID.randomUUID().toString();
- pluginControlPaymentProcessor.createPurchase(false, account, account.getPaymentMethodId(), null, amount, Currency.USD, paymentExternalKey, transactionExternalKey,
+ pluginControlPaymentProcessor.createPurchase(false, account, account.getPaymentMethodId(), null, amount, Currency.USD, null, paymentExternalKey, transactionExternalKey,
createPropertiesForInvoice(invoice), ImmutableList.<String>of(InvoicePaymentControlPluginApi.PLUGIN_NAME), callContext, internalCallContext);
Payment payment = getPaymentForExternalKey(paymentExternalKey);
pom.xml 4(+2 -2)
diff --git a/pom.xml b/pom.xml
index 41427c3..18444f3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -21,10 +21,10 @@
<parent>
<artifactId>killbill-oss-parent</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.141-SNAPSHOT</version>
+ <version>0.141.19</version>
</parent>
<artifactId>killbill</artifactId>
- <version>0.19.0-SNAPSHOT</version>
+ <version>0.19.1-SNAPSHOT</version>
<packaging>pom</packaging>
<name>killbill</name>
<description>Library for managing recurring subscriptions and the associated billing</description>
profiles/killbill/pom.xml 6(+1 -5)
diff --git a/profiles/killbill/pom.xml b/profiles/killbill/pom.xml
index 834a4e1..e5b514a 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.19.0-SNAPSHOT</version>
+ <version>0.19.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-profiles-killbill</artifactId>
@@ -233,10 +233,6 @@
<scope>runtime</scope>
</dependency>
<dependency>
- <groupId>org.jdbi</groupId>
- <artifactId>jdbi</artifactId>
- </dependency>
- <dependency>
<groupId>org.kill-bill.billing</groupId>
<artifactId>killbill-account</artifactId>
</dependency>
diff --git a/profiles/killbill/src/main/java/org/killbill/billing/server/listeners/CleanupListener.java b/profiles/killbill/src/main/java/org/killbill/billing/server/listeners/CleanupListener.java
index 902ba81..d79edbe 100644
--- a/profiles/killbill/src/main/java/org/killbill/billing/server/listeners/CleanupListener.java
+++ b/profiles/killbill/src/main/java/org/killbill/billing/server/listeners/CleanupListener.java
@@ -28,6 +28,8 @@ import javax.servlet.ServletContextListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import net.sf.log4jdbc.sql.jdbcapi.DriverSpy;
+
public class CleanupListener implements ServletContextListener {
private static final Logger logger = LoggerFactory.getLogger(CleanupListener.class);
@@ -38,16 +40,6 @@ public class CleanupListener implements ServletContextListener {
@Override
public void contextDestroyed(final ServletContextEvent servletContextEvent) {
- final Enumeration<Driver> drivers = DriverManager.getDrivers();
- while (drivers.hasMoreElements()) {
- try {
- final Driver driver = drivers.nextElement();
- DriverManager.deregisterDriver(driver);
- } catch (final SQLException e) {
- logger.warn("Unable to de-register driver", e);
- }
- }
-
// See http://docs.oracle.com/cd/E17952_01/connector-j-relnotes-en/news-5-1-23.html
try {
Class.forName("com.mysql.jdbc.AbandonedConnectionCleanupThread");
@@ -65,5 +57,23 @@ public class CleanupListener implements ServletContextListener {
} catch (final ClassNotFoundException ignored) {
// MariaDB driver not used
}
+
+ try {
+ // Invoke DriverSpy directly if it hasn't been already, as it will statically load drivers
+ DriverManager.deregisterDriver(new DriverSpy());
+ } catch (final SQLException e) {
+ logger.warn("Unable to de-register driver", e);
+ }
+
+ // This needs to be last, as drivers above will invoke registerDriver statically
+ final Enumeration<Driver> drivers = DriverManager.getDrivers();
+ while (drivers.hasMoreElements()) {
+ try {
+ final Driver driver = drivers.nextElement();
+ DriverManager.deregisterDriver(driver);
+ } catch (final SQLException e) {
+ logger.warn("Unable to de-register driver", e);
+ }
+ }
}
}
diff --git a/profiles/killbill/src/main/java/org/killbill/billing/server/listeners/KillbillGuiceListener.java b/profiles/killbill/src/main/java/org/killbill/billing/server/listeners/KillbillGuiceListener.java
index 79838b1..7893081 100644
--- a/profiles/killbill/src/main/java/org/killbill/billing/server/listeners/KillbillGuiceListener.java
+++ b/profiles/killbill/src/main/java/org/killbill/billing/server/listeners/KillbillGuiceListener.java
@@ -32,6 +32,7 @@ import org.killbill.billing.server.filters.ProfilingContainerResponseFilter;
import org.killbill.billing.server.filters.RequestDataFilter;
import org.killbill.billing.server.filters.ResponseCorsFilter;
import org.killbill.billing.server.modules.KillbillServerModule;
+import org.killbill.billing.server.notifications.PushNotificationListener;
import org.killbill.billing.server.security.TenantFilter;
import org.killbill.bus.api.PersistentBus;
import org.killbill.commons.skeleton.modules.BaseServerModuleBuilder;
@@ -60,7 +61,8 @@ public class KillbillGuiceListener extends KillbillPlatformGuiceListener {
// things like static resources, favicon, etc. are 404'ed)
final BaseServerModuleBuilder builder = new BaseServerModuleBuilder().setJaxrsUriPattern("/" + SWAGGER_PATH + "|((/" + SWAGGER_PATH + "|" + JaxRsResourceBase.PREFIX + "|" + JaxRsResourceBase.PLUGINS_PATH + ")" + "/.*)")
.addJaxrsResource("org.killbill.billing.jaxrs.mappers")
- .addJaxrsResource("org.killbill.billing.jaxrs.resources")
+ // Dont' provide resources and instead add them automatically to control which one should be seen (e.g TestResource ony in testMode)
+ //.addJaxrsResource("org.killbill.billing.jaxrs.resources")
// Swagger integration
.addJaxrsResource("io.swagger.jaxrs.listing");
@@ -127,6 +129,8 @@ public class KillbillGuiceListener extends KillbillPlatformGuiceListener {
@Override
protected void stopLifecycleStage2() {
+ super.stopLifecycleStage2();
+
try {
killbillBusService.getBus().unregister(killbilleventHandler);
} catch (final PersistentBus.EventBusException e) {
@@ -147,4 +151,12 @@ public class KillbillGuiceListener extends KillbillPlatformGuiceListener {
beanConfig.setLicenseUrl("http://www.apache.org/licenses/LICENSE-2.0.html");
beanConfig.setScan(true);
}
+
+ @Override
+ protected void stopLifecycleStage3() {
+ super.stopLifecycleStage3();
+
+ final PushNotificationListener pushNotificationListener = injector.getInstance(PushNotificationListener.class);
+ pushNotificationListener.shutdown();
+ }
}
diff --git a/profiles/killbill/src/main/java/org/killbill/billing/server/log/obfuscators/LoggingFilterObfuscator.java b/profiles/killbill/src/main/java/org/killbill/billing/server/log/obfuscators/LoggingFilterObfuscator.java
new file mode 100644
index 0000000..536682f
--- /dev/null
+++ b/profiles/killbill/src/main/java/org/killbill/billing/server/log/obfuscators/LoggingFilterObfuscator.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.server.log.obfuscators;
+
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.regex.Pattern;
+
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import com.google.common.collect.ImmutableList;
+
+public class LoggingFilterObfuscator extends Obfuscator {
+
+ private static final String[] DEFAULT_SENSITIVE_HEADERS = {
+ "Authorization",
+ "X-Killbill-ApiSecret",
+ };
+
+ private final Collection<Pattern> patterns = new LinkedList<Pattern>();
+
+ public LoggingFilterObfuscator() {
+ this(ImmutableList.<Pattern>of());
+ }
+
+ public LoggingFilterObfuscator(final Collection<Pattern> extraPatterns) {
+ super();
+
+ for (final String sensitiveKey : DEFAULT_SENSITIVE_HEADERS) {
+ this.patterns.add(buildPattern(sensitiveKey));
+ }
+ this.patterns.addAll(extraPatterns);
+ }
+
+ @Override
+ public String obfuscate(final String originalString, final ILoggingEvent event) {
+ return obfuscate(originalString, patterns, event);
+ }
+
+ private Pattern buildPattern(final String key) {
+ return Pattern.compile("\\s*" + key + ":\\s*([^\\n]+)", DEFAULT_PATTERN_FLAGS);
+ }
+}
diff --git a/profiles/killbill/src/main/java/org/killbill/billing/server/log/obfuscators/ObfuscatorConverter.java b/profiles/killbill/src/main/java/org/killbill/billing/server/log/obfuscators/ObfuscatorConverter.java
index 39b9306..6e522d6 100644
--- a/profiles/killbill/src/main/java/org/killbill/billing/server/log/obfuscators/ObfuscatorConverter.java
+++ b/profiles/killbill/src/main/java/org/killbill/billing/server/log/obfuscators/ObfuscatorConverter.java
@@ -45,6 +45,7 @@ import com.google.common.collect.ImmutableList;
public class ObfuscatorConverter extends ClassicConverter {
private final Collection<Obfuscator> obfuscators = ImmutableList.<Obfuscator>of(new ConfigMagicObfuscator(),
+ new LoggingFilterObfuscator(),
new PatternObfuscator(),
new LuhnMaskingObfuscator());
diff --git a/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillBillEmbeddedDBProvider.java b/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillBillEmbeddedDBProvider.java
index b460401..a803057 100644
--- a/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillBillEmbeddedDBProvider.java
+++ b/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillBillEmbeddedDBProvider.java
@@ -34,6 +34,7 @@ public class KillBillEmbeddedDBProvider extends EmbeddedDBProvider {
final Collection<String> ddlFiles = new LinkedList<String>();
for (final String module : new String[]{"account",
"beatrix",
+ "catalog",
"entitlement",
"invoice",
"payment",
diff --git a/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillbillServerModule.java b/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillbillServerModule.java
index ff6de44..92668d3 100644
--- a/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillbillServerModule.java
+++ b/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillbillServerModule.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
@@ -37,6 +37,7 @@ import org.killbill.billing.jaxrs.resources.ExportResource;
import org.killbill.billing.jaxrs.resources.InvoicePaymentResource;
import org.killbill.billing.jaxrs.resources.InvoiceResource;
import org.killbill.billing.jaxrs.resources.NodesInfoResource;
+import org.killbill.billing.jaxrs.resources.OverdueResource;
import org.killbill.billing.jaxrs.resources.PaymentGatewayResource;
import org.killbill.billing.jaxrs.resources.PaymentMethodResource;
import org.killbill.billing.jaxrs.resources.PaymentResource;
@@ -66,9 +67,6 @@ import org.killbill.billing.subscription.glue.DefaultSubscriptionModule;
import org.killbill.billing.tenant.glue.DefaultTenantModule;
import org.killbill.billing.usage.glue.UsageModule;
import org.killbill.billing.util.config.definition.NotificationConfig;
-import org.killbill.billing.util.dao.AuditLogModelDaoMapper;
-import org.killbill.billing.util.dao.RecordIdIdMappingsMapper;
-import org.killbill.billing.util.email.EmailModule;
import org.killbill.billing.util.email.templates.TemplateModule;
import org.killbill.billing.util.glue.AuditModule;
import org.killbill.billing.util.glue.BroadcastModule;
@@ -79,6 +77,7 @@ import org.killbill.billing.util.glue.ConfigModule;
import org.killbill.billing.util.glue.CustomFieldModule;
import org.killbill.billing.util.glue.ExportModule;
import org.killbill.billing.util.glue.GlobalLockerModule;
+import org.killbill.billing.util.glue.IDBISetup;
import org.killbill.billing.util.glue.KillBillShiroAopModule;
import org.killbill.billing.util.glue.KillbillApiAopModule;
import org.killbill.billing.util.glue.NodesModule;
@@ -86,11 +85,9 @@ import org.killbill.billing.util.glue.NonEntityDaoModule;
import org.killbill.billing.util.glue.RecordIdModule;
import org.killbill.billing.util.glue.SecurityModule;
import org.killbill.billing.util.glue.TagStoreModule;
-import org.killbill.billing.util.security.shiro.dao.SessionModelDao;
import org.killbill.clock.Clock;
import org.killbill.clock.ClockMock;
import org.killbill.commons.embeddeddb.EmbeddedDB;
-import org.killbill.commons.jdbi.mapper.LowerToCamelBeanMapperFactory;
import org.skife.config.ConfigurationObjectFactory;
import org.skife.jdbi.v2.ResultSetMapperFactory;
import org.skife.jdbi.v2.tweak.ResultSetMapper;
@@ -124,16 +121,24 @@ public class KillbillServerModule extends KillbillPlatformModule {
super.configureDao();
final Multibinder<ResultSetMapperFactory> resultSetMapperFactorySetBinder = Multibinder.newSetBinder(binder(), ResultSetMapperFactory.class);
- resultSetMapperFactorySetBinder.addBinding().toInstance(new LowerToCamelBeanMapperFactory(SessionModelDao.class));
+ for (final ResultSetMapperFactory resultSetMapperFactory : IDBISetup.mapperFactoriesToRegister()) {
+ resultSetMapperFactorySetBinder.addBinding().toInstance(resultSetMapperFactory);
+ }
final Multibinder<ResultSetMapper> resultSetMapperSetBinder = Multibinder.newSetBinder(binder(), ResultSetMapper.class);
- resultSetMapperSetBinder.addBinding().to(AuditLogModelDaoMapper.class).asEagerSingleton();
- resultSetMapperSetBinder.addBinding().to(RecordIdIdMappingsMapper.class).asEagerSingleton();
+ for (final ResultSetMapper resultSetMapper : IDBISetup.mappersToRegister()) {
+ resultSetMapperSetBinder.addBinding().toInstance(resultSetMapper);
+ }
}
@Override
- protected void configureEmbeddedDB() {
- bind(EmbeddedDB.class).toProvider(KillBillEmbeddedDBProvider.class).asEagerSingleton();
+ protected void configureEmbeddedDBs() {
+ mainEmbeddedDB = new KillBillEmbeddedDBProvider(daoConfig).get();
+ bind(EmbeddedDB.class).toInstance(mainEmbeddedDB);
+
+ // Same database, but different pool: clone the object so the shutdown sequence cleans the pool properly
+ shiroEmbeddedDB = new KillBillEmbeddedDBProvider(daoConfig).get();
+ bind(EmbeddedDB.class).annotatedWith(Names.named(SHIRO_DATA_SOURCE_ID_NAMED)).toInstance(shiroEmbeddedDB);
}
@Override
@@ -163,7 +168,6 @@ public class KillbillServerModule extends KillbillPlatformModule {
install(new DefaultJunctionModule(configSource));
install(new DefaultOverdueModule(configSource));
install(new DefaultSubscriptionModule(configSource));
- install(new EmailModule(configSource));
install(new ExportModule(configSource));
install(new GlobalLockerModule(configSource));
install(new KillBillShiroAopModule());
@@ -182,6 +186,7 @@ public class KillbillServerModule extends KillbillPlatformModule {
protected void configureResources() {
bind(AccountResource.class).asEagerSingleton();
+ bind(AdminResource.class).asEagerSingleton();
bind(BundleResource.class).asEagerSingleton();
bind(CatalogResource.class).asEagerSingleton();
bind(CreditResource.class).asEagerSingleton();
@@ -189,22 +194,22 @@ public class KillbillServerModule extends KillbillPlatformModule {
bind(ExportResource.class).asEagerSingleton();
bind(InvoicePaymentResource.class).asEagerSingleton();
bind(InvoiceResource.class).asEagerSingleton();
- bind(KillbillEventHandler.class).asEagerSingleton();
+ bind(NodesInfoResource.class).asEagerSingleton();
+ bind(OverdueResource.class).asEagerSingleton();
bind(PaymentGatewayResource.class).asEagerSingleton();
bind(PaymentMethodResource.class).asEagerSingleton();
bind(PaymentResource.class).asEagerSingleton();
+ bind(PluginInfoResource.class).asEagerSingleton();
bind(PluginResource.class).asEagerSingleton();
bind(SecurityResource.class).asEagerSingleton();
bind(SubscriptionResource.class).asEagerSingleton();
bind(TagDefinitionResource.class).asEagerSingleton();
bind(TagResource.class).asEagerSingleton();
bind(TenantResource.class).asEagerSingleton();
- bind(TestResource.class).asEagerSingleton();
bind(TransactionResource.class).asEagerSingleton();
bind(UsageResource.class).asEagerSingleton();
- bind(AdminResource.class).asEagerSingleton();
- bind(PluginInfoResource.class).asEagerSingleton();
- bind(NodesInfoResource.class).asEagerSingleton();
+
+ bind(KillbillEventHandler.class).asEagerSingleton();
}
protected void configureFilters() {
diff --git a/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillBillShiroWebModule.java b/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillBillShiroWebModule.java
index 49057fb..eaf9e55 100644
--- a/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillBillShiroWebModule.java
+++ b/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillBillShiroWebModule.java
@@ -44,6 +44,7 @@ import org.killbill.billing.util.glue.KillBillShiroModule;
import org.killbill.billing.util.security.shiro.dao.JDBCSessionDao;
import org.killbill.billing.util.security.shiro.realm.KillBillJdbcRealm;
import org.killbill.billing.util.security.shiro.realm.KillBillJndiLdapRealm;
+import org.killbill.billing.util.security.shiro.realm.KillBillOktaRealm;
import org.skife.config.ConfigSource;
import org.skife.config.ConfigurationObjectFactory;
@@ -87,6 +88,9 @@ public class KillBillShiroWebModule extends ShiroWebModuleWith435 {
if (KillBillShiroModule.isLDAPEnabled()) {
bindRealm().to(KillBillJndiLdapRealm.class).asEagerSingleton();
}
+ if (KillBillShiroModule.isOktaEnabled()) {
+ bindRealm().to(KillBillOktaRealm.class).asEagerSingleton();
+ }
bindListener(new AbstractMatcher<TypeLiteral<?>>() {
@Override
diff --git a/profiles/killbill/src/main/java/org/killbill/billing/server/notifications/PushNotificationKey.java b/profiles/killbill/src/main/java/org/killbill/billing/server/notifications/PushNotificationKey.java
index e72dec3..eb25be3 100644
--- a/profiles/killbill/src/main/java/org/killbill/billing/server/notifications/PushNotificationKey.java
+++ b/profiles/killbill/src/main/java/org/killbill/billing/server/notifications/PushNotificationKey.java
@@ -33,6 +33,7 @@ public class PushNotificationKey implements NotificationEvent {
private final UUID objectId;
private final int attemptNumber;
private final String url;
+ private final String metaData;
@JsonCreator
public PushNotificationKey(@JsonProperty("tenantId") final UUID tenantId,
@@ -41,6 +42,7 @@ public class PushNotificationKey implements NotificationEvent {
@JsonProperty("objectType") final String objectType,
@JsonProperty("objectId") final UUID objectId,
@JsonProperty("attemptNumber") final int attemptNumber,
+ @JsonProperty("metaData") final String metaData,
@JsonProperty("url") final String url) {
this.tenantId = tenantId;
this.accountId = accountId;
@@ -48,12 +50,13 @@ public class PushNotificationKey implements NotificationEvent {
this.objectType = objectType;
this.objectId = objectId;
this.attemptNumber = attemptNumber;
+ this.metaData = metaData;
this.url = url;
}
public PushNotificationKey(final PushNotificationKey key, final int attemptNumber) {
this(key.getTenantId(), key.getAccountId(), key.getEventType(), key.getObjectType(), key.getObjectId(),
- attemptNumber, key.getUrl());
+ attemptNumber, key.getMetaData(), key.getUrl());
}
public UUID getTenantId() {
@@ -84,6 +87,10 @@ public class PushNotificationKey implements NotificationEvent {
return url;
}
+ public String getMetaData() {
+ return metaData;
+ }
+
@Override
public String toString() {
return "PushNotificationKey{" +
@@ -92,6 +99,7 @@ public class PushNotificationKey implements NotificationEvent {
", eventType='" + eventType + '\'' +
", objectType='" + objectType + '\'' +
", objectId=" + objectId +
+ ", metaData=" + metaData +
", attemptNumber=" + attemptNumber +
", url='" + url + '\'' +
'}';
diff --git a/profiles/killbill/src/main/java/org/killbill/billing/server/notifications/PushNotificationListener.java b/profiles/killbill/src/main/java/org/killbill/billing/server/notifications/PushNotificationListener.java
index aa6e337..b9cb18c 100644
--- a/profiles/killbill/src/main/java/org/killbill/billing/server/notifications/PushNotificationListener.java
+++ b/profiles/killbill/src/main/java/org/killbill/billing/server/notifications/PushNotificationListener.java
@@ -113,6 +113,10 @@ public class PushNotificationListener {
}
}
+ public void shutdown() {
+ httpClient.close();
+ }
+
private void dispatchCallback(final UUID tenantId, final ExtBusEvent event, final Iterable<String> callbacks) throws IOException {
final NotificationJson notification = new NotificationJson(event);
final String body = mapper.writeValueAsString(notification);
@@ -158,7 +162,8 @@ public class PushNotificationListener {
final NotificationJson notification = new NotificationJson(key.getEventType() != null ? key.getEventType().toString() : null,
key.getAccountId() != null ? key.getAccountId().toString() : null,
key.getObjectType() != null ? key.getObjectType().toString() : null,
- key.getObjectId() != null ? key.getObjectId().toString() : null);
+ key.getObjectId() != null ? key.getObjectId().toString() : null,
+ key.getMetaData());
final String body = mapper.writeValueAsString(notification);
doPost(key.getTenantId(), key.getUrl(), body, notification, TIMEOUT_NOTIFICATION, key.getAttemptNumber());
}
@@ -169,7 +174,9 @@ public class PushNotificationListener {
notificationJson.getEventType(),
notificationJson.getObjectType(),
notificationJson.getObjectId() != null ? UUID.fromString(notificationJson.getObjectId()) : null,
- attemptRetryNumber + 1, url);
+ attemptRetryNumber + 1,
+ notificationJson.getMetaData(),
+ url);
final TenantContext tenantContext = contextFactory.createTenantContext(null, tenantId);
final DateTime nextNotificationTime = getNextNotificationTime(key.getAttemptNumber(), internalCallContextFactory.createInternalTenantContextWithoutAccountRecordId(tenantContext));
diff --git a/profiles/killbill/src/main/resources/killbill-server.properties b/profiles/killbill/src/main/resources/killbill-server.properties
index 6208aa4..2894eb4 100644
--- a/profiles/killbill/src/main/resources/killbill-server.properties
+++ b/profiles/killbill/src/main/resources/killbill-server.properties
@@ -88,3 +88,4 @@ org.killbill.payment.retry.days=1,1,1
org.killbill.notificationq.analytics.tableName=analytics_notifications
org.killbill.notificationq.analytics.historyTableName=analytics_notifications_history
+
diff --git a/profiles/killbill/src/main/webapp/WEB-INF/web.xml b/profiles/killbill/src/main/webapp/WEB-INF/web.xml
index 43dda36..12e57ed 100644
--- a/profiles/killbill/src/main/webapp/WEB-INF/web.xml
+++ b/profiles/killbill/src/main/webapp/WEB-INF/web.xml
@@ -53,6 +53,13 @@
<filter-name>guiceFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
+
+ <context-param>
+ <!-- We want to make sure the Logback shutdown happens last -->
+ <param-name>logbackDisableServletContainerInitializer</param-name>
+ <param-value>true</param-value>
+ </context-param>
+
<listener>
<!-- Jersey insists on using java.util.logging (JUL) -->
<listener-class>org.killbill.commons.skeleton.listeners.JULServletContextListener</listener-class>
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 25517ab..cd4b396 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
@@ -29,6 +29,7 @@ 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.client.KillBillClient;
+import org.killbill.billing.client.KillBillClientException;
import org.killbill.billing.client.KillBillHttpClient;
import org.killbill.billing.client.RequestOptions;
import org.killbill.billing.client.model.Account;
@@ -119,11 +120,16 @@ public abstract class KillbillClient extends GuicyKillbillTestSuiteWithEmbeddedD
protected Account createAccountWithExternalPaymentMethod() throws Exception {
final Account input = createAccount();
+ createPaymentMethod(input, true);
+
+ return killBillClient.getAccount(input.getExternalKey(), requestOptions);
+ }
+
+ protected PaymentMethod createPaymentMethod(final Account input, final boolean isDefault) throws KillBillClientException {
final PaymentMethodPluginDetail info = new PaymentMethodPluginDetail();
final PaymentMethod paymentMethodJson = new PaymentMethod(null, UUIDs.randomUUID().toString(), input.getAccountId(),
- true, ExternalPaymentProviderPlugin.PLUGIN_NAME, info);
- killBillClient.createPaymentMethod(paymentMethodJson, requestOptions);
- return killBillClient.getAccount(input.getExternalKey(), requestOptions);
+ isDefault, ExternalPaymentProviderPlugin.PLUGIN_NAME, info);
+ return killBillClient.createPaymentMethod(paymentMethodJson, requestOptions);
}
protected Account createAccount() throws Exception {
@@ -240,7 +246,7 @@ public abstract class KillbillClient extends GuicyKillbillTestSuiteWithEmbeddedD
final boolean isPaymentDelegatedToParent = parentAccountId != null;
// Note: the accountId payload is ignored on account creation
- return new Account(accountId, name, length, externalKey, email, null, currency, parentAccountId, isPaymentDelegatedToParent, null, timeZone,
+ return new Account(accountId, name, length, externalKey, email, null, currency, parentAccountId, isPaymentDelegatedToParent, null, null, timeZone,
address1, address2, postalCode, company, city, state, country, locale, phone, notes, false, false, null, null);
}
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 83f655b..b315d8d 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
@@ -116,10 +116,13 @@ public class TestAccount extends TestJaxrsBase {
// Update Account
final Account newInput = new Account(input.getAccountId(),
"zozo", 4, input.getExternalKey(), "rr@google.com", 18,
- "USD", null, false, null, "UTC",
+ "USD", null, false, null, null, "UTC",
"bl1", "bh2", "", "", "ca", "San Francisco", "usa", "en", "415-255-2991",
"notes", false, false, null, null);
+
final Account updatedAccount = killBillClient.updateAccount(newInput, requestOptions);
+ // referenceTime is set automatically by system, no way to guess it
+ newInput.setReferenceTime(updatedAccount.getReferenceTime());
Assert.assertTrue(updatedAccount.equals(newInput));
// Try search endpoint
@@ -163,6 +166,7 @@ public class TestAccount extends TestJaxrsBase {
null,
null,
null,
+ null,
"notes2",
null,
null,
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 7aa8f80..a0f6ad4 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
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014 Groupon, Inc
- * Copyright 2014 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
@@ -27,11 +27,11 @@ import java.util.UUID;
import org.joda.time.DateTime;
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;
import org.killbill.billing.client.KillBillClientException;
+import org.killbill.billing.client.KillBillHttpClient;
import org.killbill.billing.client.RequestOptions;
import org.killbill.billing.client.model.Catalog;
import org.killbill.billing.client.model.Plan;
@@ -43,7 +43,9 @@ import org.killbill.billing.client.model.Usage;
import org.testng.Assert;
import org.testng.annotations.Test;
+import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Multimap;
import com.google.common.io.Resources;
public class TestCatalog extends TestJaxrsBase {
@@ -105,7 +107,7 @@ public class TestCatalog extends TestJaxrsBase {
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(), 6);
+ Assert.assertEquals(catalogsJson.get(0).getPriceLists().size(), 7);
for (final Product productJson : catalogsJson.get(0).getProducts()) {
if (!"BASE".equals(productJson.getType())) {
@@ -149,12 +151,11 @@ public class TestCatalog extends TestJaxrsBase {
Assert.assertEquals(foundBasePlans, allBasePlans);
}
- @Test(groups = "slow", description = "Try to retrieve a json version of the catalog with an invalid date",
- expectedExceptions = KillBillClientException.class,
- expectedExceptionsMessageRegExp = "There is no catalog version that applies for the given date.*")
- public void testCatalogInvalidDate() throws Exception {
+ @Test(groups = "slow", description = "Try to retrieve catalog with an effective date in the past")
+ public void testCatalogWithEffectiveDateInThePast() throws Exception {
final List<Catalog> catalogsJson = killBillClient.getJSONCatalog(DateTime.parse("2008-01-01"), requestOptions);
- Assert.fail();
+ // We expect to see our catalogTest.xml (date in the past returns the first version. See #760
+ Assert.assertEquals(catalogsJson.size(), 1);
}
@Test(groups = "slow", description = "Can create a simple Plan into a per-tenant catalog")
@@ -222,6 +223,18 @@ public class TestCatalog extends TestJaxrsBase {
Assert.assertEquals(catalogsJson.get(0).getPriceLists().get(0).getPlans().size(), 2);
}
+ @Test(groups = "slow")
+ public void testCatalogDeletionInTestMode() throws Exception {
+
+ killBillClient.addSimplePan(new SimplePlan("something-monthly", "Something", ProductCategory.BASE, Currency.USD, BigDecimal.TEN, BillingPeriod.MONTHLY, 0, TimeUnit.UNLIMITED, ImmutableList.<String>of()), requestOptions);
+ List<Catalog> catalogsJson = killBillClient.getJSONCatalog(requestOptions);
+ Assert.assertEquals(catalogsJson.size(), 1);
+ killBillClient.deleteCatalog(requestOptions);
+ // Verify that we see no catalog -- and in particular not the KB default catalog
+ catalogsJson = killBillClient.getJSONCatalog(requestOptions);
+ Assert.assertEquals(catalogsJson.size(), 0);
+
+ }
}
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestCustomField.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestCustomField.java
index 66a3af7..c66ff3b 100644
--- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestCustomField.java
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestCustomField.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2014 Ning, Inc.
- * Copyright 2014 Groupon, Inc
- * Copyright 2014 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
@@ -27,25 +27,57 @@ import org.killbill.billing.client.KillBillClientException;
import org.killbill.billing.client.model.Account;
import org.killbill.billing.client.model.CustomField;
import org.killbill.billing.client.model.CustomFields;
+import org.killbill.billing.util.api.AuditLevel;
import org.testng.Assert;
import org.testng.annotations.Test;
+import com.google.common.collect.ImmutableList;
+
public class TestCustomField extends TestJaxrsBase {
- @Test(groups = "slow", description = "Can paginate through all custom fields")
+
+ @Test(groups = "slow", description = "Can create/modify/delete custom fields")
+ public void testBasicCustomFields() throws Exception {
+ final Account account = createAccount();
+ final CustomField customField = new CustomField();
+ customField.setName("MyName");
+ customField.setValue("InitialValue");
+ killBillClient.createAccountCustomField(account.getAccountId(), customField, requestOptions);
+
+ CustomFields allCustomFields = killBillClient.getCustomFields(requestOptions);
+ Assert.assertEquals(allCustomFields.size(), 1);
+ Assert.assertEquals(allCustomFields.get(0).getName(), "MyName");
+ Assert.assertEquals(allCustomFields.get(0).getValue(), "InitialValue");
+
+ final CustomField customFieldModified = new CustomField();
+ customFieldModified.setCustomFieldId(allCustomFields.get(0).getCustomFieldId());
+ customFieldModified.setValue("NewValue");
+ killBillClient.modifyAccountCustomFields(account.getAccountId(), ImmutableList.of(customFieldModified), requestOptions);
+
+ allCustomFields = killBillClient.getCustomFields(requestOptions);
+ Assert.assertEquals(allCustomFields.size(), 1);
+ Assert.assertEquals(allCustomFields.get(0).getName(), "MyName");
+ Assert.assertEquals(allCustomFields.get(0).getValue(), "NewValue");
+
+ killBillClient.deleteAccountCustomField(account.getAccountId(), allCustomFields.get(0).getCustomFieldId(), requestOptions);
+ allCustomFields = killBillClient.getCustomFields(requestOptions);
+ Assert.assertEquals(allCustomFields.size(), 0);
+ }
+
+ @Test(groups = "slow", description = "Can paginate through all custom fields")
public void testCustomFieldsPagination() throws Exception {
final Account account = createAccount();
for (int i = 0; i < 5; i++) {
final CustomField customField = new CustomField();
customField.setName(UUID.randomUUID().toString().substring(0, 5));
customField.setValue(UUID.randomUUID().toString().substring(0, 5));
- killBillClient.createAccountCustomField(account.getAccountId(), customField, createdBy, reason, comment);
+ killBillClient.createAccountCustomField(account.getAccountId(), customField, requestOptions);
}
- final CustomFields allCustomFields = killBillClient.getCustomFields();
+ final CustomFields allCustomFields = killBillClient.getCustomFields(requestOptions);
Assert.assertEquals(allCustomFields.size(), 5);
- CustomFields page = killBillClient.getCustomFields(0L, 1L);
+ CustomFields page = killBillClient.getCustomFields(0L, 1L, requestOptions);
for (int i = 0; i < 5; i++) {
Assert.assertNotNull(page);
Assert.assertEquals(page.size(), 1);
@@ -60,15 +92,21 @@ public class TestCustomField extends TestJaxrsBase {
doSearchCustomField(customField.getValue(), customField);
}
- final CustomFields customFields = killBillClient.searchCustomFields(ObjectType.ACCOUNT.toString());
+ final CustomFields customFields = killBillClient.searchCustomFields(ObjectType.ACCOUNT.toString(), requestOptions);
Assert.assertEquals(customFields.size(), 5);
Assert.assertEquals(customFields.getPaginationCurrentOffset(), 0);
Assert.assertEquals(customFields.getPaginationTotalNbRecords(), 5);
Assert.assertEquals(customFields.getPaginationMaxNbRecords(), 5);
+
+ final CustomFields allAccountCustomFields = killBillClient.getAllAccountCustomFields(account.getAccountId(), null, AuditLevel.FULL, requestOptions);
+ Assert.assertEquals(allAccountCustomFields.size(), 5);
+
+ final CustomFields allBundleCustomFieldsForAccount = killBillClient.getAllAccountCustomFields(account.getAccountId(), ObjectType.ACCOUNT.name(), AuditLevel.FULL, requestOptions);
+ Assert.assertEquals(allBundleCustomFieldsForAccount.size(), 5);
}
private void doSearchCustomField(final String searchKey, @Nullable final CustomField expectedCustomField) throws KillBillClientException {
- final CustomFields customFields = killBillClient.searchCustomFields(searchKey);
+ final CustomFields customFields = killBillClient.searchCustomFields(searchKey, requestOptions);
if (expectedCustomField == null) {
Assert.assertTrue(customFields.isEmpty());
Assert.assertEquals(customFields.getPaginationCurrentOffset(), 0);
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 4427ea6..94150fa 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
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2016 Groupon, Inc
- * Copyright 2014-2016 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
@@ -22,6 +22,7 @@ import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
+import java.util.regex.Pattern;
import org.joda.time.DateTime;
import org.joda.time.Interval;
@@ -41,15 +42,14 @@ import org.killbill.billing.client.model.Invoice;
import org.killbill.billing.client.model.PhasePriceOverride;
import org.killbill.billing.client.model.Subscription;
import org.killbill.billing.client.model.Tags;
-import org.killbill.billing.entitlement.EntitlementTransitionType;
import org.killbill.billing.entitlement.api.Entitlement.EntitlementActionPolicy;
+import org.killbill.billing.entitlement.api.Entitlement.EntitlementState;
import org.killbill.billing.entitlement.api.SubscriptionEventType;
import org.killbill.billing.util.api.AuditLevel;
import org.testng.Assert;
import org.testng.annotations.Test;
import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
@@ -259,20 +259,20 @@ public class TestEntitlement extends TestJaxrsBase {
Assert.assertEquals(subscription.getEvents().size(), 3);
Assert.assertEquals(subscription.getEvents().get(0).getEventType(), SubscriptionEventType.START_ENTITLEMENT.name());
- Assert.assertEquals(subscription.getEvents().get(0).getPlan(), "shotgun-monthly-1");
- Assert.assertEquals(subscription.getEvents().get(0).getPhase(), "shotgun-monthly-1-trial");
+ assertMatches(subscription.getEvents().get(0).getPlan(), "shotgun-monthly-[1-9]+");
+ assertMatches(subscription.getEvents().get(0).getPhase(), "shotgun-monthly-[1-9]+-trial");
Assert.assertEquals(subscription.getEvents().get(0).getPriceList(), PriceListSet.DEFAULT_PRICELIST_NAME.toString());
Assert.assertEquals(subscription.getEvents().get(0).getProduct(), "Shotgun");
Assert.assertEquals(subscription.getEvents().get(1).getEventType(), SubscriptionEventType.START_BILLING.name());
- Assert.assertEquals(subscription.getEvents().get(1).getPlan(), "shotgun-monthly-1");
- Assert.assertEquals(subscription.getEvents().get(1).getPhase(), "shotgun-monthly-1-trial");
+ assertMatches(subscription.getEvents().get(1).getPlan(), "shotgun-monthly-[1-9]+");
+ assertMatches(subscription.getEvents().get(1).getPhase(), "shotgun-monthly-[1-9]+-trial");
Assert.assertEquals(subscription.getEvents().get(1).getPriceList(), PriceListSet.DEFAULT_PRICELIST_NAME.toString());
Assert.assertEquals(subscription.getEvents().get(1).getProduct(), "Shotgun");
Assert.assertEquals(subscription.getEvents().get(2).getEventType(), SubscriptionEventType.PHASE.name());
- Assert.assertEquals(subscription.getEvents().get(2).getPlan(), "shotgun-monthly-1");
- Assert.assertEquals(subscription.getEvents().get(2).getPhase(), "shotgun-monthly-1-evergreen");
+ assertMatches(subscription.getEvents().get(2).getPlan(), "shotgun-monthly-[1-9]+");
+ assertMatches(subscription.getEvents().get(2).getPhase(), "shotgun-monthly-[1-9]+-evergreen");
Assert.assertEquals(subscription.getEvents().get(2).getPriceList(), PriceListSet.DEFAULT_PRICELIST_NAME.toString());
Assert.assertEquals(subscription.getEvents().get(2).getProduct(), "Shotgun");
@@ -298,20 +298,20 @@ public class TestEntitlement extends TestJaxrsBase {
Assert.assertEquals(subscription3.getEvents().size(), 4);
Assert.assertEquals(subscription3.getEvents().get(0).getEventType(), SubscriptionEventType.START_ENTITLEMENT.name());
- Assert.assertEquals(subscription3.getEvents().get(0).getPlan(), "shotgun-monthly-1");
- Assert.assertEquals(subscription3.getEvents().get(0).getPhase(), "shotgun-monthly-1-trial");
+ assertMatches(subscription3.getEvents().get(0).getPlan(), "shotgun-monthly-[1-9]+");
+ assertMatches(subscription3.getEvents().get(0).getPhase(), "shotgun-monthly-[1-9]+-trial");
Assert.assertEquals(subscription3.getEvents().get(0).getPriceList(), PriceListSet.DEFAULT_PRICELIST_NAME.toString());
Assert.assertEquals(subscription3.getEvents().get(0).getProduct(), "Shotgun");
Assert.assertEquals(subscription3.getEvents().get(1).getEventType(), SubscriptionEventType.START_BILLING.name());
- Assert.assertEquals(subscription3.getEvents().get(1).getPlan(), "shotgun-monthly-1");
- Assert.assertEquals(subscription3.getEvents().get(1).getPhase(), "shotgun-monthly-1-trial");
+ assertMatches(subscription3.getEvents().get(1).getPlan(), "shotgun-monthly-[1-9]+");
+ assertMatches(subscription3.getEvents().get(1).getPhase(), "shotgun-monthly-[1-9]+-trial");
Assert.assertEquals(subscription3.getEvents().get(1).getPriceList(), PriceListSet.DEFAULT_PRICELIST_NAME.toString());
Assert.assertEquals(subscription3.getEvents().get(1).getProduct(), "Shotgun");
Assert.assertEquals(subscription3.getEvents().get(2).getEventType(), SubscriptionEventType.PHASE.name());
- Assert.assertEquals(subscription3.getEvents().get(2).getPlan(), "shotgun-monthly-1");
- Assert.assertEquals(subscription3.getEvents().get(2).getPhase(), "shotgun-monthly-1-evergreen");
+ assertMatches(subscription3.getEvents().get(2).getPlan(), "shotgun-monthly-[1-9]+");
+ assertMatches(subscription3.getEvents().get(2).getPhase(), "shotgun-monthly-[1-9]+-evergreen");
Assert.assertEquals(subscription3.getEvents().get(2).getPriceList(), PriceListSet.DEFAULT_PRICELIST_NAME.toString());
Assert.assertEquals(subscription3.getEvents().get(2).getProduct(), "Shotgun");
@@ -322,12 +322,16 @@ public class TestEntitlement extends TestJaxrsBase {
Assert.assertEquals(subscription3.getEvents().get(3).getProduct(), "Pistol");
}
+ private void assertMatches(final String actual, final String regexp) {
+ Assert.assertTrue(Pattern.compile(regexp).matcher(actual).matches(), String.format("%s doesn't match pattern %s", actual, regexp));
+ }
+
@Test(groups = "slow", description = "Create a base entitlement and also addOns entitlements under the same bundle")
- public void testEntitlementWithAddOns() throws Exception {
+ public void testEntitlementWithAddOnsWithWRITTEN_OFF() throws Exception {
final DateTime initialDate = new DateTime(2012, 4, 25, 0, 3, 42, 0);
clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis());
- final Account accountJson = createAccountWithDefaultPaymentMethod();
+ final Account accountJson = createAccount();
final Subscription base = new Subscription();
base.setAccountId(accountJson.getAccountId());
@@ -357,23 +361,43 @@ public class TestEntitlement extends TestJaxrsBase {
subscriptions.add(base);
subscriptions.add(addOn1);
subscriptions.add(addOn2);
+
final Bundle bundle = killBillClient.createSubscriptionWithAddOns(subscriptions, null, 10, requestOptions);
assertNotNull(bundle);
assertEquals(bundle.getExternalKey(), "base");
assertEquals(bundle.getSubscriptions().size(), 3);
- final List<Invoice> invoices = killBillClient.getInvoicesForAccount(accountJson.getAccountId(), true, false, false, AuditLevel.FULL, requestOptions);
+ final List<Invoice> invoices = killBillClient.getInvoicesForAccount(accountJson.getAccountId(), true, false, false, requestOptions);
assertEquals(invoices.size(), 1);
+ assertEquals(invoices.get(0).getBalance().compareTo(BigDecimal.ZERO), 1);
+ assertEquals(killBillClient.getInvoiceTags(invoices.get(0).getInvoiceId(), requestOptions).size(), 0);
+
+ final Bundles accountBundles = killBillClient.getAccountBundles(accountJson.getAccountId(), requestOptions);
+ assertEquals(accountBundles.size(), 1);
+ for (final Subscription subscription : accountBundles.get(0).getSubscriptions()) {
+ assertEquals(subscription.getState(), EntitlementState.ACTIVE);
+ }
+
+ killBillClient.closeAccount(accountJson.getAccountId(), true, true, false, requestOptions);
+
+ final Bundles accountBundlesAfterClose = killBillClient.getAccountBundles(accountJson.getAccountId(), requestOptions);
+ assertEquals(accountBundlesAfterClose.size(), 1);
+ for (final Subscription subscription : accountBundlesAfterClose.get(0).getSubscriptions()) {
+ assertEquals(subscription.getState(), EntitlementState.CANCELLED);
+ }
+
+ final List<Invoice> invoicesAfterClose = killBillClient.getInvoicesForAccount(accountJson.getAccountId(), true, false, false, requestOptions);
+ assertEquals(invoicesAfterClose.size(), 1);
+ assertEquals(invoicesAfterClose.get(0).getBalance().compareTo(BigDecimal.ZERO), 0);
+ assertEquals(killBillClient.getInvoiceTags(invoicesAfterClose.get(0).getInvoiceId(), requestOptions).size(), 1);
}
-
-
@Test(groups = "slow", description = "Create a bulk of base entitlement and addOns under the same transaction")
- public void testCreateEntitlementsWithAddOns() throws Exception {
+ public void testCreateEntitlementsWithAddOnsThenCloseAccountWithItemAdjustment() throws Exception {
final DateTime initialDate = new DateTime(2012, 4, 25, 0, 3, 42, 0);
clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis());
- final Account accountJson = createAccountWithDefaultPaymentMethod();
+ final Account accountJson = createAccount();
final Subscription base = new Subscription();
base.setAccountId(accountJson.getAccountId());
@@ -406,13 +430,37 @@ public class TestEntitlement extends TestJaxrsBase {
bulkList.add(new BulkBaseSubscriptionAndAddOns(subscriptions));
final Bundles bundles = killBillClient.createSubscriptionsWithAddOns(bulkList, null, 10, requestOptions);
-
assertNotNull(bundles);
assertEquals(bundles.size(), 2);
assertFalse(bundles.get(0).getExternalKey().equals(bundles.get(1).getExternalKey()));
final List<Invoice> invoices = killBillClient.getInvoicesForAccount(accountJson.getAccountId(), true, false, false, requestOptions);
assertEquals(invoices.size(), 1);
+ assertEquals(invoices.get(0).getBalance().compareTo(BigDecimal.ZERO), 1);
+ assertEquals(killBillClient.getInvoiceTags(invoices.get(0).getInvoiceId(), requestOptions).size(), 0);
+
+ final Bundles accountBundles = killBillClient.getAccountBundles(accountJson.getAccountId(), requestOptions);
+ assertEquals(accountBundles.size(), 2);
+ for (final Bundle bundle : accountBundles) {
+ for (final Subscription subscription : bundle.getSubscriptions()) {
+ assertEquals(subscription.getState(), EntitlementState.ACTIVE);
+ }
+ }
+
+ killBillClient.closeAccount(accountJson.getAccountId(), true, false, true, requestOptions);
+
+ final Bundles accountBundlesAfterClose = killBillClient.getAccountBundles(accountJson.getAccountId(), requestOptions);
+ assertEquals(accountBundlesAfterClose.size(), 2);
+ for (final Bundle bundle : accountBundlesAfterClose) {
+ for (final Subscription subscription : bundle.getSubscriptions()) {
+ assertEquals(subscription.getState(), EntitlementState.CANCELLED);
+ }
+ }
+
+ final List<Invoice> invoicesAfterClose = killBillClient.getInvoicesForAccount(accountJson.getAccountId(), true, false, false, requestOptions);
+ assertEquals(invoicesAfterClose.size(), 1);
+ assertEquals(invoicesAfterClose.get(0).getBalance().compareTo(BigDecimal.ZERO), 0);
+ assertEquals(killBillClient.getInvoiceTags(invoicesAfterClose.get(0).getInvoiceId(), requestOptions).size(), 0);
}
@Test(groups = "slow", description = "Create a bulk of base entitlements and addOns under the same transaction",
@@ -497,7 +545,6 @@ public class TestEntitlement extends TestJaxrsBase {
assertEquals(invoices.size(), 2);
}
-
@Test(groups = "slow", description = "Can create an entitlement in the future")
public void testCreateEntitlementInTheFuture() throws Exception {
final DateTime initialDate = new DateTime(2012, 4, 25, 0, 3, 42, 0);
@@ -594,8 +641,6 @@ public class TestEntitlement extends TestJaxrsBase {
Assert.assertEquals(result2.getBillCycleDayLocal(), new Integer(9));
}
-
-
@Test(groups = "slow", description = "Can create subscription and change plan using planName")
public void testEntitlementUsingPlanName() throws Exception {
final DateTime initialDate = new DateTime(2012, 4, 25, 0, 3, 42, 0);
@@ -624,8 +669,61 @@ public class TestEntitlement extends TestJaxrsBase {
Assert.assertEquals(newEntitlementJson.getBillingPeriod(), BillingPeriod.MONTHLY);
Assert.assertEquals(newEntitlementJson.getPriceList(), DefaultPriceListSet.DEFAULT_PRICELIST_NAME);
Assert.assertEquals(newEntitlementJson.getPlanName(), "pistol-monthly");
+ }
+
+ @Test(groups = "slow", description = "Can changePlan and undo changePlan on a subscription")
+ public void testEntitlementUndoChangePlan() throws Exception {
+ final DateTime initialDate = new DateTime(2012, 4, 25, 0, 3, 42, 0);
+ clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis());
+
+ final Account accountJson = createAccountWithDefaultPaymentMethod();
+
+ final String productName = "Shotgun";
+ final BillingPeriod term = BillingPeriod.MONTHLY;
+
+ final Subscription entitlementJson = createEntitlement(accountJson.getAccountId(), "99999", productName,
+ ProductCategory.BASE, term, true);
+
+
+
+ // Change plan in the future
+ final String newProductName = "Assault-Rifle";
+
+ final Subscription newInput = new Subscription();
+ newInput.setAccountId(entitlementJson.getAccountId());
+ newInput.setSubscriptionId(entitlementJson.getSubscriptionId());
+ newInput.setProductName(newProductName);
+ newInput.setProductCategory(ProductCategory.BASE);
+ newInput.setBillingPeriod(entitlementJson.getBillingPeriod());
+ newInput.setPriceList(entitlementJson.getPriceList());
+
+ Subscription refreshedSubscription = killBillClient.updateSubscription(newInput, new LocalDate(2012, 4, 28), null, CALL_COMPLETION_TIMEOUT_SEC, requestOptions);
+ Assert.assertNotNull(refreshedSubscription);
+
+
+ final Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(1));
+ clock.addDeltaFromReality(it.toDurationMillis());
+
+ killBillClient.undoChangePlan(refreshedSubscription.getSubscriptionId(), requestOptions);
+
+ // MOVE AFTER TRIAL
+ final Interval it2 = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(30));
+ clock.addDeltaFromReality(it2.toDurationMillis());
+
+ crappyWaitForLackOfProperSynchonization();
+
+ // Retrieves to check EndDate
+ refreshedSubscription = killBillClient.getSubscription(entitlementJson.getSubscriptionId(), requestOptions);
+ Assert.assertEquals(refreshedSubscription.getPriceOverrides().size(), 2);
+ Assert.assertEquals(refreshedSubscription.getPriceOverrides().get(0).getPhaseName(), "shotgun-monthly-trial");
+ Assert.assertEquals(refreshedSubscription.getPriceOverrides().get(0).getFixedPrice(), BigDecimal.ZERO);
+ Assert.assertNull(refreshedSubscription.getPriceOverrides().get(0).getRecurringPrice());
+ Assert.assertEquals(refreshedSubscription.getPriceOverrides().get(1).getPhaseName(), "shotgun-monthly-evergreen");
+ Assert.assertNull(refreshedSubscription.getPriceOverrides().get(1).getFixedPrice());
+ Assert.assertEquals(refreshedSubscription.getPriceOverrides().get(1).getRecurringPrice(), new BigDecimal("249.95"));
}
+
}
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 8c611f6..e1d9f5c 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
@@ -27,6 +27,7 @@ import java.util.UUID;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.LocalDate;
+import org.killbill.billing.api.FlakyRetryAnalyzer;
import org.killbill.billing.catalog.api.BillingPeriod;
import org.killbill.billing.catalog.api.ProductCategory;
import org.killbill.billing.client.KillBillClientException;
@@ -446,6 +447,12 @@ public class TestInvoice extends TestJaxrsBase {
externalCharge.setAmount(chargeAmount);
externalCharge.setCurrency(accountJson.getCurrency());
externalCharge.setDescription(UUID.randomUUID().toString());
+
+ final LocalDate startDate = clock.getUTCToday();
+ externalCharge.setStartDate(startDate);
+ final LocalDate endDate = startDate.plusDays(10);
+ externalCharge.setEndDate(endDate);
+
final List<InvoiceItem> createdExternalCharges = killBillClient.createExternalCharges(ImmutableList.of(externalCharge), clock.getUTCToday(), false, true, requestOptions);
assertEquals(createdExternalCharges.size(), 1);
final Invoice invoiceWithItems = killBillClient.getInvoice(createdExternalCharges.get(0).getInvoiceId(), true, false, requestOptions);
@@ -453,6 +460,8 @@ public class TestInvoice extends TestJaxrsBase {
assertEquals(invoiceWithItems.getItems().size(), 1);
assertEquals(invoiceWithItems.getItems().get(0).getDescription(), externalCharge.getDescription());
assertNull(invoiceWithItems.getItems().get(0).getBundleId());
+ assertEquals(invoiceWithItems.getItems().get(0).getStartDate().compareTo(startDate), 0);
+ assertEquals(invoiceWithItems.getItems().get(0).getEndDate().compareTo(endDate), 0);
// Verify the total number of invoices
assertEquals(killBillClient.getInvoicesForAccount(accountJson.getAccountId(), requestOptions).size(), 3);
@@ -493,7 +502,8 @@ public class TestInvoice extends TestJaxrsBase {
assertEquals(killBillClient.getInvoicesForAccount(accountJson.getAccountId(), requestOptions).size(), 3);
}
- @Test(groups = "slow", description = "Can create multiple external charges with same invoice and external keys")
+ // Flaky, see https://github.com/killbill/killbill/issues/801
+ @Test(groups = "slow", description = "Can create multiple external charges with same invoice and external keys", retryAnalyzer = FlakyRetryAnalyzer.class)
public void testExternalChargesWithSameInvoiceAndExternalKeys() throws Exception {
final Account accountJson = createAccountWithPMBundleAndSubscriptionAndWaitForFirstInvoice();
@@ -590,98 +600,6 @@ public class TestInvoice extends TestJaxrsBase {
assertEquals(killBillClient.getInvoicesForAccount(accountJson.getAccountId(), requestOptions).size(), 3);
}
- @Test(groups = "slow", description = "Can create an external charge on an existing invoice")
- public void testExternalChargeOnExistingInvoice() throws Exception {
- final Account accountJson = createAccountNoPMBundleAndSubscriptionAndWaitForFirstInvoice();
-
- // Get the invoices
- final List<Invoice> invoices = killBillClient.getInvoicesForAccount(accountJson.getAccountId(), true, false, requestOptions);
- // 2 invoices but look for the non zero dollar one
- assertEquals(invoices.size(), 2);
- final UUID invoiceId = invoices.get(1).getInvoiceId();
- final BigDecimal originalInvoiceAmount = invoices.get(1).getAmount();
- final int originalNumberOfItemsForInvoice = invoices.get(1).getItems().size();
-
- // Post an external charge
- final BigDecimal chargeAmount = BigDecimal.TEN;
- final InvoiceItem externalCharge = new InvoiceItem();
- externalCharge.setAccountId(accountJson.getAccountId());
- externalCharge.setAmount(chargeAmount);
- externalCharge.setCurrency(accountJson.getCurrency());
- externalCharge.setInvoiceId(invoiceId);
- final List<InvoiceItem> createdExternalCharges = killBillClient.createExternalCharges(ImmutableList.of(externalCharge), clock.getUTCToday(), false, true, requestOptions);
- assertEquals(createdExternalCharges.size(), 1);
- final Invoice invoiceWithItems = killBillClient.getInvoice(createdExternalCharges.get(0).getInvoiceId(), true, false, requestOptions);
- assertEquals(invoiceWithItems.getItems().size(), originalNumberOfItemsForInvoice + 1);
- assertNull(invoiceWithItems.getItems().get(originalNumberOfItemsForInvoice).getBundleId());
-
- // Verify the new invoice balance
- final Invoice adjustedInvoice = killBillClient.getInvoice(invoiceId, requestOptions);
- final BigDecimal adjustedInvoiceBalance = originalInvoiceAmount.add(chargeAmount.setScale(2, RoundingMode.HALF_UP));
- assertEquals(adjustedInvoice.getBalance().compareTo(adjustedInvoiceBalance), 0);
- }
-
- @Test(groups = "slow", description = "Can create an external charge on an existing invoice and trigger a payment")
- public void testExternalChargeOnExistingInvoiceWithAutomaticPayment() throws Exception {
- final Account accountJson = createAccountWithPMBundleAndSubscriptionAndWaitForFirstInvoice();
-
- // Get the invoices
- final List<Invoice> invoices = killBillClient.getInvoicesForAccount(accountJson.getAccountId(), true, false, requestOptions);
- // 2 invoices but look for the non zero dollar one
- assertEquals(invoices.size(), 2);
- final UUID invoiceId = invoices.get(1).getInvoiceId();
- final int originalNumberOfItemsForInvoice = invoices.get(1).getItems().size();
-
- // Post an external charge
- final BigDecimal chargeAmount = BigDecimal.TEN;
- final InvoiceItem externalCharge = new InvoiceItem();
- externalCharge.setAccountId(accountJson.getAccountId());
- externalCharge.setAmount(chargeAmount);
- externalCharge.setCurrency(accountJson.getCurrency());
- externalCharge.setInvoiceId(invoiceId);
- final List<InvoiceItem> createdExternalCharges = killBillClient.createExternalCharges(ImmutableList.of(externalCharge), clock.getUTCToday(), true, true, requestOptions);
- assertEquals(createdExternalCharges.size(), 1);
- final Invoice invoiceWithItems = killBillClient.getInvoice(createdExternalCharges.get(0).getInvoiceId(), true,false, requestOptions);
- assertEquals(invoiceWithItems.getItems().size(), originalNumberOfItemsForInvoice + 1);
- assertNull(invoiceWithItems.getItems().get(originalNumberOfItemsForInvoice).getBundleId());
-
- // Verify the new invoice balance
- final Invoice adjustedInvoice = killBillClient.getInvoice(invoiceId, requestOptions);
- assertEquals(adjustedInvoice.getBalance().compareTo(BigDecimal.ZERO), 0);
- }
-
- @Test(groups = "slow", description = "Can create an external charge for a bundle on an existing invoice")
- public void testExternalChargeForBundleOnExistingInvoice() throws Exception {
- final Account accountJson = createAccountNoPMBundleAndSubscriptionAndWaitForFirstInvoice();
-
- // Get the invoices
- final List<Invoice> invoices = killBillClient.getInvoicesForAccount(accountJson.getAccountId(), true, false, requestOptions);
- // 2 invoices but look for the non zero dollar one
- assertEquals(invoices.size(), 2);
- final UUID invoiceId = invoices.get(1).getInvoiceId();
- final BigDecimal originalInvoiceAmount = invoices.get(1).getAmount();
- final int originalNumberOfItemsForInvoice = invoices.get(1).getItems().size();
-
- // Post an external charge
- final BigDecimal chargeAmount = BigDecimal.TEN;
- final UUID bundleId = UUID.randomUUID();
- final InvoiceItem externalCharge = new InvoiceItem();
- externalCharge.setAccountId(accountJson.getAccountId());
- externalCharge.setAmount(chargeAmount);
- externalCharge.setCurrency(accountJson.getCurrency());
- externalCharge.setInvoiceId(invoiceId);
- externalCharge.setBundleId(bundleId);
- final List<InvoiceItem> createdExternalCharges = killBillClient.createExternalCharges(ImmutableList.<InvoiceItem>of(externalCharge), clock.getUTCToday(), false, true, requestOptions);
- final Invoice invoiceWithItems = killBillClient.getInvoice(createdExternalCharges.get(0).getInvoiceId(), true,false, requestOptions);
- assertEquals(invoiceWithItems.getItems().size(), originalNumberOfItemsForInvoice + 1);
- assertEquals(invoiceWithItems.getItems().get(originalNumberOfItemsForInvoice).getBundleId(), bundleId);
-
- // Verify the new invoice balance
- final Invoice adjustedInvoice = killBillClient.getInvoice(invoiceId, requestOptions);
- final BigDecimal adjustedInvoiceBalance = originalInvoiceAmount.add(chargeAmount.setScale(2, RoundingMode.HALF_UP));
- assertEquals(adjustedInvoice.getBalance().compareTo(adjustedInvoiceBalance), 0);
- }
-
@Test(groups = "slow", description = "Can paginate and search through all invoices")
public void testInvoicesPagination() throws Exception {
createAccountWithPMBundleAndSubscriptionAndWaitForFirstInvoice();
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 661a1c6..14b0023 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
@@ -24,6 +24,8 @@ import java.util.List;
import java.util.UUID;
import org.joda.time.DateTime;
+import org.killbill.billing.catalog.api.BillingPeriod;
+import org.killbill.billing.catalog.api.ProductCategory;
import org.killbill.billing.client.KillBillClientException;
import org.killbill.billing.client.model.Account;
import org.killbill.billing.client.model.Invoice;
@@ -36,19 +38,24 @@ import org.killbill.billing.client.model.Payment;
import org.killbill.billing.client.model.PaymentMethod;
import org.killbill.billing.client.model.PaymentTransaction;
import org.killbill.billing.client.model.Payments;
+import org.killbill.billing.client.model.Subscription;
import org.killbill.billing.osgi.api.OSGIServiceRegistration;
import org.killbill.billing.payment.api.TransactionType;
import org.killbill.billing.payment.plugin.api.PaymentPluginApi;
import org.killbill.billing.payment.provider.MockPaymentProviderPlugin;
+import org.killbill.billing.util.tag.ControlTagType;
import org.testng.Assert;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
import com.google.inject.Inject;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
public class TestInvoicePayment extends TestJaxrsBase {
@@ -63,11 +70,40 @@ public class TestInvoicePayment extends TestJaxrsBase {
mockPaymentProviderPlugin = (MockPaymentProviderPlugin) registry.getServiceForName(PLUGIN_NAME);
}
+
+
+
@Test(groups = "slow")
public void testRetrievePayment() throws Exception {
final InvoicePayment paymentJson = setupScenarioWithPayment();
- final Payment retrievedPaymentJson = killBillClient.getPayment(paymentJson.getPaymentId(), false);
+ final Payment retrievedPaymentJson = killBillClient.getPayment(paymentJson.getPaymentId(), false, requestOptions);
+ Assert.assertTrue(retrievedPaymentJson.equals((Payment) paymentJson));
+ }
+
+
+ @Test(groups = "slow")
+ public void testInvoicePaymentCompletion() throws Exception {
+ mockPaymentProviderPlugin.makeNextPaymentPending();
+
+ final InvoicePayment paymentJson = setupScenarioWithPayment();
+
+ final Payment retrievedPaymentJson = killBillClient.getPayment(paymentJson.getPaymentId(), false, requestOptions);
Assert.assertTrue(retrievedPaymentJson.equals((Payment) paymentJson));
+ Assert.assertEquals(retrievedPaymentJson.getTransactions().size(), 1);
+ Assert.assertEquals(retrievedPaymentJson.getTransactions().get(0).getStatus(), "PENDING");
+
+ final PaymentTransaction completeTransactionByPaymentId = new PaymentTransaction();
+ completeTransactionByPaymentId.setPaymentId(retrievedPaymentJson.getPaymentId());
+
+ final Account accountWithBalance = killBillClient.getAccount(paymentJson.getAccountId(), true, false, requestOptions);
+ Assert.assertTrue(accountWithBalance.getAccountBalance().compareTo(BigDecimal.ZERO) > 0);
+
+ final Payment completedPayment = killBillClient.completeInvoicePayment(completeTransactionByPaymentId, null, ImmutableMap.<String, String>of(), requestOptions);
+ Assert.assertEquals(completedPayment.getTransactions().get(0).getStatus(), "SUCCESS");
+
+ final Account accountWithBalance2 = killBillClient.getAccount(paymentJson.getAccountId(), true, false, requestOptions);
+ Assert.assertEquals(accountWithBalance2.getAccountBalance().compareTo(BigDecimal.ZERO), 0);
+
}
@Test(groups = "slow", description = "Can create a full refund with no adjustment")
@@ -82,7 +118,7 @@ public class TestInvoicePayment extends TestJaxrsBase {
final InvoicePaymentTransaction refund = new InvoicePaymentTransaction();
refund.setPaymentId(invoicePaymentJson.getPaymentId());
refund.setAmount(refundAmount);
- final Payment paymentAfterRefundJson = killBillClient.createInvoicePaymentRefund(refund, createdBy, reason, comment);
+ final Payment paymentAfterRefundJson = killBillClient.createInvoicePaymentRefund(refund, requestOptions);
verifyRefund(invoicePaymentJson, paymentAfterRefundJson, refundAmount);
// Verify the invoice balance
@@ -101,7 +137,7 @@ public class TestInvoicePayment extends TestJaxrsBase {
final InvoicePaymentTransaction refund = new InvoicePaymentTransaction();
refund.setPaymentId(paymentJson.getPaymentId());
refund.setAmount(refundAmount);
- final Payment paymentAfterRefundJson = killBillClient.createInvoicePaymentRefund(refund, createdBy, reason, comment);
+ final Payment paymentAfterRefundJson = killBillClient.createInvoicePaymentRefund(refund, requestOptions);
verifyRefund(paymentJson, paymentAfterRefundJson, refundAmount);
// Verify the invoice balance
@@ -113,7 +149,7 @@ public class TestInvoicePayment extends TestJaxrsBase {
final InvoicePayment paymentJson = setupScenarioWithPayment();
// Get the individual items for the invoice
- final Invoice invoice = killBillClient.getInvoice(paymentJson.getTargetInvoiceId(), true);
+ final Invoice invoice = killBillClient.getInvoice(paymentJson.getTargetInvoiceId(), true, false, requestOptions);
final InvoiceItem itemToAdjust = invoice.getItems().get(0);
// Issue a refund for the full amount
@@ -129,7 +165,7 @@ public class TestInvoicePayment extends TestJaxrsBase {
adjustment.setInvoiceItemId(itemToAdjust.getInvoiceItemId());
/* null amount means full adjustment for that item */
refund.setAdjustments(ImmutableList.<InvoiceItem>of(adjustment));
- final Payment paymentAfterRefundJson = killBillClient.createInvoicePaymentRefund(refund, createdBy, reason, comment);
+ final Payment paymentAfterRefundJson = killBillClient.createInvoicePaymentRefund(refund, requestOptions);
verifyRefund(paymentJson, paymentAfterRefundJson, refundAmount);
// Verify the invoice balance
@@ -141,7 +177,7 @@ public class TestInvoicePayment extends TestJaxrsBase {
final InvoicePayment paymentJson = setupScenarioWithPayment();
// Get the individual items for the invoice
- final Invoice invoice = killBillClient.getInvoice(paymentJson.getTargetInvoiceId(), true);
+ final Invoice invoice = killBillClient.getInvoice(paymentJson.getTargetInvoiceId(), true, false, requestOptions);
final InvoiceItem itemToAdjust = invoice.getItems().get(0);
// Issue a refund for a fraction of the amount
@@ -156,7 +192,7 @@ public class TestInvoicePayment extends TestJaxrsBase {
adjustment.setInvoiceItemId(itemToAdjust.getInvoiceItemId());
adjustment.setAmount(refundAmount);
refund.setAdjustments(ImmutableList.<InvoiceItem>of(adjustment));
- final Payment paymentAfterRefundJson = killBillClient.createInvoicePaymentRefund(refund, createdBy, reason, comment);
+ final Payment paymentAfterRefundJson = killBillClient.createInvoicePaymentRefund(refund, requestOptions);
verifyRefund(paymentJson, paymentAfterRefundJson, refundAmount);
// Verify the invoice balance
@@ -202,30 +238,30 @@ public class TestInvoicePayment extends TestJaxrsBase {
final InvoicePaymentTransaction refund = new InvoicePaymentTransaction();
refund.setPaymentId(lastPayment.getPaymentId());
refund.setAmount(lastPayment.getPurchasedAmount());
- killBillClient.createInvoicePaymentRefund(refund, createdBy, reason, comment);
+ killBillClient.createInvoicePaymentRefund(refund, requestOptions);
final InvoicePayment invoicePayment = new InvoicePayment();
invoicePayment.setPurchasedAmount(lastPayment.getPurchasedAmount());
invoicePayment.setAccountId(lastPayment.getAccountId());
invoicePayment.setTargetInvoiceId(lastPayment.getTargetInvoiceId());
- final InvoicePayment payment = killBillClient.createInvoicePayment(invoicePayment, false, createdBy, reason, comment);
+ final InvoicePayment payment = killBillClient.createInvoicePayment(invoicePayment, false, requestOptions);
lastPayment = payment;
}
- final InvoicePayments allPayments = killBillClient.getInvoicePaymentsForAccount(lastPayment.getAccountId());
+ final InvoicePayments allPayments = killBillClient.getInvoicePaymentsForAccount(lastPayment.getAccountId(), requestOptions);
Assert.assertEquals(allPayments.size(), 6);
final List<PaymentTransaction> objRefundFromJson = getPaymentTransactions(allPayments, TransactionType.REFUND.toString());
Assert.assertEquals(objRefundFromJson.size(), 5);
- Payments paymentsPage = killBillClient.getPayments(0L, 1L);
+ Payments paymentsPage = killBillClient.getPayments(0L, 1L, requestOptions);
for (int i = 0; i < 6; i++) {
Assert.assertNotNull(paymentsPage);
Assert.assertEquals(paymentsPage.size(), 1);
Assert.assertTrue(paymentsPage.get(0).equals((Payment) allPayments.get(i)));
paymentsPage = paymentsPage.getNext();
}
- Assert.assertNull(paymentsPage);
+ assertNull(paymentsPage);
}
@Test(groups = "slow")
@@ -260,6 +296,56 @@ public class TestInvoicePayment extends TestJaxrsBase {
}
}
+
+ @Test(groups = "slow")
+ public void testManualInvoicePayment() throws Exception {
+
+ final Account accountJson = createAccountWithDefaultPaymentMethod();
+ assertNotNull(accountJson);
+
+ // Disable automatic payments
+ killBillClient.createAccountTag(accountJson.getAccountId(), ControlTagType.AUTO_PAY_OFF.getId(), requestOptions);
+
+ // Add a bundle, subscription and move the clock to get the first invoice
+ final Subscription subscriptionJson = createEntitlement(accountJson.getAccountId(), UUID.randomUUID().toString(), "Shotgun",
+ ProductCategory.BASE, BillingPeriod.MONTHLY, true);
+ assertNotNull(subscriptionJson);
+ clock.addDays(32);
+ crappyWaitForLackOfProperSynchonization();
+
+ final List<Invoice> invoices = killBillClient.getInvoicesForAccount(accountJson.getAccountId(), requestOptions);
+ assertEquals(invoices.size(), 2);
+
+
+ final InvoicePayment invoicePayment1 = new InvoicePayment();
+ invoicePayment1.setPurchasedAmount(invoices.get(1).getBalance().add(BigDecimal.TEN));
+ invoicePayment1.setAccountId(accountJson.getAccountId());
+ invoicePayment1.setTargetInvoiceId(invoices.get(1).getInvoiceId());
+
+ // Pay too too much => 400
+ try {
+ killBillClient.createInvoicePayment(invoicePayment1, false, requestOptions);
+ Assert.fail("InvoicePayment call should fail with 400");
+ } catch (final KillBillClientException e) {
+ assertTrue(true);
+ }
+
+
+ final InvoicePayment invoicePayment2 = new InvoicePayment();
+ invoicePayment2.setPurchasedAmount(invoices.get(1).getBalance());
+ invoicePayment2.setAccountId(accountJson.getAccountId());
+ invoicePayment2.setTargetInvoiceId(invoices.get(1).getInvoiceId());
+
+ // Just right, Yah! => 201
+ final InvoicePayment result2 = killBillClient.createInvoicePayment(invoicePayment2, false, requestOptions);
+ assertEquals(result2.getTransactions().size(), 1);
+ assertTrue(result2.getTransactions().get(0).getAmount().compareTo(invoices.get(1).getBalance()) == 0);
+
+ // Already paid -> 204
+ final InvoicePayment result3 = killBillClient.createInvoicePayment(invoicePayment2, false, requestOptions);
+ assertNull(result3);
+ }
+
private BigDecimal getFractionOfAmount(final BigDecimal amount) {
return amount.divide(BigDecimal.TEN).setScale(2, BigDecimal.ROUND_HALF_UP);
}
@@ -267,14 +353,14 @@ public class TestInvoicePayment extends TestJaxrsBase {
private InvoicePayment setupScenarioWithPayment() throws Exception {
final Account accountJson = createAccountWithPMBundleAndSubscriptionAndWaitForFirstInvoice();
- final List<InvoicePayment> paymentsForAccount = killBillClient.getInvoicePaymentsForAccount(accountJson.getAccountId());
+ final List<InvoicePayment> paymentsForAccount = killBillClient.getInvoicePaymentsForAccount(accountJson.getAccountId(), requestOptions);
Assert.assertEquals(paymentsForAccount.size(), 1);
final InvoicePayment paymentJson = paymentsForAccount.get(0);
// Check the PaymentMethod from paymentMethodId returned in the Payment object
final UUID paymentMethodId = paymentJson.getPaymentMethodId();
- final PaymentMethod paymentMethodJson = killBillClient.getPaymentMethod(paymentMethodId, true);
+ final PaymentMethod paymentMethodJson = killBillClient.getPaymentMethod(paymentMethodId, true, requestOptions);
Assert.assertEquals(paymentMethodJson.getPaymentMethodId(), paymentMethodId);
Assert.assertEquals(paymentMethodJson.getAccountId(), accountJson.getAccountId());
@@ -299,7 +385,7 @@ public class TestInvoicePayment extends TestJaxrsBase {
Assert.assertEquals(refund.getEffectiveDate().getDayOfMonth(), clock.getUTCNow().getDayOfMonth());
// Verify the refund via the payment API
- final Payment retrievedPaymentJson = killBillClient.getPayment(paymentJson.getPaymentId(), true);
+ final Payment retrievedPaymentJson = killBillClient.getPayment(paymentJson.getPaymentId(), true, requestOptions);
Assert.assertEquals(retrievedPaymentJson.getPaymentId(), paymentJson.getPaymentId());
Assert.assertEquals(retrievedPaymentJson.getPurchasedAmount().setScale(2, RoundingMode.HALF_UP), paymentJson.getPurchasedAmount().setScale(2, RoundingMode.HALF_UP));
Assert.assertEquals(retrievedPaymentJson.getAccountId(), paymentJson.getAccountId());
@@ -308,7 +394,7 @@ public class TestInvoicePayment extends TestJaxrsBase {
}
private void verifyInvoice(final InvoicePayment paymentJson, final BigDecimal expectedInvoiceBalance) throws KillBillClientException {
- final Invoice invoiceJson = killBillClient.getInvoice(paymentJson.getTargetInvoiceId());
+ final Invoice invoiceJson = killBillClient.getInvoice(paymentJson.getTargetInvoiceId(), requestOptions);
Assert.assertEquals(invoiceJson.getBalance().setScale(2, BigDecimal.ROUND_HALF_UP),
expectedInvoiceBalance.setScale(2, BigDecimal.ROUND_HALF_UP));
}
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestJaxrsBase.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestJaxrsBase.java
index 66007ef..01cc712 100644
--- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestJaxrsBase.java
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestJaxrsBase.java
@@ -18,6 +18,9 @@
package org.killbill.billing.jaxrs;
+import java.lang.management.ManagementFactory;
+import java.lang.management.ThreadInfo;
+import java.lang.management.ThreadMXBean;
import java.util.EventListener;
import java.util.Iterator;
import java.util.List;
@@ -37,13 +40,10 @@ import org.killbill.billing.GuicyKillbillTestWithEmbeddedDBModule;
import org.killbill.billing.api.TestApiListener;
import org.killbill.billing.client.KillBillClient;
import org.killbill.billing.client.KillBillHttpClient;
-import org.killbill.billing.client.RequestOptions;
import org.killbill.billing.client.model.Payment;
import org.killbill.billing.client.model.PaymentTransaction;
import org.killbill.billing.client.model.Tenant;
-import org.killbill.billing.invoice.api.InvoiceNotifier;
import org.killbill.billing.invoice.glue.DefaultInvoiceModule;
-import org.killbill.billing.invoice.notification.NullInvoiceNotifier;
import org.killbill.billing.jetty.HttpServer;
import org.killbill.billing.jetty.HttpServerConfig;
import org.killbill.billing.lifecycle.glue.BusModule;
@@ -61,6 +61,8 @@ import org.killbill.billing.util.config.definition.SecurityConfig;
import org.killbill.bus.api.PersistentBus;
import org.killbill.commons.jdbi.guice.DaoConfig;
import org.skife.config.ConfigurationObjectFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.AfterSuite;
import org.testng.annotations.BeforeClass;
@@ -77,6 +79,12 @@ import com.google.inject.util.Modules;
public class TestJaxrsBase extends KillbillClient {
+ private static final Logger log = LoggerFactory.getLogger(TestJaxrsBase.class);
+
+ protected final int DEFAULT_CONNECT_TIMEOUT_SEC = 10;
+ protected final int DEFAULT_READ_TIMEOUT_SEC = 60;
+ protected final int DEFAULT_REQUEST_TIMEOUT_SEC = DEFAULT_READ_TIMEOUT_SEC;
+
protected static final String PLUGIN_NAME = "noop";
@Inject
@@ -145,10 +153,6 @@ public class TestJaxrsBase extends KillbillClient {
super(configSource);
}
- @Override
- protected void installInvoiceNotifier() {
- bind(InvoiceNotifier.class).to(NullInvoiceNotifier.class).asEagerSingleton();
- }
}
private final class PaymentMockModule extends PaymentModule {
@@ -168,7 +172,12 @@ public class TestJaxrsBase extends KillbillClient {
username,
password,
apiKey,
- apiSecret);
+ apiSecret,
+ null,
+ null,
+ DEFAULT_CONNECT_TIMEOUT_SEC * 1000,
+ DEFAULT_READ_TIMEOUT_SEC * 1000,
+ DEFAULT_REQUEST_TIMEOUT_SEC * 1000);
killBillClient = new KillBillClient(killBillHttpClient);
}
@@ -294,4 +303,24 @@ public class TestJaxrsBase extends KillbillClient {
})));
}
+ protected void printThreadDump() {
+ final StringBuilder dump = new StringBuilder("Thread dump:\n");
+ final ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
+ final ThreadInfo[] threadInfos = threadMXBean.getThreadInfo(threadMXBean.getAllThreadIds(), 100);
+ for (final ThreadInfo threadInfo : threadInfos) {
+ dump.append('"');
+ dump.append(threadInfo.getThreadName());
+ dump.append("\" ");
+ final Thread.State state = threadInfo.getThreadState();
+ dump.append("\n java.lang.Thread.State: ");
+ dump.append(state);
+ final StackTraceElement[] stackTraceElements = threadInfo.getStackTrace();
+ for (final StackTraceElement stackTraceElement : stackTraceElements) {
+ dump.append("\n at ");
+ dump.append(stackTraceElement);
+ }
+ dump.append("\n\n");
+ }
+ log.warn(dump.toString());
+ }
}
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 13b0fa4..2b3f8a7 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
@@ -19,12 +19,15 @@ package org.killbill.billing.jaxrs;
import java.math.BigDecimal;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import javax.annotation.Nullable;
+import org.joda.time.DateTime;
+import org.killbill.billing.ObjectType;
import org.killbill.billing.client.KillBillClientException;
import org.killbill.billing.client.RequestOptions;
import org.killbill.billing.client.model.Account;
@@ -61,6 +64,7 @@ import com.google.inject.Inject;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
import static org.testng.Assert.fail;
public class TestPayment extends TestJaxrsBase {
@@ -103,6 +107,25 @@ public class TestPayment extends TestJaxrsBase {
}
@Test(groups = "slow")
+ public void testWithTransactionEffectiveDate() throws Exception {
+ final Account account = createAccountWithDefaultPaymentMethod();
+
+
+ final PaymentTransaction authTransaction = new PaymentTransaction();
+ authTransaction.setAmount(BigDecimal.ONE);
+ authTransaction.setCurrency(account.getCurrency());
+ authTransaction.setTransactionType(TransactionType.AUTHORIZE.name());
+ final DateTime effectiveDate = new DateTime(2018, 9,4, 3,5,35);
+ authTransaction.setEffectiveDate(effectiveDate);
+
+ final Payment payment = killBillClient.createPayment(account.getAccountId(), account.getPaymentMethodId(), authTransaction,
+ ImmutableMap.<String, String>of(), requestOptions);
+ final PaymentTransaction paymentTransaction = payment.getTransactions().get(0);
+ assertTrue(paymentTransaction.getEffectiveDate().compareTo(effectiveDate) == 0);
+ }
+
+
+ @Test(groups = "slow")
public void testWithFailedPayment() throws Exception {
final Account account = createAccountWithDefaultPaymentMethod();
@@ -617,6 +640,29 @@ public class TestPayment extends TestJaxrsBase {
}
@Test(groups = "slow")
+ public void testComboAuthorizationControlWithNullPaymentMethodId() throws Exception {
+ final Account accountJson = createAccount();
+
+ // Non-default payment method
+ final PaymentMethod paymentMethod = createPaymentMethod(accountJson, false);
+ mockPaymentControlProviderPlugin.setAdjustedPaymentMethodId(paymentMethod.getPaymentMethodId());
+
+ accountJson.setAccountId(null);
+ final String paymentExternalKey = UUID.randomUUID().toString();
+
+ final PaymentTransaction authTransactionJson = new PaymentTransaction();
+ authTransactionJson.setPaymentExternalKey(paymentExternalKey);
+ authTransactionJson.setAmount(BigDecimal.TEN);
+ authTransactionJson.setTransactionType("AUTHORIZE");
+ final ComboPaymentTransaction comboPaymentTransaction = new ComboPaymentTransaction(accountJson, null, authTransactionJson, ImmutableList.<PluginProperty>of(), ImmutableList.<PluginProperty>of());
+
+ final Payment payment = killBillClient.createPayment(comboPaymentTransaction, ImmutableList.<String>of(MockPaymentControlProviderPlugin.PLUGIN_NAME), ImmutableMap.<String, String>of(), requestOptions);
+ verifyComboPayment(payment, paymentExternalKey, BigDecimal.TEN, BigDecimal.ZERO, BigDecimal.ZERO, 1, 1);
+
+ assertEquals(killBillClient.getPayment(payment.getPaymentId(), false, true, ImmutableMap.<String, String>of(), AuditLevel.NONE, requestOptions).getPaymentAttempts().size(), 1);
+ }
+
+ @Test(groups = "slow")
public void testComboAuthorizationControlPluginException() throws Exception {
final Account accountJson = getAccount();
accountJson.setAccountId(null);
@@ -672,7 +718,7 @@ public class TestPayment extends TestJaxrsBase {
public void testGetTagsForPaymentTransaction() throws Exception {
UUID tagDefinitionId = UUID.randomUUID();
String tagDefinitionName = "payment-transaction";
- TagDefinition tagDefinition = new TagDefinition(tagDefinitionId, false, tagDefinitionName, "description", null);
+ TagDefinition tagDefinition = new TagDefinition(tagDefinitionId, false, tagDefinitionName, "description", ImmutableList.<ObjectType>of(ObjectType.TRANSACTION));
final TagDefinition createdTagDefinition = killBillClient.createTagDefinition(tagDefinition, requestOptions);
final Account account = createAccountWithDefaultPaymentMethod();
@@ -695,7 +741,7 @@ public class TestPayment extends TestJaxrsBase {
public void testCreateTagForPaymentTransaction() throws Exception {
UUID tagDefinitionId = UUID.randomUUID();
String tagDefinitionName = "payment-transaction";
- TagDefinition tagDefinition = new TagDefinition(tagDefinitionId, false, tagDefinitionName, "description", null);
+ TagDefinition tagDefinition = new TagDefinition(tagDefinitionId, false, tagDefinitionName, "description", ImmutableList.<ObjectType>of(ObjectType.TRANSACTION));
final TagDefinition createdTagDefinition = killBillClient.createTagDefinition(tagDefinition, requestOptions);
final Account account = createAccountWithDefaultPaymentMethod();
@@ -799,7 +845,7 @@ public class TestPayment extends TestJaxrsBase {
assertEquals(payment.getRefundedAmount().compareTo(refundedAmount), 0);
assertEquals(payment.getTransactions().size(), nbTransactions);
- final Payments Payments = killBillClient.getPayments();
+ final Payments Payments = killBillClient.getPayments(requestOptions);
assertEquals(Payments.size(), paymentNb);
assertEquals(Payments.get(paymentNb - 1), payment);
}
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 f31858f..3d87984 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
@@ -103,7 +103,7 @@ public class TestPaymentPluginProperties extends TestJaxrsBase {
for (org.killbill.billing.payment.api.PluginProperty input : properties) {
boolean found = false;
for (org.killbill.billing.payment.api.PluginProperty expect : expectedProperties) {
- if (expect.getKey().equals(input.getKey()) && expect.getValue().equals(expect.getValue())) {
+ if (expect.getKey().equals(input.getKey()) && expect.getValue().equals(input.getValue())) {
found = true;
break;
}
@@ -111,10 +111,10 @@ public class TestPaymentPluginProperties extends TestJaxrsBase {
Assert.assertTrue(found);
}
- for (org.killbill.billing.payment.api.PluginProperty expect : properties) {
+ for (org.killbill.billing.payment.api.PluginProperty expect : expectedProperties) {
boolean found = false;
- for (org.killbill.billing.payment.api.PluginProperty input : expectedProperties) {
- if (expect.getKey().equals(input.getKey()) && expect.getValue().equals(expect.getValue())) {
+ for (org.killbill.billing.payment.api.PluginProperty input : properties) {
+ if (expect.getKey().equals(input.getKey()) && expect.getValue().equals(input.getValue())) {
found = true;
break;
}
@@ -227,9 +227,25 @@ public class TestPaymentPluginProperties extends TestJaxrsBase {
params.putAll(KillBillHttpClient.CONTROL_PLUGIN_NAME, ImmutableList.<String>of(PluginPropertiesVerificator.PLUGIN_NAME));
final RequestOptions requestOptionsWithParams = basicRequestOptions.extend()
- .withQueryParams(params).build();
+ .withQueryParams(params).build();
killBillClient.completePayment(completeTransactionByPaymentId, queryProperties, requestOptionsWithParams);
+
+ //Capture the payment
+ final PaymentTransaction captureTransaction = new PaymentTransaction();
+ captureTransaction.setPaymentId(initialPayment.getPaymentId());
+ captureTransaction.setProperties(bodyProperties);
+ captureTransaction.setAmount(BigDecimal.TEN);
+ captureTransaction.setCurrency(account.getCurrency());
+ killBillClient.captureAuthorization(captureTransaction, ImmutableList.<String>of(PluginPropertiesVerificator.PLUGIN_NAME), queryProperties, requestOptions);
+
+ //Refund the payment
+ final PaymentTransaction refundTransaction = new PaymentTransaction();
+ refundTransaction.setPaymentId(initialPayment.getPaymentId());
+ refundTransaction.setProperties(bodyProperties);
+ refundTransaction.setAmount(BigDecimal.TEN);
+ refundTransaction.setCurrency(account.getCurrency());
+ killBillClient.refundPayment(refundTransaction, ImmutableList.<String>of(PluginPropertiesVerificator.PLUGIN_NAME), queryProperties, requestOptions);
}
private Payment createVerifyTransaction(final Account account,
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPushNotification.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPushNotification.java
index 1c6b542..4b32eba 100644
--- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPushNotification.java
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPushNotification.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
@@ -34,6 +34,7 @@ import org.eclipse.jetty.servlet.ServletHolder;
import org.killbill.billing.client.KillBillClientException;
import org.killbill.billing.client.model.TenantKey;
import org.killbill.billing.jaxrs.json.NotificationJson;
+import org.killbill.billing.notification.plugin.api.ExtBusEventType;
import org.killbill.billing.server.notifications.PushNotificationListener;
import org.killbill.billing.tenant.api.TenantKV;
import org.slf4j.Logger;
@@ -74,8 +75,16 @@ public class TestPushNotification extends TestJaxrsBase {
callbackServer.stopServer();
}
+ private void assertAllCallbacksCompleted() throws InterruptedException {
+ final boolean waitForCallbacksToComplete = waitForCallbacksToComplete();
+ if (!waitForCallbacksToComplete) {
+ printThreadDump();
+ }
+ Assert.assertTrue(waitForCallbacksToComplete, "Fail to see push notification callbacks");
+ }
+
private boolean waitForCallbacksToComplete() throws InterruptedException {
- long remainingMs = 30000;
+ long remainingMs = DEFAULT_REQUEST_TIMEOUT_SEC * 1000;
do {
if (callbackCompleted) {
break;
@@ -107,10 +116,7 @@ public class TestPushNotification extends TestJaxrsBase {
// Create account to trigger a push notification
createAccount();
- final boolean success = waitForCallbacksToComplete();
- if (!success) {
- Assert.fail("Fail to see push notification callbacks after 5 sec");
- }
+ assertAllCallbacksCompleted();
if (callbackCompletedWithError) {
Assert.fail("Assertion during callback failed...");
@@ -136,7 +142,6 @@ public class TestPushNotification extends TestJaxrsBase {
final TenantKey result0 = killBillClient.registerCallbackNotificationForTenant(callback, requestOptions);
Assert.assertTrue(waitForCallbacksToComplete());
- Assert.assertTrue(callbackCompletedWithError); // expected true because is not an ACCOUNT_CREATION event
Assert.assertEquals(result0.getKey(), TenantKV.TenantKey.PUSH_NOTIFICATION_CB.toString());
Assert.assertEquals(result0.getValues().size(), 1);
@@ -167,7 +172,7 @@ public class TestPushNotification extends TestJaxrsBase {
// Create account to trigger a push notification
createAccount();
- Assert.assertTrue(waitForCallbacksToComplete());
+ assertAllCallbacksCompleted();
Assert.assertTrue(callbackCompletedWithError);
resetCallbackStatusProperties();
@@ -175,7 +180,7 @@ public class TestPushNotification extends TestJaxrsBase {
// move clock 15 minutes and get 1st retry
clock.addDeltaFromReality(900000);
- Assert.assertTrue(waitForCallbacksToComplete());
+ assertAllCallbacksCompleted();
Assert.assertTrue(callbackCompletedWithError);
resetCallbackStatusProperties();
@@ -183,7 +188,7 @@ public class TestPushNotification extends TestJaxrsBase {
// move clock an hour and get 2nd retry
clock.addDeltaFromReality(3600000);
- Assert.assertTrue(waitForCallbacksToComplete());
+ assertAllCallbacksCompleted();
Assert.assertTrue(callbackCompletedWithError);
resetCallbackStatusProperties();
@@ -194,7 +199,7 @@ public class TestPushNotification extends TestJaxrsBase {
// move clock a day, get 3rd retry and wait for a success push notification
clock.addDays(1);
- Assert.assertTrue(waitForCallbacksToComplete());
+ assertAllCallbacksCompleted();
Assert.assertFalse(callbackCompletedWithError);
unregisterTenantForCallback(callback);
@@ -221,7 +226,7 @@ public class TestPushNotification extends TestJaxrsBase {
// Create account to trigger a push notification
createAccount();
- Assert.assertTrue(waitForCallbacksToComplete());
+ assertAllCallbacksCompleted();
Assert.assertTrue(callbackCompletedWithError);
resetCallbackStatusProperties();
@@ -229,7 +234,7 @@ public class TestPushNotification extends TestJaxrsBase {
// move clock 15 minutes and get 1st retry
clock.addDeltaFromReality(900000);
- Assert.assertTrue(waitForCallbacksToComplete());
+ assertAllCallbacksCompleted();
Assert.assertTrue(callbackCompletedWithError);
resetCallbackStatusProperties();
@@ -237,7 +242,7 @@ public class TestPushNotification extends TestJaxrsBase {
// move clock an hour and get 2nd retry
clock.addDeltaFromReality(3600000);
- Assert.assertTrue(waitForCallbacksToComplete());
+ assertAllCallbacksCompleted();
Assert.assertTrue(callbackCompletedWithError);
resetCallbackStatusProperties();
@@ -245,7 +250,7 @@ public class TestPushNotification extends TestJaxrsBase {
// move clock a day and get 3rd retry
clock.addDays(1);
- Assert.assertTrue(waitForCallbacksToComplete());
+ assertAllCallbacksCompleted();
Assert.assertTrue(callbackCompletedWithError);
resetCallbackStatusProperties();
@@ -253,7 +258,7 @@ public class TestPushNotification extends TestJaxrsBase {
// move clock a day and get 4rd retry
clock.addDays(2);
- Assert.assertTrue(waitForCallbacksToComplete());
+ assertAllCallbacksCompleted();
Assert.assertTrue(callbackCompletedWithError);
resetCallbackStatusProperties();
@@ -316,14 +321,12 @@ public class TestPushNotification extends TestJaxrsBase {
public CallmebackServlet(final TestPushNotification test) {
this.test = test;
this.receivedCalls = new AtomicInteger(0);
- this.withError = false;
}
@Override
protected void doPost(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException {
final int current = receivedCalls.incrementAndGet();
final String body = CharStreams.toString(new InputStreamReader(request.getInputStream(), "UTF-8"));
- withError = false;
log.info("CallmebackServlet received {} calls , current = {} at {}", current, body, getClock().getUTCNow());
@@ -338,22 +341,31 @@ public class TestPushNotification extends TestJaxrsBase {
log.info("Got body {}", body);
- try {
- final NotificationJson notification = objectMapper.readValue(body, NotificationJson.class);
- Assert.assertEquals(notification.getEventType(), "ACCOUNT_CREATION");
- Assert.assertEquals(notification.getObjectType(), "ACCOUNT");
- Assert.assertNotNull(notification.getObjectId());
- Assert.assertNotNull(notification.getAccountId());
- Assert.assertEquals(notification.getObjectId(), notification.getAccountId());
-
- test.retrieveAccountWithAsserts(notification.getObjectId());
-
- Assert.assertEquals(request.getHeader(PushNotificationListener.HTTP_HEADER_CONTENT_TYPE), PushNotificationListener.CONTENT_TYPE_JSON);
- } catch (final AssertionError e) {
- withError = true;
+ final NotificationJson notification = objectMapper.readValue(body, NotificationJson.class);
+
+ final ExtBusEventType type = ExtBusEventType.valueOf(notification.getEventType());
+ switch (type) {
+ case TENANT_CONFIG_CHANGE:
+ Assert.assertEquals(notification.getEventType(), "TENANT_CONFIG_CHANGE");
+ Assert.assertEquals(notification.getObjectType(), "TENANT_KVS");
+ Assert.assertNotNull(notification.getObjectId());
+ Assert.assertNull(notification.getAccountId());
+ Assert.assertNotNull(notification.getMetaData());
+ Assert.assertEquals(notification.getMetaData(), "PUSH_NOTIFICATION_CB");
+ break;
+ case ACCOUNT_CREATION:
+ Assert.assertEquals(notification.getEventType(), "ACCOUNT_CREATION");
+ Assert.assertEquals(notification.getObjectType(), "ACCOUNT");
+ Assert.assertNotNull(notification.getObjectId());
+ Assert.assertNotNull(notification.getAccountId());
+ Assert.assertEquals(notification.getObjectId(), notification.getAccountId());
+ break;
}
- stopServerWhenComplete(current, withError);
+ test.retrieveAccountWithAsserts(notification.getObjectId());
+
+ Assert.assertEquals(request.getHeader(PushNotificationListener.HTTP_HEADER_CONTENT_TYPE), PushNotificationListener.CONTENT_TYPE_JSON);
+ stopServerWhenComplete(current, false);
}
private void stopServerWhenComplete(final int current, final boolean withError) {
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 44a3f3d..ae69442 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
@@ -49,9 +49,9 @@ public class TestTag extends TestJaxrsBase {
@Test(groups = "slow", description = "Cannot add badly formatted TagDefinition")
public void testTagErrorHandling() throws Exception {
- final TagDefinition[] tagDefinitions = {new TagDefinition(null, false, null, null, null),
- new TagDefinition(null, false, "something", null, null),
- new TagDefinition(null, false, null, "something", null)};
+ final TagDefinition[] tagDefinitions = {new TagDefinition(null, false, null, null, ImmutableList.<ObjectType>of(ObjectType.ACCOUNT)),
+ new TagDefinition(null, false, "something", null, ImmutableList.<ObjectType>of(ObjectType.INVOICE)),
+ new TagDefinition(null, false, null, "something", ImmutableList.<ObjectType>of(ObjectType.TRANSACTION))};
for (final TagDefinition tagDefinition : tagDefinitions) {
try {
@@ -66,7 +66,7 @@ public class TestTag extends TestJaxrsBase {
@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 input = new TagDefinition(null, false, "blue", "relaxing color", ImmutableList.<ObjectType>of(ObjectType.TRANSACTION));
final TagDefinition objFromJson = killBillClient.createTagDefinition(input, requestOptions);
assertNotNull(objFromJson);
@@ -79,20 +79,17 @@ public class TestTag extends TestJaxrsBase {
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());
+ final TagDefinition inputBlue = new TagDefinition(null, false, "blue", "relaxing color", ImmutableList.<ObjectType>of(ObjectType.TRANSACTION));
killBillClient.createTagDefinition(inputBlue, requestOptions);
- final TagDefinition inputRed = new TagDefinition(null, false, "red", "hot color", ImmutableList.<ObjectType>of());
+ final TagDefinition inputRed = new TagDefinition(null, false, "red", "hot color", ImmutableList.<ObjectType>of(ObjectType.TRANSACTION));
killBillClient.createTagDefinition(inputRed, requestOptions);
- final TagDefinition inputYellow = new TagDefinition(null, false, "yellow", "vibrant color", ImmutableList.<ObjectType>of());
+ final TagDefinition inputYellow = new TagDefinition(null, false, "yellow", "vibrant color", ImmutableList.<ObjectType>of(ObjectType.TRANSACTION));
killBillClient.createTagDefinition(inputYellow, requestOptions);
- final TagDefinition inputGreen = new TagDefinition(null, false, "green", "super relaxing color", ImmutableList.<ObjectType>of());
+ final TagDefinition inputGreen = new TagDefinition(null, false, "green", "super relaxing color", ImmutableList.<ObjectType>of(ObjectType.TRANSACTION));
killBillClient.createTagDefinition(inputGreen, requestOptions);
objFromJson = killBillClient.getTagDefinitions(requestOptions);
@@ -122,7 +119,7 @@ public class TestTag extends TestJaxrsBase {
killBillClient.createAccountTag(account.getAccountId(), controlTagType.getId(), requestOptions);
}
- final TagDefinition bundleTagDefInput = new TagDefinition(null, false, "bundletagdef", "nothing special", ImmutableList.<ObjectType>of());
+ final TagDefinition bundleTagDefInput = new TagDefinition(null, false, "bundletagdef", "nothing special", ImmutableList.<ObjectType>of(ObjectType.TRANSACTION));
final TagDefinition bundleTagDef = killBillClient.createTagDefinition(bundleTagDefInput, requestOptions);
killBillClient.createBundleTag(subscriptionJson.getBundleId(), bundleTagDef.getId(), requestOptions);
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/server/log/obfuscators/TestLoggingFilterObfuscator.java b/profiles/killbill/src/test/java/org/killbill/billing/server/log/obfuscators/TestLoggingFilterObfuscator.java
new file mode 100644
index 0000000..76351fd
--- /dev/null
+++ b/profiles/killbill/src/test/java/org/killbill/billing/server/log/obfuscators/TestLoggingFilterObfuscator.java
@@ -0,0 +1,81 @@
+/*
+ * 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.server.log.obfuscators;
+
+import org.killbill.billing.server.log.ServerTestSuiteNoDB;
+import org.mockito.Mockito;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import ch.qos.logback.classic.spi.ILoggingEvent;
+
+public class TestLoggingFilterObfuscator extends ServerTestSuiteNoDB {
+
+ private final LoggingFilterObfuscator obfuscator = new LoggingFilterObfuscator();
+
+ @Test(groups = "fast")
+ public void testAuthorization() throws Exception {
+ verify("2017-08-26T10:28:21,959+0000 lvl='INFO', log='LoggingFilter', th='qtp1071550332-34', xff='', rId='70394abe-7ab6-4b7c-aaf5-17abfcdb9622', aRId='', tRId='', 1 * Server in-bound request\n" +
+ "1 > GET http://127.0.0.1:8080/1.0/kb/security/permissions\n" +
+ "1 > User-Agent: killbill/1.9.0; jruby 9.1.12.0 (2.3.3) 2017-06-15 33c6439 Java HotSpot(TM) 64-Bit Server VM 25.121-b13 on 1.8.0_121-b13 +jit [darwin-x86_64]\n" +
+ "1 > Authorization: Basic YWRtaW46cGFzc3dvcmQ=\n" +
+ "1 > Host: 127.0.0.1:8080\n" +
+ "1 > Accept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\n" +
+ "1 > Accept: application/json\n" +
+ "1 >",
+ "2017-08-26T10:28:21,959+0000 lvl='INFO', log='LoggingFilter', th='qtp1071550332-34', xff='', rId='70394abe-7ab6-4b7c-aaf5-17abfcdb9622', aRId='', tRId='', 1 * Server in-bound request\n" +
+ "1 > GET http://127.0.0.1:8080/1.0/kb/security/permissions\n" +
+ "1 > User-Agent: killbill/1.9.0; jruby 9.1.12.0 (2.3.3) 2017-06-15 33c6439 Java HotSpot(TM) 64-Bit Server VM 25.121-b13 on 1.8.0_121-b13 +jit [darwin-x86_64]\n" +
+ "1 > Authorization: **************************\n" +
+ "1 > Host: 127.0.0.1:8080\n" +
+ "1 > Accept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\n" +
+ "1 > Accept: application/json\n" +
+ "1 >");
+ }
+
+ @Test(groups = "fast")
+ public void testApiSecret() throws Exception {
+ verify("2017-08-25T15:28:34,331+0000 lvl='INFO', log='LoggingFilter', th='qtp288887829-1845', xff='', rId='59c40009-ea68-4d87-9580-fe95e9a82c23', aRId='', tRId='11', 3896 * Server in-bound request\n" +
+ "3896 > GET http://127.0.0.1:8080/1.0/kb/paymentMethods/069a4daa-e752-486c-8e40-c9c4f9a732c4?withPluginInfo=true\n" +
+ "3896 > Cookie: JSESSIONID=64faafa1-da74-4ac7-afc7-947cc9871fe5\n" +
+ "3896 > X-Killbill-Apikey: bob\n" +
+ "3896 > Accept: application/json\n" +
+ "3896 > X-Request-Id: 59c40009-ea68-4d87-9580-fe95e9a82c23\n" +
+ "3896 > X-Killbill-Apisecret: lazar\n" +
+ "3896 > User-Agent: killbill/1.9.0; ruby 2.3.1p112 (2016-04-26 revision 54768) [x86_64-darwin16]\n" +
+ "3896 > Host: 127.0.0.1:8080\n" +
+ "3896 > Accept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\n" +
+ "3896 >",
+ "2017-08-25T15:28:34,331+0000 lvl='INFO', log='LoggingFilter', th='qtp288887829-1845', xff='', rId='59c40009-ea68-4d87-9580-fe95e9a82c23', aRId='', tRId='11', 3896 * Server in-bound request\n" +
+ "3896 > GET http://127.0.0.1:8080/1.0/kb/paymentMethods/069a4daa-e752-486c-8e40-c9c4f9a732c4?withPluginInfo=true\n" +
+ "3896 > Cookie: JSESSIONID=64faafa1-da74-4ac7-afc7-947cc9871fe5\n" +
+ "3896 > X-Killbill-Apikey: bob\n" +
+ "3896 > Accept: application/json\n" +
+ "3896 > X-Request-Id: 59c40009-ea68-4d87-9580-fe95e9a82c23\n" +
+ "3896 > X-Killbill-Apisecret: *****\n" +
+ "3896 > User-Agent: killbill/1.9.0; ruby 2.3.1p112 (2016-04-26 revision 54768) [x86_64-darwin16]\n" +
+ "3896 > Host: 127.0.0.1:8080\n" +
+ "3896 > Accept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\n" +
+ "3896 >");
+ }
+
+ private void verify(final String input, final String output) {
+ final String obfuscated = obfuscator.obfuscate(input, Mockito.mock(ILoggingEvent.class));
+ Assert.assertEquals(obfuscated, output, obfuscated);
+ }
+}
diff --git a/profiles/killbill/src/test/resources/killbill.properties b/profiles/killbill/src/test/resources/killbill.properties
index 776243b..4474ab4 100644
--- a/profiles/killbill/src/test/resources/killbill.properties
+++ b/profiles/killbill/src/test/resources/killbill.properties
@@ -34,4 +34,6 @@ org.killbill.security.shiroNbHashIterations=10
org.killbill.tenant.broadcast.rate=1s
# exponential delay retries for push notifications
-org.killbill.billing.server.notifications.retries=15m,1h,1d,2d
\ No newline at end of file
+org.killbill.billing.server.notifications.retries=15m,1h,1d,2d
+
+org.killbill.server.test.mode=true
\ No newline at end of file
profiles/killpay/pom.xml 2(+1 -1)
diff --git a/profiles/killpay/pom.xml b/profiles/killpay/pom.xml
index 86bdf4a..25446b9 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.19.0-SNAPSHOT</version>
+ <version>0.19.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-profiles-killpay</artifactId>
diff --git a/profiles/killpay/src/main/java/org/killbill/billing/server/modules/KillpayServerModule.java b/profiles/killpay/src/main/java/org/killbill/billing/server/modules/KillpayServerModule.java
index ef0ce9e..b4b8f6d 100644
--- a/profiles/killpay/src/main/java/org/killbill/billing/server/modules/KillpayServerModule.java
+++ b/profiles/killpay/src/main/java/org/killbill/billing/server/modules/KillpayServerModule.java
@@ -27,12 +27,15 @@ import org.killbill.billing.entitlement.glue.DefaultEntitlementModule;
import org.killbill.billing.invoice.glue.DefaultInvoiceModule;
import org.killbill.billing.jaxrs.glue.DefaultJaxrsModule;
import org.killbill.billing.jaxrs.resources.AccountResource;
+import org.killbill.billing.jaxrs.resources.AdminResource;
+import org.killbill.billing.jaxrs.resources.CreditResource;
import org.killbill.billing.jaxrs.resources.CustomFieldResource;
import org.killbill.billing.jaxrs.resources.ExportResource;
-import org.killbill.billing.jaxrs.resources.InvoicePaymentResource;
+import org.killbill.billing.jaxrs.resources.NodesInfoResource;
import org.killbill.billing.jaxrs.resources.PaymentGatewayResource;
import org.killbill.billing.jaxrs.resources.PaymentMethodResource;
import org.killbill.billing.jaxrs.resources.PaymentResource;
+import org.killbill.billing.jaxrs.resources.PluginInfoResource;
import org.killbill.billing.jaxrs.resources.PluginResource;
import org.killbill.billing.jaxrs.resources.SecurityResource;
import org.killbill.billing.jaxrs.resources.TagDefinitionResource;
@@ -49,7 +52,6 @@ import org.killbill.billing.server.config.KillbillServerConfig;
import org.killbill.billing.subscription.glue.DefaultSubscriptionModule;
import org.killbill.billing.tenant.glue.DefaultTenantModule;
import org.killbill.billing.usage.glue.UsageModule;
-import org.killbill.billing.util.email.EmailModule;
import org.killbill.billing.util.email.templates.TemplateModule;
import org.killbill.billing.util.glue.AuditModule;
import org.killbill.billing.util.glue.BroadcastModule;
@@ -59,9 +61,9 @@ import org.killbill.billing.util.glue.ConfigModule;
import org.killbill.billing.util.glue.CustomFieldModule;
import org.killbill.billing.util.glue.ExportModule;
import org.killbill.billing.util.glue.GlobalLockerModule;
-import org.killbill.billing.util.glue.NodesModule;
import org.killbill.billing.util.glue.KillBillShiroAopModule;
import org.killbill.billing.util.glue.KillbillApiAopModule;
+import org.killbill.billing.util.glue.NodesModule;
import org.killbill.billing.util.glue.NonEntityDaoModule;
import org.killbill.billing.util.glue.RecordIdModule;
import org.killbill.billing.util.glue.SecurityModule;
@@ -109,25 +111,29 @@ public class KillpayServerModule extends KillbillServerModule {
install(new DefaultJaxrsModule(configSource));
// TODO Dependencies for AccountResource
install(new DefaultOverdueModule(configSource));
- install(new EmailModule(configSource));
}
@Override
protected void configureResources() {
bind(AccountResource.class).asEagerSingleton();
+ bind(AdminResource.class).asEagerSingleton();
+ bind(CreditResource.class).asEagerSingleton();
bind(CustomFieldResource.class).asEagerSingleton();
bind(ExportResource.class).asEagerSingleton();
- bind(InvoicePaymentResource.class).asEagerSingleton();
+ bind(NodesInfoResource.class).asEagerSingleton();
bind(KillbillEventHandler.class).asEagerSingleton();
bind(PaymentGatewayResource.class).asEagerSingleton();
bind(PaymentMethodResource.class).asEagerSingleton();
bind(PaymentResource.class).asEagerSingleton();
bind(PluginResource.class).asEagerSingleton();
+ bind(PluginInfoResource.class).asEagerSingleton();
bind(SecurityResource.class).asEagerSingleton();
bind(TagDefinitionResource.class).asEagerSingleton();
bind(TagResource.class).asEagerSingleton();
bind(TenantResource.class).asEagerSingleton();
bind(TestResource.class).asEagerSingleton();
bind(TransactionResource.class).asEagerSingleton();
+
+ bind(KillbillEventHandler.class).asEagerSingleton();
}
}
diff --git a/profiles/killpay/src/main/webapp/WEB-INF/web.xml b/profiles/killpay/src/main/webapp/WEB-INF/web.xml
index 61f297d..e73a20c 100644
--- a/profiles/killpay/src/main/webapp/WEB-INF/web.xml
+++ b/profiles/killpay/src/main/webapp/WEB-INF/web.xml
@@ -52,6 +52,13 @@
<filter-name>guiceFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
+
+ <context-param>
+ <!-- We want to make sure the Logback shutdown happens last -->
+ <param-name>logbackDisableServletContainerInitializer</param-name>
+ <param-value>true</param-value>
+ </context-param>
+
<listener>
<!-- Jersey insists on using java.util.logging (JUL) -->
<listener-class>org.killbill.commons.skeleton.listeners.JULServletContextListener</listener-class>
profiles/pom.xml 2(+1 -1)
diff --git a/profiles/pom.xml b/profiles/pom.xml
index 7206338..910bf30 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.19.0-SNAPSHOT</version>
+ <version>0.19.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-profiles</artifactId>
README.md 2(+1 -1)
diff --git a/README.md b/README.md
index c810485..3ffcee9 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
Kill Bill is the Open-Source Billing & Payment Platform.
-Among features:
+## Among features
* Subscription engine, with plans management (trial, upgrade, downgrade, etc.), support of add-ons, bundles with multiple subscriptions
* Invoicing engine, supporting different billing alignments, recurring and one-time charges, international tax, metered billing
subscription/pom.xml 8(+2 -6)
diff --git a/subscription/pom.xml b/subscription/pom.xml
index e915aae..979df0a 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.19.0-SNAPSHOT</version>
+ <version>0.19.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-subscription</artifactId>
@@ -75,7 +75,7 @@
</dependency>
<dependency>
<groupId>org.antlr</groupId>
- <artifactId>stringtemplate</artifactId>
+ <artifactId>ST4</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
@@ -84,10 +84,6 @@
<scope>test</scope>
</dependency>
<dependency>
- <groupId>org.jdbi</groupId>
- <artifactId>jdbi</artifactId>
- </dependency>
- <dependency>
<groupId>org.kill-bill.billing</groupId>
<artifactId>killbill-account</artifactId>
<scope>test</scope>
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 1bf7beb..278ea3c 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
@@ -53,11 +53,9 @@ import com.google.common.collect.Iterables;
*/
public class PlanAligner extends BaseAligner {
- private final CatalogInternalApi catalogInternalApi;
@Inject
- public PlanAligner(final CatalogInternalApi catalogInternalApi) {
- this.catalogInternalApi = catalogInternalApi;
+ public PlanAligner() {
}
private enum WhichPhase {
@@ -65,6 +63,7 @@ public class PlanAligner extends BaseAligner {
NEXT
}
+
/**
* Returns the current and next phase for the subscription in creation
*
@@ -84,11 +83,13 @@ public class PlanAligner extends BaseAligner {
@Nullable final PhaseType initialPhase,
final String priceList,
final DateTime effectiveDate,
+ final Catalog catalog,
final InternalTenantContext context) throws CatalogApiException, SubscriptionBaseApiException {
final List<TimedPhase> timedPhases = getTimedPhaseOnCreate(alignStartDate,
bundleStartDate,
plan,
initialPhase,
+ catalog,
effectiveDate,
context);
final TimedPhase[] result = new TimedPhase[2];
@@ -113,9 +114,10 @@ public class PlanAligner extends BaseAligner {
final Plan plan,
final DateTime effectiveDate,
final PhaseType newPlanInitialPhaseType,
+ final Catalog catalog,
final InternalTenantContext context) throws CatalogApiException, SubscriptionBaseApiException {
- return getTimedPhaseOnChange(subscription, plan, effectiveDate, newPlanInitialPhaseType, WhichPhase.CURRENT, context);
+ return getTimedPhaseOnChange(subscription, plan, effectiveDate, newPlanInitialPhaseType, WhichPhase.CURRENT, catalog, context);
}
/**
@@ -134,8 +136,9 @@ public class PlanAligner extends BaseAligner {
final Plan plan,
final DateTime effectiveDate,
final PhaseType newPlanInitialPhaseType,
+ final Catalog catalog,
final InternalTenantContext context) throws CatalogApiException, SubscriptionBaseApiException {
- return getTimedPhaseOnChange(subscription, plan, effectiveDate, newPlanInitialPhaseType, WhichPhase.NEXT, context);
+ return getTimedPhaseOnChange(subscription, plan, effectiveDate, newPlanInitialPhaseType, WhichPhase.NEXT, catalog, context);
}
/**
@@ -144,7 +147,7 @@ public class PlanAligner extends BaseAligner {
* @param subscription the subscription for which we need to compute the next Phase event
* @return the next phase
*/
- public TimedPhase getNextTimedPhase(final DefaultSubscriptionBase subscription, final DateTime effectiveDate, final InternalTenantContext context) {
+ public TimedPhase getNextTimedPhase(final DefaultSubscriptionBase subscription, final DateTime effectiveDate, final Catalog catalog, final InternalTenantContext context) {
try {
final SubscriptionBaseTransition pendingOrLastPlanTransition;
@@ -162,7 +165,12 @@ public class PlanAligner extends BaseAligner {
subscription.getBundleStartDate(),
pendingOrLastPlanTransition.getNextPlan(),
pendingOrLastPlanTransition.getNextPhase().getPhaseType(),
- effectiveDate,
+ catalog,
+ // Use the catalog version at subscription creation time: this allows
+ // for PHASE events and uncancel operations for plans/products/pricelists already retired
+ // This is not 100% correct in a scenario where the catalog was updated and the alignment rules changed since
+ // See https://github.com/killbill/killbill/issues/784
+ subscription.getAlignStartDate(),
context);
return getTimedPhase(timedPhases, effectiveDate, WhichPhase.NEXT);
case CHANGE:
@@ -172,10 +180,13 @@ public class PlanAligner extends BaseAligner {
pendingOrLastPlanTransition.getPreviousPlan(),
pendingOrLastPlanTransition.getNextPlan(),
effectiveDate,
+ // Same remark as above
+ subscription.getAlignStartDate(),
pendingOrLastPlanTransition.getEffectiveTransitionTime(),
subscription.getAllTransitions().get(0).getNextPhase().getPhaseType(),
null,
WhichPhase.NEXT,
+ catalog,
context);
default:
throw new SubscriptionBaseError(String.format("Unexpected initial transition %s for current plan %s on subscription %s",
@@ -190,15 +201,14 @@ public class PlanAligner extends BaseAligner {
final DateTime bundleStartDate,
final Plan plan,
@Nullable final PhaseType initialPhase,
- final DateTime effectiveDate,
+ final Catalog catalog,
+ final DateTime catalogEffectiveDate,
final InternalTenantContext context)
throws CatalogApiException, SubscriptionBaseApiException {
- final Catalog catalog = catalogInternalApi.getFullCatalog(true, true, context);
-
final PlanSpecifier planSpecifier = new PlanSpecifier(plan.getName());
final DateTime planStartDate;
- final PlanAlignmentCreate alignment = catalog.planCreateAlignment(planSpecifier, effectiveDate);
+ final PlanAlignmentCreate alignment = catalog.planCreateAlignment(planSpecifier, catalogEffectiveDate);
switch (alignment) {
case START_OF_SUBSCRIPTION:
planStartDate = subscriptionStartDate;
@@ -218,6 +228,7 @@ public class PlanAligner extends BaseAligner {
final DateTime effectiveDate,
final PhaseType newPlanInitialPhaseType,
final WhichPhase which,
+ final Catalog catalog,
final InternalTenantContext context) throws CatalogApiException, SubscriptionBaseApiException {
final SubscriptionBaseTransition pendingOrLastPlanTransition;
if (subscription.getState() == EntitlementState.PENDING) {
@@ -231,11 +242,13 @@ public class PlanAligner extends BaseAligner {
pendingOrLastPlanTransition.getNextPlan(),
nextPlan,
effectiveDate,
+ effectiveDate,
// 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,
+ catalog,
context);
}
@@ -245,19 +258,20 @@ public class PlanAligner extends BaseAligner {
final Plan currentPlan,
final Plan nextPlan,
final DateTime effectiveDate,
+ final DateTime catalogEffectiveDate,
final DateTime lastOrCurrentChangeEffectiveDate,
final PhaseType originalInitialPhase,
@Nullable final PhaseType newPlanInitialPhaseType,
final WhichPhase which,
+ final Catalog catalog,
final InternalTenantContext context) throws CatalogApiException, SubscriptionBaseApiException {
- final Catalog catalog = catalogInternalApi.getFullCatalog(true, true, context);
final PlanPhaseSpecifier fromPlanPhaseSpecifier = new PlanPhaseSpecifier(currentPlan.getName(),
currentPhase.getPhaseType());
final PlanSpecifier toPlanSpecifier = new PlanSpecifier(nextPlan.getName());
final PhaseType initialPhase;
final DateTime planStartDate;
- final PlanAlignmentChange alignment = catalog.planChangeAlignment(fromPlanPhaseSpecifier, toPlanSpecifier, effectiveDate);
+ final PlanAlignmentChange alignment = catalog.planChangeAlignment(fromPlanPhaseSpecifier, toPlanSpecifier, catalogEffectiveDate);
switch (alignment) {
case START_OF_SUBSCRIPTION:
planStartDate = subscriptionStartDate;
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/SubscriptionApiBase.java b/subscription/src/main/java/org/killbill/billing/subscription/api/SubscriptionApiBase.java
index 0d28e58..82cc967 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/api/SubscriptionApiBase.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/api/SubscriptionApiBase.java
@@ -20,6 +20,7 @@ import java.util.ArrayList;
import java.util.List;
import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.catalog.api.Catalog;
import org.killbill.billing.catalog.api.CatalogApiException;
import org.killbill.billing.catalog.api.CatalogInternalApi;
import org.killbill.billing.subscription.api.user.DefaultSubscriptionBase;
@@ -37,15 +38,12 @@ public class SubscriptionApiBase {
protected final SubscriptionBaseApiService apiService;
protected final Clock clock;
- protected final CatalogInternalApi catalogInternalApi;
- public SubscriptionApiBase(final SubscriptionDao dao, final SubscriptionBaseApiService apiService, final Clock clock, final CatalogInternalApi catalogInternalApi) {
+ public SubscriptionApiBase(final SubscriptionDao dao, final SubscriptionBaseApiService apiService, final Clock clock) {
this.dao = dao;
this.apiService = apiService;
this.clock = clock;
- this.catalogInternalApi = catalogInternalApi;
}
-
protected List<SubscriptionBase> createSubscriptionsForApiUse(final List<SubscriptionBase> internalSubscriptions) {
return new ArrayList<SubscriptionBase>(Collections2.transform(internalSubscriptions, new Function<SubscriptionBase, SubscriptionBase>() {
@Override
@@ -59,10 +57,10 @@ public class SubscriptionApiBase {
return new DefaultSubscriptionBase((DefaultSubscriptionBase) internalSubscription, apiService, clock);
}
- protected DefaultSubscriptionBase createSubscriptionForApiUse(SubscriptionBuilder builder, List<SubscriptionBaseEvent> events, final InternalTenantContext context) throws CatalogApiException {
+ protected DefaultSubscriptionBase createSubscriptionForApiUse(SubscriptionBuilder builder, List<SubscriptionBaseEvent> events, final Catalog catalog, final InternalTenantContext context) throws CatalogApiException {
final DefaultSubscriptionBase subscription = new DefaultSubscriptionBase(builder, apiService, clock);
if (events.size() > 0) {
- subscription.rebuildTransitions(events, catalogInternalApi.getFullCatalog(true, true, context));
+ subscription.rebuildTransitions(events, catalog);
}
return subscription;
}
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 d02731d..2740f09 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
@@ -25,11 +25,13 @@ import org.joda.time.DateTime;
import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.catalog.api.BillingActionPolicy;
+import org.killbill.billing.catalog.api.Catalog;
import org.killbill.billing.catalog.api.CatalogApiException;
import org.killbill.billing.catalog.api.PhaseType;
import org.killbill.billing.catalog.api.Plan;
import org.killbill.billing.catalog.api.PlanChangeResult;
import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
+import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
import org.killbill.billing.catalog.api.PlanSpecifier;
import org.killbill.billing.subscription.api.user.DefaultSubscriptionBase;
import org.killbill.billing.subscription.api.user.SubscriptionAndAddOnsSpecifier;
@@ -42,11 +44,11 @@ import org.killbill.billing.util.callcontext.TenantContext;
public interface SubscriptionBaseApiService {
public DefaultSubscriptionBase createPlan(SubscriptionBuilder builder, Plan plan, PhaseType initialPhase,
- String realPriceList, DateTime effectiveDate, DateTime processedDate,
- CallContext context)
+ String realPriceList, DateTime effectiveDate,
+ Catalog catalog, CallContext context)
throws SubscriptionBaseApiException;
- public List<SubscriptionBaseWithAddOns> createPlansWithAddOns(UUID accountId, Iterable<SubscriptionAndAddOnsSpecifier> subscriptionsAndAddOns, CallContext context)
+ public List<SubscriptionBaseWithAddOns> createPlansWithAddOns(UUID accountId, Iterable<SubscriptionAndAddOnsSpecifier> subscriptionsAndAddOns, Catalog catalog, CallContext context)
throws SubscriptionBaseApiException;
public boolean cancel(DefaultSubscriptionBase subscription, CallContext context)
@@ -58,48 +60,63 @@ public interface SubscriptionBaseApiService {
public boolean cancelWithPolicy(DefaultSubscriptionBase subscription, BillingActionPolicy policy, int accountBillCycleDayLocal, CallContext context)
throws SubscriptionBaseApiException;
- public boolean cancelWithPolicyNoValidation(Iterable<DefaultSubscriptionBase> subscriptions, BillingActionPolicy policy, int accountBillCycleDayLocal, InternalCallContext context)
+ public boolean cancelWithPolicyNoValidationAndCatalog(Iterable<DefaultSubscriptionBase> subscriptions, BillingActionPolicy policy, int accountBillCycleDayLocal, Catalog catalog, InternalCallContext context)
throws SubscriptionBaseApiException;
public boolean uncancel(DefaultSubscriptionBase subscription, CallContext context)
throws SubscriptionBaseApiException;
// Return the effective date of the change
- public DateTime dryRunChangePlan(DefaultSubscriptionBase subscription, PlanSpecifier spec, DateTime requestedDate, BillingActionPolicy policy, TenantContext context) throws SubscriptionBaseApiException;
+ public DateTime dryRunChangePlan(DefaultSubscriptionBase subscription, PlanPhaseSpecifier spec, DateTime requestedDate, BillingActionPolicy policy, TenantContext context) throws SubscriptionBaseApiException;
// Return the effective date of the change
- public DateTime changePlan(DefaultSubscriptionBase subscription, PlanSpecifier spec, List<PlanPhasePriceOverride> overrides, CallContext context)
+ public DateTime changePlan(DefaultSubscriptionBase subscription, PlanPhaseSpecifier spec, List<PlanPhasePriceOverride> overrides, CallContext context)
throws SubscriptionBaseApiException;
// Return the effective date of the change
- public DateTime changePlanWithRequestedDate(DefaultSubscriptionBase subscription, PlanSpecifier spec,
+ public DateTime changePlanWithRequestedDate(DefaultSubscriptionBase subscription, PlanPhaseSpecifier spec,
List<PlanPhasePriceOverride> overrides, DateTime requestedDate, CallContext context)
throws SubscriptionBaseApiException;
// Return the effective date of the change
- public DateTime changePlanWithPolicy(DefaultSubscriptionBase subscription, PlanSpecifier spec,
+ public DateTime changePlanWithPolicy(DefaultSubscriptionBase subscription, PlanPhaseSpecifier spec,
List<PlanPhasePriceOverride> overrides, BillingActionPolicy policy, CallContext context)
throws SubscriptionBaseApiException;
- public int handleBasePlanEvent(final DefaultSubscriptionBase subscription, final SubscriptionBaseEvent event, final CallContext context) throws CatalogApiException;
+ public int handleBasePlanEvent(final DefaultSubscriptionBase subscription, final SubscriptionBaseEvent event, Catalog fullCatalog, final CallContext context) throws CatalogApiException;
public PlanChangeResult getPlanChangeResult(final DefaultSubscriptionBase subscription, PlanSpecifier spec, final DateTime effectiveDate, TenantContext context) throws SubscriptionBaseApiException;
//
// Lower level APIs for dryRun functionality
//
- public List<SubscriptionBaseEvent> getEventsOnCreation(UUID bundleId, UUID subscriptionId, DateTime alignStartDate, DateTime bundleStartDate,
+
+ public List<SubscriptionBaseEvent> getEventsOnCreation(UUID subscriptionId, DateTime alignStartDate, DateTime bundleStartDate,
Plan plan, PhaseType initialPhase,
- String realPriceList, DateTime effectiveDate, DateTime processedDate,
+ String realPriceList, DateTime effectiveDate,
+ Catalog fullCatalog,
InternalTenantContext context)
throws CatalogApiException, SubscriptionBaseApiException;
+ /*
+ public List<SubscriptionBaseEvent> getEventsOnChangePlan(final DefaultSubscriptionBase subscription, final Plan newPlan,
+ final String newPriceList, final DateTime effectiveDate,
+ final boolean addCancellationAddOnForEventsIfRequired, final InternalTenantContext internalTenantContext) throws CatalogApiException, SubscriptionBaseApiException {
+
+ */
+
public List<SubscriptionBaseEvent> getEventsOnChangePlan(DefaultSubscriptionBase subscription, Plan newPlan,
- String newPriceList, DateTime effectiveDate, DateTime processedDate,
- boolean addCancellationAddOnForEventsIfRequired, InternalTenantContext context)
+ String newPriceList, DateTime effectiveDate,
+ boolean addCancellationAddOnForEventsIfRequired,
+ final Catalog fullCatalog,
+ InternalTenantContext context)
throws CatalogApiException, SubscriptionBaseApiException;
public List<SubscriptionBaseEvent> getEventsOnCancelPlan(final DefaultSubscriptionBase subscription,
- final DateTime effectiveDate, final DateTime processedDate,
- final boolean addCancellationAddOnForEventsIfRequired, final InternalTenantContext internalTenantContext) throws CatalogApiException;
+ final DateTime effectiveDate,
+ final boolean addCancellationAddOnForEventsIfRequired,
+ final Catalog fullCatalog,
+ final InternalTenantContext internalTenantContext) throws CatalogApiException;
+
+ boolean undoChangePlan(DefaultSubscriptionBase defaultSubscriptionBase, CallContext 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 17e695d..fab57eb 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
@@ -46,7 +46,6 @@ 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.PlanPhaseSpecifier;
-import org.killbill.billing.catalog.api.PlanSpecifier;
import org.killbill.billing.catalog.api.ProductCategory;
import org.killbill.billing.entitlement.api.BaseEntitlementWithAddOnsSpecifier;
import org.killbill.billing.entitlement.api.Entitlement.EntitlementState;
@@ -59,7 +58,6 @@ import org.killbill.billing.subscription.api.SubscriptionApiBase;
import org.killbill.billing.subscription.api.SubscriptionBase;
import org.killbill.billing.subscription.api.SubscriptionBaseApiService;
import org.killbill.billing.subscription.api.SubscriptionBaseInternalApi;
-import org.killbill.billing.subscription.api.SubscriptionBaseTransitionType;
import org.killbill.billing.subscription.api.SubscriptionBaseWithAddOns;
import org.killbill.billing.subscription.api.user.DefaultEffectiveSubscriptionEvent;
import org.killbill.billing.subscription.api.user.DefaultSubscriptionBase;
@@ -73,7 +71,6 @@ import org.killbill.billing.subscription.api.user.SubscriptionBaseTransitionData
import org.killbill.billing.subscription.api.user.SubscriptionBuilder;
import org.killbill.billing.subscription.api.user.SubscriptionSpecifier;
import org.killbill.billing.subscription.engine.addon.AddonUtils;
-import org.killbill.billing.subscription.engine.core.DefaultSubscriptionBaseService;
import org.killbill.billing.subscription.engine.dao.SubscriptionDao;
import org.killbill.billing.subscription.engine.dao.model.SubscriptionBundleModelDao;
import org.killbill.billing.subscription.events.SubscriptionBaseEvent;
@@ -90,20 +87,14 @@ import org.killbill.billing.util.entity.Pagination;
import org.killbill.billing.util.entity.dao.DefaultPaginationHelper.SourcePaginationBuilder;
import org.killbill.clock.Clock;
import org.killbill.clock.DefaultClock;
-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 org.killbill.notificationq.api.NotificationQueueService.NoSuchNotificationQueue;
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;
import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.inject.Inject;
@@ -115,8 +106,8 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
private final AddonUtils addonUtils;
private final InternalCallContextFactory internalCallContextFactory;
-
private final NotificationQueueService notificationQueueService;
+ private final CatalogInternalApi catalogInternalApi;
public static final Comparator<SubscriptionBase> SUBSCRIPTIONS_COMPARATOR = new Comparator<SubscriptionBase>() {
@@ -140,10 +131,11 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
final CatalogInternalApi catalogInternalApi,
final AddonUtils addonUtils,
final InternalCallContextFactory internalCallContextFactory) {
- super(dao, apiService, clock, catalogInternalApi);
+ super(dao, apiService, clock);
this.addonUtils = addonUtils;
this.internalCallContextFactory = internalCallContextFactory;
this.notificationQueueService = notificationQueueService;
+ this.catalogInternalApi = catalogInternalApi;
}
@Override
@@ -173,7 +165,7 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_NO_BUNDLE, bundleId);
}
- final DefaultSubscriptionBase baseSubscription = (DefaultSubscriptionBase) dao.getBaseSubscription(bundleId, context);
+ final DefaultSubscriptionBase baseSubscription = (DefaultSubscriptionBase) dao.getBaseSubscription(bundleId, catalog, context);
// verify the number of subscriptions (of the same kind) allowed per bundle
if (ProductCategory.ADD_ON.toString().equalsIgnoreCase(plan.getProduct().getCategory().toString())) {
@@ -186,7 +178,7 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
}
}
- final DateTime bundleStartDate = getBundleStartDateWithSanity(bundleId, baseSubscription, plan, effectiveDate, context);
+ final DateTime bundleStartDate = getBundleStartDateWithSanity(bundleId, baseSubscription, plan, effectiveDate, catalog, context);
return apiService.createPlan(new SubscriptionBuilder()
.setId(UUIDs.randomUUID())
.setBundleId(bundleId)
@@ -195,7 +187,7 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
.setBundleStartDate(bundleStartDate)
.setAlignStartDate(effectiveDate)
.setMigrated(isMigrated),
- plan, spec.getPhaseType(), plan.getPriceListName(), effectiveDate, now, callContext);
+ plan, spec.getPhaseType(), plan.getPriceListName(), effectiveDate, catalog, callContext);
} catch (final CatalogApiException e) {
throw new SubscriptionBaseApiException(e);
}
@@ -284,7 +276,7 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
}
@Override
- public List<SubscriptionBaseWithAddOns> createBaseSubscriptionsWithAddOns(final UUID accountId, final Iterable<BaseEntitlementWithAddOnsSpecifier> baseEntitlementWithAddOnsSpecifier, final InternalCallContext context) throws SubscriptionBaseApiException {
+ public List<SubscriptionBaseWithAddOns> createBaseSubscriptionsWithAddOns(final UUID accountId, final Iterable<BaseEntitlementWithAddOnsSpecifier> baseEntitlementWithAddOnsSpecifier, final boolean renameCancelledBundleIfExist, final InternalCallContext context) throws SubscriptionBaseApiException {
try {
final Catalog catalog = catalogInternalApi.getFullCatalog(true, true, context);
final CallContext callContext = internalCallContextFactory.createCallContext(context);
@@ -300,10 +292,10 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
final SubscriptionBaseBundle bundle;
if (isBaseSpecifierExists) {
- bundle = createBundleForAccount(accountId, entitlementWithAddOnsSpecifier.getExternalKey(), context);
+ bundle = createBundleForAccount(accountId, entitlementWithAddOnsSpecifier.getExternalKey(), renameCancelledBundleIfExist, context);
} else {
final List<SubscriptionBaseBundle> existingBundles = dao.getSubscriptionBundlesForKey(entitlementWithAddOnsSpecifier.getExternalKey(), context);
- final SubscriptionBaseBundle tmp = getActiveBundleForKeyNotException(existingBundles, dao, clock, context);
+ final SubscriptionBaseBundle tmp = getActiveBundleForKeyNotException(existingBundles, dao, clock, catalog, context);
if (tmp == null) {
throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_NO_BP, entitlementWithAddOnsSpecifier.getExternalKey());
} else if (!tmp.getAccountId().equals(accountId)) {
@@ -329,7 +321,7 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
subscriptionAndAddOns.add(subscriptionAndAddOnsSpecifier);
}
- return apiService.createPlansWithAddOns(accountId, subscriptionAndAddOns, callContext);
+ return apiService.createPlansWithAddOns(accountId, subscriptionAndAddOns, catalog, callContext);
} catch (final CatalogApiException e) {
throw new SubscriptionBaseApiException(e);
}
@@ -356,66 +348,50 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
@Override
public void cancelBaseSubscriptions(final Iterable<SubscriptionBase> subscriptions, final BillingActionPolicy policy, int accountBillCycleDayLocal, final InternalCallContext context) throws SubscriptionBaseApiException {
- apiService.cancelWithPolicyNoValidation(Iterables.<SubscriptionBase, DefaultSubscriptionBase>transform(subscriptions,
- new Function<SubscriptionBase, DefaultSubscriptionBase>() {
- @Override
- public DefaultSubscriptionBase apply(final SubscriptionBase subscriptionBase) {
- try {
- return getDefaultSubscriptionBase(subscriptionBase, context);
- } catch (final CatalogApiException e) {
- throw new RuntimeException(e);
+
+ try {
+ final Catalog catalog = catalogInternalApi.getFullCatalog(true, true, context);
+ apiService.cancelWithPolicyNoValidationAndCatalog(Iterables.<SubscriptionBase, DefaultSubscriptionBase>transform(subscriptions,
+ new Function<SubscriptionBase, DefaultSubscriptionBase>() {
+ @Override
+ public DefaultSubscriptionBase apply(final SubscriptionBase subscriptionBase) {
+ try {
+ return getDefaultSubscriptionBase(subscriptionBase, catalog, context);
+ } catch (final CatalogApiException e) {
+ throw new RuntimeException(e);
+ }
}
- }
- }),
- policy,
- accountBillCycleDayLocal,
- context);
+ }),
+ policy,
+ accountBillCycleDayLocal,
+ catalog,
+ context);
+ } catch (CatalogApiException e) {
+ throw new SubscriptionBaseApiException(e);
+ }
+
}
@Override
- 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);
- }
- }
+ public SubscriptionBaseBundle createBundleForAccount(final UUID accountId, final String bundleKey, final boolean renameCancelledBundleIfExist, final InternalCallContext context) throws SubscriptionBaseApiException {
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);
-
+ final DefaultSubscriptionBaseBundle bundle = new DefaultSubscriptionBaseBundle(bundleKey, accountId, now, now, now, now);
if (null != bundleKey && bundleKey.length() > 255) {
throw new SubscriptionBaseApiException(ErrorCode.EXTERNAL_KEY_LIMIT_EXCEEDED);
}
- return dao.createSubscriptionBundle(bundle, context);
+ try {
+ final Catalog catalog = catalogInternalApi.getFullCatalog(true, true, context);
+ return dao.createSubscriptionBundle(bundle, catalog, renameCancelledBundleIfExist, context);
+ } catch (final CatalogApiException e) {
+ throw new SubscriptionBaseApiException(e);
+ }
}
@Override
public List<SubscriptionBaseBundle> getBundlesForAccountAndKey(final UUID accountId, final String bundleKey, final InternalTenantContext context) throws SubscriptionBaseApiException {
- return dao.getSubscriptionBundlesForAccountAndKey(accountId, bundleKey, context);
+ final SubscriptionBaseBundle subscriptionBundlesForAccountAndKey = dao.getSubscriptionBundlesForAccountAndKey(accountId, bundleKey, context);
+ return subscriptionBundlesForAccountAndKey != null ? ImmutableList.of(subscriptionBundlesForAccountAndKey) : ImmutableList.<SubscriptionBaseBundle>of();
}
@Override
@@ -440,7 +416,7 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
new Function<SubscriptionBundleModelDao, SubscriptionBaseBundle>() {
@Override
public SubscriptionBaseBundle apply(final SubscriptionBundleModelDao bundleModelDao) {
- return SubscriptionBundleModelDao.toSubscriptionbundle(bundleModelDao);
+ return SubscriptionBundleModelDao.toSubscriptionBundle(bundleModelDao);
}
}
);
@@ -458,7 +434,7 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
new Function<SubscriptionBundleModelDao, SubscriptionBaseBundle>() {
@Override
public SubscriptionBaseBundle apply(final SubscriptionBundleModelDao bundleModelDao) {
- return SubscriptionBundleModelDao.toSubscriptionbundle(bundleModelDao);
+ return SubscriptionBundleModelDao.toSubscriptionBundle(bundleModelDao);
}
}
);
@@ -470,11 +446,11 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
return dao.getNonAOSubscriptionIdsForKey(bundleKey, context);
}
- public static SubscriptionBaseBundle getActiveBundleForKeyNotException(final Iterable<SubscriptionBaseBundle> existingBundles, final SubscriptionDao dao, final Clock clock, final InternalTenantContext context) {
+ public static SubscriptionBaseBundle getActiveBundleForKeyNotException(final Iterable<SubscriptionBaseBundle> existingBundles, final SubscriptionDao dao, final Clock clock, final Catalog catalog, final InternalTenantContext context) {
for (final SubscriptionBaseBundle cur : existingBundles) {
final List<SubscriptionBase> subscriptions;
try {
- subscriptions = dao.getSubscriptions(cur.getId(), ImmutableList.<SubscriptionBaseEvent>of(), context);
+ subscriptions = dao.getSubscriptions(cur.getId(), ImmutableList.<SubscriptionBaseEvent>of(), catalog, context);
for (final SubscriptionBase s : subscriptions) {
if (s.getCategory() == ProductCategory.ADD_ON) {
continue;
@@ -497,12 +473,15 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
final InternalTenantContext context) throws SubscriptionBaseApiException {
try {
+
+ final Catalog catalog = catalogInternalApi.getFullCatalog(true, true, context);
+
final List<SubscriptionBaseEvent> outputDryRunEvents = new ArrayList<SubscriptionBaseEvent>();
final List<SubscriptionBase> outputSubscriptions = new ArrayList<SubscriptionBase>();
- populateDryRunEvents(bundleId, dryRunArguments, outputDryRunEvents, outputSubscriptions, context);
+ populateDryRunEvents(bundleId, dryRunArguments, outputDryRunEvents, outputSubscriptions, catalog, context);
final List<SubscriptionBase> result;
- result = dao.getSubscriptions(bundleId, outputDryRunEvents, context);
+ result = dao.getSubscriptions(bundleId, outputDryRunEvents, catalog, context);
if (result != null && !result.isEmpty()) {
outputSubscriptions.addAll(result);
}
@@ -517,7 +496,9 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
@Override
public Map<UUID, List<SubscriptionBase>> getSubscriptionsForAccount(final InternalTenantContext context) throws SubscriptionBaseApiException {
try {
- final Map<UUID, List<SubscriptionBase>> internalSubscriptions = dao.getSubscriptionsForAccount(context);
+ final Catalog catalog = catalogInternalApi.getFullCatalog(true, true, context);
+
+ final Map<UUID, List<SubscriptionBase>> internalSubscriptions = dao.getSubscriptionsForAccount(catalog, context);
final Map<UUID, List<SubscriptionBase>> result = new HashMap<UUID, List<SubscriptionBase>>();
for (final UUID bundleId : internalSubscriptions.keySet()) {
result.put(bundleId, createSubscriptionsForApiUse(internalSubscriptions.get(bundleId)));
@@ -531,7 +512,9 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
@Override
public SubscriptionBase getBaseSubscription(final UUID bundleId, final InternalTenantContext context) throws SubscriptionBaseApiException {
try {
- final SubscriptionBase result = dao.getBaseSubscription(bundleId, context);
+ final Catalog catalog = catalogInternalApi.getFullCatalog(true, true, context);
+
+ final SubscriptionBase result = dao.getBaseSubscription(bundleId, catalog, context);
if (result == null) {
throw new SubscriptionBaseApiException(ErrorCode.SUB_GET_NO_SUCH_BASE_SUBSCRIPTION, bundleId);
}
@@ -544,7 +527,10 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
@Override
public SubscriptionBase getSubscriptionFromId(final UUID id, final InternalTenantContext context) throws SubscriptionBaseApiException {
try {
- final SubscriptionBase result = dao.getSubscriptionFromId(id, context);
+
+ final Catalog catalog = catalogInternalApi.getFullCatalog(true, true, context);
+
+ final SubscriptionBase result = dao.getSubscriptionFromId(id, catalog, context);
if (result == null) {
throw new SubscriptionBaseApiException(ErrorCode.SUB_INVALID_SUBSCRIPTION_ID, id);
}
@@ -571,7 +557,10 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
@Override
public void setChargedThroughDate(final UUID subscriptionId, final DateTime chargedThruDate, final InternalCallContext context) throws SubscriptionBaseApiException {
try {
- final DefaultSubscriptionBase subscription = (DefaultSubscriptionBase) dao.getSubscriptionFromId(subscriptionId, context);
+
+ final Catalog catalog = catalogInternalApi.getFullCatalog(true, true, context);
+
+ final DefaultSubscriptionBase subscription = (DefaultSubscriptionBase) dao.getSubscriptionFromId(subscriptionId, catalog, context);
final SubscriptionBuilder builder = new SubscriptionBuilder(subscription)
.setChargedThroughDate(chargedThruDate);
@@ -595,7 +584,7 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
@Override
public DateTime getDryRunChangePlanEffectiveDate(final SubscriptionBase subscription,
- final PlanSpecifier spec,
+ final PlanPhaseSpecifier spec,
final DateTime requestedDateWithMs,
final BillingActionPolicy requestedPolicy,
final List<PlanPhasePriceOverride> overrides,
@@ -625,7 +614,10 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
@Override
public List<EntitlementAOStatusDryRun> getDryRunChangePlanStatus(final UUID subscriptionId, @Nullable final String baseProductName, final DateTime requestedDate, final InternalTenantContext context) throws SubscriptionBaseApiException {
try {
- final SubscriptionBase subscription = dao.getSubscriptionFromId(subscriptionId, context);
+
+ final Catalog catalog = catalogInternalApi.getFullCatalog(true, true, context);
+
+ final SubscriptionBase subscription = dao.getSubscriptionFromId(subscriptionId, catalog, context);
if (subscription == null) {
throw new SubscriptionBaseApiException(ErrorCode.SUB_INVALID_SUBSCRIPTION_ID, subscriptionId);
}
@@ -635,7 +627,7 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
final List<EntitlementAOStatusDryRun> result = new LinkedList<EntitlementAOStatusDryRun>();
- final List<SubscriptionBase> bundleSubscriptions = dao.getSubscriptions(subscription.getBundleId(), ImmutableList.<SubscriptionBaseEvent>of(), context);
+ final List<SubscriptionBase> bundleSubscriptions = dao.getSubscriptions(subscription.getBundleId(), ImmutableList.<SubscriptionBaseEvent>of(), catalog, context);
for (final SubscriptionBase cur : bundleSubscriptions) {
if (cur.getId().equals(subscriptionId)) {
continue;
@@ -648,9 +640,9 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
final DryRunChangeReason reason;
// If baseProductName is null, it's a cancellation dry-run. In this case, return all addons, so they are cancelled
- if (baseProductName != null && addonUtils.isAddonIncludedFromProdName(baseProductName, cur.getCurrentPlan(), requestedDate, context)) {
+ if (baseProductName != null && addonUtils.isAddonIncludedFromProdName(baseProductName, cur.getCurrentPlan(), requestedDate, catalog, context)) {
reason = DryRunChangeReason.AO_INCLUDED_IN_NEW_PLAN;
- } else if (baseProductName != null && addonUtils.isAddonAvailableFromProdName(baseProductName, cur.getCurrentPlan(), requestedDate, context)) {
+ } else if (baseProductName != null && addonUtils.isAddonAvailableFromProdName(baseProductName, cur.getCurrentPlan(), requestedDate, catalog, context)) {
reason = DryRunChangeReason.AO_AVAILABLE_IN_NEW_PLAN;
} else {
reason = DryRunChangeReason.AO_NOT_AVAILABLE_IN_NEW_PLAN;
@@ -678,6 +670,7 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
@Nullable final DryRunArguments dryRunArguments,
final Collection<SubscriptionBaseEvent> outputDryRunEvents,
final Collection<SubscriptionBase> outputSubscriptions,
+ final Catalog catalog,
final InternalTenantContext context) throws SubscriptionBaseApiException {
if (dryRunArguments == null || dryRunArguments.getAction() == null) {
return;
@@ -689,7 +682,6 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
final PlanPhaseSpecifier inputSpec = dryRunArguments.getPlanPhaseSpecifier();
final boolean isInputSpecNullOrEmpty = inputSpec == null ||
(inputSpec.getPlanName() == null && inputSpec.getProductName() == null && inputSpec.getBillingPeriod() == null);
- final Catalog catalog = catalogInternalApi.getFullCatalog(true, true, context);
// Create an overridesWithContext with a null context to indicate this is dryRun and no price overriden plan should be created.
final PlanPhasePriceOverridesWithCallContext overridesWithContext = new DefaultPlanPhasePriceOverridesWithCallContext(dryRunArguments.getPlanPhasePriceOverrides(), null);
@@ -701,12 +693,12 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
switch (dryRunArguments.getAction()) {
case START_BILLING:
- final DefaultSubscriptionBase baseSubscription = (DefaultSubscriptionBase) dao.getBaseSubscription(bundleId, context);
+ final DefaultSubscriptionBase baseSubscription = (DefaultSubscriptionBase) dao.getBaseSubscription(bundleId, catalog, context);
final DateTime startEffectiveDate = dryRunArguments.getEffectiveDate() != null ? context.toUTCDateTime(dryRunArguments.getEffectiveDate()) : utcNow;
- final DateTime bundleStartDate = getBundleStartDateWithSanity(bundleId, baseSubscription, plan, startEffectiveDate, context);
+ final DateTime bundleStartDate = getBundleStartDateWithSanity(bundleId, baseSubscription, plan, startEffectiveDate, catalog, context);
final UUID subscriptionId = UUIDs.randomUUID();
- dryRunEvents = apiService.getEventsOnCreation(bundleId, subscriptionId, startEffectiveDate, bundleStartDate, plan, inputSpec.getPhaseType(), plan.getPriceListName(),
- startEffectiveDate, utcNow, context);
+ dryRunEvents = apiService.getEventsOnCreation(subscriptionId, startEffectiveDate, bundleStartDate, plan, inputSpec.getPhaseType(), plan.getPriceListName(),
+ startEffectiveDate, catalog, context);
final SubscriptionBuilder builder = new SubscriptionBuilder()
.setId(subscriptionId)
.setBundleId(bundleId)
@@ -720,7 +712,7 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
break;
case CHANGE:
- final DefaultSubscriptionBase subscriptionForChange = (DefaultSubscriptionBase) dao.getSubscriptionFromId(dryRunArguments.getSubscriptionId(), context);
+ final DefaultSubscriptionBase subscriptionForChange = (DefaultSubscriptionBase) dao.getSubscriptionFromId(dryRunArguments.getSubscriptionId(), catalog, context);
DateTime changeEffectiveDate = getDryRunEffectiveDate(dryRunArguments.getEffectiveDate(), subscriptionForChange, context);
if (changeEffectiveDate == null) {
@@ -732,26 +724,25 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
// We pass null for billingAlignment, accountTimezone, account BCD because this is not available which means that dryRun with START_OF_TERM BillingPolicy will fail
changeEffectiveDate = subscriptionForChange.getPlanChangeEffectiveDate(policy, null, -1, context);
}
- dryRunEvents = apiService.getEventsOnChangePlan(subscriptionForChange, plan, plan.getPriceListName(), changeEffectiveDate, utcNow, true, context);
+ dryRunEvents = apiService.getEventsOnChangePlan(subscriptionForChange, plan, plan.getPriceListName(), changeEffectiveDate, true, catalog, context);
break;
case STOP_BILLING:
- final DefaultSubscriptionBase subscriptionForCancellation = (DefaultSubscriptionBase) dao.getSubscriptionFromId(dryRunArguments.getSubscriptionId(), context);
+ final DefaultSubscriptionBase subscriptionForCancellation = (DefaultSubscriptionBase) dao.getSubscriptionFromId(dryRunArguments.getSubscriptionId(), catalog, context);
DateTime cancelEffectiveDate = getDryRunEffectiveDate(dryRunArguments.getEffectiveDate(), subscriptionForCancellation, context);
if (dryRunArguments.getEffectiveDate() == null) {
BillingActionPolicy policy = dryRunArguments.getBillingActionPolicy();
if (policy == null) {
-
final Plan currentPlan = subscriptionForCancellation.getCurrentPlan();
final PlanPhaseSpecifier spec = new PlanPhaseSpecifier(currentPlan.getName(),
subscriptionForCancellation.getCurrentPhase().getPhaseType());
- policy = catalogInternalApi.getFullCatalog(true, true, context).planCancelPolicy(spec, utcNow);
+ policy = catalog.planCancelPolicy(spec, utcNow);
}
// We pass null for billingAlignment, accountTimezone, account BCD because this is not available which means that dryRun with START_OF_TERM BillingPolicy will fail
cancelEffectiveDate = subscriptionForCancellation.getPlanChangeEffectiveDate(policy, null, -1, context);
}
- dryRunEvents = apiService.getEventsOnCancelPlan(subscriptionForCancellation, cancelEffectiveDate, utcNow, true, context);
+ dryRunEvents = apiService.getEventsOnCancelPlan(subscriptionForCancellation, cancelEffectiveDate, true, catalog, context);
break;
default:
@@ -781,56 +772,18 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
}
@Override
- public Iterable<DateTime> getFutureNotificationsForAccount(final InternalCallContext internalCallContext) {
- try {
- final NotificationQueue notificationQueue = notificationQueueService.getNotificationQueue(DefaultSubscriptionBaseService.SUBSCRIPTION_SERVICE_NAME,
- DefaultSubscriptionBaseService.NOTIFICATION_QUEUE_NAME);
- final Iterable<NotificationEventWithMetadata<NotificationEvent>> futureNotifications = notificationQueue.getFutureNotificationForSearchKeys(internalCallContext.getAccountRecordId(), internalCallContext.getTenantRecordId());
- return Iterables.transform(futureNotifications, new Function<NotificationEventWithMetadata<NotificationEvent>, DateTime>() {
- @Nullable
- @Override
- public DateTime apply(final NotificationEventWithMetadata<NotificationEvent> input) {
- return input.getEffectiveDate();
- }
- });
- } catch (final NoSuchNotificationQueue noSuchNotificationQueue) {
- throw new IllegalStateException(noSuchNotificationQueue);
- }
- }
+ public void updateBCD(final UUID subscriptionId, final int bcd, @Nullable final LocalDate effectiveFromDate, final InternalCallContext internalCallContext) throws SubscriptionBaseApiException {
- @Override
- public Map<UUID, DateTime> getNextFutureEventForSubscriptions(final SubscriptionBaseTransitionType eventType, final InternalCallContext internalCallContext) {
- final Iterable<SubscriptionBaseEvent> events = dao.getFutureEventsForAccount(internalCallContext);
- final Iterable<SubscriptionBaseEvent> filteredEvents = Iterables.filter(events, new Predicate<SubscriptionBaseEvent>() {
- @Override
- public boolean apply(final SubscriptionBaseEvent input) {
- switch (input.getType()) {
- case PHASE:
- return eventType == SubscriptionBaseTransitionType.PHASE;
- case BCD_UPDATE:
- return eventType == SubscriptionBaseTransitionType.BCD_CHANGE;
- case API_USER:
- default:
- return true;
- }
- }
- });
- final Map<UUID, DateTime> result = filteredEvents.iterator().hasNext() ? new HashMap<UUID, DateTime>() : ImmutableMap.<UUID, DateTime>of();
- for (final SubscriptionBaseEvent cur : filteredEvents) {
- final DateTime targetDate = result.get(cur.getSubscriptionId());
- if (targetDate == null || targetDate.compareTo(cur.getEffectiveDate()) > 0) {
- result.put(cur.getSubscriptionId(), cur.getEffectiveDate());
- }
+ final Catalog catalog;
+ try {
+ catalog = catalogInternalApi.getFullCatalog(true, true, internalCallContext);
+ final DefaultSubscriptionBase subscription = (DefaultSubscriptionBase) getSubscriptionFromId(subscriptionId, internalCallContext);
+ final DateTime effectiveDate = getEffectiveDateForNewBCD(bcd, effectiveFromDate, internalCallContext);
+ final BCDEvent bcdEvent = BCDEventData.createBCDEvent(subscription, effectiveDate, bcd);
+ dao.createBCDChangeEvent(subscription, bcdEvent, catalog, internalCallContext);
+ } catch (final CatalogApiException e) {
+ throw new SubscriptionBaseApiException(e);
}
- return result;
- }
-
- @Override
- public void updateBCD(final UUID subscriptionId, final int bcd, @Nullable final LocalDate effectiveFromDate, final InternalCallContext internalCallContext) throws SubscriptionBaseApiException {
- final DefaultSubscriptionBase subscription = (DefaultSubscriptionBase) getSubscriptionFromId(subscriptionId, internalCallContext);
- final DateTime effectiveDate = getEffectiveDateForNewBCD(bcd, effectiveFromDate, internalCallContext);
- final BCDEvent bcdEvent = BCDEventData.createBCDEvent(subscription, effectiveDate, bcd);
- dao.createBCDChangeEvent(subscription, bcdEvent, internalCallContext);
}
@Override
@@ -878,7 +831,7 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
}
private DateTime getBundleStartDateWithSanity(final UUID bundleId, @Nullable final DefaultSubscriptionBase baseSubscription, final Plan plan,
- final DateTime effectiveDate, final InternalTenantContext context) throws SubscriptionBaseApiException, CatalogApiException {
+ final DateTime effectiveDate, final Catalog catalog, final InternalTenantContext context) throws SubscriptionBaseApiException, CatalogApiException {
switch (plan.getProduct().getCategory()) {
case BASE:
if (baseSubscription != null &&
@@ -894,7 +847,7 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
if (effectiveDate.isBefore(baseSubscription.getStartDate())) {
throw new SubscriptionBaseApiException(ErrorCode.SUB_INVALID_REQUESTED_DATE, effectiveDate.toString(), baseSubscription.getStartDate().toString());
}
- addonUtils.checkAddonCreationRights(baseSubscription, plan, effectiveDate, context);
+ addonUtils.checkAddonCreationRights(baseSubscription, plan, effectiveDate, catalog, context);
return baseSubscription.getStartDate();
case STANDALONE:
@@ -922,12 +875,12 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
}
// For forward-compatibility
- private DefaultSubscriptionBase getDefaultSubscriptionBase(final Entity subscriptionBase, final InternalTenantContext context) throws CatalogApiException {
+ private DefaultSubscriptionBase getDefaultSubscriptionBase(final Entity subscriptionBase, final Catalog catalog, final InternalTenantContext context) throws CatalogApiException {
if (subscriptionBase instanceof DefaultSubscriptionBase) {
return (DefaultSubscriptionBase) subscriptionBase;
} else {
// Safe cast, see above
- return (DefaultSubscriptionBase) dao.getSubscriptionFromId(subscriptionBase.getId(), context);
+ return (DefaultSubscriptionBase) dao.getSubscriptionFromId(subscriptionBase.getId(), catalog, context);
}
}
}
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/timeline/DefaultSubscriptionBaseTimelineApi.java b/subscription/src/main/java/org/killbill/billing/subscription/api/timeline/DefaultSubscriptionBaseTimelineApi.java
index e7d5a5a..2ee1a66 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/api/timeline/DefaultSubscriptionBaseTimelineApi.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/api/timeline/DefaultSubscriptionBaseTimelineApi.java
@@ -26,6 +26,7 @@ import java.util.UUID;
import org.joda.time.DateTime;
import org.killbill.billing.ErrorCode;
import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.catalog.api.Catalog;
import org.killbill.billing.catalog.api.CatalogApiException;
import org.killbill.billing.catalog.api.CatalogInternalApi;
import org.killbill.billing.subscription.api.SubscriptionApiBase;
@@ -54,7 +55,7 @@ public class DefaultSubscriptionBaseTimelineApi extends SubscriptionApiBase impl
final SubscriptionDao dao,
final InternalCallContextFactory internalCallContextFactory,
final Clock clock) {
- super(dao, apiService, clock, catalogService);
+ super(dao, apiService, clock);
this.catalogInternalApi = catalogService;
this.internalCallContextFactory = internalCallContextFactory;
}
@@ -63,15 +64,19 @@ public class DefaultSubscriptionBaseTimelineApi extends SubscriptionApiBase impl
public BundleBaseTimeline getBundleTimeline(final SubscriptionBaseBundle bundle, final TenantContext context)
throws SubscriptionBaseRepairException {
try {
+
+
final InternalTenantContext internalTenantContext = internalCallContextFactory.createInternalTenantContext(bundle.getAccountId(), context);
+ final Catalog fullCatalog = catalogInternalApi.getFullCatalog(true, true, internalTenantContext);
final List<SubscriptionBase> subscriptions = dao.getSubscriptions(bundle.getId(),
ImmutableList.<SubscriptionBaseEvent>of(),
+ fullCatalog,
internalTenantContext);
if (subscriptions.size() == 0) {
throw new SubscriptionBaseRepairException(ErrorCode.SUB_NO_ACTIVE_SUBSCRIPTIONS, bundle.getId());
}
final String viewId = getViewId(((DefaultSubscriptionBaseBundle) bundle).getLastSysUpdateDate(), subscriptions);
- final List<SubscriptionBaseTimeline> repairs = createGetSubscriptionRepairList(subscriptions, Collections.<SubscriptionBaseTimeline>emptyList(), internalTenantContext);
+ final List<SubscriptionBaseTimeline> repairs = createGetSubscriptionRepairList(subscriptions, Collections.<SubscriptionBaseTimeline>emptyList(), fullCatalog, internalTenantContext);
return createGetBundleRepair(bundle.getId(), bundle.getExternalKey(), viewId, repairs);
} catch (CatalogApiException e) {
throw new SubscriptionBaseRepairException(e);
@@ -125,7 +130,7 @@ public class DefaultSubscriptionBaseTimelineApi extends SubscriptionApiBase impl
};
}
- private List<SubscriptionBaseTimeline> createGetSubscriptionRepairList(final List<SubscriptionBase> subscriptions, final List<SubscriptionBaseTimeline> inRepair, final InternalTenantContext tenantContext) throws CatalogApiException {
+ private List<SubscriptionBaseTimeline> createGetSubscriptionRepairList(final List<SubscriptionBase> subscriptions, final List<SubscriptionBaseTimeline> inRepair, final Catalog fullCatalog, final InternalTenantContext tenantContext) throws CatalogApiException {
final List<SubscriptionBaseTimeline> result = new LinkedList<SubscriptionBaseTimeline>();
final Set<UUID> repairIds = new TreeSet<UUID>();
@@ -136,7 +141,7 @@ public class DefaultSubscriptionBaseTimelineApi extends SubscriptionApiBase impl
for (final SubscriptionBase cur : subscriptions) {
if (!repairIds.contains(cur.getId())) {
- result.add(new DefaultSubscriptionBaseTimeline((DefaultSubscriptionBase) cur, catalogInternalApi.getFullCatalog(true, true, tenantContext)));
+ result.add(new DefaultSubscriptionBaseTimeline((DefaultSubscriptionBase) cur, fullCatalog));
}
}
return result;
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 bc01cc5..f93883e 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
@@ -71,19 +71,16 @@ public class DefaultSubscriptionBaseTransferApi extends SubscriptionApiBase impl
@Inject
public DefaultSubscriptionBaseTransferApi(final Clock clock, final SubscriptionDao dao, final SubscriptionBaseTimelineApi timelineApi, final CatalogInternalApi catalogInternalApi,
final SubscriptionBaseApiService apiService, final InternalCallContextFactory internalCallContextFactory) {
- super(dao, apiService, clock, catalogInternalApi);
+ super(dao, apiService, clock);
this.catalogInternalApi = catalogInternalApi;
this.timelineApi = timelineApi;
this.internalCallContextFactory = internalCallContextFactory;
}
- private SubscriptionBaseEvent createEvent(final boolean firstEvent, final ExistingEvent existingEvent, final DefaultSubscriptionBase subscription, final DateTime transferDate, final InternalTenantContext context)
+ private SubscriptionBaseEvent createEvent(final boolean firstEvent, final ExistingEvent existingEvent, final DefaultSubscriptionBase subscription, final DateTime transferDate, final Catalog catalog, final InternalTenantContext context)
throws CatalogApiException {
SubscriptionBaseEvent newEvent = null;
-
- final Catalog catalog = catalogInternalApi.getFullCatalog(true, true, context);
-
final DateTime effectiveDate = existingEvent.getEffectiveDate().isBefore(transferDate) ? transferDate : existingEvent.getEffectiveDate();
final PlanPhaseSpecifier spec = existingEvent.getPlanPhaseSpecifier();
@@ -128,9 +125,11 @@ public class DefaultSubscriptionBaseTransferApi extends SubscriptionApiBase impl
@VisibleForTesting
List<SubscriptionBaseEvent> toEvents(final List<ExistingEvent> existingEvents, final DefaultSubscriptionBase subscription,
- final DateTime transferDate, final InternalTenantContext context) throws SubscriptionBaseTransferApiException {
+ final DateTime transferDate, final Catalog catalog, final InternalTenantContext context) throws SubscriptionBaseTransferApiException {
+
try {
+
final List<SubscriptionBaseEvent> result = new LinkedList<SubscriptionBaseEvent>();
SubscriptionBaseEvent event = null;
@@ -145,7 +144,7 @@ public class DefaultSubscriptionBaseTransferApi extends SubscriptionApiBase impl
// Add previous event the first time if needed
if (prevEvent != null) {
- event = createEvent(firstEvent, prevEvent, subscription, transferDate, context);
+ event = createEvent(firstEvent, prevEvent, subscription, transferDate, catalog, context);
if (event != null) {
result.add(event);
firstEvent = false;
@@ -153,7 +152,7 @@ public class DefaultSubscriptionBaseTransferApi extends SubscriptionApiBase impl
prevEvent = null;
}
- event = createEvent(firstEvent, cur, subscription, transferDate, context);
+ event = createEvent(firstEvent, cur, subscription, transferDate, catalog, context);
if (event != null) {
result.add(event);
firstEvent = false;
@@ -162,7 +161,7 @@ public class DefaultSubscriptionBaseTransferApi extends SubscriptionApiBase impl
// Previous loop did not get anything because transferDate is greater than effectiveDate of last event
if (prevEvent != null) {
- event = createEvent(firstEvent, prevEvent, subscription, transferDate, context);
+ event = createEvent(firstEvent, prevEvent, subscription, transferDate, catalog, context);
if (event != null) {
result.add(event);
}
@@ -182,7 +181,11 @@ public class DefaultSubscriptionBaseTransferApi extends SubscriptionApiBase impl
final InternalCallContext fromInternalCallContext = internalCallContextFactory.createInternalCallContext(sourceAccountId, context);
final InternalCallContext toInternalCallContext = internalCallContextFactory.createInternalCallContext(destAccountId, context);
+
try {
+ final Catalog catalog = catalogInternalApi.getFullCatalog(true, true, fromInternalCallContext);
+
+
final DateTime effectiveTransferDate = transferDate == null ? clock.getUTCNow() : transferDate;
if (effectiveTransferDate.isAfter(clock.getUTCNow())) {
// The transfer event for the migrated bundle will be the first one, which cannot be in the future
@@ -190,12 +193,13 @@ public class DefaultSubscriptionBaseTransferApi extends SubscriptionApiBase impl
throw new SubscriptionBaseTransferApiException(ErrorCode.SUB_TRANSFER_INVALID_EFF_DATE, effectiveTransferDate);
}
- final List<SubscriptionBaseBundle> bundlesForAccountAndKey = dao.getSubscriptionBundlesForAccountAndKey(sourceAccountId, bundleKey, fromInternalCallContext);
- final SubscriptionBaseBundle bundle = DefaultSubscriptionInternalApi.getActiveBundleForKeyNotException(bundlesForAccountAndKey, dao, clock, fromInternalCallContext);
+ final SubscriptionBaseBundle bundleForAccountAndKey = dao.getSubscriptionBundlesForAccountAndKey(sourceAccountId, bundleKey, fromInternalCallContext);
+ final SubscriptionBaseBundle bundle = DefaultSubscriptionInternalApi.getActiveBundleForKeyNotException(ImmutableList.of(bundleForAccountAndKey), dao, clock, catalog, fromInternalCallContext);
if (bundle == null) {
throw new SubscriptionBaseTransferApiException(ErrorCode.SUB_CREATE_NO_BUNDLE, bundleKey);
}
+
// Get the bundle timeline for the old account
final BundleBaseTimeline bundleBaseTimeline = timelineApi.getBundleTimeline(bundle, context);
@@ -208,7 +212,7 @@ public class DefaultSubscriptionBaseTransferApi extends SubscriptionApiBase impl
DateTime bundleStartdate = null;
for (final SubscriptionBaseTimeline cur : bundleBaseTimeline.getSubscriptions()) {
- final DefaultSubscriptionBase oldSubscription = (DefaultSubscriptionBase) dao.getSubscriptionFromId(cur.getId(), fromInternalCallContext);
+ final DefaultSubscriptionBase oldSubscription = (DefaultSubscriptionBase) dao.getSubscriptionFromId(cur.getId(), catalog, fromInternalCallContext);
// Skip already cancelled subscriptions
if (oldSubscription.getState() == EntitlementState.CANCELLED) {
continue;
@@ -251,16 +255,16 @@ public class DefaultSubscriptionBaseTransferApi extends SubscriptionApiBase impl
.setCategory(productCategory)
.setBundleStartDate(effectiveTransferDate)
.setAlignStartDate(subscriptionAlignStartDate),
- ImmutableList.<SubscriptionBaseEvent>of(), fromInternalCallContext);
+ ImmutableList.<SubscriptionBaseEvent>of(), catalog, fromInternalCallContext);
- final List<SubscriptionBaseEvent> events = toEvents(existingEvents, defaultSubscriptionBase, effectiveTransferDate, fromInternalCallContext);
+ final List<SubscriptionBaseEvent> events = toEvents(existingEvents, defaultSubscriptionBase, effectiveTransferDate, catalog, fromInternalCallContext);
final SubscriptionTransferData curData = new SubscriptionTransferData(defaultSubscriptionBase, events, null);
subscriptionTransferDataList.add(curData);
}
- BundleTransferData bundleTransferData = new BundleTransferData(subscriptionBundleData, subscriptionTransferDataList);
+ final BundleTransferData bundleTransferData = new BundleTransferData(subscriptionBundleData, subscriptionTransferDataList);
// Atomically cancelWithRequestedDate all subscription on old account and create new bundle, subscriptions, events for new account
- dao.transfer(sourceAccountId, destAccountId, bundleTransferData, transferCancelDataList, fromInternalCallContext, toInternalCallContext);
+ dao.transfer(sourceAccountId, destAccountId, bundleTransferData, transferCancelDataList, catalog, fromInternalCallContext, toInternalCallContext);
return bundleTransferData.getData();
} catch (SubscriptionBaseRepairException e) {
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 05c2133..30d18ba 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
@@ -40,7 +40,7 @@ import org.killbill.billing.catalog.api.PhaseType;
import org.killbill.billing.catalog.api.Plan;
import org.killbill.billing.catalog.api.PlanPhase;
import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
-import org.killbill.billing.catalog.api.PlanSpecifier;
+import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
import org.killbill.billing.catalog.api.PriceList;
import org.killbill.billing.catalog.api.Product;
import org.killbill.billing.catalog.api.ProductCategory;
@@ -273,19 +273,24 @@ public class DefaultSubscriptionBase extends EntityBase implements SubscriptionB
}
@Override
- public DateTime changePlan(final PlanSpecifier spec,
+ public DateTime changePlan(final PlanPhaseSpecifier spec,
final List<PlanPhasePriceOverride> overrides, final CallContext context) throws SubscriptionBaseApiException {
return apiService.changePlan(this, spec, overrides, context);
}
@Override
- public DateTime changePlanWithDate(final PlanSpecifier spec, final List<PlanPhasePriceOverride> overrides,
+ public boolean undoChangePlan(final CallContext context) throws SubscriptionBaseApiException {
+ return apiService.undoChangePlan(this, context);
+ }
+
+ @Override
+ public DateTime changePlanWithDate(final PlanPhaseSpecifier spec, final List<PlanPhasePriceOverride> overrides,
final DateTime requestedDate, final CallContext context) throws SubscriptionBaseApiException {
return apiService.changePlanWithRequestedDate(this, spec, overrides, requestedDate, context);
}
@Override
- public DateTime changePlanWithPolicy(final PlanSpecifier spec,
+ public DateTime changePlanWithPolicy(final PlanPhaseSpecifier spec,
final List<PlanPhasePriceOverride> overrides, final BillingActionPolicy policy, final CallContext context) throws SubscriptionBaseApiException {
return apiService.changePlanWithPolicy(this, spec, overrides, policy, context);
}
@@ -500,10 +505,10 @@ public class DefaultSubscriptionBase extends EntityBase implements SubscriptionB
prev = curData;
}
}
- // Since UNCANCEL are not part of the transitions, we compute a new 'UNCANCEL' transition based on the event right before that UNCANCEL
- // This is used to be able to send a bus event for uncancellation
- if (prev != null && event.getType() == EventType.API_USER && ((ApiEvent) event).getApiEventType() == ApiEventType.UNCANCEL) {
- final SubscriptionBaseTransitionData withSeq = new SubscriptionBaseTransitionData((SubscriptionBaseTransitionData) prev, EventType.API_USER, ApiEventType.UNCANCEL, seqId);
+ // Since UNCANCEL/UNDO_CHANGE are not part of the transitions, we compute a new transition based on the event right before
+ // This is used to be able to send a bus event for uncancellation/undo_change
+ if (prev != null && event.getType() == EventType.API_USER && (((ApiEvent) event).getApiEventType() == ApiEventType.UNCANCEL || ((ApiEvent) event).getApiEventType() == ApiEventType.UNDO_CHANGE)) {
+ final SubscriptionBaseTransitionData withSeq = new SubscriptionBaseTransitionData(prev, EventType.API_USER, ((ApiEvent) event).getApiEventType(), seqId);
return withSeq;
}
return null;
@@ -568,10 +573,18 @@ public class DefaultSubscriptionBase extends EntityBase implements SubscriptionB
throw new SubscriptionBaseError(String.format("Failed to find InitialTransitionForCurrentPlan id = %s", getId()));
}
- public boolean isSubscriptionFutureCancelled() {
+ public boolean isFutureCancelled() {
return getFutureEndDate() != null;
}
+
+ public boolean isPendingChangePlan() {
+ final SubscriptionBaseTransition pendingTransition = getPendingTransition();
+ return pendingTransition != null && pendingTransition.getTransitionType() == SubscriptionBaseTransitionType.CHANGE;
+ }
+
+
+
public DateTime getPlanChangeEffectiveDate(final BillingActionPolicy policy, @Nullable final BillingAlignment alignment, @Nullable final Integer accountBillCycleDayLocal, final InternalTenantContext context) {
final DateTime candidateResult;
@@ -732,6 +745,7 @@ public class DefaultSubscriptionBase extends EntityBase implements SubscriptionB
nextPhaseName = null;
break;
case UNCANCEL:
+ case UNDO_CHANGE:
default:
throw new SubscriptionBaseError(String.format(
"Unexpected UserEvent type = %s", userEV
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 75b46b0..07a5515 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
@@ -68,6 +68,7 @@ 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.subscription.events.user.ApiEventUndoChange;
import org.killbill.billing.util.callcontext.CallContext;
import org.killbill.billing.util.callcontext.InternalCallContextFactory;
import org.killbill.billing.util.callcontext.TenantContext;
@@ -102,7 +103,7 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
@Override
public DefaultSubscriptionBase createPlan(final SubscriptionBuilder builder, final Plan plan, final PhaseType initialPhase,
- final String realPriceList, final DateTime effectiveDate, final DateTime processedDate,
+ final String realPriceList, final DateTime effectiveDate, final Catalog fullCatalog,
final CallContext context) throws SubscriptionBaseApiException {
final DefaultSubscriptionBase subscription = new DefaultSubscriptionBase(builder, this, clock);
@@ -110,10 +111,10 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
try {
- final List<SubscriptionBaseEvent> events = getEventsOnCreation(subscription.getBundleId(), subscription.getId(), subscription.getAlignStartDate(), subscription.getBundleStartDate(),
- plan, initialPhase, realPriceList, effectiveDate, processedDate, internalCallContext);
- dao.createSubscription(subscription, events, internalCallContext);
- subscription.rebuildTransitions(dao.getEventsForSubscription(subscription.getId(), internalCallContext), catalogInternalApi.getFullCatalog(true, true, internalCallContext));
+ final List<SubscriptionBaseEvent> events = getEventsOnCreation(subscription.getId(), subscription.getAlignStartDate(), subscription.getBundleStartDate(),
+ plan, initialPhase, realPriceList, effectiveDate, fullCatalog, internalCallContext);
+ dao.createSubscription(subscription, events, fullCatalog, internalCallContext);
+ subscription.rebuildTransitions(dao.getEventsForSubscription(subscription.getId(), internalCallContext), fullCatalog);
return subscription;
} catch (final CatalogApiException e) {
throw new SubscriptionBaseApiException(e);
@@ -121,30 +122,30 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
}
@Override
- public List<SubscriptionBaseWithAddOns> createPlansWithAddOns(final UUID accountId, final Iterable<SubscriptionAndAddOnsSpecifier> subscriptionsAndAddOns, final CallContext context) throws SubscriptionBaseApiException {
+ public List<SubscriptionBaseWithAddOns> createPlansWithAddOns(final UUID accountId, final Iterable<SubscriptionAndAddOnsSpecifier> subscriptionsAndAddOns, final Catalog fullCatalog, final CallContext context) throws SubscriptionBaseApiException {
final Map<UUID, List<SubscriptionBaseEvent>> eventsMap = new HashMap<UUID, List<SubscriptionBaseEvent>>();
final Collection<List<SubscriptionBase>> subscriptionBaseAndAddOnsList = new ArrayList<List<SubscriptionBase>>();
- final List<SubscriptionBaseWithAddOns> allSubscriptions = new ArrayList<SubscriptionBaseWithAddOns>();
- for (final SubscriptionAndAddOnsSpecifier subscriptionAndAddOns : subscriptionsAndAddOns) {
- final List<SubscriptionBase> subscriptionBaseList = new ArrayList<SubscriptionBase>();
- createEvents(subscriptionAndAddOns.getSubscriptionSpecifiers(), context, eventsMap, subscriptionBaseList);
- subscriptionBaseAndAddOnsList.add(subscriptionBaseList);
+ final InternalCallContext internalCallContext = createCallContextFromAccountId(accountId, context);
+ try {
- final SubscriptionBaseWithAddOns subscriptionBaseWithAddOns = new DefaultSubscriptionBaseWithAddOns(subscriptionAndAddOns.getBundleId(),
- subscriptionBaseList,
- subscriptionAndAddOns.getEffectiveDate());
- allSubscriptions.add(subscriptionBaseWithAddOns);
- }
+ final List<SubscriptionBaseWithAddOns> allSubscriptions = new ArrayList<SubscriptionBaseWithAddOns>();
+ for (final SubscriptionAndAddOnsSpecifier subscriptionAndAddOns : subscriptionsAndAddOns) {
+ final List<SubscriptionBase> subscriptionBaseList = new ArrayList<SubscriptionBase>();
+ createEvents(subscriptionAndAddOns.getSubscriptionSpecifiers(), context, eventsMap, subscriptionBaseList, fullCatalog);
+ subscriptionBaseAndAddOnsList.add(subscriptionBaseList);
- final InternalCallContext internalCallContext = createCallContextFromAccountId(accountId, context);
- dao.createSubscriptionsWithAddOns(allSubscriptions, eventsMap, internalCallContext);
+ final SubscriptionBaseWithAddOns subscriptionBaseWithAddOns = new DefaultSubscriptionBaseWithAddOns(subscriptionAndAddOns.getBundleId(),
+ subscriptionBaseList,
+ subscriptionAndAddOns.getEffectiveDate());
+ allSubscriptions.add(subscriptionBaseWithAddOns);
+ }
+
+ dao.createSubscriptionsWithAddOns(allSubscriptions, eventsMap, fullCatalog, internalCallContext);
- try {
for (final List<SubscriptionBase> subscriptions : subscriptionBaseAndAddOnsList) {
for (final SubscriptionBase input : subscriptions) {
- ((DefaultSubscriptionBase) input).rebuildTransitions(dao.getEventsForSubscription(input.getId(), internalCallContext),
- catalogInternalApi.getFullCatalog(true, true, internalCallContext));
+ ((DefaultSubscriptionBase) input).rebuildTransitions(dao.getEventsForSubscription(input.getId(), internalCallContext), fullCatalog);
}
}
return allSubscriptions;
@@ -153,15 +154,15 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
}
}
- private void createEvents(final Iterable<SubscriptionSpecifier> subscriptions, final CallContext context, final Map<UUID, List<SubscriptionBaseEvent>> eventsMap, final Collection<SubscriptionBase> subscriptionBaseList) throws SubscriptionBaseApiException {
+ private void createEvents(final Iterable<SubscriptionSpecifier> subscriptions, final CallContext context, final Map<UUID, List<SubscriptionBaseEvent>> eventsMap, final Collection<SubscriptionBase> subscriptionBaseList, final Catalog fullCatalog) throws SubscriptionBaseApiException {
for (final SubscriptionSpecifier subscription : subscriptions) {
try {
final DefaultSubscriptionBase subscriptionBase = new DefaultSubscriptionBase(subscription.getBuilder(), this, clock);
final InternalCallContext internalCallContext = createCallContextFromBundleId(subscriptionBase.getBundleId(), context);
- final List<SubscriptionBaseEvent> events = getEventsOnCreation(subscriptionBase.getBundleId(), subscriptionBase.getId(), subscriptionBase.getAlignStartDate(),
+ final List<SubscriptionBaseEvent> events = getEventsOnCreation(subscriptionBase.getId(), subscriptionBase.getAlignStartDate(),
subscriptionBase.getBundleStartDate(), subscription.getPlan(),
subscription.getInitialPhase(), subscription.getRealPriceList(),
- subscription.getEffectiveDate(), subscription.getProcessedDate(), internalCallContext);
+ subscription.getEffectiveDate(), fullCatalog, internalCallContext);
eventsMap.put(subscriptionBase.getId(), events);
subscriptionBaseList.add(subscriptionBase);
} catch (final CatalogApiException e) {
@@ -183,13 +184,14 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
try {
final InternalCallContext internalCallContext = createCallContextFromBundleId(subscription.getBundleId(), context);
- final BillingActionPolicy policy = catalogInternalApi.getFullCatalog(true, true, internalCallContext).planCancelPolicy(planPhase, now);
+ final Catalog fullCatalog = catalogInternalApi.getFullCatalog(true, true, internalCallContext);
+ final BillingActionPolicy policy = fullCatalog.planCancelPolicy(planPhase, now);
Preconditions.checkState(policy != BillingActionPolicy.START_OF_TERM, "A default START_OF_TERM policy is not availaible");
final DateTime effectiveDate = subscription.getPlanChangeEffectiveDate(policy, null, -1, null);
- return doCancelPlan(ImmutableMap.<DefaultSubscriptionBase, DateTime>of(subscription, effectiveDate), now, internalCallContext);
+ return doCancelPlan(ImmutableMap.<DefaultSubscriptionBase, DateTime>of(subscription, effectiveDate), fullCatalog, internalCallContext);
} catch (final CatalogApiException e) {
throw new SubscriptionBaseApiException(e);
}
@@ -201,11 +203,17 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
if (currentState == EntitlementState.CANCELLED) {
throw new SubscriptionBaseApiException(ErrorCode.SUB_CANCEL_BAD_STATE, subscription.getId(), currentState);
}
- final DateTime now = clock.getUTCNow();
- final DateTime effectiveDate = (requestedDateWithMs != null) ? DefaultClock.truncateMs(requestedDateWithMs) : now;
-
final InternalCallContext internalCallContext = createCallContextFromBundleId(subscription.getBundleId(), context);
- return doCancelPlan(ImmutableMap.<DefaultSubscriptionBase, DateTime>of(subscription, effectiveDate), now, internalCallContext);
+ final Catalog fullCatalog;
+ try {
+ fullCatalog = catalogInternalApi.getFullCatalog(true, true, internalCallContext);
+ final DateTime now = clock.getUTCNow();
+ final DateTime effectiveDate = (requestedDateWithMs != null) ? DefaultClock.truncateMs(requestedDateWithMs) : now;
+
+ return doCancelPlan(ImmutableMap.<DefaultSubscriptionBase, DateTime>of(subscription, effectiveDate), fullCatalog, internalCallContext);
+ } catch (final CatalogApiException e) {
+ throw new SubscriptionBaseApiException(e);
+ }
}
@Override
@@ -216,29 +224,32 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
}
final InternalCallContext internalCallContext = createCallContextFromBundleId(subscription.getBundleId(), context);
- return cancelWithPolicyNoValidation(ImmutableList.<DefaultSubscriptionBase>of(subscription), policy, accountBillCycleDayLocal, internalCallContext);
+ final Catalog fullCatalog;
+ try {
+ fullCatalog = catalogInternalApi.getFullCatalog(true, true, internalCallContext);
+ return cancelWithPolicyNoValidationAndCatalog(ImmutableList.<DefaultSubscriptionBase>of(subscription), policy, accountBillCycleDayLocal, fullCatalog, internalCallContext);
+ } catch (CatalogApiException e) {
+ throw new SubscriptionBaseApiException(e);
+ }
}
@Override
- public boolean cancelWithPolicyNoValidation(final Iterable<DefaultSubscriptionBase> subscriptions, final BillingActionPolicy policy, final int accountBillCycleDayLocal, final InternalCallContext context) throws SubscriptionBaseApiException {
+ public boolean cancelWithPolicyNoValidationAndCatalog(final Iterable<DefaultSubscriptionBase> subscriptions, final BillingActionPolicy policy, final int accountBillCycleDayLocal, final Catalog catalog, final InternalCallContext context) throws SubscriptionBaseApiException {
final Map<DefaultSubscriptionBase, DateTime> subscriptionsWithEffectiveDate = new HashMap<DefaultSubscriptionBase, DateTime>();
- final DateTime now = clock.getUTCNow();
try {
-
for (final DefaultSubscriptionBase subscription : subscriptions) {
- final BillingAlignment billingAlignment = (subscription.getState() == EntitlementState.PENDING ? null : catalogInternalApi.getFullCatalog(true, true, context).billingAlignment(new PlanPhaseSpecifier(subscription.getLastActivePlan().getName(), subscription.getLastActivePhase().getPhaseType()), clock.getUTCNow()));
+ final BillingAlignment billingAlignment = (subscription.getState() == EntitlementState.PENDING ? null : catalog.billingAlignment(new PlanPhaseSpecifier(subscription.getLastActivePlan().getName(), subscription.getLastActivePhase().getPhaseType()), clock.getUTCNow()));
final DateTime effectiveDate = subscription.getPlanChangeEffectiveDate(policy, billingAlignment, accountBillCycleDayLocal, context);
subscriptionsWithEffectiveDate.put(subscription, effectiveDate);
}
+ return doCancelPlan(subscriptionsWithEffectiveDate, catalog, context);
} catch (final CatalogApiException e) {
throw new SubscriptionBaseApiException(e);
}
-
- return doCancelPlan(subscriptionsWithEffectiveDate, now, context);
}
- private boolean doCancelPlan(final Map<DefaultSubscriptionBase, DateTime> subscriptions, final DateTime now, final InternalCallContext internalCallContext) throws SubscriptionBaseApiException {
+ private boolean doCancelPlan(final Map<DefaultSubscriptionBase, DateTime> subscriptions, final Catalog fullCatalog, final InternalCallContext internalCallContext) throws SubscriptionBaseApiException {
final List<DefaultSubscriptionBase> subscriptionsToBeCancelled = new LinkedList<DefaultSubscriptionBase>();
final List<SubscriptionBaseEvent> cancelEvents = new LinkedList<SubscriptionBaseEvent>();
@@ -248,18 +259,17 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
validateEffectiveDate(subscription, effectiveDate);
subscriptionsToBeCancelled.add(subscription);
- cancelEvents.addAll(getEventsOnCancelPlan(subscription, effectiveDate, now, false, internalCallContext));
+ cancelEvents.addAll(getEventsOnCancelPlan(subscription, effectiveDate, false, fullCatalog, internalCallContext));
if (subscription.getCategory() == ProductCategory.BASE) {
- subscriptionsToBeCancelled.addAll(computeAddOnsToCancel(cancelEvents, null, subscription.getBundleId(), effectiveDate, internalCallContext));
+ subscriptionsToBeCancelled.addAll(computeAddOnsToCancel(cancelEvents, null, subscription.getBundleId(), effectiveDate, fullCatalog, internalCallContext));
}
}
- dao.cancelSubscriptions(subscriptionsToBeCancelled, cancelEvents, internalCallContext);
+ dao.cancelSubscriptions(subscriptionsToBeCancelled, cancelEvents, fullCatalog, internalCallContext);
boolean allSubscriptionsCancelled = true;
for (final DefaultSubscriptionBase subscription : subscriptions.keySet()) {
- final Catalog fullCatalog = catalogInternalApi.getFullCatalog(true, true, internalCallContext);
subscription.rebuildTransitions(dao.getEventsForSubscription(subscription.getId(), internalCallContext), fullCatalog);
allSubscriptionsCancelled = allSubscriptionsCancelled && (subscription.getState() == EntitlementState.CANCELLED);
}
@@ -272,38 +282,39 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
@Override
public boolean uncancel(final DefaultSubscriptionBase subscription, final CallContext context) throws SubscriptionBaseApiException {
- if (!subscription.isSubscriptionFutureCancelled()) {
+ if (!subscription.isFutureCancelled()) {
throw new SubscriptionBaseApiException(ErrorCode.SUB_UNCANCEL_BAD_STATE, subscription.getId().toString());
}
+ try {
- final DateTime now = clock.getUTCNow();
- final SubscriptionBaseEvent uncancelEvent = new ApiEventUncancel(new ApiEventBuilder()
- .setSubscriptionId(subscription.getId())
- .setEffectiveDate(now)
- .setFromDisk(true));
+ final InternalCallContext internalCallContext = createCallContextFromBundleId(subscription.getBundleId(), context);
+ final Catalog fullCatalog = catalogInternalApi.getFullCatalog(true, true, internalCallContext);
- final List<SubscriptionBaseEvent> uncancelEvents = new ArrayList<SubscriptionBaseEvent>();
- uncancelEvents.add(uncancelEvent);
+ final DateTime now = clock.getUTCNow();
+ final SubscriptionBaseEvent uncancelEvent = new ApiEventUncancel(new ApiEventBuilder()
+ .setSubscriptionId(subscription.getId())
+ .setEffectiveDate(now)
+ .setFromDisk(true));
- final InternalCallContext internalCallContext = createCallContextFromBundleId(subscription.getBundleId(), context);
- //
- // 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;
- if (nextPhaseEvent != null) {
- uncancelEvents.add(nextPhaseEvent);
- }
+ final List<SubscriptionBaseEvent> uncancelEvents = new ArrayList<SubscriptionBaseEvent>();
+ uncancelEvents.add(uncancelEvent);
+
+ //
+ // 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, fullCatalog, internalCallContext);
+ final PhaseEvent nextPhaseEvent = (nextTimedPhase != null) ?
+ PhaseEventData.createNextPhaseEvent(subscription.getId(), nextTimedPhase.getPhase().getName(), nextTimedPhase.getStartPhase()) :
+ null;
+ if (nextPhaseEvent != null) {
+ uncancelEvents.add(nextPhaseEvent);
+ }
- dao.uncancelSubscription(subscription, uncancelEvents, internalCallContext);
- try {
- final Catalog fullCatalog = catalogInternalApi.getFullCatalog(true, true, internalCallContext);
+ dao.uncancelSubscription(subscription, uncancelEvents, internalCallContext);
subscription.rebuildTransitions(dao.getEventsForSubscription(subscription.getId(), internalCallContext), fullCatalog);
return true;
} catch (final CatalogApiException e) {
@@ -313,7 +324,7 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
@Override
public DateTime dryRunChangePlan(final DefaultSubscriptionBase subscription,
- final PlanSpecifier spec,
+ final PlanPhaseSpecifier spec,
@Nullable final DateTime requestedDateWithMs,
@Nullable final BillingActionPolicy requestedPolicy,
final TenantContext context) throws SubscriptionBaseApiException {
@@ -335,10 +346,10 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
}
@Override
- public DateTime changePlan(final DefaultSubscriptionBase subscription, final PlanSpecifier spec, final List<PlanPhasePriceOverride> overrides, final CallContext context) throws SubscriptionBaseApiException {
+ public DateTime changePlan(final DefaultSubscriptionBase subscription, final PlanPhaseSpecifier spec, final List<PlanPhasePriceOverride> overrides, final CallContext context) throws SubscriptionBaseApiException {
final DateTime now = clock.getUTCNow();
- validateSubscriptionState(subscription, null);
+ validateSubscriptionStateForChangePlan(subscription, null);
final PlanChangeResult planChangeResult = getPlanChangeResult(subscription, spec, now, context);
final DateTime effectiveDate = dryRunChangePlan(subscription, spec, null, planChangeResult.getPolicy(), context);
@@ -354,11 +365,11 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
}
@Override
- public DateTime changePlanWithRequestedDate(final DefaultSubscriptionBase subscription, final PlanSpecifier spec, final List<PlanPhasePriceOverride> overrides,
+ public DateTime changePlanWithRequestedDate(final DefaultSubscriptionBase subscription, final PlanPhaseSpecifier spec, final List<PlanPhasePriceOverride> overrides,
final DateTime requestedDateWithMs, final CallContext context) throws SubscriptionBaseApiException {
final DateTime effectiveDate = dryRunChangePlan(subscription, spec, requestedDateWithMs, null, context);
validateEffectiveDate(subscription, effectiveDate);
- validateSubscriptionState(subscription, requestedDateWithMs);
+ validateSubscriptionStateForChangePlan(subscription, requestedDateWithMs);
try {
doChangePlan(subscription, spec, overrides, effectiveDate, context);
@@ -370,11 +381,11 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
}
@Override
- public DateTime changePlanWithPolicy(final DefaultSubscriptionBase subscription, final PlanSpecifier spec, final List<PlanPhasePriceOverride> overrides, final BillingActionPolicy policy, final CallContext context) throws SubscriptionBaseApiException {
+ public DateTime changePlanWithPolicy(final DefaultSubscriptionBase subscription, final PlanPhaseSpecifier spec, final List<PlanPhasePriceOverride> overrides, final BillingActionPolicy policy, final CallContext context) throws SubscriptionBaseApiException {
final DateTime effectiveDate = dryRunChangePlan(subscription, spec, null, policy, context);
- validateSubscriptionState(subscription, effectiveDate);
+ validateSubscriptionStateForChangePlan(subscription, effectiveDate);
try {
doChangePlan(subscription, spec, overrides, effectiveDate, context);
} catch (final CatalogApiException e) {
@@ -402,28 +413,22 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
}
private void doChangePlan(final DefaultSubscriptionBase subscription,
- final PlanSpecifier spec,
+ final PlanPhaseSpecifier spec,
final List<PlanPhasePriceOverride> overrides,
final DateTime effectiveDate,
final CallContext context) throws SubscriptionBaseApiException, CatalogApiException {
+
final InternalCallContext internalCallContext = createCallContextFromBundleId(subscription.getBundleId(), context);
final PlanPhasePriceOverridesWithCallContext overridesWithContext = new DefaultPlanPhasePriceOverridesWithCallContext(overrides, context);
- final Plan newPlan = catalogInternalApi.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;
- }
+ final Catalog fullCatalog = catalogInternalApi.getFullCatalog(true, true, internalCallContext);
+ final Plan newPlan = fullCatalog.createOrFindPlan(spec, overridesWithContext, effectiveDate, subscription.getStartDate());
+
+ final PhaseType initialPhaseType = spec.getPhaseType();
if (ProductCategory.ADD_ON.toString().equalsIgnoreCase(newPlan.getProduct().getCategory().toString())) {
if (newPlan.getPlansAllowedInBundle() != -1
&& newPlan.getPlansAllowedInBundle() > 0
- && addonUtils.countExistingAddOnsWithSamePlanName(dao.getSubscriptions(subscription.getBundleId(), null, internalCallContext), newPlan.getName())
+ && addonUtils.countExistingAddOnsWithSamePlanName(dao.getSubscriptions(subscription.getBundleId(), null, fullCatalog, internalCallContext), newPlan.getName())
>= newPlan.getPlansAllowedInBundle()) {
// the plan can be changed to the new value, because it has reached its limit by bundle
throw new SubscriptionBaseApiException(ErrorCode.SUB_CHANGE_AO_MAX_PLAN_ALLOWED_BY_BUNDLE, newPlan.getName());
@@ -436,21 +441,21 @@ 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, initialPhaseType, internalCallContext);
+ final List<SubscriptionBaseEvent> changeEvents = getEventsOnChangePlan(subscription, newPlan, newPlan.getPriceListName(), effectiveDate, true, addOnSubscriptionsToBeCancelled, addOnCancelEvents, initialPhaseType, fullCatalog, internalCallContext);
- dao.changePlan(subscription, changeEvents, addOnSubscriptionsToBeCancelled, addOnCancelEvents, internalCallContext);
+ dao.changePlan(subscription, changeEvents, addOnSubscriptionsToBeCancelled, addOnCancelEvents, fullCatalog, internalCallContext);
- final Catalog fullCatalog = catalogInternalApi.getFullCatalog(true, true, internalCallContext);
subscription.rebuildTransitions(dao.getEventsForSubscription(subscription.getId(), internalCallContext), fullCatalog);
}
@Override
- public List<SubscriptionBaseEvent> getEventsOnCreation(final UUID bundleId, final UUID subscriptionId, final DateTime alignStartDate, final DateTime bundleStartDate,
+ public List<SubscriptionBaseEvent> getEventsOnCreation(final UUID subscriptionId, final DateTime alignStartDate, final DateTime bundleStartDate,
final Plan plan, final PhaseType initialPhase,
- final String realPriceList, final DateTime effectiveDate, final DateTime processedDate,
+ final String realPriceList, final DateTime effectiveDate,
+ final Catalog fullCatalog,
final InternalTenantContext internalTenantContext) throws CatalogApiException, SubscriptionBaseApiException {
final TimedPhase[] curAndNextPhases = planAligner.getCurrentAndNextTimedPhaseOnCreate(alignStartDate, bundleStartDate, plan, initialPhase,
- realPriceList, effectiveDate, internalTenantContext);
+ realPriceList, effectiveDate, fullCatalog, internalTenantContext);
final ApiEventBuilder createBuilder = new ApiEventBuilder()
.setSubscriptionId(subscriptionId)
@@ -475,12 +480,14 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
@Override
public List<SubscriptionBaseEvent> getEventsOnChangePlan(final DefaultSubscriptionBase subscription, final Plan newPlan,
- final String newPriceList, final DateTime effectiveDate, final DateTime processedDate,
- final boolean addCancellationAddOnForEventsIfRequired, final InternalTenantContext internalTenantContext) throws CatalogApiException, SubscriptionBaseApiException {
+ final String newPriceList, final DateTime effectiveDate,
+ final boolean addCancellationAddOnForEventsIfRequired,
+ final Catalog fullCatalog,
+ final InternalTenantContext internalTenantContext) throws CatalogApiException, SubscriptionBaseApiException {
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, null, internalTenantContext);
+ final List<SubscriptionBaseEvent> changeEvents = getEventsOnChangePlan(subscription, newPlan, newPriceList, effectiveDate, addCancellationAddOnForEventsIfRequired, addOnSubscriptionsToBeCancelled, addOnCancelEvents, null, fullCatalog, internalTenantContext);
changeEvents.addAll(addOnCancelEvents);
return changeEvents;
}
@@ -491,11 +498,12 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
final Collection<DefaultSubscriptionBase> addOnSubscriptionsToBeCancelled,
final Collection<SubscriptionBaseEvent> addOnCancelEvents,
final PhaseType initialPhaseType,
+ final Catalog fullCatalog,
final InternalTenantContext internalTenantContext) throws CatalogApiException, SubscriptionBaseApiException {
- final TimedPhase currentTimedPhase = planAligner.getCurrentTimedPhaseOnChange(subscription, newPlan, effectiveDate, initialPhaseType, internalTenantContext);
+ final TimedPhase currentTimedPhase = planAligner.getCurrentTimedPhaseOnChange(subscription, newPlan, effectiveDate, initialPhaseType, fullCatalog, internalTenantContext);
- validateSubscriptionState(subscription, effectiveDate);
+ validateSubscriptionStateForChangePlan(subscription, effectiveDate);
final SubscriptionBaseEvent changeEvent = new ApiEventChange(new ApiEventBuilder()
.setSubscriptionId(subscription.getId())
@@ -505,7 +513,7 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
.setEffectiveDate(effectiveDate)
.setFromDisk(true));
- final TimedPhase nextTimedPhase = planAligner.getNextTimedPhaseOnChange(subscription, newPlan, effectiveDate, initialPhaseType, internalTenantContext);
+ final TimedPhase nextTimedPhase = planAligner.getNextTimedPhaseOnChange(subscription, newPlan, effectiveDate, initialPhaseType, fullCatalog, internalTenantContext);
final PhaseEvent nextPhaseEvent = (nextTimedPhase != null) ?
PhaseEventData.createNextPhaseEvent(subscription.getId(),
nextTimedPhase.getPhase().getName(), nextTimedPhase.getStartPhase()) :
@@ -520,15 +528,17 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
if (subscription.getCategory() == ProductCategory.BASE && addCancellationAddOnForEventsIfRequired) {
final Product currentBaseProduct = changeEvent.getEffectiveDate().compareTo(clock.getUTCNow()) <= 0 ? newPlan.getProduct() : subscription.getCurrentOrPendingPlan().getProduct();
- addOnSubscriptionsToBeCancelled.addAll(addCancellationAddOnForEventsIfRequired(addOnCancelEvents, currentBaseProduct, subscription.getBundleId(), effectiveDate, internalTenantContext));
+ addOnSubscriptionsToBeCancelled.addAll(addCancellationAddOnForEventsIfRequired(addOnCancelEvents, currentBaseProduct, subscription.getBundleId(), effectiveDate, fullCatalog, internalTenantContext));
}
return changeEvents;
}
@Override
public List<SubscriptionBaseEvent> getEventsOnCancelPlan(final DefaultSubscriptionBase subscription,
- final DateTime effectiveDate, final DateTime processedDate,
- final boolean addCancellationAddOnForEventsIfRequired, final InternalTenantContext internalTenantContext) throws CatalogApiException {
+ final DateTime effectiveDate,
+ final boolean addCancellationAddOnForEventsIfRequired,
+ final Catalog fullCatalog,
+ final InternalTenantContext internalTenantContext) throws CatalogApiException {
final List<SubscriptionBaseEvent> cancelEvents = new ArrayList<SubscriptionBaseEvent>();
final SubscriptionBaseEvent cancelEvent = new ApiEventCancel(new ApiEventBuilder()
.setSubscriptionId(subscription.getId())
@@ -537,45 +547,87 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
cancelEvents.add(cancelEvent);
if (subscription.getCategory() == ProductCategory.BASE && addCancellationAddOnForEventsIfRequired) {
final Product currentBaseProduct = cancelEvent.getEffectiveDate().compareTo(clock.getUTCNow()) <= 0 ? null : subscription.getCurrentPlan().getProduct();
- addCancellationAddOnForEventsIfRequired(cancelEvents, currentBaseProduct, subscription.getBundleId(), effectiveDate, internalTenantContext);
+ addCancellationAddOnForEventsIfRequired(cancelEvents, currentBaseProduct, subscription.getBundleId(), effectiveDate, fullCatalog, internalTenantContext);
}
return cancelEvents;
}
@Override
- public int handleBasePlanEvent(final DefaultSubscriptionBase subscription, final SubscriptionBaseEvent event, final CallContext context) throws CatalogApiException {
+ public boolean undoChangePlan(final DefaultSubscriptionBase subscription, final CallContext context) throws SubscriptionBaseApiException {
+ if (!subscription.isPendingChangePlan()) {
+ throw new SubscriptionBaseApiException(ErrorCode.SUB_UNDO_CHANGE_BAD_STATE, subscription.getId().toString());
+ }
+ try {
+
+ final InternalCallContext internalCallContext = createCallContextFromBundleId(subscription.getBundleId(), context);
+ final Catalog fullCatalog = catalogInternalApi.getFullCatalog(true, true, internalCallContext);
+
+ final DateTime now = clock.getUTCNow();
+ final SubscriptionBaseEvent undoChangePlanEvent = new ApiEventUndoChange(new ApiEventBuilder()
+ .setSubscriptionId(subscription.getId())
+ .setEffectiveDate(now)
+ .setFromDisk(true));
+
+ final List<SubscriptionBaseEvent> undoChangePlanEvents = new ArrayList<SubscriptionBaseEvent>();
+ undoChangePlanEvents.add(undoChangePlanEvent);
+
+ //
+ // 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, fullCatalog, internalCallContext);
+ final PhaseEvent nextPhaseEvent = (nextTimedPhase != null) ?
+ PhaseEventData.createNextPhaseEvent(subscription.getId(), nextTimedPhase.getPhase().getName(), nextTimedPhase.getStartPhase()) :
+ null;
+ if (nextPhaseEvent != null) {
+ undoChangePlanEvents.add(nextPhaseEvent);
+ }
+
+ dao.undoChangePlan(subscription, undoChangePlanEvents, internalCallContext);
+ subscription.rebuildTransitions(dao.getEventsForSubscription(subscription.getId(), internalCallContext), fullCatalog);
+ return true;
+ } catch (final CatalogApiException e) {
+ throw new SubscriptionBaseApiException(e);
+ }
+ }
+
+ @Override
+ public int handleBasePlanEvent(final DefaultSubscriptionBase subscription, final SubscriptionBaseEvent event, final Catalog catalog, final CallContext context) throws CatalogApiException {
final InternalCallContext internalCallContext = createCallContextFromBundleId(subscription.getBundleId(), context);
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);
+ final List<DefaultSubscriptionBase> subscriptionsToBeCancelled = computeAddOnsToCancel(cancelEvents, baseProduct, subscription.getBundleId(), event.getEffectiveDate(), catalog, internalCallContext);
+ dao.cancelSubscriptionsOnBasePlanEvent(subscription, event, subscriptionsToBeCancelled, cancelEvents, catalog, internalCallContext);
return subscriptionsToBeCancelled.size();
} else {
- dao.notifyOnBasePlanEvent(subscription, event, internalCallContext);
+ dao.notifyOnBasePlanEvent(subscription, event, catalog, 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 {
+ private List<DefaultSubscriptionBase> computeAddOnsToCancel(final Collection<SubscriptionBaseEvent> cancelEvents, final CatalogEntity baseProduct, final UUID bundleId, final DateTime effectiveDate, final Catalog catalog, final InternalCallContext internalCallContext) throws CatalogApiException {
// If cancellation/change occur in the future, there is nothing to do
final DateTime now = clock.getUTCNow();
if (effectiveDate.compareTo(now) > 0) {
return ImmutableList.<DefaultSubscriptionBase>of();
} else {
- return addCancellationAddOnForEventsIfRequired(cancelEvents, baseProduct, bundleId, effectiveDate, internalCallContext);
+ return addCancellationAddOnForEventsIfRequired(cancelEvents, baseProduct, bundleId, effectiveDate, catalog, internalCallContext);
}
}
private List<DefaultSubscriptionBase> addCancellationAddOnForEventsIfRequired(final Collection<SubscriptionBaseEvent> events, final CatalogEntity baseProduct, final UUID bundleId,
- final DateTime effectiveDate, final InternalTenantContext internalTenantContext) throws CatalogApiException {
+ final DateTime effectiveDate, final Catalog catalog, final InternalTenantContext internalTenantContext) throws CatalogApiException {
final List<DefaultSubscriptionBase> subscriptionsToBeCancelled = new ArrayList<DefaultSubscriptionBase>();
- final List<SubscriptionBase> subscriptions = dao.getSubscriptions(bundleId, ImmutableList.<SubscriptionBaseEvent>of(), internalTenantContext);
+ final List<SubscriptionBase> subscriptions = dao.getSubscriptions(bundleId, ImmutableList.<SubscriptionBaseEvent>of(), catalog, internalTenantContext);
for (final SubscriptionBase subscription : subscriptions) {
final DefaultSubscriptionBase cur = (DefaultSubscriptionBase) subscription;
@@ -586,8 +638,8 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
final Plan addonCurrentPlan = cur.getCurrentPlan();
if (baseProduct == null ||
- addonUtils.isAddonIncludedFromProdName(baseProduct.getName(), addonCurrentPlan, effectiveDate, internalTenantContext) ||
- !addonUtils.isAddonAvailableFromProdName(baseProduct.getName(), addonCurrentPlan, effectiveDate, internalTenantContext)) {
+ addonUtils.isAddonIncludedFromProdName(baseProduct.getName(), addonCurrentPlan, effectiveDate, catalog, internalTenantContext) ||
+ !addonUtils.isAddonAvailableFromProdName(baseProduct.getName(), addonCurrentPlan, effectiveDate, catalog, internalTenantContext)) {
//
// Perform AO cancellation using the effectiveDate of the BP
//
@@ -614,12 +666,15 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
}
}
- private void validateSubscriptionState(final DefaultSubscriptionBase subscription, @Nullable final DateTime effectiveDate) throws SubscriptionBaseApiException {
+ private void validateSubscriptionStateForChangePlan(final DefaultSubscriptionBase subscription, @Nullable final DateTime effectiveDate) throws SubscriptionBaseApiException {
+
final EntitlementState currentState = subscription.getState();
- if (effectiveDate != null && effectiveDate.compareTo(subscription.getStartDate()) < 0) {
+ if (currentState == EntitlementState.CANCELLED ||
+ // We don't look for PENDING because as long as change is after startDate, we want to allow it.
+ effectiveDate != null && effectiveDate.compareTo(subscription.getStartDate()) < 0) {
throw new SubscriptionBaseApiException(ErrorCode.SUB_CHANGE_NON_ACTIVE, subscription.getId(), currentState);
}
- if (subscription.isSubscriptionFutureCancelled()) {
+ if (subscription.isFutureCancelled()) {
throw new SubscriptionBaseApiException(ErrorCode.SUB_CHANGE_FUTURE_CANCELLED, subscription.getId());
}
}
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/engine/addon/AddonUtils.java b/subscription/src/main/java/org/killbill/billing/subscription/engine/addon/AddonUtils.java
index e01da44..c8c84b3 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/engine/addon/AddonUtils.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/engine/addon/AddonUtils.java
@@ -22,6 +22,7 @@ import java.util.List;
import org.joda.time.DateTime;
import org.killbill.billing.ErrorCode;
import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.catalog.api.Catalog;
import org.killbill.billing.catalog.api.CatalogApiException;
import org.killbill.billing.catalog.api.CatalogInternalApi;
import org.killbill.billing.catalog.api.Plan;
@@ -37,14 +38,12 @@ import com.google.inject.Inject;
public class AddonUtils {
- private final CatalogInternalApi catalogInternalApi;
@Inject
- public AddonUtils(final CatalogInternalApi catalogInternalApi) {
- this.catalogInternalApi = catalogInternalApi;
+ public AddonUtils() {
}
- public void checkAddonCreationRights(final DefaultSubscriptionBase baseSubscription, final Plan targetAddOnPlan, final DateTime requestedDate, final InternalTenantContext context)
+ public void checkAddonCreationRights(final DefaultSubscriptionBase baseSubscription, final Plan targetAddOnPlan, final DateTime requestedDate, final Catalog catalog, final InternalTenantContext context)
throws SubscriptionBaseApiException, CatalogApiException {
if (baseSubscription.getState() == EntitlementState.CANCELLED ||
@@ -53,7 +52,7 @@ public class AddonUtils {
}
final Plan currentOrPendingPlan = baseSubscription.getCurrentOrPendingPlan();
- final Product baseProduct = catalogInternalApi.getFullCatalog(true, true, context).findProduct(currentOrPendingPlan.getProduct().getName(), requestedDate);
+ final Product baseProduct = catalog.findProduct(currentOrPendingPlan.getProduct().getName(), requestedDate);
if (isAddonIncluded(baseProduct, targetAddOnPlan)) {
throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_AO_ALREADY_INCLUDED,
targetAddOnPlan.getName(), currentOrPendingPlan.getProduct().getName());
@@ -65,18 +64,18 @@ public class AddonUtils {
}
}
- public boolean isAddonAvailableFromProdName(final String baseProductName, final Plan targetAddOnPlan, final DateTime requestedDate, final InternalTenantContext context) {
+ public boolean isAddonAvailableFromProdName(final String baseProductName, final Plan targetAddOnPlan, final DateTime requestedDate, final Catalog catalog, final InternalTenantContext context) {
try {
- final Product product = catalogInternalApi.getFullCatalog(true, true, context).findProduct(baseProductName, requestedDate);
+ final Product product = catalog.findProduct(baseProductName, requestedDate);
return isAddonAvailable(product, targetAddOnPlan);
} catch (CatalogApiException e) {
throw new SubscriptionBaseError(e);
}
}
- public boolean isAddonAvailableFromPlanName(final String basePlanName, final Plan targetAddOnPlan, final DateTime requestedDate, final InternalTenantContext context) {
+ public boolean isAddonAvailableFromPlanName(final String basePlanName, final Plan targetAddOnPlan, final DateTime requestedDate, final Catalog catalog, final InternalTenantContext context) {
try {
- final Plan plan = catalogInternalApi.getFullCatalog(true, true, context).findPlan(basePlanName, requestedDate);
+ final Plan plan = catalog.findPlan(basePlanName, requestedDate);
final Product product = plan.getProduct();
return isAddonAvailable(product, targetAddOnPlan);
} catch (CatalogApiException e) {
@@ -84,9 +83,9 @@ public class AddonUtils {
}
}
- public boolean isAddonIncludedFromProdName(final String baseProductName, final Plan targetAddOnPlan, final DateTime requestedDate, final InternalTenantContext context) {
+ public boolean isAddonIncludedFromProdName(final String baseProductName, final Plan targetAddOnPlan, final DateTime requestedDate, final Catalog catalog, final InternalTenantContext context) {
try {
- final Product product = catalogInternalApi.getFullCatalog(true, true, context).findProduct(baseProductName, requestedDate);
+ final Product product = catalog.findProduct(baseProductName, requestedDate);
return isAddonIncluded(product, targetAddOnPlan);
} catch (CatalogApiException e) {
throw new SubscriptionBaseError(e);
@@ -94,9 +93,9 @@ public class AddonUtils {
}
- public boolean isAddonIncludedFromPlanName(final String basePlanName, final Plan targetAddOnPlan, final DateTime requestedDate, final InternalTenantContext context) {
+ public boolean isAddonIncludedFromPlanName(final String basePlanName, final Plan targetAddOnPlan, final DateTime requestedDate, final Catalog catalog, final InternalTenantContext context) {
try {
- final Plan plan = catalogInternalApi.getFullCatalog(true, true, context).findPlan(basePlanName, requestedDate);
+ final Plan plan = catalog.findPlan(basePlanName, requestedDate);
final Product product = plan.getProduct();
return isAddonIncluded(product, targetAddOnPlan);
} catch (CatalogApiException e) {
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 183814e..2d80b2e 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
@@ -22,7 +22,9 @@ import java.util.UUID;
import org.joda.time.DateTime;
import org.killbill.billing.callcontext.InternalCallContext;
+import org.killbill.billing.catalog.api.Catalog;
import org.killbill.billing.catalog.api.CatalogApiException;
+import org.killbill.billing.catalog.api.CatalogInternalApi;
import org.killbill.billing.catalog.api.ProductCategory;
import org.killbill.billing.platform.api.LifecycleHandlerType;
import org.killbill.billing.platform.api.LifecycleHandlerType.LifecycleLevel;
@@ -72,15 +74,19 @@ public class DefaultSubscriptionBaseService implements EventListener, Subscripti
private final NotificationQueueService notificationQueueService;
private final InternalCallContextFactory internalCallContextFactory;
private final SubscriptionBaseApiService apiService;
+ private final CatalogInternalApi catalogInternalApi;
private NotificationQueue subscriptionEventQueue;
@Inject
- public DefaultSubscriptionBaseService(final Clock clock, final SubscriptionDao dao, final PlanAligner planAligner,
+ public DefaultSubscriptionBaseService(final Clock clock,
+ final SubscriptionDao dao,
+ final PlanAligner planAligner,
final PersistentBus eventBus,
final NotificationQueueService notificationQueueService,
final InternalCallContextFactory internalCallContextFactory,
- final SubscriptionBaseApiService apiService) {
+ final SubscriptionBaseApiService apiService,
+ final CatalogInternalApi catalogInternalApi) {
this.clock = clock;
this.dao = dao;
this.planAligner = planAligner;
@@ -88,6 +94,7 @@ public class DefaultSubscriptionBaseService implements EventListener, Subscripti
this.notificationQueueService = notificationQueueService;
this.internalCallContextFactory = internalCallContextFactory;
this.apiService = apiService;
+ this.catalogInternalApi = catalogInternalApi;
}
@Override
@@ -147,7 +154,8 @@ public class DefaultSubscriptionBaseService implements EventListener, Subscripti
}
try {
- final DefaultSubscriptionBase subscription = (DefaultSubscriptionBase) dao.getSubscriptionFromId(event.getSubscriptionId(), context);
+ final Catalog fullCatalog = catalogInternalApi.getFullCatalog(true, true, context);
+ final DefaultSubscriptionBase subscription = (DefaultSubscriptionBase) dao.getSubscriptionFromId(event.getSubscriptionId(), fullCatalog, context);
if (subscription == null) {
log.warn("Error retrieving subscriptionId='{}'", event.getSubscriptionId());
return;
@@ -161,10 +169,10 @@ public class DefaultSubscriptionBaseService implements EventListener, Subscripti
boolean eventSent = false;
if (event.getType() == EventType.PHASE) {
- eventSent = onPhaseEvent(subscription, event, context);
+ eventSent = onPhaseEvent(subscription, event, fullCatalog, context);
} else if (event.getType() == EventType.API_USER && subscription.getCategory() == ProductCategory.BASE) {
final CallContext callContext = internalCallContextFactory.createCallContext(context);
- eventSent = onBasePlanEvent(subscription, event, callContext);
+ eventSent = onBasePlanEvent(subscription, event, fullCatalog, callContext);
} else if (event.getType() == EventType.BCD_UPDATE) {
eventSent = false;
}
@@ -185,9 +193,9 @@ public class DefaultSubscriptionBaseService implements EventListener, Subscripti
}
}
- private boolean onPhaseEvent(final DefaultSubscriptionBase subscription, final SubscriptionBaseEvent readyPhaseEvent, final InternalCallContext context) {
+ private boolean onPhaseEvent(final DefaultSubscriptionBase subscription, final SubscriptionBaseEvent readyPhaseEvent, final Catalog fullCatalog, final InternalCallContext context) {
try {
- final TimedPhase nextTimedPhase = planAligner.getNextTimedPhase(subscription, readyPhaseEvent.getEffectiveDate(), context);
+ final TimedPhase nextTimedPhase = planAligner.getNextTimedPhase(subscription, readyPhaseEvent.getEffectiveDate(), fullCatalog, context);
final PhaseEvent nextPhaseEvent = (nextTimedPhase != null) ?
PhaseEventData.createNextPhaseEvent(subscription.getId(),
nextTimedPhase.getPhase().getName(), nextTimedPhase.getStartPhase()) :
@@ -203,8 +211,8 @@ public class DefaultSubscriptionBaseService implements EventListener, Subscripti
return false;
}
- private boolean onBasePlanEvent(final DefaultSubscriptionBase baseSubscription, final SubscriptionBaseEvent event, final CallContext context) throws CatalogApiException {
- apiService.handleBasePlanEvent(baseSubscription, event, context);
+ private boolean onBasePlanEvent(final DefaultSubscriptionBase baseSubscription, final SubscriptionBaseEvent event, final Catalog fullCatalog, final CallContext context) throws CatalogApiException {
+ apiService.handleBasePlanEvent(baseSubscription, event, fullCatalog, context);
return true;
}
}
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/BundleSqlDao.java b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/BundleSqlDao.java
index 585e013..587d390 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/BundleSqlDao.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/BundleSqlDao.java
@@ -19,11 +19,6 @@ package org.killbill.billing.subscription.engine.dao;
import java.util.Date;
import java.util.List;
-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.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.subscription.api.user.SubscriptionBaseBundle;
@@ -31,33 +26,44 @@ import org.killbill.billing.subscription.engine.dao.model.SubscriptionBundleMode
import org.killbill.billing.util.audit.ChangeType;
import org.killbill.billing.util.entity.dao.Audited;
import org.killbill.billing.util.entity.dao.EntitySqlDao;
-import org.killbill.billing.util.entity.dao.EntitySqlDaoStringTemplate;
+import org.killbill.commons.jdbi.binder.SmartBindBean;
+import org.killbill.commons.jdbi.template.KillBillSqlDaoStringTemplate;
+import org.skife.jdbi.v2.sqlobject.Bind;
+import org.skife.jdbi.v2.sqlobject.SqlQuery;
+import org.skife.jdbi.v2.sqlobject.SqlUpdate;
+import org.skife.jdbi.v2.sqlobject.customizers.Define;
-@EntitySqlDaoStringTemplate
+@KillBillSqlDaoStringTemplate
public interface BundleSqlDao extends EntitySqlDao<SubscriptionBundleModelDao, SubscriptionBaseBundle> {
@SqlUpdate
@Audited(ChangeType.UPDATE)
public void updateBundleExternalKey(@Bind("id") String id,
@Bind("externalKey") String externalKey,
- @BindBean final InternalCallContext context);
+ @SmartBindBean final InternalCallContext context);
+
+ @SqlUpdate
+ @Audited(ChangeType.UPDATE)
+ public void renameBundleExternalKey(@Bind("externalKey") String externalKey,
+ @Define("prefix") final String prefix,
+ @SmartBindBean final InternalCallContext context);
@SqlUpdate
@Audited(ChangeType.UPDATE)
public void updateBundleLastSysTime(@Bind("id") String id,
@Bind("lastSysUpdateDate") Date lastSysUpdate,
- @BindBean final InternalCallContext context);
+ @SmartBindBean final InternalCallContext context);
@SqlQuery
- public List<SubscriptionBundleModelDao> getBundlesFromAccountAndKey(@Bind("accountId") String accountId,
- @Bind("externalKey") String externalKey,
- @BindBean final InternalTenantContext context);
+ public SubscriptionBundleModelDao getBundlesFromAccountAndKey(@Bind("accountId") String accountId,
+ @Bind("externalKey") String externalKey,
+ @SmartBindBean final InternalTenantContext context);
@SqlQuery
public List<SubscriptionBundleModelDao> getBundleFromAccount(@Bind("accountId") String accountId,
- @BindBean final InternalTenantContext context);
+ @SmartBindBean final InternalTenantContext context);
@SqlQuery
- public List<SubscriptionBundleModelDao> getBundlesForKey(@Bind("externalKey") String externalKey,
- @BindBean final InternalTenantContext context);
+ public List<SubscriptionBundleModelDao> getBundlesForLikeKey(@Bind("externalKey") String externalKey,
+ @SmartBindBean final InternalTenantContext context);
}
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 3ee2332..3af29b0 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
@@ -24,10 +24,12 @@ import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.UUID;
import javax.annotation.Nullable;
@@ -35,14 +37,13 @@ import javax.inject.Inject;
import org.joda.time.DateTime;
import org.killbill.billing.ErrorCode;
-import org.killbill.billing.ObjectType;
import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.catalog.api.Catalog;
import org.killbill.billing.catalog.api.CatalogApiException;
-import org.killbill.billing.catalog.api.CatalogInternalApi;
import org.killbill.billing.catalog.api.Plan;
import org.killbill.billing.catalog.api.ProductCategory;
+import org.killbill.billing.entitlement.api.Entitlement.EntitlementState;
import org.killbill.billing.entitlement.api.SubscriptionApiException;
import org.killbill.billing.entity.EntityPersistenceException;
import org.killbill.billing.subscription.api.SubscriptionBase;
@@ -56,6 +57,7 @@ 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.SubscriptionBaseApiException;
import org.killbill.billing.subscription.api.user.SubscriptionBaseBundle;
import org.killbill.billing.subscription.api.user.SubscriptionBaseTransitionData;
import org.killbill.billing.subscription.api.user.SubscriptionBuilder;
@@ -119,20 +121,17 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
private final NotificationQueueService notificationQueueService;
private final AddonUtils addonUtils;
private final PersistentBus eventBus;
- private final CatalogInternalApi catalogInternalApi;
- private final InternalCallContextFactory internalCallContextFactory;
@Inject
public DefaultSubscriptionDao(final IDBI dbi, final Clock clock, final AddonUtils addonUtils,
- final NotificationQueueService notificationQueueService, final PersistentBus eventBus, final CatalogInternalApi catalogInternalApi,
- final CacheControllerDispatcher cacheControllerDispatcher, final NonEntityDao nonEntityDao, final InternalCallContextFactory internalCallContextFactory) {
+ final NotificationQueueService notificationQueueService, final PersistentBus eventBus,
+ final CacheControllerDispatcher cacheControllerDispatcher, final NonEntityDao nonEntityDao,
+ final InternalCallContextFactory internalCallContextFactory) {
super(new EntitySqlDaoTransactionalJdbiWrapper(dbi, clock, cacheControllerDispatcher, nonEntityDao, internalCallContextFactory), BundleSqlDao.class);
this.clock = clock;
this.notificationQueueService = notificationQueueService;
this.addonUtils = addonUtils;
this.eventBus = eventBus;
- this.catalogInternalApi = catalogInternalApi;
- this.internalCallContextFactory = internalCallContextFactory;
}
@Override
@@ -141,17 +140,12 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
}
@Override
- public List<SubscriptionBaseBundle> getSubscriptionBundlesForAccountAndKey(final UUID accountId, final String bundleKey, final InternalTenantContext context) {
- return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<List<SubscriptionBaseBundle>>() {
+ public SubscriptionBaseBundle getSubscriptionBundlesForAccountAndKey(final UUID accountId, final String bundleKey, final InternalTenantContext context) {
+ return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<SubscriptionBaseBundle>() {
@Override
- public List<SubscriptionBaseBundle> inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
- final List<SubscriptionBundleModelDao> models = entitySqlDaoWrapperFactory.become(BundleSqlDao.class).getBundlesFromAccountAndKey(accountId.toString(), bundleKey, context);
- return new ArrayList<SubscriptionBaseBundle>(Collections2.transform(models, new Function<SubscriptionBundleModelDao, SubscriptionBaseBundle>() {
- @Override
- public SubscriptionBaseBundle apply(@Nullable final SubscriptionBundleModelDao input) {
- return SubscriptionBundleModelDao.toSubscriptionbundle(input);
- }
- }));
+ public SubscriptionBaseBundle inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
+ final SubscriptionBundleModelDao input = entitySqlDaoWrapperFactory.become(BundleSqlDao.class).getBundlesFromAccountAndKey(accountId.toString(), bundleKey, context);
+ return SubscriptionBundleModelDao.toSubscriptionBundle(input);
}
});
}
@@ -166,7 +160,7 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
return new ArrayList<SubscriptionBaseBundle>(Collections2.transform(models, new Function<SubscriptionBundleModelDao, SubscriptionBaseBundle>() {
@Override
public SubscriptionBaseBundle apply(@Nullable final SubscriptionBundleModelDao input) {
- return SubscriptionBundleModelDao.toSubscriptionbundle(input);
+ return SubscriptionBundleModelDao.toSubscriptionBundle(input);
}
}));
}
@@ -179,21 +173,22 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
@Override
public SubscriptionBaseBundle inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
final SubscriptionBundleModelDao model = entitySqlDaoWrapperFactory.become(BundleSqlDao.class).getById(bundleId.toString(), context);
- return SubscriptionBundleModelDao.toSubscriptionbundle(model);
+ return SubscriptionBundleModelDao.toSubscriptionBundle(model);
}
});
}
@Override
public List<SubscriptionBaseBundle> getSubscriptionBundlesForKey(final String bundleKey, final InternalTenantContext context) {
+
return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<List<SubscriptionBaseBundle>>() {
@Override
public List<SubscriptionBaseBundle> inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
- final List<SubscriptionBundleModelDao> models = entitySqlDaoWrapperFactory.become(BundleSqlDao.class).getBundlesForKey(bundleKey, context);
+ final List<SubscriptionBundleModelDao> models = entitySqlDaoWrapperFactory.become(BundleSqlDao.class).getBundlesForLikeKey(bundleKey, context);
return new ArrayList<SubscriptionBaseBundle>(Collections2.transform(models, new Function<SubscriptionBundleModelDao, SubscriptionBaseBundle>() {
@Override
public SubscriptionBaseBundle apply(@Nullable final SubscriptionBundleModelDao input) {
- return SubscriptionBundleModelDao.toSubscriptionbundle(input);
+ return SubscriptionBundleModelDao.toSubscriptionBundle(input);
}
}));
}
@@ -227,7 +222,7 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
final BundleSqlDao bundleSqlDao = entitySqlDaoWrapperFactory.become(BundleSqlDao.class);
- final List<SubscriptionBundleModelDao> bundles = bundleSqlDao.getBundlesForKey(bundleKey, context);
+ final List<SubscriptionBundleModelDao> bundles = bundleSqlDao.getBundlesForLikeKey(bundleKey, context);
final Collection<UUID> nonAOSubscriptionIdsForKey = new LinkedList<UUID>();
final SubscriptionSqlDao subscriptionSqlDao = entitySqlDaoWrapperFactory.become(SubscriptionSqlDao.class);
@@ -258,14 +253,89 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
}
@Override
- public SubscriptionBaseBundle createSubscriptionBundle(final DefaultSubscriptionBaseBundle bundle, final InternalCallContext context) {
- return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<SubscriptionBaseBundle>() {
+ public SubscriptionBaseBundle createSubscriptionBundle(final DefaultSubscriptionBaseBundle bundle, final Catalog catalog, final boolean renameCancelledBundleIfExist, final InternalCallContext context) throws SubscriptionBaseApiException {
+
+
+ return transactionalSqlDao.execute(SubscriptionBaseApiException.class, new EntitySqlDaoTransactionWrapper<SubscriptionBaseBundle>() {
+
+ //
+ // 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)
+ //
+
+ private SubscriptionBaseBundle findExistingUnusedBundleForExternalKeyAndAccount(final List<SubscriptionBundleModelDao> existingBundles, final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) {
+ final SubscriptionBundleModelDao existingBundleForAccount = Iterables.tryFind(existingBundles, new Predicate<SubscriptionBundleModelDao>() {
+ @Override
+ public boolean apply(final SubscriptionBundleModelDao input) {
+ return input.getAccountId().equals(bundle.getAccountId()) &&
+ // We look for strict equality ignoring tsf items with keys 'kbtsf-343453:'
+ bundle.getExternalKey().equals(input.getExternalKey());
+ }
+
+ }).orNull();
+
+ // If Bundle already exists, and there is 0 Subscription associated with this bundle, we reuse
+ if (existingBundleForAccount != null) {
+ final List<SubscriptionModelDao> accountSubscriptions = entitySqlDaoWrapperFactory.become(SubscriptionSqlDao.class).getByAccountRecordId(context);
+ if (accountSubscriptions == null ||
+ ! Iterables.any(accountSubscriptions, new Predicate<SubscriptionModelDao>() {
+ @Override
+ public boolean apply(final SubscriptionModelDao input) {
+ return input.getBundleId().equals(existingBundleForAccount.getId());
+ }
+ })) {
+ return SubscriptionBundleModelDao.toSubscriptionBundle(existingBundleForAccount);
+ }
+ }
+ return null;
+ }
+
+
@Override
- public SubscriptionBaseBundle inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws EntityPersistenceException {
- final SubscriptionBundleModelDao model = new SubscriptionBundleModelDao(bundle);
+ public SubscriptionBaseBundle inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
+ final List<SubscriptionBundleModelDao> existingBundles = entitySqlDaoWrapperFactory.become(BundleSqlDao.class).getBundlesForLikeKey(bundle.getExternalKey(), context);
+
+ final SubscriptionBaseBundle unusedBundle = findExistingUnusedBundleForExternalKeyAndAccount(existingBundles, entitySqlDaoWrapperFactory);
+ if (unusedBundle != null) {
+ return unusedBundle;
+ }
final BundleSqlDao bundleSqlDao = entitySqlDaoWrapperFactory.become(BundleSqlDao.class);
+
+ for (SubscriptionBundleModelDao cur : existingBundles) {
+ final List<SubscriptionModelDao> subscriptions = entitySqlDaoWrapperFactory.become(SubscriptionSqlDao.class).getSubscriptionsFromBundleId(cur.getId().toString(), context);
+ final Iterable<SubscriptionModelDao> filtered = subscriptions != null ? Iterables.filter(subscriptions, new Predicate<SubscriptionModelDao>() {
+ @Override
+ public boolean apply(final SubscriptionModelDao input) {
+ return input.getCategory() != ProductCategory.ADD_ON;
+ }
+ }) : ImmutableList.<SubscriptionModelDao>of();
+ for (SubscriptionModelDao f : filtered) {
+ try {
+ final SubscriptionBase s = buildSubscription(SubscriptionModelDao.toSubscription(f, cur.getExternalKey()), catalog, context);
+ if (s.getState() != EntitlementState.CANCELLED) {
+ throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_ACTIVE_BUNDLE_KEY_EXISTS, bundle.getExternalKey());
+ } else if (renameCancelledBundleIfExist) {
+ // Note that if bundle belongs to a different account, context is not the context for this target account,
+ // but the underlying sql operation does not use the account info
+ bundleSqlDao.renameBundleExternalKey(bundle.getExternalKey(), "cncl", context);
+ } /* else {
+ Code will throw SQLIntegrityConstraintViolationException because of unique constraint on externalKey; might be worth having an ErrorCode just for that
+ } */
+ } catch (CatalogApiException e) {
+ throw new SubscriptionBaseApiException(e);
+ }
+ }
+ }
+
+ final SubscriptionBundleModelDao model = new SubscriptionBundleModelDao(bundle);
+ // Preserve Original created date
+ if (!existingBundles.isEmpty()) {
+ model.setOriginalCreatedDate(existingBundles.get(0).getCreatedDate());
+ }
final SubscriptionBundleModelDao result = createAndRefresh(bundleSqlDao, model, context);
- return SubscriptionBundleModelDao.toSubscriptionbundle(result);
+ return SubscriptionBundleModelDao.toSubscriptionBundle(result);
}
});
}
@@ -299,12 +369,12 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
}
@Override
- public SubscriptionBase getBaseSubscription(final UUID bundleId, final InternalTenantContext context) throws CatalogApiException {
- return getBaseSubscription(bundleId, true, context);
+ public SubscriptionBase getBaseSubscription(final UUID bundleId, final Catalog catalog, final InternalTenantContext context) throws CatalogApiException {
+ return getBaseSubscription(bundleId, true, catalog, context);
}
@Override
- public SubscriptionBase getSubscriptionFromId(final UUID subscriptionId, final InternalTenantContext context) throws CatalogApiException {
+ public SubscriptionBase getSubscriptionFromId(final UUID subscriptionId, final Catalog catalog, final InternalTenantContext context) throws CatalogApiException {
final SubscriptionBase shellSubscription = transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<SubscriptionBase>() {
@Override
public SubscriptionBase inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
@@ -313,12 +383,12 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
return SubscriptionModelDao.toSubscription(subscriptionModel, bundleModel.getExternalKey());
}
});
- return buildSubscription(shellSubscription, context);
+ return buildSubscription(shellSubscription, catalog, context);
}
@Override
- public List<SubscriptionBase> getSubscriptions(final UUID bundleId, final List<SubscriptionBaseEvent> dryRunEvents, final InternalTenantContext context) throws CatalogApiException {
- return buildBundleSubscriptions(getSubscriptionFromBundleId(bundleId, context), null, dryRunEvents, context);
+ public List<SubscriptionBase> getSubscriptions(final UUID bundleId, final List<SubscriptionBaseEvent> dryRunEvents, final Catalog catalog, final InternalTenantContext context) throws CatalogApiException {
+ return buildBundleSubscriptions(getSubscriptionFromBundleId(bundleId, context), null, dryRunEvents, catalog, context);
}
private List<SubscriptionBase> getSubscriptionFromBundleId(final UUID bundleId, final InternalTenantContext context) {
@@ -340,7 +410,7 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
}
@Override
- public Map<UUID, List<SubscriptionBase>> getSubscriptionsForAccount(final InternalTenantContext context) throws CatalogApiException {
+ public Map<UUID, List<SubscriptionBase>> getSubscriptionsForAccount(final Catalog catalog, final InternalTenantContext context) throws CatalogApiException {
final Map<UUID, List<SubscriptionBase>> subscriptionsFromAccountId = getSubscriptionsFromAccountId(context);
final List<SubscriptionBaseEvent> eventsForAccount = getEventsForAccountId(context);
@@ -362,7 +432,7 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
eventsForSubscriptions.putAll(cur.getId(), ImmutableList.copyOf(events));
}
- result.put(bundleId, buildBundleSubscriptions(subscriptionsForBundle, eventsForSubscriptions, null, context));
+ result.put(bundleId, buildBundleSubscriptions(subscriptionsForBundle, eventsForSubscriptions, null, catalog, context));
}
return result;
}
@@ -465,24 +535,6 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
}
@Override
- public Iterable<SubscriptionBaseEvent> getFutureEventsForAccount(final InternalTenantContext context) {
- return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<Iterable<SubscriptionBaseEvent>>() {
- @Override
- public Iterable<SubscriptionBaseEvent> inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
- final SubscriptionEventSqlDao transactional = entitySqlDaoWrapperFactory.become(SubscriptionEventSqlDao.class);
- final List<SubscriptionEventModelDao> activeEvents = transactional.getFutureActiveEventsForAccount(clock.getUTCNow().toDate(), context);
- return Iterables.transform(activeEvents, new Function<SubscriptionEventModelDao, SubscriptionBaseEvent>() {
-
- @Override
- public SubscriptionBaseEvent apply(final SubscriptionEventModelDao input) {
- return SubscriptionEventModelDao.toSubscriptionEvent(input);
- }
- });
- }
- });
- }
-
- @Override
public List<SubscriptionBaseEvent> getPendingEventsForSubscription(final UUID subscriptionId, final InternalTenantContext context) {
final Date now = clock.getUTCNow().toDate();
@@ -501,7 +553,7 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
}
@Override
- public void createSubscription(final DefaultSubscriptionBase subscription, final List<SubscriptionBaseEvent> initialEvents, final InternalCallContext context) {
+ public void createSubscription(final DefaultSubscriptionBase subscription, final List<SubscriptionBaseEvent> initialEvents, final Catalog catalog, final InternalCallContext context) {
transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<Void>() {
@Override
public Void inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
@@ -513,7 +565,7 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
createAndRefresh(eventsDaoFromSameTransaction, new SubscriptionEventModelDao(cur), context);
final boolean isBusEvent = cur.getEffectiveDate().compareTo(clock.getUTCNow()) <= 0 && (cur.getType() == EventType.API_USER);
- recordBusOrFutureNotificationFromTransaction(subscription, cur, entitySqlDaoWrapperFactory, isBusEvent, 0, context);
+ recordBusOrFutureNotificationFromTransaction(subscription, cur, entitySqlDaoWrapperFactory, isBusEvent, 0, catalog, context);
}
// Notify the Bus of the latest requested change, if needed
@@ -526,7 +578,7 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
}
@Override
- public void createSubscriptionsWithAddOns(final List<SubscriptionBaseWithAddOns> subscriptions, final Map<UUID, List<SubscriptionBaseEvent>> initialEventsMap, final InternalCallContext context) {
+ public void createSubscriptionsWithAddOns(final List<SubscriptionBaseWithAddOns> subscriptions, final Map<UUID, List<SubscriptionBaseEvent>> initialEventsMap, final Catalog catalog, final InternalCallContext context) {
transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<Void>() {
@Override
public Void inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
@@ -544,7 +596,7 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
createAndRefresh(eventsDaoFromSameTransaction, new SubscriptionEventModelDao(cur), context);
final boolean isBusEvent = cur.getEffectiveDate().compareTo(clock.getUTCNow()) <= 0 && (cur.getType() == EventType.API_USER);
- recordBusOrFutureNotificationFromTransaction(defaultSubscriptionBase, cur, entitySqlDaoWrapperFactory, isBusEvent, 0, context);
+ recordBusOrFutureNotificationFromTransaction(defaultSubscriptionBase, cur, entitySqlDaoWrapperFactory, isBusEvent, 0, catalog, context);
}
// Notify the Bus of the latest requested change, if needed
@@ -559,11 +611,11 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
}
@Override
- public void cancelSubscriptionsOnBasePlanEvent(final DefaultSubscriptionBase subscription, final SubscriptionBaseEvent event, final List<DefaultSubscriptionBase> subscriptions, final List<SubscriptionBaseEvent> cancelEvents, final InternalCallContext context) {
+ public void cancelSubscriptionsOnBasePlanEvent(final DefaultSubscriptionBase subscription, final SubscriptionBaseEvent event, final List<DefaultSubscriptionBase> subscriptions, final List<SubscriptionBaseEvent> cancelEvents, final Catalog catalog, final InternalCallContext context) {
transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<Void>() {
@Override
public Void inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
- cancelSubscriptionsFromTransaction(entitySqlDaoWrapperFactory, subscriptions, cancelEvents, context);
+ cancelSubscriptionsFromTransaction(entitySqlDaoWrapperFactory, subscriptions, cancelEvents, catalog, context);
// Make sure to always send the event, even if there were no subscriptions to cancel
notifyBusOfEffectiveImmediateChange(entitySqlDaoWrapperFactory, subscription, event, subscriptions.size(), context);
return null;
@@ -572,7 +624,7 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
}
@Override
- public void notifyOnBasePlanEvent(final DefaultSubscriptionBase subscription, final SubscriptionBaseEvent event, final InternalCallContext context) {
+ public void notifyOnBasePlanEvent(final DefaultSubscriptionBase subscription, final SubscriptionBaseEvent event, final Catalog catalog, final InternalCallContext context) {
transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<Void>() {
@Override
public Void inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
@@ -584,26 +636,36 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
}
@Override
- public void cancelSubscriptions(final List<DefaultSubscriptionBase> subscriptions, final List<SubscriptionBaseEvent> cancelEvents, final InternalCallContext context) {
+ public void cancelSubscriptions(final List<DefaultSubscriptionBase> subscriptions, final List<SubscriptionBaseEvent> cancelEvents, final Catalog catalog, final InternalCallContext context) {
transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<Void>() {
@Override
public Void inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
- cancelSubscriptionsFromTransaction(entitySqlDaoWrapperFactory, subscriptions, cancelEvents, context);
+ cancelSubscriptionsFromTransaction(entitySqlDaoWrapperFactory, subscriptions, cancelEvents, catalog, context);
return null;
}
});
}
- private void cancelSubscriptionsFromTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory, final List<DefaultSubscriptionBase> subscriptions, final List<SubscriptionBaseEvent> cancelEvents, final InternalCallContext context) throws EntityPersistenceException {
+ private void cancelSubscriptionsFromTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory, final List<DefaultSubscriptionBase> subscriptions, final List<SubscriptionBaseEvent> cancelEvents, final Catalog catalog, final InternalCallContext context) throws EntityPersistenceException {
for (int i = 0; i < subscriptions.size(); i++) {
final DefaultSubscriptionBase subscription = subscriptions.get(i);
final SubscriptionBaseEvent cancelEvent = cancelEvents.get(i);
- cancelSubscriptionFromTransaction(subscription, cancelEvent, entitySqlDaoWrapperFactory, context, subscriptions.size() - i - 1);
+ cancelSubscriptionFromTransaction(subscription, cancelEvent, entitySqlDaoWrapperFactory, catalog, context, subscriptions.size() - i - 1);
}
}
@Override
public void uncancelSubscription(final DefaultSubscriptionBase subscription, final List<SubscriptionBaseEvent> uncancelEvents, final InternalCallContext context) {
+ undoOperation(subscription, uncancelEvents, ApiEventType.CANCEL, SubscriptionBaseTransitionType.UNCANCEL, context);
+ }
+
+ @Override
+ public void undoChangePlan(final DefaultSubscriptionBase subscription, final List<SubscriptionBaseEvent> undoChangePlanEvents, final InternalCallContext context) {
+ undoOperation(subscription, undoChangePlanEvents, ApiEventType.CHANGE, SubscriptionBaseTransitionType.UNDO_CHANGE, context);
+ }
+
+
+ private void undoOperation(final DefaultSubscriptionBase subscription, final List<SubscriptionBaseEvent> inputEvents, final ApiEventType targetOperation, final SubscriptionBaseTransitionType transitionType, final InternalCallContext context) {
final InternalCallContext contextWithUpdatedDate = contextWithUpdatedDate(context);
transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<Void>() {
@@ -612,23 +674,24 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
final SubscriptionEventSqlDao transactional = entitySqlDaoWrapperFactory.become(SubscriptionEventSqlDao.class);
final UUID subscriptionId = subscription.getId();
- SubscriptionEventModelDao cancelledEvent = null;
+
+ Set<SubscriptionEventModelDao> targetEvents = new HashSet<SubscriptionEventModelDao>();
final Date now = clock.getUTCNow().toDate();
final List<SubscriptionEventModelDao> eventModels = transactional.getFutureActiveEventForSubscription(subscriptionId.toString(), now, contextWithUpdatedDate);
for (final SubscriptionEventModelDao cur : eventModels) {
- if (cur.getUserType() == ApiEventType.CANCEL) {
- if (cancelledEvent != null) {
- throw new SubscriptionBaseError(String.format("Found multiple cancelWithRequestedDate active events for subscriptions %s", subscriptionId.toString()));
- }
- cancelledEvent = cur;
+ if (cur.getEventType() == EventType.API_USER && cur.getUserType() == targetOperation) {
+ targetEvents.add(cur);
+ } else if (cur.getEventType() == EventType.PHASE) {
+ targetEvents.add(cur);
}
}
- if (cancelledEvent != null) {
- final String cancelledEventId = cancelledEvent.getId().toString();
- transactional.unactiveEvent(cancelledEventId, contextWithUpdatedDate);
- for (final SubscriptionBaseEvent cur : uncancelEvents) {
+ if (!targetEvents.isEmpty()) {
+ for (SubscriptionEventModelDao target : targetEvents) {
+ transactional.unactiveEvent(target.getId().toString(), contextWithUpdatedDate);
+ }
+ for (final SubscriptionBaseEvent cur : inputEvents) {
transactional.create(new SubscriptionEventModelDao(cur), contextWithUpdatedDate);
recordFutureNotificationFromTransaction(entitySqlDaoWrapperFactory,
cur.getEffectiveDate(),
@@ -637,7 +700,7 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
}
// Notify the Bus of the latest requested change
- notifyBusOfRequestedChange(entitySqlDaoWrapperFactory, subscription, uncancelEvents.get(uncancelEvents.size() - 1), SubscriptionBaseTransitionType.UNCANCEL, contextWithUpdatedDate);
+ notifyBusOfRequestedChange(entitySqlDaoWrapperFactory, subscription, inputEvents.get(inputEvents.size() - 1), transitionType, contextWithUpdatedDate);
}
return null;
@@ -646,7 +709,7 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
}
@Override
- public void changePlan(final DefaultSubscriptionBase subscription, final List<SubscriptionBaseEvent> originalInputChangeEvents, final List<DefaultSubscriptionBase> subscriptionsToBeCancelled, final List<SubscriptionBaseEvent> cancelEvents, final InternalCallContext context) {
+ public void changePlan(final DefaultSubscriptionBase subscription, final List<SubscriptionBaseEvent> originalInputChangeEvents, final List<DefaultSubscriptionBase> subscriptionsToBeCancelled, final List<SubscriptionBaseEvent> cancelEvents, final Catalog catalog, final InternalCallContext context) {
// First event is expected to be the subscription CHANGE event
final SubscriptionBaseEvent inputChangeEvent = originalInputChangeEvents.get(0);
@@ -700,7 +763,7 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
createAndRefresh(transactional, new SubscriptionEventModelDao(cur), context);
final boolean isBusEvent = cur.getEffectiveDate().compareTo(clock.getUTCNow()) <= 0 && (cur.getType() == EventType.API_USER);
- recordBusOrFutureNotificationFromTransaction(subscription, cur, entitySqlDaoWrapperFactory, isBusEvent, 0, context);
+ recordBusOrFutureNotificationFromTransaction(subscription, cur, entitySqlDaoWrapperFactory, isBusEvent, 0, catalog, context);
}
// Notify the Bus of the latest requested change
@@ -708,18 +771,19 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
notifyBusOfRequestedChange(entitySqlDaoWrapperFactory, subscription, finalEvent, SubscriptionBaseTransitionType.CHANGE, context);
// Cancel associated add-ons
- cancelSubscriptionsFromTransaction(entitySqlDaoWrapperFactory, subscriptionsToBeCancelled, cancelEvents, context);
+ cancelSubscriptionsFromTransaction(entitySqlDaoWrapperFactory, subscriptionsToBeCancelled, cancelEvents, catalog, context);
return null;
}
});
}
+
private List<SubscriptionBaseEvent> filterSubscriptionBaseEvents(final Collection<SubscriptionEventModelDao> models) {
final Collection<SubscriptionEventModelDao> filteredModels = Collections2.filter(models, new Predicate<SubscriptionEventModelDao>() {
@Override
public boolean apply(@Nullable final SubscriptionEventModelDao input) {
- return input.getUserType() != ApiEventType.UNCANCEL;
+ return input.getUserType() != ApiEventType.UNCANCEL && input.getUserType() != ApiEventType.UNDO_CHANGE ;
}
});
return new ArrayList<SubscriptionBaseEvent>(Collections2.transform(filteredModels, new Function<SubscriptionEventModelDao, SubscriptionBaseEvent>() {
@@ -740,7 +804,7 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
});
}
- private void cancelSubscriptionFromTransaction(final DefaultSubscriptionBase subscription, final SubscriptionBaseEvent cancelEvent, final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory, final InternalCallContext context, final int seqId)
+ private void cancelSubscriptionFromTransaction(final DefaultSubscriptionBase subscription, final SubscriptionBaseEvent cancelEvent, final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory, final Catalog catalog, final InternalCallContext context, final int seqId)
throws EntityPersistenceException {
final UUID subscriptionId = subscription.getId();
cancelFutureEventsFromTransaction(subscriptionId, cancelEvent.getEffectiveDate(), entitySqlDaoWrapperFactory, true, context);
@@ -748,7 +812,7 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
createAndRefresh(subscriptionEventSqlDao, new SubscriptionEventModelDao(cancelEvent), context);
final boolean isBusEvent = cancelEvent.getEffectiveDate().compareTo(clock.getUTCNow()) <= 0;
- recordBusOrFutureNotificationFromTransaction(subscription, cancelEvent, entitySqlDaoWrapperFactory, isBusEvent, seqId, context);
+ recordBusOrFutureNotificationFromTransaction(subscription, cancelEvent, entitySqlDaoWrapperFactory, isBusEvent, seqId, catalog, context);
// Notify the Bus of the requested change
notifyBusOfRequestedChange(entitySqlDaoWrapperFactory, subscription, cancelEvent, SubscriptionBaseTransitionType.CANCEL, context);
@@ -811,14 +875,14 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
}
}
- private SubscriptionBase buildSubscription(final SubscriptionBase input, final InternalTenantContext context) throws CatalogApiException {
+ private SubscriptionBase buildSubscription(final SubscriptionBase input, final Catalog catalog, final InternalTenantContext context) throws CatalogApiException {
if (input == null) {
return null;
}
final List<SubscriptionBase> bundleInput = new ArrayList<SubscriptionBase>();
if (input.getCategory() == ProductCategory.ADD_ON) {
- final SubscriptionBase baseSubscription = getBaseSubscription(input.getBundleId(), false, context);
+ final SubscriptionBase baseSubscription = getBaseSubscription(input.getBundleId(), false, catalog, context);
if (baseSubscription == null) {
return null;
}
@@ -829,7 +893,7 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
bundleInput.add(input);
}
- final List<SubscriptionBase> reloadedSubscriptions = buildBundleSubscriptions(bundleInput, null, null, context);
+ final List<SubscriptionBase> reloadedSubscriptions = buildBundleSubscriptions(bundleInput, null, null, catalog, context);
for (final SubscriptionBase cur : reloadedSubscriptions) {
if (cur.getId().equals(input.getId())) {
return cur;
@@ -840,7 +904,7 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
}
private List<SubscriptionBase> buildBundleSubscriptions(final List<SubscriptionBase> input, @Nullable final Multimap<UUID, SubscriptionBaseEvent> eventsForSubscription,
- @Nullable final Collection<SubscriptionBaseEvent> dryRunEvents, final InternalTenantContext context) throws CatalogApiException {
+ @Nullable final Collection<SubscriptionBaseEvent> dryRunEvents, final Catalog catalog, final InternalTenantContext context) throws CatalogApiException {
if (input == null || input.isEmpty()) {
return Collections.emptyList();
}
@@ -857,7 +921,7 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
getEventsForSubscription(cur.getId(), context);
mergeDryRunEvents(cur.getId(), events, dryRunEvents);
- SubscriptionBase reloaded = createSubscriptionForInternalUse(cur, events, context);
+ SubscriptionBase reloaded = createSubscriptionForInternalUse(cur, events, catalog, context);
switch (cur.getCategory()) {
case BASE:
@@ -885,8 +949,8 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
for (final ApiEventChange baseChangeEvent : baseChangeEvents) {
final String baseProductName = baseChangeEvent.getEventPlan();
- if ((!addonUtils.isAddonAvailableFromPlanName(baseProductName, targetAddOnPlan, baseChangeEvent.getEffectiveDate(), context)) ||
- (addonUtils.isAddonIncludedFromPlanName(baseProductName, targetAddOnPlan, baseChangeEvent.getEffectiveDate(), context))) {
+ if ((!addonUtils.isAddonAvailableFromPlanName(baseProductName, targetAddOnPlan, baseChangeEvent.getEffectiveDate(), catalog, context)) ||
+ (addonUtils.isAddonIncludedFromPlanName(baseProductName, targetAddOnPlan, baseChangeEvent.getEffectiveDate(), catalog, context))) {
if (baseTriggerEventForAddOnCancellation != null) {
if (baseTriggerEventForAddOnCancellation.getEffectiveDate().isAfter(baseChangeEvent.getEffectiveDate())) {
baseTriggerEventForAddOnCancellation = baseChangeEvent;
@@ -909,7 +973,7 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
events.add(addOnCancelEvent);
// Finally reload subscription with full set of events
- reloaded = createSubscriptionForInternalUse(cur, events, context);
+ reloaded = createSubscriptionForInternalUse(cur, events, catalog, context);
}
break;
default:
@@ -973,18 +1037,23 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
@Override
public void transfer(final UUID srcAccountId, final UUID destAccountId, final BundleTransferData bundleTransferData,
- final List<TransferCancelData> transferCancelData, final InternalCallContext fromContext, final InternalCallContext toContext) {
+ final List<TransferCancelData> transferCancelData, final Catalog catalog, final InternalCallContext fromContext, final InternalCallContext toContext) {
transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<Void>() {
@Override
public Void inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
- final SubscriptionEventSqlDao transactional = entitySqlDaoWrapperFactory.become(SubscriptionEventSqlDao.class);
// Cancel the subscriptions for the old bundle
for (final TransferCancelData cancel : transferCancelData) {
- cancelSubscriptionFromTransaction(cancel.getSubscription(), cancel.getCancelEvent(), entitySqlDaoWrapperFactory, fromContext, 0);
+ cancelSubscriptionFromTransaction(cancel.getSubscription(), cancel.getCancelEvent(), entitySqlDaoWrapperFactory, catalog, fromContext, 0);
}
+
+ // Rename externalKey from source bundle
+ final BundleSqlDao bundleSqlDao = entitySqlDaoWrapperFactory.become(BundleSqlDao.class);
+ bundleSqlDao.renameBundleExternalKey(bundleTransferData.getData().getExternalKey(), "tsf", fromContext);
+
+ final SubscriptionEventSqlDao transactional = entitySqlDaoWrapperFactory.become(SubscriptionEventSqlDao.class);
transferBundleDataFromTransaction(bundleTransferData, transactional, entitySqlDaoWrapperFactory, toContext);
return null;
}
@@ -1005,7 +1074,7 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
}
@Override
- public void createBCDChangeEvent(final DefaultSubscriptionBase subscription, final SubscriptionBaseEvent bcdEvent, final InternalCallContext context) {
+ public void createBCDChangeEvent(final DefaultSubscriptionBase subscription, final SubscriptionBaseEvent bcdEvent, final Catalog catalog, final InternalCallContext context) {
transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<Void>() {
@Override
public Void inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
@@ -1015,7 +1084,7 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
// Notify the Bus
notifyBusOfRequestedChange(entitySqlDaoWrapperFactory, subscription, bcdEvent, SubscriptionBaseTransitionType.BCD_CHANGE, context);
final boolean isBusEvent = bcdEvent.getEffectiveDate().compareTo(clock.getUTCNow()) <= 0;
- recordBusOrFutureNotificationFromTransaction(subscription, bcdEvent, entitySqlDaoWrapperFactory, isBusEvent, 0, context);
+ recordBusOrFutureNotificationFromTransaction(subscription, bcdEvent, entitySqlDaoWrapperFactory, isBusEvent, 0, catalog, context);
return null;
}
@@ -1023,21 +1092,20 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
}
- private SubscriptionBase createSubscriptionForInternalUse(final SubscriptionBase shellSubscription, final List<SubscriptionBaseEvent> events, final InternalTenantContext context) throws CatalogApiException {
+ private SubscriptionBase createSubscriptionForInternalUse(final SubscriptionBase shellSubscription, final List<SubscriptionBaseEvent> events, final Catalog catalog, final InternalTenantContext context) throws CatalogApiException {
final DefaultSubscriptionBase result = new DefaultSubscriptionBase(new SubscriptionBuilder(((DefaultSubscriptionBase) shellSubscription)), null, clock);
if (!events.isEmpty()) {
- final Catalog fullCatalog = getFullCatalog(result.getId(), ObjectType.SUBSCRIPTION, context);
- result.rebuildTransitions(events, fullCatalog);
+ result.rebuildTransitions(events, catalog);
}
return result;
}
- private SubscriptionBase getBaseSubscription(final UUID bundleId, final boolean rebuildSubscription, final InternalTenantContext context) throws CatalogApiException {
+ private SubscriptionBase getBaseSubscription(final UUID bundleId, final boolean rebuildSubscription, final Catalog catalog, final InternalTenantContext context) throws CatalogApiException {
final List<SubscriptionBase> subscriptions = getSubscriptionFromBundleId(bundleId, context);
for (final SubscriptionBase cur : subscriptions) {
if (cur.getCategory() == ProductCategory.BASE) {
- return rebuildSubscription ? buildSubscription(cur, context) : cur;
+ return rebuildSubscription ? buildSubscription(cur, catalog, context) : cur;
}
}
return null;
@@ -1047,9 +1115,9 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
// Either records a notification or sends a bus event if operation is immediate
//
private void recordBusOrFutureNotificationFromTransaction(final DefaultSubscriptionBase subscription, final SubscriptionBaseEvent event, final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory, final boolean busEvent,
- final int seqId, final InternalCallContext context) {
+ final int seqId, final Catalog catalog, final InternalCallContext context) {
if (busEvent) {
- rebuildSubscriptionAndNotifyBusOfEffectiveImmediateChange(entitySqlDaoWrapperFactory, subscription, event, seqId, context);
+ rebuildSubscriptionAndNotifyBusOfEffectiveImmediateChange(entitySqlDaoWrapperFactory, subscription, event, seqId, catalog, context);
} else {
recordFutureNotificationFromTransaction(entitySqlDaoWrapperFactory,
event.getEffectiveDate(),
@@ -1060,9 +1128,9 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
// Sends bus notification for event on effective date -- only used for operation that happen immediately
private void rebuildSubscriptionAndNotifyBusOfEffectiveImmediateChange(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory, final DefaultSubscriptionBase subscription,
- final SubscriptionBaseEvent immediateEvent, final int seqId, final InternalCallContext context) {
+ final SubscriptionBaseEvent immediateEvent, final int seqId, final Catalog catalog, final InternalCallContext context) {
try {
- final DefaultSubscriptionBase upToDateSubscription = createSubscriptionWithNewEvent(subscription, immediateEvent, context);
+ final DefaultSubscriptionBase upToDateSubscription = createSubscriptionWithNewEvent(subscription, immediateEvent, catalog, context);
notifyBusOfEffectiveImmediateChange(entitySqlDaoWrapperFactory, upToDateSubscription, immediateEvent, seqId, context);
} catch (final CatalogApiException e) {
log.warn("Failed to post effective event for subscriptionId='{}'", subscription.getId(), e);
@@ -1117,8 +1185,8 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
final DefaultSubscriptionBaseBundle bundleData = bundleTransferData.getData();
- final List<SubscriptionBundleModelDao> existingBundleModels = transBundleDao.getBundlesFromAccountAndKey(bundleData.getAccountId().toString(), bundleData.getExternalKey(), context);
- if (!existingBundleModels.isEmpty()) {
+ final SubscriptionBundleModelDao existingBundleForAccount = transBundleDao.getBundlesFromAccountAndKey(bundleData.getAccountId().toString(), bundleData.getExternalKey(), context);
+ if (existingBundleForAccount != null) {
log.warn("Bundle already exists for accountId='{}', bundleExternalKey='{}'", bundleData.getAccountId(), bundleData.getExternalKey());
return;
}
@@ -1145,7 +1213,7 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
//
// Creates a copy of the existing subscriptions whose 'transitions' will reflect the new event
//
- private DefaultSubscriptionBase createSubscriptionWithNewEvent(final DefaultSubscriptionBase subscription, final SubscriptionBaseEvent newEvent, final InternalTenantContext context) throws CatalogApiException {
+ private DefaultSubscriptionBase createSubscriptionWithNewEvent(final DefaultSubscriptionBase subscription, final SubscriptionBaseEvent newEvent, final Catalog catalog, final InternalTenantContext context) throws CatalogApiException {
final DefaultSubscriptionBase subscriptionWithNewEvent = new DefaultSubscriptionBase(subscription, null, clock);
final List<SubscriptionBaseEvent> allEvents = new LinkedList<SubscriptionBaseEvent>();
@@ -1153,17 +1221,10 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
allEvents.addAll(subscriptionWithNewEvent.getEvents());
}
allEvents.add(newEvent);
- subscriptionWithNewEvent.rebuildTransitions(allEvents, getFullCatalog(subscription.getId(), ObjectType.SUBSCRIPTION, context));
+ subscriptionWithNewEvent.rebuildTransitions(allEvents, catalog);
return subscriptionWithNewEvent;
}
- private Catalog getFullCatalog(final UUID objectId, final ObjectType objectType, final InternalTenantContext contextWithOrWithoutAccountId) throws CatalogApiException {
-
- final InternalTenantContext context = contextWithOrWithoutAccountId.getAccountRecordId() == null ?
- internalCallContextFactory.recreateInternalTenantContextWithAccountRecordId(objectId, objectType, contextWithOrWithoutAccountId) :
- contextWithOrWithoutAccountId;
- return catalogInternalApi.getFullCatalog(true, true, context);
- }
private InternalCallContext contextWithUpdatedDate(final InternalCallContext input) {
return new InternalCallContext(input, clock.getUTCNow());
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/model/SubscriptionBundleModelDao.java b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/model/SubscriptionBundleModelDao.java
index 2c9489f..e5938dc 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/model/SubscriptionBundleModelDao.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/model/SubscriptionBundleModelDao.java
@@ -17,6 +17,8 @@
package org.killbill.billing.subscription.engine.dao.model;
import java.util.UUID;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import org.joda.time.DateTime;
import org.killbill.billing.subscription.api.user.DefaultSubscriptionBaseBundle;
@@ -29,6 +31,11 @@ import com.google.common.base.MoreObjects;
public class SubscriptionBundleModelDao extends EntityModelDaoBase implements EntityModelDao<SubscriptionBaseBundle> {
+ // Any key that starts with kb<some_prefix>-<some_number>:<something> is interpreted as a <something> key that got renamed for internal purpose
+ // KB core currently only use the prefix 'tsf' for renaming such keys during bundle transfer
+ //
+ private static Pattern BUNDLE_KEY_PATTERN = Pattern.compile("kb(?:\\w+)-\\d+:(.*)");
+
private String externalKey;
private UUID accountId;
private DateTime lastSysUpdateDate;
@@ -81,11 +88,15 @@ public class SubscriptionBundleModelDao extends EntityModelDaoBase implements En
this.originalCreatedDate = originalCreatedDate;
}
- public static SubscriptionBaseBundle toSubscriptionbundle(final SubscriptionBundleModelDao src) {
+ public static SubscriptionBaseBundle toSubscriptionBundle(final SubscriptionBundleModelDao src) {
if (src == null) {
return null;
}
- return new DefaultSubscriptionBaseBundle(src.getId(), src.getExternalKey(), src.getAccountId(), src.getLastSysUpdateDate(), src.getOriginalCreatedDate(), src.getCreatedDate(), src.getUpdatedDate());
+
+ // Fix externalKey to remove internal prefix used for tsf
+ final Matcher m = BUNDLE_KEY_PATTERN.matcher(src.getExternalKey());
+ final String externalKey = m.matches() ? m.group(1) : src.getExternalKey();
+ return new DefaultSubscriptionBaseBundle(src.getId(), externalKey, src.getAccountId(), src.getLastSysUpdateDate(), src.getOriginalCreatedDate(), src.getCreatedDate(), src.getUpdatedDate());
}
@Override
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/model/SubscriptionEventModelDao.java b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/model/SubscriptionEventModelDao.java
index 9063bba..c5d14bc 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/model/SubscriptionEventModelDao.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/model/SubscriptionEventModelDao.java
@@ -19,6 +19,7 @@ package org.killbill.billing.subscription.engine.dao.model;
import java.util.UUID;
import org.joda.time.DateTime;
+import org.killbill.billing.subscription.api.SubscriptionBaseTransitionType;
import org.killbill.billing.subscription.events.EventBaseBuilder;
import org.killbill.billing.subscription.events.SubscriptionBaseEvent;
import org.killbill.billing.subscription.events.SubscriptionBaseEvent.EventType;
@@ -35,7 +36,6 @@ import org.killbill.billing.util.entity.dao.EntityModelDao;
import org.killbill.billing.util.entity.dao.EntityModelDaoBase;
public class SubscriptionEventModelDao extends EntityModelDaoBase implements EntityModelDao<SubscriptionBaseEvent> {
-
private long totalOrdering;
private EventType eventType;
private ApiEventType userType;
@@ -216,6 +216,29 @@ public class SubscriptionEventModelDao extends EntityModelDaoBase implements Ent
return result;
}
+ public boolean isOfSubscriptionBaseTransitionType(final SubscriptionBaseTransitionType type) {
+ switch(type) {
+ case CREATE:
+ return eventType == EventType.API_USER && userType == ApiEventType.CREATE;
+ case TRANSFER:
+ return eventType == EventType.API_USER && userType == ApiEventType.TRANSFER;
+ case CHANGE:
+ return eventType == EventType.API_USER && userType == ApiEventType.CHANGE;
+ case CANCEL:
+ return eventType == EventType.API_USER && userType == ApiEventType.CANCEL;
+ case UNCANCEL:
+ return eventType == EventType.API_USER && userType == ApiEventType.UNCANCEL;
+ case PHASE:
+ return eventType == EventType.PHASE;
+ case BCD_CHANGE:
+ return eventType == EventType.BCD_UPDATE;
+ case START_BILLING_DISABLED:
+ case END_BILLING_DISABLED:
+ default:
+ return false;
+ }
+ }
+
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
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 516d80c..0939c7f 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
@@ -22,15 +22,17 @@ import java.util.UUID;
import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.catalog.api.Catalog;
import org.killbill.billing.catalog.api.CatalogApiException;
import org.killbill.billing.entitlement.api.SubscriptionApiException;
import org.killbill.billing.subscription.api.SubscriptionBase;
+import org.killbill.billing.subscription.api.SubscriptionBaseTransitionType;
import org.killbill.billing.subscription.api.SubscriptionBaseWithAddOns;
import org.killbill.billing.subscription.api.transfer.BundleTransferData;
import org.killbill.billing.subscription.api.transfer.TransferCancelData;
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.SubscriptionBaseApiException;
import org.killbill.billing.subscription.api.user.SubscriptionBaseBundle;
import org.killbill.billing.subscription.engine.dao.model.SubscriptionBundleModelDao;
import org.killbill.billing.subscription.events.SubscriptionBaseEvent;
@@ -48,23 +50,23 @@ public interface SubscriptionDao extends EntityDao<SubscriptionBundleModelDao, S
public Iterable<UUID> getNonAOSubscriptionIdsForKey(String bundleKey, InternalTenantContext context);
- public List<SubscriptionBaseBundle> getSubscriptionBundlesForAccountAndKey(UUID accountId, String bundleKey, InternalTenantContext context);
+ public SubscriptionBaseBundle getSubscriptionBundlesForAccountAndKey(UUID accountId, String bundleKey, InternalTenantContext context);
public SubscriptionBaseBundle getSubscriptionBundleFromId(UUID bundleId, InternalTenantContext context);
- public SubscriptionBaseBundle createSubscriptionBundle(DefaultSubscriptionBaseBundle bundle, InternalCallContext context);
+ public SubscriptionBaseBundle createSubscriptionBundle(DefaultSubscriptionBaseBundle bundle, final Catalog catalog, final boolean renameCancelledBundleIfExist, InternalCallContext context) throws SubscriptionBaseApiException;
- public SubscriptionBase getSubscriptionFromId(UUID subscriptionId, InternalTenantContext context) throws CatalogApiException;
+ public SubscriptionBase getSubscriptionFromId(UUID subscriptionId, final Catalog catalog, InternalTenantContext context) throws CatalogApiException;
// ACCOUNT retrieval
public UUID getAccountIdFromSubscriptionId(UUID subscriptionId, InternalTenantContext context);
// SubscriptionBase retrieval
- public SubscriptionBase getBaseSubscription(UUID bundleId, InternalTenantContext context) throws CatalogApiException;
+ public SubscriptionBase getBaseSubscription(UUID bundleId, final Catalog catalog, InternalTenantContext context) throws CatalogApiException;
- public List<SubscriptionBase> getSubscriptions(UUID bundleId, List<SubscriptionBaseEvent> dryRunEvents, InternalTenantContext context) throws CatalogApiException;
+ public List<SubscriptionBase> getSubscriptions(UUID bundleId, List<SubscriptionBaseEvent> dryRunEvents, final Catalog catalog, InternalTenantContext context) throws CatalogApiException;
- public Map<UUID, List<SubscriptionBase>> getSubscriptionsForAccount(InternalTenantContext context) throws CatalogApiException;
+ public Map<UUID, List<SubscriptionBase>> getSubscriptionsForAccount(final Catalog catalog, InternalTenantContext context) throws CatalogApiException;
// Update
public void updateChargedThroughDate(DefaultSubscriptionBase subscription, InternalCallContext context);
@@ -74,31 +76,31 @@ public interface SubscriptionDao extends EntityDao<SubscriptionBundleModelDao, S
public SubscriptionBaseEvent getEventById(UUID eventId, InternalTenantContext context);
- public Iterable<SubscriptionBaseEvent> getFutureEventsForAccount(InternalTenantContext context);
-
public List<SubscriptionBaseEvent> getEventsForSubscription(UUID subscriptionId, InternalTenantContext context);
public List<SubscriptionBaseEvent> getPendingEventsForSubscription(UUID subscriptionId, InternalTenantContext context);
// SubscriptionBase creation, cancellation, changePlanWithRequestedDate apis
- public void createSubscription(DefaultSubscriptionBase subscription, List<SubscriptionBaseEvent> initialEvents, InternalCallContext context);
+ public void createSubscription(DefaultSubscriptionBase subscription, List<SubscriptionBaseEvent> initialEvents, final Catalog catalog, InternalCallContext context);
- public void createSubscriptionsWithAddOns(List<SubscriptionBaseWithAddOns> subscriptions, Map<UUID, List<SubscriptionBaseEvent>> initialEventsMap, InternalCallContext context);
+ public void createSubscriptionsWithAddOns(List<SubscriptionBaseWithAddOns> subscriptions, Map<UUID, List<SubscriptionBaseEvent>> initialEventsMap, final Catalog catalog, InternalCallContext context);
- public void cancelSubscriptionsOnBasePlanEvent(DefaultSubscriptionBase subscription, SubscriptionBaseEvent event, List<DefaultSubscriptionBase> subscriptions, List<SubscriptionBaseEvent> cancelEvents, InternalCallContext context);
+ public void cancelSubscriptionsOnBasePlanEvent(DefaultSubscriptionBase subscription, SubscriptionBaseEvent event, List<DefaultSubscriptionBase> subscriptions, List<SubscriptionBaseEvent> cancelEvents, final Catalog catalog, InternalCallContext context);
- public void notifyOnBasePlanEvent(final DefaultSubscriptionBase subscription, final SubscriptionBaseEvent event, final InternalCallContext context);
+ public void notifyOnBasePlanEvent(final DefaultSubscriptionBase subscription, final SubscriptionBaseEvent event, final Catalog catalog, final InternalCallContext context);
- public void cancelSubscriptions(List<DefaultSubscriptionBase> subscriptions, List<SubscriptionBaseEvent> cancelEvents, InternalCallContext context);
+ public void cancelSubscriptions(List<DefaultSubscriptionBase> subscriptions, List<SubscriptionBaseEvent> cancelEvents, final Catalog catalog, InternalCallContext context);
public void uncancelSubscription(DefaultSubscriptionBase subscription, List<SubscriptionBaseEvent> uncancelEvents, InternalCallContext context);
- public void changePlan(DefaultSubscriptionBase subscription, List<SubscriptionBaseEvent> changeEvents, List<DefaultSubscriptionBase> subscriptionsToBeCancelled, List<SubscriptionBaseEvent> cancelEvents, InternalCallContext context);
+ public void changePlan(DefaultSubscriptionBase subscription, List<SubscriptionBaseEvent> changeEvents, List<DefaultSubscriptionBase> subscriptionsToBeCancelled, List<SubscriptionBaseEvent> cancelEvents, final Catalog catalog, InternalCallContext context);
+
+ public void undoChangePlan(DefaultSubscriptionBase subscription, List<SubscriptionBaseEvent> undoChangePlanEvents, InternalCallContext context);
- public void transfer(UUID srcAccountId, UUID destAccountId, BundleTransferData data, List<TransferCancelData> transferCancelData, InternalCallContext fromContext, InternalCallContext toContext);
+ public void transfer(UUID srcAccountId, UUID destAccountId, BundleTransferData data, List<TransferCancelData> transferCancelData, final Catalog catalog, InternalCallContext fromContext, InternalCallContext toContext);
public void updateBundleExternalKey(UUID bundleId, String externalKey, InternalCallContext context);
- public void createBCDChangeEvent(DefaultSubscriptionBase subscription, SubscriptionBaseEvent bcdEvent, InternalCallContext context);
+ public void createBCDChangeEvent(DefaultSubscriptionBase subscription, SubscriptionBaseEvent bcdEvent, final Catalog catalog, InternalCallContext context);
}
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionEventSqlDao.java b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionEventSqlDao.java
index c47d482..0e195db 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionEventSqlDao.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionEventSqlDao.java
@@ -26,34 +26,34 @@ import org.killbill.billing.subscription.events.SubscriptionBaseEvent;
import org.killbill.billing.util.audit.ChangeType;
import org.killbill.billing.util.entity.dao.Audited;
import org.killbill.billing.util.entity.dao.EntitySqlDao;
-import org.killbill.billing.util.entity.dao.EntitySqlDaoStringTemplate;
+import org.killbill.commons.jdbi.template.KillBillSqlDaoStringTemplate;
import org.skife.jdbi.v2.sqlobject.Bind;
-import org.skife.jdbi.v2.sqlobject.BindBean;
+import org.killbill.commons.jdbi.binder.SmartBindBean;
import org.skife.jdbi.v2.sqlobject.SqlQuery;
import org.skife.jdbi.v2.sqlobject.SqlUpdate;
-@EntitySqlDaoStringTemplate
+@KillBillSqlDaoStringTemplate
public interface SubscriptionEventSqlDao extends EntitySqlDao<SubscriptionEventModelDao, SubscriptionBaseEvent> {
@SqlUpdate
@Audited(ChangeType.UPDATE)
public void unactiveEvent(@Bind("id") String id,
- @BindBean final InternalCallContext context);
+ @SmartBindBean final InternalCallContext context);
@SqlQuery
public List<SubscriptionEventModelDao> getFutureActiveEventForSubscription(@Bind("subscriptionId") String subscriptionId,
@Bind("now") Date now,
- @BindBean final InternalTenantContext context);
+ @SmartBindBean final InternalTenantContext context);
@SqlQuery
public List<SubscriptionEventModelDao> getFutureOrPresentActiveEventForSubscription(@Bind("subscriptionId") String subscriptionId,
@Bind("now") Date now,
- @BindBean final InternalTenantContext context);
+ @SmartBindBean final InternalTenantContext context);
@SqlQuery
public List<SubscriptionEventModelDao> getActiveEventsForSubscription(@Bind("subscriptionId") String subscriptionId,
- @BindBean final InternalTenantContext context);
+ @SmartBindBean final InternalTenantContext context);
@SqlQuery
- public List<SubscriptionEventModelDao> getFutureActiveEventsForAccount(@Bind("now") Date now, @BindBean final InternalTenantContext context);
+ public List<SubscriptionEventModelDao> getFutureActiveEventsForAccount(@Bind("now") Date now, @SmartBindBean final InternalTenantContext context);
}
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionSqlDao.java b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionSqlDao.java
index b47b5ba..c0c2495 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionSqlDao.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionSqlDao.java
@@ -20,7 +20,7 @@ import java.util.Date;
import java.util.List;
import org.skife.jdbi.v2.sqlobject.Bind;
-import org.skife.jdbi.v2.sqlobject.BindBean;
+import org.killbill.commons.jdbi.binder.SmartBindBean;
import org.skife.jdbi.v2.sqlobject.SqlQuery;
import org.skife.jdbi.v2.sqlobject.SqlUpdate;
@@ -31,18 +31,18 @@ import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.util.entity.dao.Audited;
import org.killbill.billing.util.entity.dao.EntitySqlDao;
-import org.killbill.billing.util.entity.dao.EntitySqlDaoStringTemplate;
+import org.killbill.commons.jdbi.template.KillBillSqlDaoStringTemplate;
-@EntitySqlDaoStringTemplate
+@KillBillSqlDaoStringTemplate
public interface SubscriptionSqlDao extends EntitySqlDao<SubscriptionModelDao, SubscriptionBase> {
@SqlQuery
public List<SubscriptionModelDao> getSubscriptionsFromBundleId(@Bind("bundleId") String bundleId,
- @BindBean final InternalTenantContext context);
+ @SmartBindBean final InternalTenantContext context);
@SqlUpdate
@Audited(ChangeType.UPDATE)
public void updateChargedThroughDate(@Bind("id") String id, @Bind("chargedThroughDate") Date chargedThroughDate,
- @BindBean final InternalCallContext context);
+ @SmartBindBean final InternalCallContext context);
}
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/events/user/ApiEventBuilder.java b/subscription/src/main/java/org/killbill/billing/subscription/events/user/ApiEventBuilder.java
index 7e3ce7a..e09d14a 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/events/user/ApiEventBuilder.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/events/user/ApiEventBuilder.java
@@ -100,6 +100,8 @@ public class ApiEventBuilder extends EventBaseBuilder<ApiEventBuilder> {
result = new ApiEventCancel(this);
} else if (apiEventType == ApiEventType.UNCANCEL) {
result = new ApiEventUncancel(this);
+ } else if (apiEventType == ApiEventType.UNDO_CHANGE) {
+ result = new ApiEventUndoChange(this);
} else {
throw new IllegalStateException("Unknown ApiEventType " + apiEventType);
}
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/events/user/ApiEventType.java b/subscription/src/main/java/org/killbill/billing/subscription/events/user/ApiEventType.java
index 1646243..88e4e7c 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/events/user/ApiEventType.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/events/user/ApiEventType.java
@@ -44,6 +44,12 @@ public enum ApiEventType {
return SubscriptionBaseTransitionType.CANCEL;
}
},
+ UNDO_CHANGE {
+ @Override
+ public SubscriptionBaseTransitionType getSubscriptionTransitionType() {
+ return SubscriptionBaseTransitionType.UNDO_CHANGE;
+ }
+ },
UNCANCEL {
@Override
public SubscriptionBaseTransitionType getSubscriptionTransitionType() {
diff --git a/subscription/src/main/resources/org/killbill/billing/subscription/ddl.sql b/subscription/src/main/resources/org/killbill/billing/subscription/ddl.sql
index ac3b021..0807094 100644
--- a/subscription/src/main/resources/org/killbill/billing/subscription/ddl.sql
+++ b/subscription/src/main/resources/org/killbill/billing/subscription/ddl.sql
@@ -65,7 +65,7 @@ CREATE TABLE bundles (
PRIMARY KEY(record_id)
) /*! CHARACTER SET utf8 COLLATE utf8_bin */;
CREATE UNIQUE INDEX bundles_id ON bundles(id);
-CREATE INDEX bundles_key ON bundles(external_key);
+CREATE UNIQUE INDEX bundles_external_key ON bundles(external_key, tenant_record_id);
CREATE INDEX bundles_account ON bundles(account_id);
CREATE INDEX bundles_tenant_account_record_id ON bundles(tenant_record_id, account_record_id);
diff --git a/subscription/src/main/resources/org/killbill/billing/subscription/engine/dao/BundleSqlDao.sql.stg b/subscription/src/main/resources/org/killbill/billing/subscription/engine/dao/BundleSqlDao.sql.stg
index 4495409..369469b 100644
--- a/subscription/src/main/resources/org/killbill/billing/subscription/engine/dao/BundleSqlDao.sql.stg
+++ b/subscription/src/main/resources/org/killbill/billing/subscription/engine/dao/BundleSqlDao.sql.stg
@@ -1,4 +1,4 @@
-group BundleSqlDao: EntitySqlDao;
+import "org/killbill/billing/util/entity/dao/EntitySqlDao.sql.stg"
tableName() ::= "bundles"
@@ -32,7 +32,7 @@ last_sys_update_date = :lastSysUpdateDate
, updated_by = :createdBy
, updated_date = :updatedDate
where id = :id
-<AND_CHECK_TENANT()>
+<AND_CHECK_TENANT("")>
;
>>
@@ -43,38 +43,53 @@ external_key = :externalKey
, updated_by = :createdBy
, updated_date = :updatedDate
where id = :id
-<AND_CHECK_TENANT()>
+<AND_CHECK_TENANT("")>
;
>>
-getBundlesForKey() ::= <<
-select <allTableFields()>
+
+renameBundleExternalKey(prefix) ::= <<
+update bundles b
+join (select
+ record_id
+ , external_key
+ from
+ bundles
+ where external_key = :externalKey <AND_CHECK_TENANT("")>) t
+on b.record_id = t.record_id
+set b.external_key = concat('kb', '<prefix>', '-', t.record_id, ':', t.external_key)
+;
+>>
+
+getBundlesForLikeKey() ::= <<
+select <allTableFields("")>
from bundles
where
external_key = :externalKey
-<AND_CHECK_TENANT()>
-<defaultOrderBy()>
+or external_key like concat('kb%-%:', :externalKey)
+<AND_CHECK_TENANT("")>
+<defaultOrderBy("")>
;
>>
getBundlesFromAccountAndKey() ::= <<
-select <allTableFields()>
+select <allTableFields("")>
from bundles
where
external_key = :externalKey
and account_id = :accountId
-<AND_CHECK_TENANT()>
-<defaultOrderBy()>
+<AND_CHECK_TENANT("")>
+<defaultOrderBy("")>
;
>>
getBundleFromAccount() ::= <<
-select <allTableFields()>
+select <allTableFields("")>
from bundles
where
account_id = :accountId
-<AND_CHECK_TENANT()>
-<defaultOrderBy()>
+<AND_CHECK_TENANT("")>
+<defaultOrderBy("")>
;
>>
diff --git a/subscription/src/main/resources/org/killbill/billing/subscription/engine/dao/SubscriptionEventSqlDao.sql.stg b/subscription/src/main/resources/org/killbill/billing/subscription/engine/dao/SubscriptionEventSqlDao.sql.stg
index cfb3ec4..94d407f 100644
--- a/subscription/src/main/resources/org/killbill/billing/subscription/engine/dao/SubscriptionEventSqlDao.sql.stg
+++ b/subscription/src/main/resources/org/killbill/billing/subscription/engine/dao/SubscriptionEventSqlDao.sql.stg
@@ -1,4 +1,4 @@
-group EventSqlDao: EntitySqlDao;
+import "org/killbill/billing/util/entity/dao/EntitySqlDao.sql.stg"
tableName() ::= "subscription_events"
@@ -53,59 +53,59 @@ is_active = false
, updated_date = :updatedDate
where
id = :id
-<AND_CHECK_TENANT()>
+<AND_CHECK_TENANT("")>
;
>>
getFutureActiveEventForSubscription() ::= <<
-select <allTableFields()>
+select <allTableFields("")>
, record_id as total_ordering
from <tableName()>
where
subscription_id = :subscriptionId
and is_active = true
and effective_date > :now
-<AND_CHECK_TENANT()>
-<defaultOrderBy()>
+<AND_CHECK_TENANT("")>
+<defaultOrderBy("")>
;
>>
getFutureOrPresentActiveEventForSubscription() ::= <<
-select <allTableFields()>
+select <allTableFields("")>
, record_id as total_ordering
from <tableName()>
where
subscription_id = :subscriptionId
and is_active = true
and effective_date >= :now
-<AND_CHECK_TENANT()>
-<defaultOrderBy()>
+<AND_CHECK_TENANT("")>
+<defaultOrderBy("")>
;
>>
getActiveEventsForSubscription() ::= <<
-select <allTableFields()>
+select <allTableFields("")>
, record_id as total_ordering
from <tableName()>
where
subscription_id = :subscriptionId
and is_active = true
-<AND_CHECK_TENANT()>
-<defaultOrderBy()>
+<AND_CHECK_TENANT("")>
+<defaultOrderBy("")>
;
>>
getFutureActiveEventsForAccount() ::= <<
-select <allTableFields()>
+select <allTableFields("")>
, record_id as total_ordering
from <tableName()>
where
account_record_id = :accountRecordId
and is_active = true
and effective_date > :now
-<AND_CHECK_TENANT()>
-<defaultOrderBy()>
+<AND_CHECK_TENANT("")>
+<defaultOrderBy("")>
;
>>
diff --git a/subscription/src/main/resources/org/killbill/billing/subscription/engine/dao/SubscriptionSqlDao.sql.stg b/subscription/src/main/resources/org/killbill/billing/subscription/engine/dao/SubscriptionSqlDao.sql.stg
index 47ae310..3f9388d 100644
--- a/subscription/src/main/resources/org/killbill/billing/subscription/engine/dao/SubscriptionSqlDao.sql.stg
+++ b/subscription/src/main/resources/org/killbill/billing/subscription/engine/dao/SubscriptionSqlDao.sql.stg
@@ -1,4 +1,4 @@
-group SubscriptionSqlDao: EntitySqlDao;
+import "org/killbill/billing/util/entity/dao/EntitySqlDao.sql.stg"
tableName() ::= "subscriptions"
@@ -31,11 +31,11 @@ tableValues() ::= <<
getSubscriptionsFromBundleId() ::= <<
select
-<allTableFields()>
+<allTableFields("")>
from <tableName()>
where bundle_id = :bundleId
-<AND_CHECK_TENANT()>
-<defaultOrderBy()>
+<AND_CHECK_TENANT("")>
+<defaultOrderBy("")>
;
>>
@@ -46,6 +46,6 @@ charged_through_date = :chargedThroughDate
, updated_by = :createdBy
, updated_date = :updatedDate
where id = :id
-<AND_CHECK_TENANT()>
+<AND_CHECK_TENANT("")>
;
>>
diff --git a/subscription/src/main/resources/org/killbill/billing/subscription/migration/V20170920200757__bundle_external_key.sql b/subscription/src/main/resources/org/killbill/billing/subscription/migration/V20170920200757__bundle_external_key.sql
new file mode 100644
index 0000000..c175f86
--- /dev/null
+++ b/subscription/src/main/resources/org/killbill/billing/subscription/migration/V20170920200757__bundle_external_key.sql
@@ -0,0 +1,2 @@
+drop index bundles_key on bundles;
+create unique index bundles_external_key on bundles(external_key, tenant_record_id);
\ No newline at end of file
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 21a112f..7b2ec78 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
@@ -51,7 +51,7 @@ public class TestPlanAligner extends SubscriptionTestSuiteNoDB {
@BeforeClass(groups = "fast")
public void beforeClass() throws Exception {
super.beforeClass();
- planAligner = new PlanAligner(catalogInternalApi);
+ planAligner = new PlanAligner();
}
@@ -75,7 +75,7 @@ public class TestPlanAligner extends SubscriptionTestSuiteNoDB {
Assert.assertEquals(phases[1].getStartPhase(), defaultSubscriptionBase.getBundleStartDate().plusDays(30));
// Verify the next phase via the other API
- final TimedPhase nextTimePhase = planAligner.getNextTimedPhase(defaultSubscriptionBase, effectiveDate, internalCallContext);
+ final TimedPhase nextTimePhase = planAligner.getNextTimedPhase(defaultSubscriptionBase, effectiveDate, catalog, internalCallContext);
Assert.assertEquals(nextTimePhase.getStartPhase(), defaultSubscriptionBase.getBundleStartDate().plusDays(30));
// Now look at the past, before the bundle started
@@ -117,7 +117,7 @@ public class TestPlanAligner extends SubscriptionTestSuiteNoDB {
Assert.assertEquals(phases[1].getStartPhase(), defaultSubscriptionBase.getStartDate().plusMonths(1));
// Verify the next phase via the other API
- final TimedPhase nextTimePhase = planAligner.getNextTimedPhase(defaultSubscriptionBase, effectiveDate, internalCallContext);
+ final TimedPhase nextTimePhase = planAligner.getNextTimedPhase(defaultSubscriptionBase, effectiveDate, catalog, internalCallContext);
Assert.assertEquals(nextTimePhase.getStartPhase(), defaultSubscriptionBase.getStartDate().plusMonths(1));
// Now look at the past, before the subscription started
@@ -152,7 +152,7 @@ public class TestPlanAligner extends SubscriptionTestSuiteNoDB {
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);
+ final TimedPhase [] phases = planAligner.getCurrentAndNextTimedPhaseOnCreate(alignStartDate, bundleStartDate, plan, PhaseType.EVERGREEN, PriceListSet.DEFAULT_PRICELIST_NAME, now, catalog, internalCallContext);
Assert.assertEquals(phases.length, 2);
Assert.assertEquals(phases[0].getPhase().getPhaseType(), PhaseType.EVERGREEN);
Assert.assertEquals(phases[0].getStartPhase(), now);
@@ -164,7 +164,7 @@ public class TestPlanAligner extends SubscriptionTestSuiteNoDB {
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);
+ final TimedPhase currentPhase = planAligner.getCurrentTimedPhaseOnChange(defaultSubscriptionBase, newPlan, effectiveChangeDate, null, catalog, internalCallContext);
Assert.assertEquals(currentPhase.getStartPhase(), alignStartDate);
Assert.assertEquals(currentPhase.getPhase().getPhaseType(), PhaseType.EVERGREEN);
}
@@ -182,7 +182,7 @@ public class TestPlanAligner extends SubscriptionTestSuiteNoDB {
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);
+ final TimedPhase [] phases = planAligner.getCurrentAndNextTimedPhaseOnCreate(alignStartDate, bundleStartDate, plan, PhaseType.EVERGREEN, PriceListSet.DEFAULT_PRICELIST_NAME, now, catalog, internalCallContext);
Assert.assertEquals(phases.length, 2);
Assert.assertEquals(phases[0].getPhase().getPhaseType(), PhaseType.EVERGREEN);
Assert.assertEquals(phases[0].getStartPhase(), now);
@@ -194,7 +194,7 @@ public class TestPlanAligner extends SubscriptionTestSuiteNoDB {
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);
+ final TimedPhase currentPhase = planAligner.getCurrentTimedPhaseOnChange(defaultSubscriptionBase, newPlan, effectiveChangeDate, null, catalog, internalCallContext);
Assert.assertEquals(currentPhase.getStartPhase(), alignStartDate);
Assert.assertEquals(currentPhase.getPhase().getPhaseType(), PhaseType.EVERGREEN);
}
@@ -212,7 +212,7 @@ public class TestPlanAligner extends SubscriptionTestSuiteNoDB {
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);
+ final TimedPhase [] phases = planAligner.getCurrentAndNextTimedPhaseOnCreate(alignStartDate, bundleStartDate, plan, PhaseType.EVERGREEN, PriceListSet.DEFAULT_PRICELIST_NAME, now, catalog, internalCallContext);
Assert.assertEquals(phases.length, 2);
Assert.assertEquals(phases[0].getPhase().getPhaseType(), PhaseType.EVERGREEN);
Assert.assertEquals(phases[0].getStartPhase(), now);
@@ -225,7 +225,7 @@ public class TestPlanAligner extends SubscriptionTestSuiteNoDB {
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);
+ final TimedPhase currentPhase = planAligner.getCurrentTimedPhaseOnChange(defaultSubscriptionBase, newPlan, effectiveChangeDate, null, catalog, internalCallContext);
Assert.assertEquals(currentPhase.getStartPhase(), alignStartDate);
Assert.assertEquals(currentPhase.getPhase().getPhaseType(), PhaseType.EVERGREEN);
@@ -243,7 +243,7 @@ public class TestPlanAligner extends SubscriptionTestSuiteNoDB {
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);
+ final TimedPhase [] phases = planAligner.getCurrentAndNextTimedPhaseOnCreate(alignStartDate, bundleStartDate, plan, PhaseType.EVERGREEN, PriceListSet.DEFAULT_PRICELIST_NAME, now, catalog, internalCallContext);
Assert.assertEquals(phases.length, 2);
Assert.assertEquals(phases[0].getPhase().getPhaseType(), PhaseType.EVERGREEN);
Assert.assertEquals(phases[0].getStartPhase(), now);
@@ -256,7 +256,7 @@ public class TestPlanAligner extends SubscriptionTestSuiteNoDB {
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);
+ final TimedPhase currentPhase = planAligner.getCurrentTimedPhaseOnChange(defaultSubscriptionBase, newPlan, effectiveChangeDate, null, catalog, internalCallContext);
Assert.assertEquals(currentPhase.getStartPhase(), alignStartDate);
Assert.assertEquals(currentPhase.getPhase().getPhaseType(), PhaseType.EVERGREEN);
}
@@ -273,7 +273,7 @@ public class TestPlanAligner extends SubscriptionTestSuiteNoDB {
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);
+ final TimedPhase [] phases = planAligner.getCurrentAndNextTimedPhaseOnCreate(alignStartDate, bundleStartDate, plan, PhaseType.EVERGREEN, PriceListSet.DEFAULT_PRICELIST_NAME, now, catalog, internalCallContext);
Assert.assertEquals(phases.length, 2);
Assert.assertEquals(phases[0].getPhase().getPhaseType(), PhaseType.EVERGREEN);
Assert.assertEquals(phases[0].getStartPhase(), now);
@@ -285,13 +285,13 @@ public class TestPlanAligner extends SubscriptionTestSuiteNoDB {
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);
+ final TimedPhase currentPhase = planAligner.getCurrentTimedPhaseOnChange(defaultSubscriptionBase, newPlan, effectiveChangeDate, null, catalog, 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);
+ final TimedPhase nextPhase = planAligner.getNextTimedPhaseOnChange(defaultSubscriptionBase, newPlan, effectiveChangeDate, null, catalog, internalCallContext);
Assert.assertEquals(nextPhase.getStartPhase(), alignStartDate.plusDays(30));
Assert.assertEquals(nextPhase.getPhase().getPhaseType(), PhaseType.FIXEDTERM);
}
@@ -309,7 +309,7 @@ public class TestPlanAligner extends SubscriptionTestSuiteNoDB {
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);
+ final TimedPhase [] phases = planAligner.getCurrentAndNextTimedPhaseOnCreate(alignStartDate, bundleStartDate, plan, null, PriceListSet.DEFAULT_PRICELIST_NAME, now, catalog, internalCallContext);
Assert.assertEquals(phases.length, 2);
Assert.assertEquals(phases[0].getPhase().getPhaseType(), PhaseType.TRIAL);
Assert.assertEquals(phases[0].getStartPhase(), now);
@@ -322,13 +322,13 @@ public class TestPlanAligner extends SubscriptionTestSuiteNoDB {
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);
+ final TimedPhase currentPhase = planAligner.getCurrentTimedPhaseOnChange(defaultSubscriptionBase, newPlan, effectiveChangeDate, PhaseType.DISCOUNT, catalog, 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);
+ final TimedPhase nextPhase = planAligner.getNextTimedPhaseOnChange(defaultSubscriptionBase, newPlan, effectiveChangeDate, PhaseType.DISCOUNT, catalog, internalCallContext);
Assert.assertEquals(nextPhase.getStartPhase(), alignStartDate.plusMonths(6));
Assert.assertEquals(nextPhase.getPhase().getPhaseType(), PhaseType.EVERGREEN);
}
@@ -345,7 +345,7 @@ public class TestPlanAligner extends SubscriptionTestSuiteNoDB {
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);
+ final TimedPhase [] phases = planAligner.getCurrentAndNextTimedPhaseOnCreate(alignStartDate, bundleStartDate, plan, null, PriceListSet.DEFAULT_PRICELIST_NAME, now, catalog, internalCallContext);
Assert.assertEquals(phases.length, 2);
Assert.assertEquals(phases[0].getPhase().getPhaseType(), PhaseType.TRIAL);
Assert.assertEquals(phases[0].getStartPhase(), now);
@@ -358,13 +358,13 @@ public class TestPlanAligner extends SubscriptionTestSuiteNoDB {
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);
+ final TimedPhase currentPhase = planAligner.getCurrentTimedPhaseOnChange(defaultSubscriptionBase, newPlan, effectiveChangeDate, PhaseType.EVERGREEN, catalog, 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);
+ final TimedPhase nextPhase = planAligner.getNextTimedPhaseOnChange(defaultSubscriptionBase, newPlan, effectiveChangeDate, PhaseType.EVERGREEN, catalog, internalCallContext);
Assert.assertNull(nextPhase);
}
@@ -444,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, effectiveChangeDate, null, internalCallContext);
+ return planAligner.getNextTimedPhaseOnChange(defaultSubscriptionBase, newPlan, effectiveChangeDate, null, catalog, internalCallContext);
}
private TimedPhase[] getTimedPhasesOnCreate(final String productName,
@@ -456,7 +456,7 @@ public class TestPlanAligner extends SubscriptionTestSuiteNoDB {
// Same here for the requested date
final TimedPhase[] phases = planAligner.getCurrentAndNextTimedPhaseOnCreate(defaultSubscriptionBase.getAlignStartDate(), defaultSubscriptionBase.getBundleStartDate(),
- plan, initialPhase, priceList, effectiveDate, internalCallContext);
+ plan, initialPhase, priceList, effectiveDate, catalog, internalCallContext);
Assert.assertEquals(phases.length, 2);
return phases;
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/api/transfer/TestDefaultSubscriptionTransferApi.java b/subscription/src/test/java/org/killbill/billing/subscription/api/transfer/TestDefaultSubscriptionTransferApi.java
index 1f034e4..95ce7e2 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/api/transfer/TestDefaultSubscriptionTransferApi.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/api/transfer/TestDefaultSubscriptionTransferApi.java
@@ -74,6 +74,8 @@ public class TestDefaultSubscriptionTransferApi extends SubscriptionTestSuiteNoD
final SubscriptionBaseApiService apiService = Mockito.mock(SubscriptionBaseApiService.class);
final SubscriptionBaseTimelineApi timelineApi = Mockito.mock(SubscriptionBaseTimelineApi.class);
transferApi = new DefaultSubscriptionBaseTransferApi(clock, dao, timelineApi, catalogInternalApiWithMockCatalogService, apiService, internalCallContextFactory);
+ // Overrride catalog with our Mock CatalogService
+ this.catalog = catalogInternalApiWithMockCatalogService.getFullCatalog(true, true, internalCallContext);
}
@Test(groups = "fast")
@@ -86,7 +88,7 @@ public class TestDefaultSubscriptionTransferApi extends SubscriptionTestSuiteNoD
final DefaultSubscriptionBase subscription = new DefaultSubscriptionBase(subscriptionBuilder);
final DateTime transferDate = subscriptionStartTime.plusDays(10);
- final List<SubscriptionBaseEvent> events = transferApi.toEvents(existingEvents, subscription, transferDate, internalCallContext);
+ final List<SubscriptionBaseEvent> events = transferApi.toEvents(existingEvents, subscription, transferDate, catalog, internalCallContext);
Assert.assertEquals(events.size(), 0);
}
@@ -102,7 +104,7 @@ public class TestDefaultSubscriptionTransferApi extends SubscriptionTestSuiteNoD
final DefaultSubscriptionBase subscription = new DefaultSubscriptionBase(subscriptionBuilder);
final DateTime transferDate = subscriptionStartTime.plusHours(1);
- final List<SubscriptionBaseEvent> events = transferApi.toEvents(existingEvents, subscription, transferDate, internalCallContext);
+ final List<SubscriptionBaseEvent> events = transferApi.toEvents(existingEvents, subscription, transferDate, catalog, internalCallContext);
Assert.assertEquals(events.size(), 1);
Assert.assertEquals(events.get(0).getType(), EventType.API_USER);
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/api/transfer/TestTransfer.java b/subscription/src/test/java/org/killbill/billing/subscription/api/transfer/TestTransfer.java
index bbbd1e4..75333b7 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/api/transfer/TestTransfer.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/api/transfer/TestTransfer.java
@@ -28,7 +28,7 @@ import org.killbill.billing.api.TestApiListener.NextEvent;
import org.killbill.billing.catalog.api.BillingPeriod;
import org.killbill.billing.catalog.api.PhaseType;
import org.killbill.billing.catalog.api.Plan;
-import org.killbill.billing.catalog.api.PlanSpecifier;
+import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
import org.killbill.billing.catalog.api.PriceListSet;
import org.killbill.billing.catalog.api.Product;
import org.killbill.billing.entitlement.api.Entitlement.EntitlementState;
@@ -60,17 +60,16 @@ public class TestTransfer extends SubscriptionTestSuiteWithEmbeddedDB {
// Note: this will cleanup all tables
super.beforeMethod();
- final AccountData accountData2 = subscriptionTestInitializer.initAccountData();
+ final AccountData accountData2 = subscriptionTestInitializer.initAccountData(clock);
final Account account2 = createAccount(accountData2);
finalNewAccountId = account2.getId();
- // internal context will be configured for newAccountId
- final AccountData accountData = subscriptionTestInitializer.initAccountData();
+ // internal context will be configured for accountId
+ final AccountData accountData = subscriptionTestInitializer.initAccountData(clock);
final Account account = createAccount(accountData);
newAccountId = account.getId();
}
-
@Test(groups = "slow")
public void testTransferBPInTrialWithNoCTD() throws Exception {
final String baseProduct = "Shotgun";
@@ -267,7 +266,7 @@ public class TestTransfer extends SubscriptionTestSuiteWithEmbeddedDB {
final String newBaseProduct1 = "Assault-Rifle";
final BillingPeriod newBaseTerm1 = BillingPeriod.ANNUAL;
testListener.pushExpectedEvent(NextEvent.CHANGE);
- newBaseSubscription.changePlan(new PlanSpecifier(newBaseProduct1, newBaseTerm1, basePriceList), null, callContext);
+ newBaseSubscription.changePlan(new PlanPhaseSpecifier(newBaseProduct1, newBaseTerm1, basePriceList), null, callContext);
assertListenerStatus();
newPlan = newBaseSubscription.getCurrentPlan();
@@ -283,7 +282,7 @@ public class TestTransfer extends SubscriptionTestSuiteWithEmbeddedDB {
final String newBaseProduct2 = "Pistol";
final BillingPeriod newBaseTerm2 = BillingPeriod.ANNUAL;
- newBaseSubscriptionWithCtd.changePlan(new PlanSpecifier(newBaseProduct2, newBaseTerm2, basePriceList), null, callContext);
+ newBaseSubscriptionWithCtd.changePlan(new PlanPhaseSpecifier(newBaseProduct2, newBaseTerm2, basePriceList), null, callContext);
newPlan = newBaseSubscriptionWithCtd.getCurrentPlan();
assertEquals(newPlan.getProduct().getName(), newBaseProduct1);
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiAddOn.java b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiAddOn.java
index cb9e43a..4503630 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiAddOn.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiAddOn.java
@@ -20,8 +20,6 @@ import java.util.List;
import org.joda.time.DateTime;
import org.joda.time.Interval;
-import org.testng.annotations.Test;
-
import org.killbill.billing.api.TestApiListener.NextEvent;
import org.killbill.billing.catalog.api.BillingPeriod;
import org.killbill.billing.catalog.api.CatalogApiException;
@@ -30,6 +28,7 @@ import org.killbill.billing.catalog.api.PhaseType;
import org.killbill.billing.catalog.api.Plan;
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.catalog.api.PriceListSet;
import org.killbill.billing.catalog.api.ProductCategory;
@@ -38,6 +37,7 @@ import org.killbill.billing.entitlement.api.EntitlementAOStatusDryRun;
import org.killbill.billing.entitlement.api.EntitlementAOStatusDryRun.DryRunChangeReason;
import org.killbill.billing.subscription.SubscriptionTestSuiteWithEmbeddedDB;
import org.killbill.billing.subscription.api.SubscriptionBaseTransitionType;
+import org.testng.annotations.Test;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
@@ -115,13 +115,13 @@ public class TestUserApiAddOn extends SubscriptionTestSuiteWithEmbeddedDB {
aoSubscription.cancel(callContext);
aoSubscription = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(aoSubscription.getId(), internalCallContext);
assertEquals(aoSubscription.getState(), EntitlementState.ACTIVE);
- assertTrue(aoSubscription.isSubscriptionFutureCancelled());
+ assertTrue(aoSubscription.isFutureCancelled());
// CANCEL BASE NOW
baseSubscription.cancel(callContext);
baseSubscription = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(baseSubscription.getId(), internalCallContext);
assertEquals(baseSubscription.getState(), EntitlementState.ACTIVE);
- assertTrue(baseSubscription.isSubscriptionFutureCancelled());
+ assertTrue(baseSubscription.isFutureCancelled());
aoSubscription = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(aoSubscription.getId(), internalCallContext);
List<SubscriptionBaseTransition> aoTransitions = aoSubscription.getAllTransitions();
@@ -183,7 +183,7 @@ public class TestUserApiAddOn extends SubscriptionTestSuiteWithEmbeddedDB {
// REFETCH AO SUBSCRIPTION AND CHECK THIS IS ACTIVE
aoSubscription = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(aoSubscription.getId(), internalCallContext);
assertEquals(aoSubscription.getState(), EntitlementState.ACTIVE);
- assertTrue(aoSubscription.isSubscriptionFutureCancelled());
+ assertTrue(aoSubscription.isFutureCancelled());
// MOVE AFTER CANCELLATION
testListener.pushExpectedEvent(NextEvent.CANCEL);
@@ -237,7 +237,7 @@ public class TestUserApiAddOn extends SubscriptionTestSuiteWithEmbeddedDB {
// REFETCH AO SUBSCRIPTION AND CHECK THIS IS ACTIVE
aoSubscription = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(aoSubscription.getId(), internalCallContext);
assertEquals(aoSubscription.getState(), EntitlementState.ACTIVE);
- assertTrue(aoSubscription.isSubscriptionFutureCancelled());
+ assertTrue(aoSubscription.isFutureCancelled());
testListener.pushExpectedEvent(NextEvent.UNCANCEL);
baseSubscription = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(baseSubscription.getId(), internalCallContext);
@@ -246,7 +246,7 @@ public class TestUserApiAddOn extends SubscriptionTestSuiteWithEmbeddedDB {
aoSubscription = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(aoSubscription.getId(), internalCallContext);
assertEquals(aoSubscription.getState(), EntitlementState.ACTIVE);
- assertFalse(aoSubscription.isSubscriptionFutureCancelled());
+ assertFalse(aoSubscription.isFutureCancelled());
// CANCEL AGAIN
it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(1));
@@ -256,11 +256,11 @@ public class TestUserApiAddOn extends SubscriptionTestSuiteWithEmbeddedDB {
baseSubscription.cancel(callContext);
baseSubscription = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(baseSubscription.getId(), internalCallContext);
assertEquals(baseSubscription.getState(), EntitlementState.ACTIVE);
- assertTrue(baseSubscription.isSubscriptionFutureCancelled());
+ assertTrue(baseSubscription.isFutureCancelled());
aoSubscription = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(aoSubscription.getId(), internalCallContext);
assertEquals(aoSubscription.getState(), EntitlementState.ACTIVE);
- assertTrue(aoSubscription.isSubscriptionFutureCancelled());
+ assertTrue(aoSubscription.isFutureCancelled());
assertListenerStatus();
}
@@ -311,7 +311,7 @@ public class TestUserApiAddOn extends SubscriptionTestSuiteWithEmbeddedDB {
testListener.pushExpectedEvent(NextEvent.CHANGE);
testListener.pushExpectedEvent(NextEvent.CANCEL);
- baseSubscription.changePlan(new PlanSpecifier(newBaseProduct, newBaseTerm, newBasePriceList), null, callContext);
+ baseSubscription.changePlan(new PlanPhaseSpecifier(newBaseProduct, newBaseTerm, newBasePriceList, null), null, callContext);
assertListenerStatus();
// REFETCH AO SUBSCRIPTION AND CHECK THIS CANCELLED
@@ -367,12 +367,12 @@ public class TestUserApiAddOn extends SubscriptionTestSuiteWithEmbeddedDB {
assertEquals(aoStatus.get(0).getPriceList(), aoSubscription.getCurrentPriceList().getName());
assertEquals(aoStatus.get(0).getReason(), DryRunChangeReason.AO_NOT_AVAILABLE_IN_NEW_PLAN);
- baseSubscription.changePlan(new PlanSpecifier(newBaseProduct, newBaseTerm, newBasePriceList), null, callContext);
+ baseSubscription.changePlan(new PlanPhaseSpecifier(newBaseProduct, newBaseTerm, newBasePriceList), null, callContext);
// REFETCH AO SUBSCRIPTION AND CHECK THIS IS ACTIVE
aoSubscription = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(aoSubscription.getId(), internalCallContext);
assertEquals(aoSubscription.getState(), EntitlementState.ACTIVE);
- assertTrue(aoSubscription.isSubscriptionFutureCancelled());
+ assertTrue(aoSubscription.isFutureCancelled());
// MOVE AFTER CHANGE
testListener.pushExpectedEvent(NextEvent.CHANGE);
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiChangePlan.java b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiChangePlan.java
index 613bb69..6d6e1f6 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiChangePlan.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiChangePlan.java
@@ -1,7 +1,9 @@
/*
* Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
*
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
@@ -16,19 +18,23 @@
package org.killbill.billing.subscription.api.user;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import javax.inject.Inject;
+
import org.joda.time.DateTime;
import org.joda.time.Interval;
import org.joda.time.LocalDate;
import org.killbill.billing.ErrorCode;
import org.killbill.billing.api.TestApiListener.NextEvent;
-import org.killbill.billing.catalog.api.BillingActionPolicy;
import org.killbill.billing.catalog.api.BillingPeriod;
import org.killbill.billing.catalog.api.Duration;
import org.killbill.billing.catalog.api.PhaseType;
import org.killbill.billing.catalog.api.Plan;
import org.killbill.billing.catalog.api.PlanPhase;
import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
-import org.killbill.billing.catalog.api.PlanSpecifier;
import org.killbill.billing.catalog.api.PriceListSet;
import org.killbill.billing.catalog.api.ProductCategory;
import org.killbill.billing.entitlement.api.Entitlement;
@@ -49,14 +55,8 @@ import org.skife.jdbi.v2.tweak.HandleCallback;
import org.testng.Assert;
import org.testng.annotations.Test;
-import javax.inject.Inject;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.UUID;
-
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
-import static org.testng.Assert.assertNull;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.fail;
@@ -104,7 +104,7 @@ public class TestUserApiChangePlan extends SubscriptionTestSuiteWithEmbeddedDB {
// CHANGE PLAN
testListener.pushExpectedEvent(NextEvent.CHANGE);
- subscription.changePlan(new PlanSpecifier(toProd, toTerm, toPlanSet), null, callContext);
+ subscription.changePlan(new PlanPhaseSpecifier(toProd, toTerm, toPlanSet), null, callContext);
assertListenerStatus();
// CHECK CHANGE PLAN
@@ -145,7 +145,7 @@ public class TestUserApiChangePlan extends SubscriptionTestSuiteWithEmbeddedDB {
// RE READ SUBSCRIPTION + CHANGE PLAN
subscription = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(subscription.getId(), internalCallContext);
- subscription.changePlan(new PlanSpecifier(toProd, toTerm, toPlanSet), null, callContext);
+ subscription.changePlan(new PlanPhaseSpecifier(toProd, toTerm, toPlanSet), null, callContext);
assertListenerStatus();
// CHECK CHANGE PLAN
@@ -188,7 +188,7 @@ public class TestUserApiChangePlan extends SubscriptionTestSuiteWithEmbeddedDB {
clock.addDeltaFromReality(it.toDurationMillis());
// CHANGE PLAN IMM
- subscription.changePlan(new PlanSpecifier(toProd, toTerm, toPlanSet), null, callContext);
+ subscription.changePlan(new PlanPhaseSpecifier(toProd, toTerm, toPlanSet), null, callContext);
checkChangePlan(subscription, toProd, ProductCategory.BASE, toTerm, PhaseType.TRIAL);
assertListenerStatus();
@@ -242,7 +242,7 @@ public class TestUserApiChangePlan extends SubscriptionTestSuiteWithEmbeddedDB {
// CHANGE PLAN
currentTime = clock.getUTCNow();
- subscription.changePlan(new PlanSpecifier(toProd, toTerm, toPlanSet), null, callContext);
+ subscription.changePlan(new PlanPhaseSpecifier(toProd, toTerm, toPlanSet), null, callContext);
checkChangePlan(subscription, fromProd, ProductCategory.BASE, fromTerm, PhaseType.EVERGREEN);
@@ -308,12 +308,12 @@ public class TestUserApiChangePlan extends SubscriptionTestSuiteWithEmbeddedDB {
subscription = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(subscription.getId(), internalCallContext);
// CHANGE EOT
- subscription.changePlan(new PlanSpecifier("Pistol", BillingPeriod.MONTHLY, "gunclubDiscount"), null, callContext);
+ subscription.changePlan(new PlanPhaseSpecifier("Pistol", BillingPeriod.MONTHLY, "gunclubDiscount"), null, callContext);
assertListenerStatus();
// CHANGE
testListener.pushExpectedEvent(NextEvent.CHANGE);
- subscription.changePlan(new PlanSpecifier("Assault-Rifle", BillingPeriod.ANNUAL, "gunclubDiscount"), null, callContext);
+ subscription.changePlan(new PlanPhaseSpecifier("Assault-Rifle", BillingPeriod.ANNUAL, "gunclubDiscount"), null, callContext);
assertListenerStatus();
final Plan currentPlan = subscription.getCurrentPlan();
@@ -350,11 +350,11 @@ public class TestUserApiChangePlan extends SubscriptionTestSuiteWithEmbeddedDB {
subscription = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(subscription.getId(), internalCallContext);
// CHANGE EOT
- subscription.changePlan(new PlanSpecifier("Shotgun", BillingPeriod.MONTHLY, "gunclubDiscount"), null, callContext);
+ subscription.changePlan(new PlanPhaseSpecifier("Shotgun", BillingPeriod.MONTHLY, "gunclubDiscount"), null, callContext);
assertListenerStatus();
// CHANGE EOT
- subscription.changePlan(new PlanSpecifier("Pistol", BillingPeriod.ANNUAL, "gunclubDiscount"), null, callContext);
+ subscription.changePlan(new PlanPhaseSpecifier("Pistol", BillingPeriod.ANNUAL, "gunclubDiscount"), null, callContext);
assertListenerStatus();
// CHECK NO CHANGE OCCURED YET
@@ -416,7 +416,7 @@ public class TestUserApiChangePlan extends SubscriptionTestSuiteWithEmbeddedDB {
// CHANGE IMMEDIATE TO A 3 PHASES PLAN
testListener.pushExpectedEvent(NextEvent.CHANGE);
- subscription.changePlan(new PlanSpecifier("Assault-Rifle", BillingPeriod.ANNUAL, "gunclubDiscount"), null, callContext);
+ subscription.changePlan(new PlanPhaseSpecifier("Assault-Rifle", BillingPeriod.ANNUAL, "gunclubDiscount"), null, callContext);
assertListenerStatus();
// CHECK EVERYTHING LOOKS CORRECT
@@ -470,14 +470,13 @@ public class TestUserApiChangePlan extends SubscriptionTestSuiteWithEmbeddedDB {
DefaultSubscriptionBase aoSubscription = testUtil.createSubscription(bundle, aoProduct, aoTerm, aoPriceList);
try {
- aoSubscription.changePlanWithDate(new PlanSpecifier(baseProduct, baseTerm, basePriceList), null, clock.getUTCNow(), callContext);
+ aoSubscription.changePlanWithDate(new PlanPhaseSpecifier(baseProduct, baseTerm, basePriceList), null, clock.getUTCNow(), callContext);
Assert.fail("Should not allow plan change across product type");
} catch (final SubscriptionBaseApiException e) {
Assert.assertEquals(e.getCode(), ErrorCode.SUB_CHANGE_INVALID.getCode());
}
}
-
@Test(groups = "slow")
public void testChangePlanOnPendingSubscription() throws SubscriptionBaseApiException {
@@ -493,9 +492,8 @@ public class TestUserApiChangePlan extends SubscriptionTestSuiteWithEmbeddedDB {
final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Pistol", baseTerm, basePriceList, null);
-
// First try with default api (no date -> IMM) => Call should fail because subscription is PENDING
- final DryRunArguments dryRunArguments1 = testUtil.createDryRunArguments(subscription.getId(), subscription.getBundleId(), spec, null, SubscriptionEventType.CHANGE, null);
+ final DryRunArguments dryRunArguments1 = testUtil.createDryRunArguments(subscription.getId(), subscription.getBundleId(), spec, null, SubscriptionEventType.CHANGE, null);
final List<SubscriptionBase> result1 = subscriptionInternalApi.getSubscriptionsForBundle(bundle.getId(), dryRunArguments1, internalCallContext);
// Check we are seeing the right PENDING transition (pistol-monthly), not the START but the CHANGE on the same date
@@ -504,7 +502,7 @@ public class TestUserApiChangePlan extends SubscriptionTestSuiteWithEmbeddedDB {
// Second try with date prior to startDate => Call should fail because subscription is PENDING
try {
- final DryRunArguments dryRunArguments2 = testUtil.createDryRunArguments(subscription.getId(), subscription.getBundleId(), spec, new LocalDate(startDate.minusDays(1)), SubscriptionEventType.CHANGE, null);
+ final DryRunArguments dryRunArguments2 = testUtil.createDryRunArguments(subscription.getId(), subscription.getBundleId(), spec, new LocalDate(startDate.minusDays(1)), SubscriptionEventType.CHANGE, null);
subscriptionInternalApi.getSubscriptionsForBundle(bundle.getId(), dryRunArguments2, internalCallContext);
fail("Change plan should have failed : subscription PENDING");
} catch (final SubscriptionBaseApiException e) {
@@ -518,12 +516,11 @@ public class TestUserApiChangePlan extends SubscriptionTestSuiteWithEmbeddedDB {
}
// Third try with date equals to startDate Call should succeed, but no event because action in future
- final DryRunArguments dryRunArguments3 = testUtil.createDryRunArguments(subscription.getId(), subscription.getBundleId(), spec, internalCallContext.toLocalDate(startDate), SubscriptionEventType.CHANGE, null);
+ final DryRunArguments dryRunArguments3 = testUtil.createDryRunArguments(subscription.getId(), subscription.getBundleId(), spec, internalCallContext.toLocalDate(startDate), SubscriptionEventType.CHANGE, null);
final List<SubscriptionBase> result2 = subscriptionInternalApi.getSubscriptionsForBundle(bundle.getId(), dryRunArguments3, internalCallContext);
// Check we are seeing the right PENDING transition (pistol-monthly), not the START but the CHANGE on the same date
assertEquals(((DefaultSubscriptionBase) result2.get(0)).getCurrentOrPendingPlan().getName(), "pistol-monthly");
-
// To spice up the test, we insert manually an additional CHANGE event on the exact same dateas the CREATE to verify that code will also invalidate such event when doing the changePlanXX
final SubscriptionEventModelDao event = new SubscriptionEventModelDao(subscription.getEvents().get(0));
@@ -540,7 +537,7 @@ public class TestUserApiChangePlan extends SubscriptionTestSuiteWithEmbeddedDB {
}
});
- final DefaultSubscriptionBase refreshed1 = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(subscription.getId(), internalCallContext);
+ final DefaultSubscriptionBase refreshed1 = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(subscription.getId(), internalCallContext);
assertEquals(refreshed1.getEvents().size(), subscription.getEvents().size() + 1);
subscription.changePlanWithDate(spec, null, startDate, callContext);
@@ -559,4 +556,80 @@ public class TestUserApiChangePlan extends SubscriptionTestSuiteWithEmbeddedDB {
assertEquals(subscription2.getEvents().size(), subscription.getEvents().size());
}
+ @Test(groups = "slow")
+ public void testChangePlanOnCreate() throws SubscriptionBaseApiException {
+ final DefaultSubscriptionBase subscription = testUtil.createSubscription(bundle, "Shotgun", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME);
+
+ // CHANGE PLAN IMMEDIATELY: the CHANGE event will be transformed into a CREATE
+ testListener.pushExpectedEvent(NextEvent.CREATE);
+ subscription.changePlanWithDate(new PlanPhaseSpecifier("Pistol", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME), null, subscription.getStartDate(), callContext);
+ assertListenerStatus();
+
+ checkChangePlan(subscription, "Pistol", ProductCategory.BASE, BillingPeriod.MONTHLY, PhaseType.TRIAL);
+
+ final SubscriptionBase refreshedSubscription = subscriptionInternalApi.getSubscriptionFromId(subscription.getId(), internalCallContext);
+ assertEquals(refreshedSubscription.getAllTransitions().size(), 2);
+ assertEquals(refreshedSubscription.getAllTransitions().get(0).getTransitionType(), SubscriptionBaseTransitionType.CREATE);
+ assertEquals(refreshedSubscription.getAllTransitions().get(1).getTransitionType(), SubscriptionBaseTransitionType.PHASE);
+ }
+
+ @Test(groups = "slow")
+ public void testChangePlanRightAfterCreate() throws SubscriptionBaseApiException {
+ final DefaultSubscriptionBase subscription = testUtil.createSubscription(bundle, "Shotgun", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME);
+
+ clock.setTime(clock.getUTCNow().plusSeconds(1));
+
+ // CHANGE PLAN ALMOST IMMEDIATELY
+ testListener.pushExpectedEvent(NextEvent.CHANGE);
+ subscription.changePlan(new PlanPhaseSpecifier("Pistol", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME), null, callContext);
+ assertListenerStatus();
+
+ checkChangePlan(subscription, "Pistol", ProductCategory.BASE, BillingPeriod.MONTHLY, PhaseType.TRIAL);
+
+ final SubscriptionBase refreshedSubscription = subscriptionInternalApi.getSubscriptionFromId(subscription.getId(), internalCallContext);
+ assertEquals(refreshedSubscription.getAllTransitions().size(), 3);
+ assertEquals(refreshedSubscription.getAllTransitions().get(0).getTransitionType(), SubscriptionBaseTransitionType.CREATE);
+ assertEquals(refreshedSubscription.getAllTransitions().get(1).getTransitionType(), SubscriptionBaseTransitionType.CHANGE);
+ assertEquals(refreshedSubscription.getAllTransitions().get(2).getTransitionType(), SubscriptionBaseTransitionType.PHASE);
+ }
+
+ @Test(groups = "slow")
+ public void testUndoChangePlan() throws SubscriptionBaseApiException {
+
+ final DefaultSubscriptionBase subscription = testUtil.createSubscription(bundle, "Shotgun", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME);
+
+ clock.setTime(clock.getUTCNow().plusSeconds(1));
+
+ // Change plan in the future
+ final DateTime targetDate = clock.getUTCNow().plusDays(3);
+ subscription.changePlanWithDate(new PlanPhaseSpecifier("Pistol", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME), null, targetDate, callContext);assertListenerStatus();
+
+ DefaultSubscriptionBase refreshedSubscription = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(subscription.getId(), internalCallContext);
+ assertEquals(refreshedSubscription.getAllTransitions().size(), 3);
+ assertEquals(refreshedSubscription.getAllTransitions().get(0).getTransitionType(), SubscriptionBaseTransitionType.CREATE);
+ assertEquals(refreshedSubscription.getAllTransitions().get(1).getTransitionType(), SubscriptionBaseTransitionType.CHANGE);
+ assertEquals(refreshedSubscription.getAllTransitions().get(2).getTransitionType(), SubscriptionBaseTransitionType.PHASE);
+
+ clock.addDays(1);
+
+ testListener.pushExpectedEvent(NextEvent.UNDO_CHANGE);
+ subscription.undoChangePlan(callContext);
+ assertListenerStatus();
+
+ // No CHANGE_PLAN
+ clock.addDays(3);
+ assertListenerStatus();
+
+ // Verify PHASE event for Shotgun is active
+ testListener.pushExpectedEvent(NextEvent.PHASE);
+ clock.addDays(26);
+ assertListenerStatus();
+
+
+ refreshedSubscription = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(subscription.getId(), internalCallContext);
+ assertEquals(refreshedSubscription.getAllTransitions().size(), 2);
+ assertEquals(refreshedSubscription.getAllTransitions().get(0).getTransitionType(), SubscriptionBaseTransitionType.CREATE);
+ assertEquals(refreshedSubscription.getAllTransitions().get(1).getTransitionType(), SubscriptionBaseTransitionType.PHASE);
+
+ }
}
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiCreate.java b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiCreate.java
index 4776379..143041e 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiCreate.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiCreate.java
@@ -16,10 +16,12 @@
package org.killbill.billing.subscription.api.user;
+import java.sql.SQLIntegrityConstraintViolationException;
import java.util.List;
import org.joda.time.DateTime;
import org.joda.time.Interval;
+import org.killbill.billing.ErrorCode;
import org.killbill.billing.api.TestApiListener.NextEvent;
import org.killbill.billing.catalog.api.BillingPeriod;
import org.killbill.billing.catalog.api.PhaseType;
@@ -32,6 +34,7 @@ import org.killbill.billing.subscription.DefaultSubscriptionTestInitializer;
import org.killbill.billing.subscription.SubscriptionTestSuiteWithEmbeddedDB;
import org.killbill.billing.subscription.events.SubscriptionBaseEvent;
import org.killbill.billing.subscription.events.phase.PhaseEvent;
+import org.mariadb.jdbc.internal.util.dao.QueryException;
import org.testng.Assert;
import org.testng.annotations.Test;
@@ -43,7 +46,7 @@ public class TestUserApiCreate extends SubscriptionTestSuiteWithEmbeddedDB {
@Test(groups = "slow")
public void testCreateBundleWithNoExternalKey() throws Exception {
- final SubscriptionBaseBundle newBundle = subscriptionInternalApi.createBundleForAccount(bundle.getAccountId(), null, internalCallContext);
+ final SubscriptionBaseBundle newBundle = subscriptionInternalApi.createBundleForAccount(bundle.getAccountId(), null, true, internalCallContext);
assertNotNull(newBundle.getExternalKey());
}
@@ -62,14 +65,31 @@ public class TestUserApiCreate extends SubscriptionTestSuiteWithEmbeddedDB {
assertListenerStatus();
assertNotNull(subscription);
+
+ // Verify we can't create a second bundle with the same key
+ try {
+ subscriptionInternalApi.createBundleForAccount(bundle.getAccountId(), DefaultSubscriptionTestInitializer.DEFAULT_BUNDLE_KEY, true, internalCallContext);
+ Assert.fail("Should not be able to create a bundle with same externalKey");
+ } catch (final SubscriptionBaseApiException e) {
+ Assert.assertEquals(e.getCode(), ErrorCode.SUB_CREATE_ACTIVE_BUNDLE_KEY_EXISTS.getCode());
+ }
+
testListener.pushExpectedEvent(NextEvent.CANCEL);
subscription.cancelWithDate(clock.getUTCNow(), callContext);
assertListenerStatus();
- final SubscriptionBaseBundle newBundle = subscriptionInternalApi.createBundleForAccount(bundle.getAccountId(), DefaultSubscriptionTestInitializer.DEFAULT_BUNDLE_KEY, internalCallContext);
+ try {
+ subscriptionInternalApi.createBundleForAccount(bundle.getAccountId(), DefaultSubscriptionTestInitializer.DEFAULT_BUNDLE_KEY, false, internalCallContext);
+ Assert.fail("createBundleForAccount should fail because key already exists");
+ } catch (final RuntimeException e) {
+ assertTrue(e.getCause() instanceof SQLIntegrityConstraintViolationException);
+ }
+
+ final SubscriptionBaseBundle newBundle = subscriptionInternalApi.createBundleForAccount(bundle.getAccountId(), DefaultSubscriptionTestInitializer.DEFAULT_BUNDLE_KEY, true, internalCallContext);
assertNotNull(newBundle);
assertEquals(newBundle.getOriginalCreatedDate().compareTo(bundle.getCreatedDate()), 0);
+
testListener.pushExpectedEvents(NextEvent.CREATE, NextEvent.PHASE);
final DefaultSubscriptionBase newSubscription = (DefaultSubscriptionBase) subscriptionInternalApi.createSubscription(newBundle.getId(),
testUtil.getProductSpecifier(productName, planSetName, term, null), null, requestedDate, false, internalCallContext);
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiError.java b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiError.java
index 6891b28..04f0ab5 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiError.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiError.java
@@ -30,7 +30,7 @@ import org.killbill.billing.catalog.api.BillingActionPolicy;
import org.killbill.billing.catalog.api.BillingPeriod;
import org.killbill.billing.catalog.api.Duration;
import org.killbill.billing.catalog.api.PlanPhase;
-import org.killbill.billing.catalog.api.PlanSpecifier;
+import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
import org.killbill.billing.catalog.api.PriceListSet;
import org.killbill.billing.subscription.SubscriptionTestSuiteNoDB;
import org.killbill.billing.subscription.api.SubscriptionBase;
@@ -76,7 +76,7 @@ public class TestUserApiError extends SubscriptionTestSuiteNoDB {
@Test(groups = "fast")
public void testCreateSubscriptionAddOnNotAvailable() throws SubscriptionBaseApiException {
- final SubscriptionBaseBundle aoBundle = subscriptionInternalApi.createBundleForAccount(bundle.getAccountId(), "myAOBundle", internalCallContext);
+ final SubscriptionBaseBundle aoBundle = subscriptionInternalApi.createBundleForAccount(bundle.getAccountId(), "myAOBundle", true, internalCallContext);
mockNonEntityDao.addTenantRecordIdMapping(aoBundle.getId(), internalCallContext);
mockNonEntityDao.addAccountRecordIdMapping(aoBundle.getId(), internalCallContext);
@@ -86,7 +86,7 @@ public class TestUserApiError extends SubscriptionTestSuiteNoDB {
@Test(groups = "fast")
public void testCreateSubscriptionAddOnIncluded() throws SubscriptionBaseApiException {
- final SubscriptionBaseBundle aoBundle = subscriptionInternalApi.createBundleForAccount(bundle.getAccountId(), "myAOBundle", internalCallContext);
+ final SubscriptionBaseBundle aoBundle = subscriptionInternalApi.createBundleForAccount(bundle.getAccountId(), "myAOBundle", true, internalCallContext);
mockNonEntityDao.addTenantRecordIdMapping(aoBundle.getId(), internalCallContext);
mockNonEntityDao.addAccountRecordIdMapping(aoBundle.getId(), internalCallContext);
@@ -113,7 +113,8 @@ public class TestUserApiError extends SubscriptionTestSuiteNoDB {
testListener.pushExpectedEvent(NextEvent.CANCEL);
subscription.cancelWithDate(clock.getUTCNow(), callContext);
try {
- subscription.changePlanWithDate(new PlanSpecifier("Pistol", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME), null, clock.getUTCNow(), callContext);
+ subscription.changePlanWithDate(new PlanPhaseSpecifier("Pistol", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME), null, clock.getUTCNow(), callContext);
+ Assert.fail("Exception expected, error code: " + ErrorCode.SUB_CHANGE_NON_ACTIVE);
} catch (final SubscriptionBaseApiException e) {
assertEquals(e.getCode(), ErrorCode.SUB_CHANGE_NON_ACTIVE.getCode());
}
@@ -124,15 +125,15 @@ public class TestUserApiError extends SubscriptionTestSuiteNoDB {
final SubscriptionBase subscription = testUtil.createSubscription(bundle, "Shotgun", BillingPeriod.ANNUAL, PriceListSet.DEFAULT_PRICELIST_NAME);
try {
- subscription.changePlanWithPolicy(new PlanSpecifier("Pistol", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME), null, BillingActionPolicy.ILLEGAL, callContext);
- Assert.fail();
+ subscription.changePlanWithPolicy(new PlanPhaseSpecifier("Pistol", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME), null, BillingActionPolicy.ILLEGAL, callContext);
+ Assert.fail("Call changePlanWithPolicy should have failed");
} catch (final SubscriptionBaseError error) {
assertTrue(true);
assertEquals(subscriptionInternalApi.getSubscriptionFromId(subscription.getId(), internalCallContext).getCurrentPlan().getRecurringBillingPeriod(), BillingPeriod.ANNUAL);
}
// Assume the call takes less than a second
- assertEquals(DefaultClock.truncateMs(subscription.changePlanWithPolicy(new PlanSpecifier("Pistol", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME), null, BillingActionPolicy.IMMEDIATE, callContext)),
+ assertEquals(DefaultClock.truncateMs(subscription.changePlanWithPolicy(new PlanPhaseSpecifier("Pistol", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME), null, BillingActionPolicy.IMMEDIATE, callContext)),
DefaultClock.truncateMs(clock.getUTCNow()));
assertEquals(subscriptionInternalApi.getSubscriptionFromId(subscription.getId(), internalCallContext).getCurrentPlan().getRecurringBillingPeriod(), BillingPeriod.MONTHLY);
}
@@ -159,7 +160,8 @@ public class TestUserApiError extends SubscriptionTestSuiteNoDB {
subscription.cancelWithPolicy(BillingActionPolicy.END_OF_TERM, -1, callContext);
try {
- subscription.changePlanWithDate(new PlanSpecifier("Pistol", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME), null, clock.getUTCNow(), callContext);
+ subscription.changePlanWithDate(new PlanPhaseSpecifier("Pistol", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME), null, clock.getUTCNow(), callContext);
+ Assert.fail("Exception expected, error code: " + ErrorCode.SUB_CHANGE_FUTURE_CANCELLED);
} catch (final SubscriptionBaseApiException e) {
assertEquals(e.getCode(), ErrorCode.SUB_CHANGE_FUTURE_CANCELLED.getCode());
}
@@ -173,6 +175,7 @@ public class TestUserApiError extends SubscriptionTestSuiteNoDB {
try {
subscription.uncancel(callContext);
+ Assert.fail("Exception expected, error code: " + ErrorCode.SUB_UNCANCEL_BAD_STATE);
} catch (final SubscriptionBaseApiException e) {
assertEquals(e.getCode(), ErrorCode.SUB_UNCANCEL_BAD_STATE.getCode());
}
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/DefaultSubscriptionTestInitializer.java b/subscription/src/test/java/org/killbill/billing/subscription/DefaultSubscriptionTestInitializer.java
index 0964ae8..744a07c 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/DefaultSubscriptionTestInitializer.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/DefaultSubscriptionTestInitializer.java
@@ -37,6 +37,7 @@ import org.killbill.billing.subscription.api.SubscriptionBaseService;
import org.killbill.billing.subscription.api.user.SubscriptionBaseBundle;
import org.killbill.billing.subscription.engine.core.DefaultSubscriptionBaseService;
import org.killbill.billing.util.UUIDs;
+import org.killbill.clock.Clock;
import org.killbill.clock.ClockMock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -60,7 +61,7 @@ public class DefaultSubscriptionTestInitializer implements SubscriptionTestIniti
return catalog;
}
- public AccountData initAccountData() {
+ public AccountData initAccountData(final Clock clock) {
final AccountData accountData = new MockAccountBuilder().name(UUIDs.randomUUID().toString().substring(1, 8))
.firstNameLength(6)
.email(UUIDs.randomUUID().toString().substring(1, 8))
@@ -71,6 +72,7 @@ public class DefaultSubscriptionTestInitializer implements SubscriptionTestIniti
.billingCycleDayLocal(1)
.currency(Currency.USD)
.paymentMethodId(UUIDs.randomUUID())
+ .referenceTime(clock.getUTCNow())
.timeZone(DateTimeZone.forID("Europe/Paris"))
.build();
@@ -79,7 +81,7 @@ public class DefaultSubscriptionTestInitializer implements SubscriptionTestIniti
}
public SubscriptionBaseBundle initBundle(final UUID accountId, final SubscriptionBaseInternalApi subscriptionApi, final InternalCallContext callContext) throws Exception {
- final SubscriptionBaseBundle bundle = subscriptionApi.createBundleForAccount(accountId, DEFAULT_BUNDLE_KEY, callContext);
+ final SubscriptionBaseBundle bundle = subscriptionApi.createBundleForAccount(accountId, DEFAULT_BUNDLE_KEY, true, callContext);
assertNotNull(bundle);
return bundle;
}
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 a4ae105..5d7c296 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
@@ -31,6 +31,7 @@ import java.util.UUID;
import org.joda.time.DateTime;
import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.catalog.api.Catalog;
import org.killbill.billing.catalog.api.CatalogApiException;
import org.killbill.billing.catalog.api.CatalogService;
import org.killbill.billing.catalog.api.ProductCategory;
@@ -162,25 +163,25 @@ public class MockSubscriptionDaoMemory extends MockEntityDaoBase<SubscriptionBun
}
@Override
- public List<SubscriptionBaseBundle> getSubscriptionBundlesForAccountAndKey(final UUID accountId, final String bundleKey, final InternalTenantContext context) {
+ public SubscriptionBaseBundle getSubscriptionBundlesForAccountAndKey(final UUID accountId, final String bundleKey, final InternalTenantContext context) {
final List<SubscriptionBaseBundle> results = new ArrayList<SubscriptionBaseBundle>();
for (final SubscriptionBaseBundle cur : bundles) {
if (cur.getExternalKey().equals(bundleKey) && cur.getAccountId().equals(accountId)) {
- results.add(cur);
+ return cur;
}
}
- return results;
+ return null;
}
@Override
- public SubscriptionBaseBundle createSubscriptionBundle(final DefaultSubscriptionBaseBundle bundle, final InternalCallContext context) {
+ public SubscriptionBaseBundle createSubscriptionBundle(final DefaultSubscriptionBaseBundle bundle, final Catalog catalog, final boolean renameCancelledBundleIfExist, final InternalCallContext context) {
bundles.add(bundle);
mockNonEntityDao.addTenantRecordIdMapping(bundle.getId(), context);
return getSubscriptionBundleFromId(bundle.getId(), context);
}
@Override
- public SubscriptionBase getSubscriptionFromId(final UUID subscriptionId, final InternalTenantContext context) {
+ public SubscriptionBase getSubscriptionFromId(final UUID subscriptionId, final Catalog catalog, final InternalTenantContext context) {
for (final SubscriptionBase cur : subscriptions) {
if (cur.getId().equals(subscriptionId)) {
return buildSubscription((DefaultSubscriptionBase) cur, context);
@@ -209,7 +210,7 @@ public class MockSubscriptionDaoMemory extends MockEntityDaoBase<SubscriptionBun
@Override
public void createSubscription(final DefaultSubscriptionBase subscription, final List<SubscriptionBaseEvent> initialEvents,
- final InternalCallContext context) {
+ final Catalog catalog, final InternalCallContext context) {
synchronized (events) {
events.addAll(initialEvents);
for (final SubscriptionBaseEvent cur : initialEvents) {
@@ -224,6 +225,7 @@ public class MockSubscriptionDaoMemory extends MockEntityDaoBase<SubscriptionBun
@Override
public void createSubscriptionsWithAddOns(final List<SubscriptionBaseWithAddOns> subscriptions,
final Map<UUID, List<SubscriptionBaseEvent>> initialEventsMap,
+ final Catalog catalog,
final InternalCallContext context) {
synchronized (events) {
for (final SubscriptionBaseWithAddOns subscription : subscriptions) {
@@ -242,7 +244,7 @@ public class MockSubscriptionDaoMemory extends MockEntityDaoBase<SubscriptionBun
}
@Override
- public List<SubscriptionBase> getSubscriptions(final UUID bundleId, final List<SubscriptionBaseEvent> dryRunEvents, final InternalTenantContext context) {
+ public List<SubscriptionBase> getSubscriptions(final UUID bundleId, final List<SubscriptionBaseEvent> dryRunEvents, final Catalog catalog, final InternalTenantContext context) {
final List<SubscriptionBase> results = new ArrayList<SubscriptionBase>();
for (final SubscriptionBase cur : subscriptions) {
if (cur.getBundleId().equals(bundleId)) {
@@ -253,7 +255,7 @@ public class MockSubscriptionDaoMemory extends MockEntityDaoBase<SubscriptionBun
}
@Override
- public Map<UUID, List<SubscriptionBase>> getSubscriptionsForAccount(final InternalTenantContext context) {
+ public Map<UUID, List<SubscriptionBase>> getSubscriptionsForAccount(final Catalog catalog, final InternalTenantContext context) {
final Map<UUID, List<SubscriptionBase>> results = new HashMap<UUID, List<SubscriptionBase>>();
for (final SubscriptionBase cur : subscriptions) {
if (results.get(cur.getBundleId()) == null) {
@@ -293,7 +295,7 @@ public class MockSubscriptionDaoMemory extends MockEntityDaoBase<SubscriptionBun
}
@Override
- public SubscriptionBase getBaseSubscription(final UUID bundleId, final InternalTenantContext context) {
+ public SubscriptionBase getBaseSubscription(final UUID bundleId, final Catalog catalog, final InternalTenantContext context) {
for (final SubscriptionBase cur : subscriptions) {
if (cur.getBundleId().equals(bundleId) &&
cur.getCurrentPlan().getProduct().getCategory() == ProductCategory.BASE) {
@@ -305,7 +307,7 @@ public class MockSubscriptionDaoMemory extends MockEntityDaoBase<SubscriptionBun
@Override
public void createNextPhaseEvent(final DefaultSubscriptionBase subscription, final SubscriptionBaseEvent readyPhaseEvent, final SubscriptionBaseEvent nextPhase, final InternalCallContext context) {
- cancelNextPhaseEvent(subscription.getId(), context);
+ cancelNextPhaseEvent(subscription.getId(), null, context);
insertEvent(nextPhase, context);
notifyBusOfEffectiveImmediateChange(subscription, readyPhaseEvent, 0, context);
}
@@ -341,40 +343,41 @@ public class MockSubscriptionDaoMemory extends MockEntityDaoBase<SubscriptionBun
}
@Override
- public void cancelSubscriptionsOnBasePlanEvent(final DefaultSubscriptionBase subscription, final SubscriptionBaseEvent event, final List<DefaultSubscriptionBase> subscriptions, final List<SubscriptionBaseEvent> cancelEvents, final InternalCallContext context) {
- cancelSubscriptions(subscriptions, cancelEvents, context);
+ public void cancelSubscriptionsOnBasePlanEvent(final DefaultSubscriptionBase subscription, final SubscriptionBaseEvent event, final List<DefaultSubscriptionBase> subscriptions, final List<SubscriptionBaseEvent> cancelEvents, final Catalog catalog, final InternalCallContext context) {
+ cancelSubscriptions(subscriptions, cancelEvents, catalog, context);
notifyBusOfEffectiveImmediateChange(subscription, event, subscriptions.size(), context);
}
@Override
- public void notifyOnBasePlanEvent(final DefaultSubscriptionBase subscription, final SubscriptionBaseEvent event, final InternalCallContext context) {
+ public void notifyOnBasePlanEvent(final DefaultSubscriptionBase subscription, final SubscriptionBaseEvent event, final Catalog catalog, final InternalCallContext context) {
notifyBusOfEffectiveImmediateChange(subscription, event, subscriptions.size(), context);
}
@Override
- public void cancelSubscriptions(final List<DefaultSubscriptionBase> subscriptions, final List<SubscriptionBaseEvent> cancelEvents, final InternalCallContext context) {
+ public void cancelSubscriptions(final List<DefaultSubscriptionBase> subscriptions, final List<SubscriptionBaseEvent> cancelEvents, final Catalog catalog, final InternalCallContext context) {
synchronized (events) {
for (int i = 0; i < subscriptions.size(); i++) {
- cancelNextPhaseEvent(subscriptions.get(i).getId(), context);
+ cancelNextPhaseEvent(subscriptions.get(i).getId(), catalog, context);
insertEvent(cancelEvents.get(i), context);
}
}
}
@Override
- public void changePlan(final DefaultSubscriptionBase subscription, final List<SubscriptionBaseEvent> changeEvents, final List<DefaultSubscriptionBase> subscriptionsToBeCancelled, final List<SubscriptionBaseEvent> cancelEvents, final InternalCallContext context) {
+ public void changePlan(final DefaultSubscriptionBase subscription, final List<SubscriptionBaseEvent> changeEvents, final List<DefaultSubscriptionBase> subscriptionsToBeCancelled, final List<SubscriptionBaseEvent> cancelEvents, final Catalog catalog, final InternalCallContext context) {
synchronized (events) {
cancelNextChangeEvent(subscription.getId());
- cancelNextPhaseEvent(subscription.getId(), context);
+ cancelNextPhaseEvent(subscription.getId(), catalog, context);
events.addAll(changeEvents);
for (final SubscriptionBaseEvent cur : changeEvents) {
recordFutureNotificationFromTransaction(null, cur.getEffectiveDate(), new SubscriptionNotificationKey(cur.getId()), context);
}
}
- cancelSubscriptions(subscriptionsToBeCancelled, cancelEvents, context);
+ cancelSubscriptions(subscriptionsToBeCancelled, cancelEvents, catalog, context);
}
+
private void insertEvent(final SubscriptionBaseEvent event, final InternalCallContext context) {
synchronized (events) {
events.add(event);
@@ -383,8 +386,8 @@ public class MockSubscriptionDaoMemory extends MockEntityDaoBase<SubscriptionBun
}
}
- private void cancelNextPhaseEvent(final UUID subscriptionId, final InternalTenantContext context) {
- final SubscriptionBase curSubscription = getSubscriptionFromId(subscriptionId, context);
+ private void cancelNextPhaseEvent(final UUID subscriptionId, final Catalog catalog, final InternalTenantContext context) {
+ final SubscriptionBase curSubscription = getSubscriptionFromId(subscriptionId, catalog, context);
if (curSubscription.getCurrentPhase() == null ||
curSubscription.getCurrentPhase().getDuration().getUnit() == TimeUnit.UNLIMITED) {
return;
@@ -431,9 +434,19 @@ public class MockSubscriptionDaoMemory extends MockEntityDaoBase<SubscriptionBun
@Override
public void uncancelSubscription(final DefaultSubscriptionBase subscription, final List<SubscriptionBaseEvent> uncancelEvents,
final InternalCallContext context) {
+ undoPendingOperation(subscription, uncancelEvents, ApiEventType.CANCEL, context);
+ }
+ @Override
+ public void undoChangePlan(final DefaultSubscriptionBase subscription, final List<SubscriptionBaseEvent> undoChangePlanEvents, final InternalCallContext context) {
+ undoPendingOperation(subscription, undoChangePlanEvents, ApiEventType.CHANGE, context);
+ }
+
+
+ private void undoPendingOperation(final DefaultSubscriptionBase subscription, final List<SubscriptionBaseEvent> inputEvents,
+ final ApiEventType targetType, final InternalCallContext context) {
synchronized (events) {
- boolean foundCancel = false;
+ boolean foundEvent = false;
final Iterator<SubscriptionBaseEvent> it = events.descendingIterator();
while (it.hasNext()) {
final SubscriptionBaseEvent cur = it.next();
@@ -441,14 +454,14 @@ public class MockSubscriptionDaoMemory extends MockEntityDaoBase<SubscriptionBun
continue;
}
if (cur.getType() == EventType.API_USER &&
- ((ApiEvent) cur).getApiEventType() == ApiEventType.CANCEL) {
+ ((ApiEvent) cur).getApiEventType() == targetType) {
it.remove();
- foundCancel = true;
+ foundEvent = true;
break;
}
}
- if (foundCancel) {
- for (final SubscriptionBaseEvent cur : uncancelEvents) {
+ if (foundEvent) {
+ for (final SubscriptionBaseEvent cur : inputEvents) {
insertEvent(cur, context);
}
}
@@ -495,14 +508,10 @@ public class MockSubscriptionDaoMemory extends MockEntityDaoBase<SubscriptionBun
}
}
- @Override
- public Iterable<SubscriptionBaseEvent> getFutureEventsForAccount(final InternalTenantContext context) {
- return null;
- }
@Override
public void transfer(final UUID srcAccountId, final UUID destAccountId, final BundleTransferData data,
- final List<TransferCancelData> transferCancelData, final InternalCallContext fromContext,
+ final List<TransferCancelData> transferCancelData, final Catalog catalog, final InternalCallContext fromContext,
final InternalCallContext toContext) {
}
@@ -511,7 +520,7 @@ public class MockSubscriptionDaoMemory extends MockEntityDaoBase<SubscriptionBun
}
@Override
- public void createBCDChangeEvent(final DefaultSubscriptionBase subscription, final SubscriptionBaseEvent bcdEvent, final InternalCallContext context) {
+ public void createBCDChangeEvent(final DefaultSubscriptionBase subscription, final SubscriptionBaseEvent bcdEvent, final Catalog catalog, final InternalCallContext context) {
}
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/engine/dao/MockSubscriptionDaoSql.java b/subscription/src/test/java/org/killbill/billing/subscription/engine/dao/MockSubscriptionDaoSql.java
index a5b2251..790ef04 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/engine/dao/MockSubscriptionDaoSql.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/engine/dao/MockSubscriptionDaoSql.java
@@ -36,8 +36,8 @@ public class MockSubscriptionDaoSql extends DefaultSubscriptionDao {
@Inject
public MockSubscriptionDaoSql(final IDBI dbi, final Clock clock, final AddonUtils addonUtils, final NotificationQueueService notificationQueueService,
- final PersistentBus eventBus, final CatalogInternalApi catalogService, final CacheControllerDispatcher cacheControllerDispatcher,
+ final PersistentBus eventBus, final CacheControllerDispatcher cacheControllerDispatcher,
final NonEntityDao nonEntityDao, final InternalCallContextFactory internalCallContextFactory) {
- super(dbi, clock, addonUtils, notificationQueueService, eventBus, catalogService, cacheControllerDispatcher, nonEntityDao, internalCallContextFactory);
+ super(dbi, clock, addonUtils, notificationQueueService, eventBus, cacheControllerDispatcher, nonEntityDao, internalCallContextFactory);
}
}
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/engine/dao/TestSubscriptionDao.java b/subscription/src/test/java/org/killbill/billing/subscription/engine/dao/TestSubscriptionDao.java
new file mode 100644
index 0000000..99b6544
--- /dev/null
+++ b/subscription/src/test/java/org/killbill/billing/subscription/engine/dao/TestSubscriptionDao.java
@@ -0,0 +1,169 @@
+/*
+ * 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.engine.dao;
+
+import java.util.List;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.killbill.billing.ErrorCode;
+import org.killbill.billing.account.api.Account;
+import org.killbill.billing.account.api.AccountData;
+import org.killbill.billing.api.TestApiListener.NextEvent;
+import org.killbill.billing.catalog.DefaultPriceListSet;
+import org.killbill.billing.catalog.api.ProductCategory;
+import org.killbill.billing.subscription.SubscriptionTestSuiteWithEmbeddedDB;
+import org.killbill.billing.subscription.api.user.DefaultSubscriptionBase;
+import org.killbill.billing.subscription.api.user.DefaultSubscriptionBaseBundle;
+import org.killbill.billing.subscription.api.user.SubscriptionBaseApiException;
+import org.killbill.billing.subscription.api.user.SubscriptionBaseBundle;
+import org.killbill.billing.subscription.api.user.SubscriptionBuilder;
+import org.killbill.billing.subscription.events.SubscriptionBaseEvent;
+import org.killbill.billing.subscription.events.user.ApiEventBuilder;
+import org.killbill.billing.subscription.events.user.ApiEventCreate;
+import org.killbill.billing.util.UUIDs;
+import org.testng.Assert;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+
+import static org.testng.Assert.assertEquals;
+
+public class TestSubscriptionDao extends SubscriptionTestSuiteWithEmbeddedDB {
+
+ protected UUID accountId;
+
+ @Override
+ @BeforeMethod(groups = "slow")
+ public void beforeMethod() throws Exception {
+ // Note: this will cleanup all tables
+ super.beforeMethod();
+
+ // internal context will be configured for accountId
+ final AccountData accountData = subscriptionTestInitializer.initAccountData(clock);
+ final Account account = createAccount(accountData);
+ accountId = account.getId();
+ }
+
+ @Override // to ignore events
+ @AfterMethod(groups = "slow")
+ public void afterMethod() throws Exception {
+ subscriptionTestInitializer.stopTestFramework(testListener, busService, subscriptionBaseService);
+ }
+
+ @Test(groups = "slow")
+ public void testBundleExternalKeyReused() throws Exception {
+
+ final String externalKey = "12345";
+ final DateTime startDate = clock.getUTCNow();
+ final DateTime createdDate = startDate.plusSeconds(10);
+
+ final DefaultSubscriptionBaseBundle bundleDef = new DefaultSubscriptionBaseBundle(externalKey, accountId, startDate, startDate, createdDate, createdDate);
+ final SubscriptionBaseBundle bundle = dao.createSubscriptionBundle(bundleDef, catalog, true, internalCallContext);
+
+ final List<SubscriptionBaseBundle> result = dao.getSubscriptionBundlesForKey(externalKey, internalCallContext);
+ assertEquals(result.size(), 1);
+ assertEquals(result.get(0).getExternalKey(), bundle.getExternalKey());
+
+ // Operation succeeds but nothing new got created because bundle is empty
+ dao.createSubscriptionBundle(bundleDef, catalog, true, internalCallContext);
+ final List<SubscriptionBaseBundle> result2 = dao.getSubscriptionBundlesForKey(externalKey, internalCallContext);
+ assertEquals(result2.size(), 1);
+
+ // Create a subscription and this time operation should fail
+ final SubscriptionBuilder builder = new SubscriptionBuilder()
+ .setId(UUIDs.randomUUID())
+ .setBundleId(bundle.getId())
+ .setBundleExternalKey(bundle.getExternalKey())
+ .setCategory(ProductCategory.BASE)
+ .setBundleStartDate(startDate)
+ .setAlignStartDate(startDate)
+ .setMigrated(false);
+
+ final ApiEventBuilder createBuilder = new ApiEventBuilder()
+ .setSubscriptionId(builder.getId())
+ .setEventPlan("shotgun-monthly")
+ .setEventPlanPhase("shotgun-monthly-trial")
+ .setEventPriceList(DefaultPriceListSet.DEFAULT_PRICELIST_NAME)
+ .setEffectiveDate(startDate)
+ .setFromDisk(true);
+ final SubscriptionBaseEvent creationEvent = new ApiEventCreate(createBuilder);
+
+ final DefaultSubscriptionBase subscription = new DefaultSubscriptionBase(builder);
+ testListener.pushExpectedEvents(NextEvent.CREATE);
+ dao.createSubscription(subscription, ImmutableList.of(creationEvent), catalog, internalCallContext);
+ assertListenerStatus();
+
+ // Operation Should now fail
+ try {
+ dao.createSubscriptionBundle(bundleDef, catalog, true, internalCallContext);
+ Assert.fail("Should fail to create new subscription bundle with existing key");
+ } catch (SubscriptionBaseApiException e) {
+ assertEquals(ErrorCode.SUB_CREATE_ACTIVE_BUNDLE_KEY_EXISTS.getCode(), e.getCode());
+ }
+ }
+
+ @Test(groups = "slow")
+ public void testBundleExternalKeyTransferred() throws Exception {
+
+ final String externalKey = "2534125sdfsd";
+ final DateTime startDate = clock.getUTCNow();
+ final DateTime createdDate = startDate.plusSeconds(10);
+
+ final DefaultSubscriptionBaseBundle bundleDef = new DefaultSubscriptionBaseBundle(externalKey, accountId, startDate, startDate, createdDate, createdDate);
+ final SubscriptionBaseBundle bundle = dao.createSubscriptionBundle(bundleDef, catalog, true, internalCallContext);
+
+ final List<SubscriptionBaseBundle> result = dao.getSubscriptionBundlesForKey(externalKey, internalCallContext);
+ assertEquals(result.size(), 1);
+ assertEquals(result.get(0).getExternalKey(), bundle.getExternalKey());
+
+ // Update key to 'internal KB value 'kbtsf-12345:'
+ dao.updateBundleExternalKey(bundle.getId(), "kbtsf-12345:" + bundle.getExternalKey(), internalCallContext);
+ final List<SubscriptionBaseBundle> result2 = dao.getSubscriptionBundlesForKey(externalKey, internalCallContext);
+ assertEquals(result2.size(), 1);
+
+ // Create new bundle with original key, verify all results show original key, stripping down internal prefix
+ final DefaultSubscriptionBaseBundle bundleDef2 = new DefaultSubscriptionBaseBundle(externalKey, accountId, startDate, startDate, createdDate, createdDate);
+ final SubscriptionBaseBundle bundle2 = dao.createSubscriptionBundle(bundleDef2, catalog, true, internalCallContext);
+ final List<SubscriptionBaseBundle> result3 = dao.getSubscriptionBundlesForKey(externalKey, internalCallContext);
+ assertEquals(result3.size(), 2);
+ assertEquals(result3.get(0).getExternalKey(), bundle2.getExternalKey());
+ assertEquals(result3.get(1).getExternalKey(), bundle2.getExternalKey());
+
+ // This time we call the lower SqlDao to rename the bundle automatically and verify we still get same # results,
+ // with original key
+ dbi.onDemand(BundleSqlDao.class).renameBundleExternalKey(externalKey, "foo", internalCallContext);
+ final List<SubscriptionBaseBundle> result4 = dao.getSubscriptionBundlesForKey(externalKey, internalCallContext);
+ assertEquals(result4.size(), 2);
+ assertEquals(result4.get(0).getExternalKey(), bundle2.getExternalKey());
+ assertEquals(result4.get(1).getExternalKey(), bundle2.getExternalKey());
+
+ // Create bundle one more time
+ final DefaultSubscriptionBaseBundle bundleDef3 = new DefaultSubscriptionBaseBundle(externalKey, accountId, startDate, startDate, createdDate, createdDate);
+ final SubscriptionBaseBundle bundle3 = dao.createSubscriptionBundle(bundleDef3, catalog, true, internalCallContext);
+ final List<SubscriptionBaseBundle> result5 = dao.getSubscriptionBundlesForKey(externalKey, internalCallContext);
+ assertEquals(result5.size(), 3);
+ assertEquals(result5.get(0).getExternalKey(), bundle2.getExternalKey());
+ assertEquals(result5.get(1).getExternalKey(), bundle2.getExternalKey());
+ assertEquals(result5.get(2).getExternalKey(), bundle2.getExternalKey());
+
+
+ }
+}
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/engine/dao/TestSubscriptionModelDao.java b/subscription/src/test/java/org/killbill/billing/subscription/engine/dao/TestSubscriptionModelDao.java
new file mode 100644
index 0000000..2f6bf69
--- /dev/null
+++ b/subscription/src/test/java/org/killbill/billing/subscription/engine/dao/TestSubscriptionModelDao.java
@@ -0,0 +1,51 @@
+/*
+ * 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.engine.dao;
+
+import org.killbill.billing.subscription.SubscriptionTestSuiteNoDB;
+import org.killbill.billing.subscription.engine.dao.model.SubscriptionBundleModelDao;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertEquals;
+
+public class TestSubscriptionModelDao extends SubscriptionTestSuiteNoDB {
+
+ @Test(groups = "fast")
+ public void testBundleExternalKeyPattern1() throws Exception {
+ final SubscriptionBundleModelDao b = new SubscriptionBundleModelDao();
+ b.setExternalKey("1235");
+
+ assertEquals(SubscriptionBundleModelDao.toSubscriptionBundle(b).getExternalKey(), "1235");
+ }
+
+
+ @Test(groups = "fast")
+ public void testBundleExternalKeyPattern2() throws Exception {
+ final SubscriptionBundleModelDao b = new SubscriptionBundleModelDao();
+ b.setExternalKey("kbtsf-343453:1235");
+ assertEquals(SubscriptionBundleModelDao.toSubscriptionBundle(b).getExternalKey(), "1235");
+ }
+
+ @Test(groups = "fast")
+ public void testBundleExternalKeyPattern3() throws Exception {
+ final SubscriptionBundleModelDao b = new SubscriptionBundleModelDao();
+ b.setExternalKey("kbXXXX-343453:1235");
+ assertEquals(SubscriptionBundleModelDao.toSubscriptionBundle(b).getExternalKey(), "1235");
+ }
+
+}
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/SubscriptionTestInitializer.java b/subscription/src/test/java/org/killbill/billing/subscription/SubscriptionTestInitializer.java
index ca1baec..a00a661 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/SubscriptionTestInitializer.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/SubscriptionTestInitializer.java
@@ -30,13 +30,14 @@ import org.killbill.billing.lifecycle.api.BusService;
import org.killbill.billing.subscription.api.SubscriptionBaseInternalApi;
import org.killbill.billing.subscription.api.SubscriptionBaseService;
import org.killbill.billing.subscription.api.user.SubscriptionBaseBundle;
+import org.killbill.clock.Clock;
import org.killbill.clock.ClockMock;
public interface SubscriptionTestInitializer {
public Catalog initCatalog(final CatalogService catalogService, final InternalTenantContext context) throws Exception;
- public AccountData initAccountData();
+ public AccountData initAccountData(Clock clock);
public SubscriptionBaseBundle initBundle(final UUID accountId, final SubscriptionBaseInternalApi subscriptionApi, final InternalCallContext callContext) throws Exception;
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/SubscriptionTestSuiteNoDB.java b/subscription/src/test/java/org/killbill/billing/subscription/SubscriptionTestSuiteNoDB.java
index 8f1b379..b531dfe 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/SubscriptionTestSuiteNoDB.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/SubscriptionTestSuiteNoDB.java
@@ -138,7 +138,7 @@ public class SubscriptionTestSuiteNoDB extends GuicyKillbillTestSuiteNoDB {
subscriptionTestInitializer.startTestFramework(testListener, clock, busService, subscriptionBaseService);
this.catalog = subscriptionTestInitializer.initCatalog(catalogService, internalCallContext);
- this.accountData = subscriptionTestInitializer.initAccountData();
+ this.accountData = subscriptionTestInitializer.initAccountData(clock);
final UUID accountId = UUIDs.randomUUID();
mockNonEntityDao.addTenantRecordIdMapping(accountId, internalCallContext);
@@ -155,6 +155,7 @@ public class SubscriptionTestSuiteNoDB extends GuicyKillbillTestSuiteNoDB {
subscriptionTestInitializer.stopTestFramework(testListener, busService, subscriptionBaseService);
}
+ @Override
protected void assertListenerStatus() {
testListener.assertListenerStatus();
}
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/SubscriptionTestSuiteWithEmbeddedDB.java b/subscription/src/test/java/org/killbill/billing/subscription/SubscriptionTestSuiteWithEmbeddedDB.java
index e97f3e0..fa1c027 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/SubscriptionTestSuiteWithEmbeddedDB.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/SubscriptionTestSuiteWithEmbeddedDB.java
@@ -112,19 +112,13 @@ public class SubscriptionTestSuiteWithEmbeddedDB extends GuicyKillbillTestSuiteW
subscriptionTestInitializer.startTestFramework(testListener, clock, busService, subscriptionBaseService);
this.catalog = subscriptionTestInitializer.initCatalog(catalogService, internalCallContext);
- this.accountData = subscriptionTestInitializer.initAccountData();
+ this.accountData = subscriptionTestInitializer.initAccountData(clock);
final Account account = createAccount(accountData);
this.bundle = subscriptionTestInitializer.initBundle(account.getId(), subscriptionInternalApi, internalCallContext);
-
- // Make sure we start with a clean state
- assertListenerStatus();
}
@AfterMethod(groups = "slow")
public void afterMethod() throws Exception {
- // Make sure we finish in a clean state
- assertListenerStatus();
-
subscriptionTestInitializer.stopTestFramework(testListener, busService, subscriptionBaseService);
}
@@ -136,6 +130,7 @@ public class SubscriptionTestSuiteWithEmbeddedDB extends GuicyKillbillTestSuiteW
return account;
}
+ @Override
protected void assertListenerStatus() {
testListener.assertListenerStatus();
}
tenant/pom.xml 8(+2 -6)
diff --git a/tenant/pom.xml b/tenant/pom.xml
index 644a027..9c42d0b 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.19.0-SNAPSHOT</version>
+ <version>0.19.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-tenant</artifactId>
@@ -74,7 +74,7 @@
</dependency>
<dependency>
<groupId>org.antlr</groupId>
- <artifactId>stringtemplate</artifactId>
+ <artifactId>ST4</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
@@ -82,10 +82,6 @@
<artifactId>shiro-core</artifactId>
</dependency>
<dependency>
- <groupId>org.jdbi</groupId>
- <artifactId>jdbi</artifactId>
- </dependency>
- <dependency>
<groupId>org.kill-bill.billing</groupId>
<artifactId>killbill-api</artifactId>
</dependency>
diff --git a/tenant/src/main/java/org/killbill/billing/tenant/dao/TenantBroadcastSqlDao.java b/tenant/src/main/java/org/killbill/billing/tenant/dao/TenantBroadcastSqlDao.java
index 1925151..281d07c 100644
--- a/tenant/src/main/java/org/killbill/billing/tenant/dao/TenantBroadcastSqlDao.java
+++ b/tenant/src/main/java/org/killbill/billing/tenant/dao/TenantBroadcastSqlDao.java
@@ -21,11 +21,11 @@ import java.util.List;
import org.killbill.billing.util.entity.Entity;
import org.killbill.billing.util.entity.dao.EntitySqlDao;
-import org.killbill.billing.util.entity.dao.EntitySqlDaoStringTemplate;
+import org.killbill.commons.jdbi.template.KillBillSqlDaoStringTemplate;
import org.skife.jdbi.v2.sqlobject.Bind;
import org.skife.jdbi.v2.sqlobject.SqlQuery;
-@EntitySqlDaoStringTemplate
+@KillBillSqlDaoStringTemplate
public interface TenantBroadcastSqlDao extends EntitySqlDao<TenantBroadcastModelDao, Entity> {
@SqlQuery
diff --git a/tenant/src/main/java/org/killbill/billing/tenant/dao/TenantKVSqlDao.java b/tenant/src/main/java/org/killbill/billing/tenant/dao/TenantKVSqlDao.java
index c0cac34..fe99bea 100644
--- a/tenant/src/main/java/org/killbill/billing/tenant/dao/TenantKVSqlDao.java
+++ b/tenant/src/main/java/org/killbill/billing/tenant/dao/TenantKVSqlDao.java
@@ -24,32 +24,32 @@ import org.killbill.billing.tenant.api.TenantKV;
import org.killbill.billing.util.audit.ChangeType;
import org.killbill.billing.util.entity.dao.Audited;
import org.killbill.billing.util.entity.dao.EntitySqlDao;
-import org.killbill.billing.util.entity.dao.EntitySqlDaoStringTemplate;
+import org.killbill.commons.jdbi.template.KillBillSqlDaoStringTemplate;
import org.skife.jdbi.v2.sqlobject.Bind;
-import org.skife.jdbi.v2.sqlobject.BindBean;
+import org.killbill.commons.jdbi.binder.SmartBindBean;
import org.skife.jdbi.v2.sqlobject.SqlQuery;
import org.skife.jdbi.v2.sqlobject.SqlUpdate;
-@EntitySqlDaoStringTemplate
+@KillBillSqlDaoStringTemplate
public interface TenantKVSqlDao extends EntitySqlDao<TenantKVModelDao, TenantKV> {
@SqlQuery
public List<TenantKVModelDao> getTenantValueForKey(@Bind("tenantKey") final String key,
- @BindBean final InternalTenantContext context);
+ @SmartBindBean final InternalTenantContext context);
@SqlQuery
public List<TenantKVModelDao> searchTenantKeyValues(@Bind("tenantKeyPrefix") final String tenantKeyPrefix,
- @BindBean final InternalTenantContext context);
+ @SmartBindBean final InternalTenantContext context);
@SqlUpdate
@Audited(ChangeType.DELETE)
public void markTenantKeyAsDeleted(@Bind("id")final String id,
- @BindBean final InternalCallContext context);
+ @SmartBindBean final InternalCallContext context);
@SqlUpdate
@Audited(ChangeType.UPDATE)
public Object updateTenantValueKey(@Bind("id") final String id,
@Bind("tenantValue") final String tenantValue,
- @BindBean final InternalCallContext context);
+ @SmartBindBean final InternalCallContext context);
}
diff --git a/tenant/src/main/java/org/killbill/billing/tenant/dao/TenantSqlDao.java b/tenant/src/main/java/org/killbill/billing/tenant/dao/TenantSqlDao.java
index cf97271..ab4ded9 100644
--- a/tenant/src/main/java/org/killbill/billing/tenant/dao/TenantSqlDao.java
+++ b/tenant/src/main/java/org/killbill/billing/tenant/dao/TenantSqlDao.java
@@ -24,9 +24,9 @@ import org.skife.jdbi.v2.sqlobject.SqlQuery;
import org.killbill.billing.tenant.api.Tenant;
import org.killbill.billing.util.entity.dao.EntitySqlDao;
-import org.killbill.billing.util.entity.dao.EntitySqlDaoStringTemplate;
+import org.killbill.commons.jdbi.template.KillBillSqlDaoStringTemplate;
-@EntitySqlDaoStringTemplate
+@KillBillSqlDaoStringTemplate
public interface TenantSqlDao extends EntitySqlDao<TenantModelDao, Tenant> {
@SqlQuery
diff --git a/tenant/src/main/resources/org/killbill/billing/tenant/dao/TenantBroadcastSqlDao.sql.stg b/tenant/src/main/resources/org/killbill/billing/tenant/dao/TenantBroadcastSqlDao.sql.stg
index b0e5bc3..b843665 100644
--- a/tenant/src/main/resources/org/killbill/billing/tenant/dao/TenantBroadcastSqlDao.sql.stg
+++ b/tenant/src/main/resources/org/killbill/billing/tenant/dao/TenantBroadcastSqlDao.sql.stg
@@ -1,4 +1,4 @@
-group TenantBroadcastSqlDao: EntitySqlDao;
+import "org/killbill/billing/util/entity/dao/EntitySqlDao.sql.stg"
tableName() ::= "tenant_broadcasts"
@@ -26,7 +26,7 @@ tableValues() ::= <<
/* No account_record_id field */
accountRecordIdFieldWithComma(prefix) ::= ""
-accountRecordIdValueWithComma(prefix) ::= ""
+accountRecordIdValueWithComma() ::= ""
getLatestEntries() ::= <<
diff --git a/tenant/src/main/resources/org/killbill/billing/tenant/dao/TenantKVSqlDao.sql.stg b/tenant/src/main/resources/org/killbill/billing/tenant/dao/TenantKVSqlDao.sql.stg
index 9495969..19cfb11 100644
--- a/tenant/src/main/resources/org/killbill/billing/tenant/dao/TenantKVSqlDao.sql.stg
+++ b/tenant/src/main/resources/org/killbill/billing/tenant/dao/TenantKVSqlDao.sql.stg
@@ -1,4 +1,4 @@
-group TenantKVSqlDao: EntitySqlDao;
+import "org/killbill/billing/util/entity/dao/EntitySqlDao.sql.stg"
tableName() ::= "tenant_kvs"
diff --git a/tenant/src/main/resources/org/killbill/billing/tenant/dao/TenantSqlDao.sql.stg b/tenant/src/main/resources/org/killbill/billing/tenant/dao/TenantSqlDao.sql.stg
index 56c8986..25cfc6f 100644
--- a/tenant/src/main/resources/org/killbill/billing/tenant/dao/TenantSqlDao.sql.stg
+++ b/tenant/src/main/resources/org/killbill/billing/tenant/dao/TenantSqlDao.sql.stg
@@ -1,4 +1,4 @@
-group TenantDaoSql: EntitySqlDao;
+import "org/killbill/billing/util/entity/dao/EntitySqlDao.sql.stg"
tableName() ::= "tenants"
@@ -23,7 +23,7 @@ tableValues() ::= <<
/* No account_record_id field */
accountRecordIdFieldWithComma(prefix) ::= ""
-accountRecordIdValueWithComma(prefix) ::= ""
+accountRecordIdValueWithComma() ::= ""
/* No tenant_record_id field */
tenantRecordIdFieldWithComma(prefix) ::= ""
@@ -33,8 +33,8 @@ CHECK_TENANT(prefix) ::= "1 = 1"
/* Override default create call to include secrets */
create() ::= <<
insert into <tableName()> (
- <idField()>
-, <tableFields()>
+ <idField("")>
+, <tableFields("")>
, api_secret
, api_salt
)
diff --git a/tenant/src/test/java/org/killbill/billing/tenant/TenantTestSuiteWithEmbeddedDb.java b/tenant/src/test/java/org/killbill/billing/tenant/TenantTestSuiteWithEmbeddedDb.java
index 060a087..45486dc 100644
--- a/tenant/src/test/java/org/killbill/billing/tenant/TenantTestSuiteWithEmbeddedDb.java
+++ b/tenant/src/test/java/org/killbill/billing/tenant/TenantTestSuiteWithEmbeddedDb.java
@@ -58,13 +58,4 @@ public class TenantTestSuiteWithEmbeddedDb extends GuicyKillbillTestSuiteWithEmb
final Injector injector = Guice.createInjector(new TestTenantModuleWithEmbeddedDB(configSource));
injector.injectMembers(this);
}
-
- @BeforeMethod(groups = "slow")
- public void beforeMethod() throws Exception {
- super.beforeMethod();
- }
-
- @AfterMethod(groups = "slow")
- public void afterMethod() {
- }
}
usage/pom.xml 12(+6 -6)
diff --git a/usage/pom.xml b/usage/pom.xml
index 25c9467..866aef7 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.19.0-SNAPSHOT</version>
+ <version>0.19.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-usage</artifactId>
@@ -70,14 +70,10 @@
</dependency>
<dependency>
<groupId>org.antlr</groupId>
- <artifactId>stringtemplate</artifactId>
+ <artifactId>ST4</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
- <groupId>org.jdbi</groupId>
- <artifactId>jdbi</artifactId>
- </dependency>
- <dependency>
<groupId>org.kill-bill.billing</groupId>
<artifactId>killbill-account</artifactId>
<scope>test</scope>
@@ -143,6 +139,10 @@
<scope>test</scope>
</dependency>
<dependency>
+ <groupId>org.kill-bill.commons</groupId>
+ <artifactId>killbill-jdbi</artifactId>
+ </dependency>
+ <dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<scope>test</scope>
diff --git a/usage/src/main/java/org/killbill/billing/usage/dao/RolledUpUsageSqlDao.java b/usage/src/main/java/org/killbill/billing/usage/dao/RolledUpUsageSqlDao.java
index 89fc3a2..e947fa5 100644
--- a/usage/src/main/java/org/killbill/billing/usage/dao/RolledUpUsageSqlDao.java
+++ b/usage/src/main/java/org/killbill/billing/usage/dao/RolledUpUsageSqlDao.java
@@ -27,17 +27,17 @@ import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.util.callcontext.InternalTenantContextBinder;
import org.killbill.billing.util.entity.Entity;
import org.killbill.billing.util.entity.dao.EntitySqlDao;
-import org.killbill.billing.util.entity.dao.EntitySqlDaoStringTemplate;
+import org.killbill.commons.jdbi.template.KillBillSqlDaoStringTemplate;
import org.skife.jdbi.v2.sqlobject.Bind;
-import org.skife.jdbi.v2.sqlobject.BindBean;
+import org.killbill.commons.jdbi.binder.SmartBindBean;
import org.skife.jdbi.v2.sqlobject.SqlBatch;
import org.skife.jdbi.v2.sqlobject.SqlQuery;
-@EntitySqlDaoStringTemplate
+@KillBillSqlDaoStringTemplate
public interface RolledUpUsageSqlDao extends EntitySqlDao<RolledUpUsageModelDao, Entity> {
@SqlBatch
- void create(@BindBean Iterable<RolledUpUsageModelDao> usages,
+ void create(@SmartBindBean Iterable<RolledUpUsageModelDao> usages,
@InternalTenantContextBinder final InternalCallContext context);
@SqlQuery
diff --git a/usage/src/main/resources/org/killbill/billing/usage/dao/RolledUpUsageSqlDao.sql.stg b/usage/src/main/resources/org/killbill/billing/usage/dao/RolledUpUsageSqlDao.sql.stg
index b1d739c..44db9b9 100644
--- a/usage/src/main/resources/org/killbill/billing/usage/dao/RolledUpUsageSqlDao.sql.stg
+++ b/usage/src/main/resources/org/killbill/billing/usage/dao/RolledUpUsageSqlDao.sql.stg
@@ -1,4 +1,4 @@
-group RolledUpUsageSqlDao : EntitySqlDao;
+import "org/killbill/billing/util/entity/dao/EntitySqlDao.sql.stg"
tableName() ::= "rolled_up_usage"
@@ -29,45 +29,45 @@ select
from <tableName()>
where subscription_id = :subscriptionId
and tracking_id = :trackingId
-<AND_CHECK_TENANT()>
+<AND_CHECK_TENANT("")>
limit 1
;
>>
getUsageForSubscription() ::= <<
select
- <allTableFields()>
+ <allTableFields("")>
from <tableName()>
where subscription_id = :subscriptionId
and record_date >= :startDate
and record_date \< :endDate
and unit_type = :unitType
-<AND_CHECK_TENANT()>
-<defaultOrderBy()>
+<AND_CHECK_TENANT("")>
+<defaultOrderBy("")>
;
>>
getAllUsageForSubscription() ::= <<
select
- <allTableFields()>
+ <allTableFields("")>
from <tableName()>
where subscription_id = :subscriptionId
and record_date >= :startDate
and record_date \< :endDate
-<AND_CHECK_TENANT()>
-<defaultOrderBy()>
+<AND_CHECK_TENANT("")>
+<defaultOrderBy("")>
;
>>
getRawUsageForAccount() ::= <<
select
- <allTableFields()>
+ <allTableFields("")>
from <tableName()>
where account_record_id = :accountRecordId
and record_date >= :startDate
and record_date \< :endDate
-<AND_CHECK_TENANT()>
-<defaultOrderBy()>
+<AND_CHECK_TENANT("")>
+<defaultOrderBy("")>
;
>>
util/pom.xml 29(+19 -10)
diff --git a/util/pom.xml b/util/pom.xml
index 6f3e019..f7a100f 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.19.0-SNAPSHOT</version>
+ <version>0.19.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-util</artifactId>
@@ -74,6 +74,15 @@
<artifactId>guice-multibindings</artifactId>
</dependency>
<dependency>
+ <groupId>com.ning</groupId>
+ <artifactId>async-http-client</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.ning</groupId>
+ <artifactId>compress-lzf</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
<groupId>com.samskivert</groupId>
<artifactId>jmustache</artifactId>
</dependency>
@@ -130,11 +139,7 @@
</dependency>
<dependency>
<groupId>org.antlr</groupId>
- <artifactId>stringtemplate</artifactId>
- </dependency>
- <dependency>
- <groupId>org.apache.commons</groupId>
- <artifactId>commons-email</artifactId>
+ <artifactId>ST4</artifactId>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
@@ -169,10 +174,6 @@
<scope>test</scope>
</dependency>
<dependency>
- <groupId>org.jdbi</groupId>
- <artifactId>jdbi</artifactId>
- </dependency>
- <dependency>
<groupId>org.joda</groupId>
<artifactId>joda-money</artifactId>
<version>0.9</version>
@@ -191,6 +192,10 @@
</dependency>
<dependency>
<groupId>org.kill-bill.billing</groupId>
+ <artifactId>killbill-platform-lifecycle</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.kill-bill.billing</groupId>
<artifactId>killbill-platform-osgi</artifactId>
</dependency>
<dependency>
@@ -205,6 +210,10 @@
</dependency>
<dependency>
<groupId>org.kill-bill.billing.plugin</groupId>
+ <artifactId>killbill-plugin-api-invoice</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.kill-bill.billing.plugin</groupId>
<artifactId>killbill-plugin-api-notification</artifactId>
</dependency>
<dependency>
diff --git a/util/src/main/java/org/killbill/billing/util/account/AccountDateTimeUtils.java b/util/src/main/java/org/killbill/billing/util/account/AccountDateTimeUtils.java
index 58f9157..1a0f1a5 100644
--- a/util/src/main/java/org/killbill/billing/util/account/AccountDateTimeUtils.java
+++ b/util/src/main/java/org/killbill/billing/util/account/AccountDateTimeUtils.java
@@ -26,16 +26,14 @@ import org.killbill.billing.util.entity.dao.TimeZoneAwareEntity;
public abstract class AccountDateTimeUtils {
public static DateTimeZone getFixedOffsetTimeZone(final TimeZoneAwareEntity account) {
- return getFixedOffsetTimeZone(account.getTimeZone(), account);
+ return getFixedOffsetTimeZone(account.getTimeZone(), account.getReferenceTime());
}
public static DateTimeZone getFixedOffsetTimeZone(final Account account) {
- return getFixedOffsetTimeZone(account.getTimeZone(), account);
+ return getFixedOffsetTimeZone(account.getTimeZone(), account.getReferenceTime());
}
- public static DateTimeZone getFixedOffsetTimeZone(final DateTimeZone referenceDateTimeZone, final Entity account) {
- final DateTime referenceDateTime = getReferenceDateTime(account);
-
+ private static DateTimeZone getFixedOffsetTimeZone(final DateTimeZone referenceDateTimeZone, final DateTime referenceDateTime) {
// Check if DST was in effect at the reference date time
final boolean shouldUseDST = !referenceDateTimeZone.isStandardOffset(referenceDateTime.getMillis());
if (shouldUseDST) {
@@ -44,8 +42,4 @@ public abstract class AccountDateTimeUtils {
return DateTimeZone.forOffsetMillis(referenceDateTimeZone.getStandardOffset(referenceDateTime.getMillis()));
}
}
-
- public static DateTime getReferenceDateTime(final Entity account) {
- return account.getCreatedDate();
- }
}
diff --git a/util/src/main/java/org/killbill/billing/util/broadcast/dao/BroadcastSqlDao.java b/util/src/main/java/org/killbill/billing/util/broadcast/dao/BroadcastSqlDao.java
index 9efa110..fb6e7a5 100644
--- a/util/src/main/java/org/killbill/billing/util/broadcast/dao/BroadcastSqlDao.java
+++ b/util/src/main/java/org/killbill/billing/util/broadcast/dao/BroadcastSqlDao.java
@@ -19,17 +19,17 @@ package org.killbill.billing.util.broadcast.dao;
import java.util.List;
-import org.killbill.billing.util.entity.dao.EntitySqlDaoStringTemplate;
+import org.killbill.commons.jdbi.template.KillBillSqlDaoStringTemplate;
import org.skife.jdbi.v2.sqlobject.Bind;
-import org.skife.jdbi.v2.sqlobject.BindBean;
+import org.killbill.commons.jdbi.binder.SmartBindBean;
import org.skife.jdbi.v2.sqlobject.SqlQuery;
import org.skife.jdbi.v2.sqlobject.SqlUpdate;
-@EntitySqlDaoStringTemplate
+@KillBillSqlDaoStringTemplate
public interface BroadcastSqlDao {
@SqlUpdate
- public void create(@BindBean final BroadcastModelDao broadcastModelDao);
+ public void create(@SmartBindBean final BroadcastModelDao broadcastModelDao);
@SqlQuery
public List<BroadcastModelDao> getLatestEntries(@Bind("recordId") final Long recordId);
diff --git a/util/src/main/java/org/killbill/billing/util/broadcast/dao/DefaultBroadcastDao.java b/util/src/main/java/org/killbill/billing/util/broadcast/dao/DefaultBroadcastDao.java
index 6d7f32a..8ceda0e 100644
--- a/util/src/main/java/org/killbill/billing/util/broadcast/dao/DefaultBroadcastDao.java
+++ b/util/src/main/java/org/killbill/billing/util/broadcast/dao/DefaultBroadcastDao.java
@@ -39,8 +39,6 @@ public class DefaultBroadcastDao implements BroadcastDao {
public DefaultBroadcastDao(final IDBI dbi, final Clock clock) {
this.dbi = dbi;
this.clock = clock;
- ((DBI) dbi).registerMapper(new LowerToCamelBeanMapperFactory(BroadcastModelDao.class));
-
}
@Override
diff --git a/util/src/main/java/org/killbill/billing/util/cache/CacheControllerDispatcherProvider.java b/util/src/main/java/org/killbill/billing/util/cache/CacheControllerDispatcherProvider.java
index 7d9ce76..2ce2b6f 100644
--- a/util/src/main/java/org/killbill/billing/util/cache/CacheControllerDispatcherProvider.java
+++ b/util/src/main/java/org/killbill/billing/util/cache/CacheControllerDispatcherProvider.java
@@ -31,6 +31,8 @@ import org.killbill.billing.util.cache.Cachable.CacheType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.google.common.base.Preconditions;
+
// Build the abstraction layer between EhCache and Kill Bill
public class CacheControllerDispatcherProvider implements Provider<CacheControllerDispatcher> {
@@ -57,6 +59,7 @@ public class CacheControllerDispatcherProvider implements Provider<CacheControll
logger.warn("Cache for cacheName='{}' not configured - check your ehcache.xml", cacheLoader.getCacheType().getCacheName());
continue;
}
+ Preconditions.checkState(!cache.isClosed(), "Cache '%s' should not be closed", cacheType.getCacheName());
final CacheController<Object, Object> ehCacheBasedCacheController = new EhCacheBasedCacheController<Object, Object>(cache, cacheLoader);
cacheControllers.put(cacheType, ehCacheBasedCacheController);
diff --git a/util/src/main/java/org/killbill/billing/util/cache/EhCacheBasedCacheController.java b/util/src/main/java/org/killbill/billing/util/cache/EhCacheBasedCacheController.java
index c53b879..7f059fc 100644
--- a/util/src/main/java/org/killbill/billing/util/cache/EhCacheBasedCacheController.java
+++ b/util/src/main/java/org/killbill/billing/util/cache/EhCacheBasedCacheController.java
@@ -115,7 +115,7 @@ public class EhCacheBasedCacheController<K, V> implements CacheController<K, V>
@Override
public int size() {
- return Iterables.<Cache.Entry<K, V>>size(cache);
+ return Iterables.size(cache);
}
@Override
diff --git a/util/src/main/java/org/killbill/billing/util/callcontext/InternalCallContextFactory.java b/util/src/main/java/org/killbill/billing/util/callcontext/InternalCallContextFactory.java
index 947eae9..9a8abae 100644
--- a/util/src/main/java/org/killbill/billing/util/callcontext/InternalCallContextFactory.java
+++ b/util/src/main/java/org/killbill/billing/util/callcontext/InternalCallContextFactory.java
@@ -262,7 +262,7 @@ public class InternalCallContextFactory {
public InternalCallContext createInternalCallContext(final TimeZoneAwareEntity accountModelDao, final Long accountRecordId, final InternalCallContext context) {
// See DefaultImmutableAccountData implementation
final DateTimeZone fixedOffsetTimeZone = AccountDateTimeUtils.getFixedOffsetTimeZone(accountModelDao);
- final DateTime referenceTime = AccountDateTimeUtils.getReferenceDateTime(accountModelDao);
+ final DateTime referenceTime = accountModelDao.getReferenceTime();
populateMDCContext(accountRecordId, context.getTenantRecordId());
return new InternalCallContext(context, accountRecordId, fixedOffsetTimeZone, referenceTime, clock.getUTCNow());
}
diff --git a/util/src/main/java/org/killbill/billing/util/config/definition/InvoiceConfig.java b/util/src/main/java/org/killbill/billing/util/config/definition/InvoiceConfig.java
index 8170bd6..65ea53b 100644
--- a/util/src/main/java/org/killbill/billing/util/config/definition/InvoiceConfig.java
+++ b/util/src/main/java/org/killbill/billing/util/config/definition/InvoiceConfig.java
@@ -17,6 +17,8 @@
package org.killbill.billing.util.config.definition;
+import java.util.List;
+
import org.killbill.billing.callcontext.InternalTenantContext;
import org.skife.config.Config;
import org.skife.config.Default;
@@ -81,6 +83,16 @@ public interface InvoiceConfig extends KillbillConfig {
@Description("Maximum number of times the system will retry to grab global lock (with a 100ms wait each time)")
int getMaxGlobalLockRetries();
+ @Config("org.killbill.invoice.plugin")
+ @Default("")
+ @Description("Default invoice plugin names")
+ List<String> getInvoicePluginNames();
+
+ @Config("org.killbill.invoice.plugin")
+ @Default("")
+ @Description("Default invoice plugin names")
+ List<String> getInvoicePluginNames(@Param("dummy") final InternalTenantContext tenantContext);
+
@Config("org.killbill.invoice.emailNotificationsEnabled")
@Default("false")
@Description("Whether to send email notifications on invoice creation (for configured accounts)")
@@ -91,6 +103,16 @@ public interface InvoiceConfig extends KillbillConfig {
@Description("Whether the invoicing system is enabled")
boolean isInvoicingSystemEnabled();
+ @Config("org.killbill.invoice.parent.commit.local.utc.time")
+ @Default("23:59:59.999")
+ @Description("UTC Time when parent invoice gets committed")
+ String getParentAutoCommitUtcTime();
+
+ @Config("org.killbill.invoice.parent.commit.local.utc.time")
+ @Default("23:59:59.999")
+ @Description("UTC Time when parent invoice gets committed")
+ String getParentAutoCommitUtcTime(@Param("dummy") final InternalTenantContext tenantContext);
+
@Config("org.killbill.invoice.enabled")
@Default("true")
@Description("Whether the invoicing system is enabled")
diff --git a/util/src/main/java/org/killbill/billing/util/config/definition/SecurityConfig.java b/util/src/main/java/org/killbill/billing/util/config/definition/SecurityConfig.java
index cfd6a84..f0e5c99 100644
--- a/util/src/main/java/org/killbill/billing/util/config/definition/SecurityConfig.java
+++ b/util/src/main/java/org/killbill/billing/util/config/definition/SecurityConfig.java
@@ -41,6 +41,11 @@ public interface SecurityConfig extends KillbillConfig {
@Description("LDAP server's User DN format (e.g. uid={0},ou=users,dc=mycompany,dc=com)")
public String getShiroLDAPUserDnTemplate();
+ @Config("org.killbill.security.ldap.dnSearchTemplate")
+ @DefaultNull
+ @Description("LDAP server's DN search template (e.g. sAMAccountName={0}) for search-then-bind authentication (in case a static DN format template isn't enough)")
+ public String getShiroLDAPDnSearchTemplate();
+
@Config("org.killbill.security.ldap.searchBase")
@DefaultNull
@Description("LDAP search base to use")
@@ -87,4 +92,28 @@ public interface SecurityConfig extends KillbillConfig {
@Default("false")
@Description("Whether to ignore SSL certificates checks")
public boolean disableShiroLDAPSSLCheck();
+
+ @Config("org.killbill.security.ldap.followReferrals")
+ @Default("false")
+ @Description("Whether to follow referrals")
+ public boolean followShiroLDAPReferrals();
+
+ // Okta realm
+
+ @Config("org.killbill.security.okta.url")
+ @DefaultNull
+ @Description("Okta org full url")
+ public String getShiroOktaUrl();
+
+ @Config("org.killbill.security.okta.apiToken")
+ @DefaultNull
+ @Description("Okta API token")
+ public String getShiroOktaAPIToken();
+
+ @Config("org.killbill.security.okta.permissionsByGroup")
+ @Default("admin = *:*\n" +
+ "finance = invoice:*, payment:*\n" +
+ "support = entitlement:*, invoice:item_adjust")
+ @Description("Okta permissions by Okta group")
+ public String getShiroOktaPermissionsByGroup();
}
diff --git a/util/src/main/java/org/killbill/billing/util/config/tenant/MultiTenantConfigBase.java b/util/src/main/java/org/killbill/billing/util/config/tenant/MultiTenantConfigBase.java
index 39562ee..10c9e38 100644
--- a/util/src/main/java/org/killbill/billing/util/config/tenant/MultiTenantConfigBase.java
+++ b/util/src/main/java/org/killbill/billing/util/config/tenant/MultiTenantConfigBase.java
@@ -108,7 +108,11 @@ public abstract class MultiTenantConfigBase {
private List<String> getTokens(final Method method, final String value) {
final Separator separator = method.getAnnotation(Separator.class);
- return ImmutableList.copyOf(value.split(separator == null ? Separator.DEFAULT : separator.value()));
+ if (value == null || value.isEmpty()) {
+ return ImmutableList.of();
+ } else {
+ return ImmutableList.copyOf(value.split(separator == null ? Separator.DEFAULT : separator.value()));
+ }
}
protected Method getConfigStaticMethod(final String methodName) {
diff --git a/util/src/main/java/org/killbill/billing/util/customfield/api/DefaultCustomFieldUserApi.java b/util/src/main/java/org/killbill/billing/util/customfield/api/DefaultCustomFieldUserApi.java
index 23acc3e..45d033b 100644
--- a/util/src/main/java/org/killbill/billing/util/customfield/api/DefaultCustomFieldUserApi.java
+++ b/util/src/main/java/org/killbill/billing/util/customfield/api/DefaultCustomFieldUserApi.java
@@ -17,14 +17,11 @@
package org.killbill.billing.util.customfield.api;
import java.util.Collection;
-import java.util.HashMap;
-import java.util.LinkedList;
import java.util.List;
-import java.util.Map;
import java.util.UUID;
-import org.killbill.billing.ErrorCode;
import org.killbill.billing.ObjectType;
+import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.util.api.CustomFieldApiException;
import org.killbill.billing.util.api.CustomFieldUserApi;
import org.killbill.billing.util.callcontext.CallContext;
@@ -34,6 +31,7 @@ import org.killbill.billing.util.customfield.CustomField;
import org.killbill.billing.util.customfield.StringCustomField;
import org.killbill.billing.util.customfield.dao.CustomFieldDao;
import org.killbill.billing.util.customfield.dao.CustomFieldModelDao;
+import org.killbill.billing.util.customfield.dao.DefaultCustomFieldDao;
import org.killbill.billing.util.entity.Pagination;
import org.killbill.billing.util.entity.dao.DefaultPaginationHelper.SourcePaginationBuilder;
@@ -89,45 +87,50 @@ public class DefaultCustomFieldUserApi implements CustomFieldUserApi {
@Override
public void addCustomFields(final List<CustomField> customFields, final CallContext context) throws CustomFieldApiException {
- // TODO make it transactional
-
- final Map<UUID, ObjectType> mapping = new HashMap<UUID, ObjectType>();
- for (final CustomField cur : customFields) {
- mapping.put(cur.getObjectId(), cur.getObjectType());
+ if (!customFields.isEmpty()) {
+ final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(customFields.get(0).getObjectId(), customFields.get(0).getObjectType(), context);
+ final Iterable<CustomFieldModelDao> transformed = Iterables.transform(customFields, new Function<CustomField, CustomFieldModelDao>() {
+ @Override
+ public CustomFieldModelDao apply(final CustomField input) {
+ // Respect user-specified ID
+ // TODO See https://github.com/killbill/killbill/issues/35
+ if (input.getId() != null) {
+ return new CustomFieldModelDao(input.getId(), context.getCreatedDate(), context.getCreatedDate(), input.getFieldName(), input.getFieldValue(), input.getObjectId(), input.getObjectType());
+ } else {
+ return new CustomFieldModelDao(context.getCreatedDate(), input.getFieldName(), input.getFieldValue(), input.getObjectId(), input.getObjectType());
+ }
+ }
+ });
+ ((DefaultCustomFieldDao) customFieldDao).create(transformed, internalCallContext);
}
+ }
- final List<CustomFieldModelDao> all = new LinkedList<CustomFieldModelDao>();
- for (UUID cur : mapping.keySet()) {
- final ObjectType type = mapping.get(cur);
- all.addAll(customFieldDao.getCustomFieldsForObject(cur, type, internalCallContextFactory.createInternalCallContext(cur, type, context)));
- }
- final List<CustomField> toBeInserted = new LinkedList<CustomField>();
- for (final CustomField cur : customFields) {
- final CustomFieldModelDao match = Iterables.tryFind(all, new com.google.common.base.Predicate<CustomFieldModelDao>() {
+ @Override
+ public void updateCustomFields(final List<CustomField> customFields, final CallContext context) throws CustomFieldApiException {
+ if (!customFields.isEmpty()) {
+ final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(customFields.get(0).getObjectId(), customFields.get(0).getObjectType(), context);
+ final Iterable<CustomFieldModelDao> customFieldIds = Iterables.transform(customFields, new Function<CustomField, CustomFieldModelDao>() {
@Override
- public boolean apply(final CustomFieldModelDao input) {
- return input.getObjectId().equals(cur.getObjectId()) &&
- input.getObjectType() == cur.getObjectType() &&
- input.getFieldName().equals(cur.getFieldName());
-
+ public CustomFieldModelDao apply(final CustomField input) {
+ return new CustomFieldModelDao(input.getId(), internalCallContext.getCreatedDate(), internalCallContext.getUpdatedDate(), input.getFieldName(), input.getFieldValue(), input.getObjectId(), input.getObjectType());
}
- }).orNull();
- if (match != null) {
- throw new CustomFieldApiException(ErrorCode.CUSTOM_FIELD_ALREADY_EXISTS, match.getId());
- }
- toBeInserted.add(cur);
- }
+ });
+ customFieldDao.updateCustomFields(customFieldIds, internalCallContext);
- for (CustomField cur : toBeInserted) {
- customFieldDao.create(new CustomFieldModelDao(context.getCreatedDate(), cur.getFieldName(), cur.getFieldValue(), cur.getObjectId(), cur.getObjectType()), internalCallContextFactory.createInternalCallContext(cur.getObjectId(), cur.getObjectType(), context));
}
}
@Override
public void removeCustomFields(final List<CustomField> customFields, final CallContext context) throws CustomFieldApiException {
- // TODO make it transactional
- for (final CustomField cur : customFields) {
- customFieldDao.deleteCustomField(cur.getId(), internalCallContextFactory.createInternalCallContext(cur.getObjectId(), cur.getObjectType(), context));
+ if (!customFields.isEmpty()) {
+ final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(customFields.get(0).getObjectId(), customFields.get(0).getObjectType(), context);
+ final Iterable<UUID> curstomFieldIds = Iterables.transform(customFields, new Function<CustomField, UUID>() {
+ @Override
+ public UUID apply(final CustomField input) {
+ return input.getId();
+ }
+ });
+ customFieldDao.deleteCustomFields(curstomFieldIds, internalCallContext);
}
}
diff --git a/util/src/main/java/org/killbill/billing/util/customfield/dao/CustomFieldDao.java b/util/src/main/java/org/killbill/billing/util/customfield/dao/CustomFieldDao.java
index 977ddaa..6dea17a 100644
--- a/util/src/main/java/org/killbill/billing/util/customfield/dao/CustomFieldDao.java
+++ b/util/src/main/java/org/killbill/billing/util/customfield/dao/CustomFieldDao.java
@@ -37,5 +37,7 @@ public interface CustomFieldDao extends EntityDao<CustomFieldModelDao, CustomFie
public List<CustomFieldModelDao> getCustomFieldsForAccount(final InternalTenantContext context);
- void deleteCustomField(UUID customFieldId, InternalCallContext context) throws CustomFieldApiException;
+ void deleteCustomFields(Iterable<UUID> customFieldIds, InternalCallContext context) throws CustomFieldApiException;
+
+ void updateCustomFields(Iterable<CustomFieldModelDao> customFieldIds, InternalCallContext context) throws CustomFieldApiException;
}
diff --git a/util/src/main/java/org/killbill/billing/util/customfield/dao/CustomFieldModelDao.java b/util/src/main/java/org/killbill/billing/util/customfield/dao/CustomFieldModelDao.java
index 9f4371b..6147e25 100644
--- a/util/src/main/java/org/killbill/billing/util/customfield/dao/CustomFieldModelDao.java
+++ b/util/src/main/java/org/killbill/billing/util/customfield/dao/CustomFieldModelDao.java
@@ -25,6 +25,7 @@ import org.killbill.billing.util.customfield.CustomField;
import org.killbill.billing.util.dao.TableName;
import org.killbill.billing.util.entity.dao.EntityModelDao;
import org.killbill.billing.util.entity.dao.EntityModelDaoBase;
+import org.killbill.billing.util.tag.dao.TagModelDao;
public class CustomFieldModelDao extends EntityModelDaoBase implements EntityModelDao<CustomField> {
@@ -34,7 +35,6 @@ public class CustomFieldModelDao extends EntityModelDaoBase implements EntityMod
private ObjectType objectType;
private Boolean isActive;
-
public CustomFieldModelDao() { /* For the DAO mapper */ }
public CustomFieldModelDao(final UUID id, final DateTime createdDate, final DateTime updatedDate, final String fieldName,
@@ -137,6 +137,27 @@ public class CustomFieldModelDao extends EntityModelDaoBase implements EntityMod
return true;
}
+ public boolean isSame(final CustomFieldModelDao that) {
+ if (fieldName != null ? !fieldName.equals(that.fieldName) : that.fieldName != null) {
+ return false;
+ }
+ if (fieldValue != null ? !fieldValue.equals(that.fieldValue) : that.fieldValue != null) {
+ return false;
+ }
+ if (objectId != null ? !objectId.equals(that.objectId) : that.objectId != null) {
+ return false;
+ }
+ if (objectType != that.objectType) {
+ return false;
+ }
+ if (isActive != null ? !isActive.equals(that.isActive) : that.isActive != null) {
+ return false;
+ }
+ return true;
+ }
+
+
+
@Override
public int hashCode() {
int result = super.hashCode();
diff --git a/util/src/main/java/org/killbill/billing/util/customfield/dao/CustomFieldSqlDao.java b/util/src/main/java/org/killbill/billing/util/customfield/dao/CustomFieldSqlDao.java
index 53e589e..e3d88b2 100644
--- a/util/src/main/java/org/killbill/billing/util/customfield/dao/CustomFieldSqlDao.java
+++ b/util/src/main/java/org/killbill/billing/util/customfield/dao/CustomFieldSqlDao.java
@@ -19,11 +19,6 @@ package org.killbill.billing.util.customfield.dao;
import java.util.List;
import java.util.UUID;
-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.killbill.billing.ObjectType;
import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.callcontext.InternalTenantContext;
@@ -31,18 +26,29 @@ import org.killbill.billing.util.audit.ChangeType;
import org.killbill.billing.util.customfield.CustomField;
import org.killbill.billing.util.entity.dao.Audited;
import org.killbill.billing.util.entity.dao.EntitySqlDao;
-import org.killbill.billing.util.entity.dao.EntitySqlDaoStringTemplate;
+import org.killbill.commons.jdbi.binder.SmartBindBean;
+import org.killbill.commons.jdbi.template.KillBillSqlDaoStringTemplate;
+import org.skife.jdbi.v2.sqlobject.Bind;
+import org.skife.jdbi.v2.sqlobject.SqlQuery;
+import org.skife.jdbi.v2.sqlobject.SqlUpdate;
-@EntitySqlDaoStringTemplate
+@KillBillSqlDaoStringTemplate
public interface CustomFieldSqlDao extends EntitySqlDao<CustomFieldModelDao, CustomField> {
@SqlUpdate
+ @Audited(ChangeType.UPDATE)
+ void updateValue(@Bind("id") String customFieldId,
+ @Bind("fieldValue") String fieldValue,
+ @SmartBindBean InternalCallContext context);
+
+
+ @SqlUpdate
@Audited(ChangeType.DELETE)
void markTagAsDeleted(@Bind("id") String customFieldId,
- @BindBean InternalCallContext context);
+ @SmartBindBean InternalCallContext context);
@SqlQuery
List<CustomFieldModelDao> getCustomFieldsForObject(@Bind("objectId") UUID objectId,
@Bind("objectType") ObjectType objectType,
- @BindBean InternalTenantContext internalTenantContext);
+ @SmartBindBean InternalTenantContext internalTenantContext);
}
diff --git a/util/src/main/java/org/killbill/billing/util/customfield/dao/DefaultCustomFieldDao.java b/util/src/main/java/org/killbill/billing/util/customfield/dao/DefaultCustomFieldDao.java
index 742bf5d..d89595a 100644
--- a/util/src/main/java/org/killbill/billing/util/customfield/dao/DefaultCustomFieldDao.java
+++ b/util/src/main/java/org/killbill/billing/util/customfield/dao/DefaultCustomFieldDao.java
@@ -24,37 +24,38 @@ import java.util.UUID;
import javax.annotation.Nullable;
-import org.killbill.billing.util.callcontext.InternalCallContextFactory;
-import org.killbill.billing.util.entity.dao.DefaultPaginationSqlDaoHelper.Ordering;
-import org.skife.jdbi.v2.IDBI;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
import org.killbill.billing.BillingExceptionBase;
import org.killbill.billing.ErrorCode;
import org.killbill.billing.ObjectType;
-import org.killbill.bus.api.PersistentBus;
import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.callcontext.InternalTenantContext;
-import org.killbill.clock.Clock;
import org.killbill.billing.events.BusInternalEvent;
import org.killbill.billing.util.api.CustomFieldApiException;
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.customfield.CustomField;
import org.killbill.billing.util.customfield.api.DefaultCustomFieldCreationEvent;
import org.killbill.billing.util.customfield.api.DefaultCustomFieldDeletionEvent;
import org.killbill.billing.util.dao.NonEntityDao;
import org.killbill.billing.util.entity.Pagination;
+import org.killbill.billing.util.entity.dao.DefaultPaginationSqlDaoHelper.Ordering;
import org.killbill.billing.util.entity.dao.DefaultPaginationSqlDaoHelper.PaginationIteratorBuilder;
import org.killbill.billing.util.entity.dao.EntityDaoBase;
+import org.killbill.billing.util.entity.dao.EntitySqlDao;
import org.killbill.billing.util.entity.dao.EntitySqlDaoTransactionWrapper;
import org.killbill.billing.util.entity.dao.EntitySqlDaoTransactionalJdbiWrapper;
import org.killbill.billing.util.entity.dao.EntitySqlDaoWrapperFactory;
+import org.killbill.bus.api.PersistentBus;
+import org.killbill.clock.Clock;
+import org.skife.jdbi.v2.IDBI;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
import com.google.inject.Inject;
public class DefaultCustomFieldDao extends EntityDaoBase<CustomFieldModelDao, CustomField, CustomFieldApiException> implements CustomFieldDao {
@@ -103,21 +104,61 @@ public class DefaultCustomFieldDao extends EntityDaoBase<CustomFieldModelDao, Cu
}
@Override
- public void deleteCustomField(final UUID customFieldId, final InternalCallContext context) throws CustomFieldApiException {
+ public void deleteCustomFields(final Iterable<UUID> customFieldIds, final InternalCallContext context) throws CustomFieldApiException {
transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<Void>() {
@Override
public Void inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
final CustomFieldSqlDao sqlDao = entitySqlDaoWrapperFactory.become(CustomFieldSqlDao.class);
- final CustomFieldModelDao customField = sqlDao.getById(customFieldId.toString(), context);
- sqlDao.markTagAsDeleted(customFieldId.toString(), context);
+ for (final UUID cur : customFieldIds) {
+ final CustomFieldModelDao customField = sqlDao.getById(cur.toString(), context);
+ if (customField != null) {
+ sqlDao.markTagAsDeleted(cur.toString(), context);
+ postBusEventFromTransaction(customField, customField, ChangeType.DELETE, entitySqlDaoWrapperFactory, context);
+ }
+ }
+ return null;
+ }
+ });
+
+ }
+
+ @Override
+ public void updateCustomFields(final Iterable<CustomFieldModelDao> customFieldIds, final InternalCallContext context) throws CustomFieldApiException {
+
+ transactionalSqlDao.execute(CustomFieldApiException.class, new EntitySqlDaoTransactionWrapper<Void>() {
+
+ private void validateCustomField(final CustomFieldModelDao input, @Nullable CustomFieldModelDao existing) throws CustomFieldApiException {
+ if (existing == null) {
+ throw new CustomFieldApiException(ErrorCode.CUSTOM_FIELD_DOES_NOT_EXISTS_FOR_ID, input.getId());
+ }
+ if (input.getObjectId() != null & !input.getObjectId().equals(existing.getObjectId())) {
+ throw new CustomFieldApiException(ErrorCode.CUSTOM_FIELD_INVALID_UPDATE, input.getId(), input.getObjectId(), "ObjectId");
+ }
+ if (input.getObjectType() != null && input.getObjectType() != existing.getObjectType()) {
+ throw new CustomFieldApiException(ErrorCode.CUSTOM_FIELD_INVALID_UPDATE, input.getId(), input.getObjectType(), "ObjectType");
+ }
+ if (input.getFieldName() != null && !input.getFieldName().equals(existing.getFieldName())) {
+ throw new CustomFieldApiException(ErrorCode.CUSTOM_FIELD_INVALID_UPDATE, input.getId(), input.getFieldName(), "FieldName");
+ }
+ }
+
+ @Override
+ public Void inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
+ final CustomFieldSqlDao sqlDao = entitySqlDaoWrapperFactory.become(CustomFieldSqlDao.class);
+
+ for (final CustomFieldModelDao cur : customFieldIds) {
+ final CustomFieldModelDao customField = sqlDao.getById(cur.getId().toString(), context);
+
+ validateCustomField(cur, customField);
- postBusEventFromTransaction(customField, customField, ChangeType.DELETE, entitySqlDaoWrapperFactory, context);
+ sqlDao.updateValue(cur.getId().toString(), cur.getFieldValue(), context);
+ postBusEventFromTransaction(customField, customField, ChangeType.UPDATE, entitySqlDaoWrapperFactory, context);
+ }
return null;
}
});
-
}
@Override
@@ -126,6 +167,18 @@ public class DefaultCustomFieldDao extends EntityDaoBase<CustomFieldModelDao, Cu
}
@Override
+ protected boolean checkEntityAlreadyExists(final EntitySqlDao<CustomFieldModelDao, CustomField> transactional, final CustomFieldModelDao entity, final InternalCallContext context) {
+ return Iterables.find(transactional.getByAccountRecordId(context),
+ new Predicate<CustomFieldModelDao>() {
+ @Override
+ public boolean apply(final CustomFieldModelDao existingCustomField) {
+ return entity.isSame(existingCustomField);
+ }
+ },
+ null) != null;
+ }
+
+ @Override
protected void postBusEventFromTransaction(final CustomFieldModelDao customField, final CustomFieldModelDao savedCustomField, final ChangeType changeType,
final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory, final InternalCallContext context)
throws BillingExceptionBase {
diff --git a/util/src/main/java/org/killbill/billing/util/dao/AuditSqlDao.java b/util/src/main/java/org/killbill/billing/util/dao/AuditSqlDao.java
index 7ca13e0..e9cf5b8 100644
--- a/util/src/main/java/org/killbill/billing/util/dao/AuditSqlDao.java
+++ b/util/src/main/java/org/killbill/billing/util/dao/AuditSqlDao.java
@@ -21,8 +21,9 @@ package org.killbill.billing.util.dao;
import java.util.Iterator;
import java.util.List;
+import org.killbill.commons.jdbi.template.KillBillSqlDaoStringTemplate;
import org.skife.jdbi.v2.sqlobject.Bind;
-import org.skife.jdbi.v2.sqlobject.BindBean;
+import org.killbill.commons.jdbi.binder.SmartBindBean;
import org.skife.jdbi.v2.sqlobject.SqlQuery;
import org.skife.jdbi.v2.sqlobject.SqlUpdate;
import org.skife.jdbi.v2.sqlobject.customizers.Define;
@@ -34,7 +35,6 @@ import org.killbill.billing.util.audit.dao.AuditLogModelDao;
import org.killbill.billing.util.cache.Cachable;
import org.killbill.billing.util.cache.Cachable.CacheType;
import org.killbill.billing.util.cache.CachableKey;
-import org.killbill.billing.util.entity.dao.EntitySqlDaoStringTemplate;
/**
* Note 1: cache invalidation has to happen for audit logs (which is tricky in the multi-nodes scenario).
@@ -45,33 +45,33 @@ import org.killbill.billing.util.entity.dao.EntitySqlDaoStringTemplate;
* <p/>
* Note 2: in the queries below, tableName always refers to the TableName enum, not the actual table name (TableName.getTableName()).
*/
-@EntitySqlDaoStringTemplate("/org/killbill/billing/util/entity/dao/EntitySqlDao.sql.stg")
+@KillBillSqlDaoStringTemplate("/org/killbill/billing/util/entity/dao/EntitySqlDao.sql.stg")
// Note: @RegisterMapper annotation won't work here as we build the SqlObject via EntitySqlDao (annotations won't be inherited for JDBI)
public interface AuditSqlDao {
@SqlUpdate
- public void insertAuditFromTransaction(@BindBean final EntityAudit audit,
- @BindBean final InternalCallContext context);
+ public void insertAuditFromTransaction(@SmartBindBean final EntityAudit audit,
+ @SmartBindBean final InternalCallContext context);
@SqlQuery
@SmartFetchSize(shouldStream = true)
- public Iterator<AuditLogModelDao> getAuditLogsForAccountRecordId(@BindBean final InternalTenantContext context);
+ public Iterator<AuditLogModelDao> getAuditLogsForAccountRecordId(@SmartBindBean final InternalTenantContext context);
@SqlQuery
@SmartFetchSize(shouldStream = true)
public Iterator<AuditLogModelDao> getAuditLogsForTableNameAndAccountRecordId(@Bind("tableName") final String tableName,
- @BindBean final InternalTenantContext context);
+ @SmartBindBean final InternalTenantContext context);
@SqlQuery
@Cachable(CacheType.AUDIT_LOG)
public List<AuditLogModelDao> getAuditLogsForTargetRecordId(@CachableKey(1) @Bind("tableName") final String tableName,
@CachableKey(2) @Bind("targetRecordId") final long targetRecordId,
- @BindBean final InternalTenantContext context);
+ @SmartBindBean final InternalTenantContext context);
@SqlQuery
@Cachable(CacheType.AUDIT_LOG_VIA_HISTORY)
public List<AuditLogModelDao> getAuditLogsViaHistoryForTargetRecordId(@CachableKey(1) @Bind("tableName") final String historyTableName, /* Uppercased - used to find entries in audit_log table */
@CachableKey(2) @Define("historyTableName") final String actualHistoryTableName, /* Actual table name, used in the inner join query */
@CachableKey(3) @Bind("targetRecordId") final long targetRecordId,
- @BindBean final InternalTenantContext context);
+ @SmartBindBean final InternalTenantContext context);
}
diff --git a/util/src/main/java/org/killbill/billing/util/dao/EntityHistoryBinder.java b/util/src/main/java/org/killbill/billing/util/dao/EntityHistoryBinder.java
index e5afcf6..42d7ceb 100644
--- a/util/src/main/java/org/killbill/billing/util/dao/EntityHistoryBinder.java
+++ b/util/src/main/java/org/killbill/billing/util/dao/EntityHistoryBinder.java
@@ -53,7 +53,7 @@ public @interface EntityHistoryBinder {
@Override
public void bind(final SQLStatement<?> q, final EntityHistoryBinder bind, final EntityHistoryModelDao<M, E> history) {
try {
- // Emulate @BindBean
+ // Emulate @SmartBindBean
final M arg = history.getEntity();
final BeanInfo infos = Introspector.getBeanInfo(arg.getClass());
final PropertyDescriptor[] props = infos.getPropertyDescriptors();
diff --git a/util/src/main/java/org/killbill/billing/util/dao/HistorySqlDao.java b/util/src/main/java/org/killbill/billing/util/dao/HistorySqlDao.java
index 5f6d2d1..e1f7cee 100644
--- a/util/src/main/java/org/killbill/billing/util/dao/HistorySqlDao.java
+++ b/util/src/main/java/org/killbill/billing/util/dao/HistorySqlDao.java
@@ -24,7 +24,7 @@ import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.util.entity.Entity;
import org.killbill.billing.util.entity.dao.EntityModelDao;
import org.skife.jdbi.v2.sqlobject.Bind;
-import org.skife.jdbi.v2.sqlobject.BindBean;
+import org.killbill.commons.jdbi.binder.SmartBindBean;
import org.skife.jdbi.v2.sqlobject.GetGeneratedKeys;
import org.skife.jdbi.v2.sqlobject.SqlQuery;
import org.skife.jdbi.v2.sqlobject.SqlUpdate;
@@ -33,9 +33,9 @@ public interface HistorySqlDao<M extends EntityModelDao<E>, E extends Entity> {
@SqlQuery
public List<EntityHistoryModelDao<M, E>> getHistoryForTargetRecordId(@Bind("targetRecordId") final long targetRecordId,
- @BindBean InternalCallContext context);
+ @SmartBindBean InternalCallContext context);
@SqlUpdate
@GetGeneratedKeys
public Long addHistoryFromTransaction(@EntityHistoryBinder EntityHistoryModelDao<M, E> history,
- @BindBean InternalCallContext context);
+ @SmartBindBean InternalCallContext context);
}
diff --git a/util/src/main/java/org/killbill/billing/util/dao/NonEntitySqlDao.java b/util/src/main/java/org/killbill/billing/util/dao/NonEntitySqlDao.java
index 798b334..650f052 100644
--- a/util/src/main/java/org/killbill/billing/util/dao/NonEntitySqlDao.java
+++ b/util/src/main/java/org/killbill/billing/util/dao/NonEntitySqlDao.java
@@ -21,15 +21,15 @@ package org.killbill.billing.util.dao;
import java.util.UUID;
import org.killbill.billing.callcontext.InternalTenantContext;
-import org.killbill.billing.util.entity.dao.EntitySqlDaoStringTemplate;
+import org.killbill.commons.jdbi.template.KillBillSqlDaoStringTemplate;
import org.skife.jdbi.v2.sqlobject.Bind;
-import org.skife.jdbi.v2.sqlobject.BindBean;
+import org.killbill.commons.jdbi.binder.SmartBindBean;
import org.skife.jdbi.v2.sqlobject.SqlQuery;
import org.skife.jdbi.v2.sqlobject.customizers.Define;
import org.skife.jdbi.v2.sqlobject.mixins.CloseMe;
import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
-@EntitySqlDaoStringTemplate
+@KillBillSqlDaoStringTemplate
public interface NonEntitySqlDao extends Transactional<NonEntitySqlDao>, CloseMe {
@SqlQuery
@@ -62,19 +62,19 @@ public interface NonEntitySqlDao extends Transactional<NonEntitySqlDao>, CloseMe
@SqlQuery
public Iterable<RecordIdIdMappings> getHistoryRecordIdIdMappings(@Define("tableName") String tableName,
@Define("historyTableName") String historyTableName,
- @BindBean final InternalTenantContext context);
+ @SmartBindBean final InternalTenantContext context);
@SqlQuery
public Iterable<RecordIdIdMappings> getHistoryRecordIdIdMappingsForAccountsTable(@Define("tableName") String tableName,
@Define("historyTableName") String historyTableName,
- @BindBean final InternalTenantContext context);
+ @SmartBindBean final InternalTenantContext context);
@SqlQuery
public Iterable<RecordIdIdMappings> getHistoryRecordIdIdMappingsForTablesWithoutAccountRecordId(@Define("tableName") String tableName,
@Define("historyTableName") String historyTableName,
- @BindBean final InternalTenantContext context);
+ @SmartBindBean final InternalTenantContext context);
@SqlQuery
public Iterable<RecordIdIdMappings> getRecordIdIdMappings(@Define("tableName") String tableName,
- @BindBean final InternalTenantContext context);
+ @SmartBindBean final InternalTenantContext context);
}
diff --git a/util/src/main/java/org/killbill/billing/util/entity/dao/EntityDaoBase.java b/util/src/main/java/org/killbill/billing/util/entity/dao/EntityDaoBase.java
index f06589f..8f02941 100644
--- a/util/src/main/java/org/killbill/billing/util/entity/dao/EntityDaoBase.java
+++ b/util/src/main/java/org/killbill/billing/util/entity/dao/EntityDaoBase.java
@@ -18,7 +18,11 @@
package org.killbill.billing.util.entity.dao;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
import java.util.Iterator;
+import java.util.List;
import java.util.UUID;
import org.killbill.billing.BillingExceptionBase;
@@ -32,39 +36,78 @@ import org.killbill.billing.util.entity.Pagination;
import org.killbill.billing.util.entity.dao.DefaultPaginationSqlDaoHelper.Ordering;
import org.killbill.billing.util.entity.dao.DefaultPaginationSqlDaoHelper.PaginationIteratorBuilder;
+import com.google.common.collect.ImmutableList;
+
public abstract class EntityDaoBase<M extends EntityModelDao<E>, E extends Entity, U extends BillingExceptionBase> implements EntityDao<M, E, U> {
protected final EntitySqlDaoTransactionalJdbiWrapper transactionalSqlDao;
protected final DefaultPaginationSqlDaoHelper paginationHelper;
private final Class<? extends EntitySqlDao<M, E>> realSqlDao;
+ private final Class<U> targetExceptionClass;
public EntityDaoBase(final EntitySqlDaoTransactionalJdbiWrapper transactionalSqlDao, final Class<? extends EntitySqlDao<M, E>> realSqlDao) {
this.transactionalSqlDao = transactionalSqlDao;
this.realSqlDao = realSqlDao;
this.paginationHelper = new DefaultPaginationSqlDaoHelper(transactionalSqlDao);
+
+ try {
+ final Type genericSuperclass = this.getClass().getGenericSuperclass();
+ targetExceptionClass = (genericSuperclass instanceof ParameterizedType) ?
+ (Class<U>) Class.forName(((ParameterizedType) genericSuperclass).getActualTypeArguments()[2].getTypeName()) :
+ null; // Mock class
+ } catch (final ClassNotFoundException e) {
+ throw new RuntimeException(e);
+ }
}
@Override
public void create(final M entity, final InternalCallContext context) throws U {
- final M refreshedEntity = transactionalSqlDao.execute(getCreateEntitySqlDaoTransactionWrapper(entity, context));
+ final M refreshedEntity = transactionalSqlDao.execute(targetExceptionClass, getCreateEntitySqlDaoTransactionWrapper(entity, context));
// Populate the caches only after the transaction has been committed, in case of rollbacks
transactionalSqlDao.populateCaches(refreshedEntity);
}
- protected EntitySqlDaoTransactionWrapper<M> getCreateEntitySqlDaoTransactionWrapper(final M entity, final InternalCallContext context) {
- return new EntitySqlDaoTransactionWrapper<M>() {
+
+ public void create(final Iterable<M> entities, final InternalCallContext context) throws U {
+ final List<M> refreshedEntities = transactionalSqlDao.execute(targetExceptionClass, getCreateEntitySqlDaoTransactionWrapper(entities, context));
+ // Populate the caches only after the transaction has been committed, in case of rollbacks
+ for (M refreshedEntity : refreshedEntities) {
+ transactionalSqlDao.populateCaches(refreshedEntity);
+ }
+ }
+
+
+ protected EntitySqlDaoTransactionWrapper<List<M>> getCreateEntitySqlDaoTransactionWrapper(final Iterable<M> entities, final InternalCallContext context) {
+
+ final List<M> result = new ArrayList<M>();
+ return new EntitySqlDaoTransactionWrapper<List<M>>() {
@Override
- public M inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
+ public List<M> inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
+
final EntitySqlDao<M, E> transactional = entitySqlDaoWrapperFactory.become(realSqlDao);
- if (checkEntityAlreadyExists(transactional, entity, context)) {
- throw generateAlreadyExistsException(entity, context);
+ for (M entity : entities) {
+ if (checkEntityAlreadyExists(transactional, entity, context)) {
+ throw generateAlreadyExistsException(entity, context);
+ }
+ final M refreshedEntity = createAndRefresh(transactional, entity, context);
+ result.add(refreshedEntity);
+ postBusEventFromTransaction(entity, refreshedEntity, ChangeType.INSERT, entitySqlDaoWrapperFactory, context);
}
- final M refreshedEntity = createAndRefresh(transactional, entity, context);
+ return result;
+ }
+ };
+ }
- postBusEventFromTransaction(entity, refreshedEntity, ChangeType.INSERT, entitySqlDaoWrapperFactory, context);
- return refreshedEntity;
+
+ protected EntitySqlDaoTransactionWrapper<M> getCreateEntitySqlDaoTransactionWrapper(final M entity, final InternalCallContext context) {
+ final EntitySqlDaoTransactionWrapper<List<M>> entityWrapperList = getCreateEntitySqlDaoTransactionWrapper(ImmutableList.<M>of(entity), context);
+ return new EntitySqlDaoTransactionWrapper<M>() {
+ @Override
+ public M inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
+ final List<M> result = entityWrapperList.inTransaction(entitySqlDaoWrapperFactory);
+ return result.isEmpty() ? null : result.get(0);
}
};
}
diff --git a/util/src/main/java/org/killbill/billing/util/entity/dao/EntitySqlDao.java b/util/src/main/java/org/killbill/billing/util/entity/dao/EntitySqlDao.java
index ede762c..a4ddb91 100644
--- a/util/src/main/java/org/killbill/billing/util/entity/dao/EntitySqlDao.java
+++ b/util/src/main/java/org/killbill/billing/util/entity/dao/EntitySqlDao.java
@@ -32,40 +32,41 @@ import org.killbill.billing.util.dao.AuditSqlDao;
import org.killbill.billing.util.dao.HistorySqlDao;
import org.killbill.billing.util.entity.Entity;
import org.killbill.commons.jdbi.statement.SmartFetchSize;
+import org.killbill.commons.jdbi.template.KillBillSqlDaoStringTemplate;
import org.skife.jdbi.v2.sqlobject.Bind;
-import org.skife.jdbi.v2.sqlobject.BindBean;
+import org.killbill.commons.jdbi.binder.SmartBindBean;
import org.skife.jdbi.v2.sqlobject.SqlQuery;
import org.skife.jdbi.v2.sqlobject.SqlUpdate;
import org.skife.jdbi.v2.sqlobject.customizers.Define;
import org.skife.jdbi.v2.sqlobject.mixins.CloseMe;
import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
-@EntitySqlDaoStringTemplate
+@KillBillSqlDaoStringTemplate
public interface EntitySqlDao<M extends EntityModelDao<E>, E extends Entity> extends AuditSqlDao, HistorySqlDao<M, E>, Transactional<EntitySqlDao<M, E>>, CloseMe {
@SqlUpdate
@Audited(ChangeType.INSERT)
- public Object create(@BindBean final M entity,
- @BindBean final InternalCallContext context) throws EntityPersistenceException;
+ public Object create(@SmartBindBean final M entity,
+ @SmartBindBean final InternalCallContext context) throws EntityPersistenceException;
@SqlQuery
public M getById(@Bind("id") final String id,
- @BindBean final InternalTenantContext context);
+ @SmartBindBean final InternalTenantContext context);
@SqlQuery
public M getByRecordId(@Bind("recordId") final Long recordId,
- @BindBean final InternalTenantContext context);
+ @SmartBindBean final InternalTenantContext context);
@SqlQuery
- public List<M> getByAccountRecordId(@BindBean final InternalTenantContext context);
+ public List<M> getByAccountRecordId(@SmartBindBean final InternalTenantContext context);
@SqlQuery
- public List<M> getByAccountRecordIdIncludedDeleted(@BindBean final InternalTenantContext context);
+ public List<M> getByAccountRecordIdIncludedDeleted(@SmartBindBean final InternalTenantContext context);
@SqlQuery
@Cachable(CacheType.RECORD_ID)
public Long getRecordId(@CachableKey(1) @Bind("id") final String id,
- @BindBean final InternalTenantContext context);
+ @SmartBindBean final InternalTenantContext context);
@SqlQuery
@SmartFetchSize(shouldStream = true)
@@ -74,16 +75,16 @@ public interface EntitySqlDao<M extends EntityModelDao<E>, E extends Entity> ext
@Bind("offset") final Long offset,
@Bind("rowCount") final Long rowCount,
@Define("ordering") final String ordering,
- @BindBean final InternalTenantContext context);
+ @SmartBindBean final InternalTenantContext context);
@SqlQuery
public Long getSearchCount(@Bind("searchKey") final String searchKey,
@Bind("likeSearchKey") final String likeSearchKey,
- @BindBean final InternalTenantContext context);
+ @SmartBindBean final InternalTenantContext context);
@SqlQuery
@SmartFetchSize(shouldStream = true)
- public Iterator<M> getAll(@BindBean final InternalTenantContext context);
+ public Iterator<M> getAll(@SmartBindBean final InternalTenantContext context);
@SqlQuery
@SmartFetchSize(shouldStream = true)
@@ -91,11 +92,11 @@ public interface EntitySqlDao<M extends EntityModelDao<E>, E extends Entity> ext
@Bind("rowCount") final Long rowCount,
@Define("orderBy") final String orderBy,
@Define("ordering") final String ordering,
- @BindBean final InternalTenantContext context);
+ @SmartBindBean final InternalTenantContext context);
@SqlQuery
- public Long getCount(@BindBean final InternalTenantContext context);
+ public Long getCount(@SmartBindBean final InternalTenantContext context);
@SqlUpdate
- public void test(@BindBean final InternalTenantContext context);
+ public void test(@SmartBindBean final InternalTenantContext context);
}
diff --git a/util/src/main/java/org/killbill/billing/util/entity/dao/EntitySqlDaoTransactionalJdbiWrapper.java b/util/src/main/java/org/killbill/billing/util/entity/dao/EntitySqlDaoTransactionalJdbiWrapper.java
index b16601d..0366e3a 100644
--- a/util/src/main/java/org/killbill/billing/util/entity/dao/EntitySqlDaoTransactionalJdbiWrapper.java
+++ b/util/src/main/java/org/killbill/billing/util/entity/dao/EntitySqlDaoTransactionalJdbiWrapper.java
@@ -18,6 +18,8 @@
package org.killbill.billing.util.entity.dao;
+import javax.annotation.Nullable;
+
import org.killbill.billing.util.cache.CacheControllerDispatcher;
import org.killbill.billing.util.callcontext.InternalCallContextFactory;
import org.killbill.billing.util.dao.NonEntityDao;
@@ -102,11 +104,11 @@ public class EntitySqlDaoTransactionalJdbiWrapper {
* @param <E> checked exception which can be thrown from the transaction
* @return result from the transaction fo type ReturnType
*/
- public <ReturnType, E extends Exception> ReturnType execute(final Class<E> exception, final EntitySqlDaoTransactionWrapper<ReturnType> entitySqlDaoTransactionWrapper) throws E {
+ public <ReturnType, E extends Exception> ReturnType execute(@Nullable final Class<E> exception, final EntitySqlDaoTransactionWrapper<ReturnType> entitySqlDaoTransactionWrapper) throws E {
try {
return execute(entitySqlDaoTransactionWrapper);
} catch (RuntimeException e) {
- if (e.getCause() != null && e.getCause().getClass().isAssignableFrom(exception)) {
+ if (e.getCause() != null && exception != null && e.getCause().getClass().isAssignableFrom(exception)) {
throw (E) e.getCause();
} else if (e.getCause() != null && e.getCause() instanceof RuntimeException) {
throw (RuntimeException) e.getCause();
diff --git a/util/src/main/java/org/killbill/billing/util/entity/dao/EntitySqlDaoWrapperInvocationHandler.java b/util/src/main/java/org/killbill/billing/util/entity/dao/EntitySqlDaoWrapperInvocationHandler.java
index 654d0da..f6ba247 100644
--- a/util/src/main/java/org/killbill/billing/util/entity/dao/EntitySqlDaoWrapperInvocationHandler.java
+++ b/util/src/main/java/org/killbill/billing/util/entity/dao/EntitySqlDaoWrapperInvocationHandler.java
@@ -32,6 +32,7 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
import javax.annotation.Nullable;
@@ -51,7 +52,6 @@ import org.killbill.billing.util.dao.EntityHistoryModelDao;
import org.killbill.billing.util.dao.NonEntityDao;
import org.killbill.billing.util.dao.TableName;
import org.killbill.billing.util.entity.Entity;
-import org.killbill.billing.util.tag.dao.UUIDCollectionBinder;
import org.killbill.clock.Clock;
import org.killbill.commons.profiling.Profiling;
import org.killbill.commons.profiling.Profiling.WithProfilingCallback;
@@ -62,6 +62,7 @@ import org.skife.jdbi.v2.StatementContext;
import org.skife.jdbi.v2.exceptions.DBIException;
import org.skife.jdbi.v2.exceptions.StatementException;
import org.skife.jdbi.v2.sqlobject.Bind;
+import org.skife.jdbi.v2.unstable.BindIn;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -84,6 +85,8 @@ public class EntitySqlDaoWrapperInvocationHandler<S extends EntitySqlDao<M, E>,
private final Logger logger = LoggerFactory.getLogger(EntitySqlDaoWrapperInvocationHandler.class);
+ private final Map<String, Annotation[][]> parameterAnnotationsByMethod = new ConcurrentHashMap<String, Annotation[][]>();
+
private final Class<S> sqlDaoClass;
private final S sqlDao;
private final Handle handle;
@@ -227,7 +230,7 @@ public class EntitySqlDaoWrapperInvocationHandler<S extends EntitySqlDao<M, E>,
if (cache != null && objectType != null) {
// Find all arguments marked with @CachableKey
final Map<Integer, Object> keyPieces = new LinkedHashMap<Integer, Object>();
- final Annotation[][] annotations = method.getParameterAnnotations();
+ final Annotation[][] annotations = getAnnotations(method);
for (int i = 0; i < annotations.length; i++) {
for (int j = 0; j < annotations[i].length; j++) {
final Annotation annotation = annotations[i][j];
@@ -408,7 +411,8 @@ public class EntitySqlDaoWrapperInvocationHandler<S extends EntitySqlDao<M, E>,
}
private List<String> retrieveEntityIdsFromArguments(final Method method, final Object[] args) {
- final Annotation[][] parameterAnnotations = method.getParameterAnnotations();
+ final Annotation[][] parameterAnnotations = getAnnotations(method);
+
int i = -1;
for (final Object arg : args) {
i++;
@@ -430,7 +434,7 @@ public class EntitySqlDaoWrapperInvocationHandler<S extends EntitySqlDao<M, E>,
for (final Annotation annotation : parameterAnnotations[i]) {
if (arg instanceof String && Bind.class.equals(annotation.annotationType()) && ("id").equals(((Bind) annotation).value())) {
return ImmutableList.<String>of((String) arg);
- } else if (arg instanceof Collection && UUIDCollectionBinder.class.equals(annotation.annotationType())) {
+ } else if (arg instanceof Collection && BindIn.class.equals(annotation.annotationType()) && ("ids").equals(((BindIn) annotation).value())) {
return ImmutableList.<String>copyOf((Collection) arg);
}
}
@@ -438,6 +442,19 @@ public class EntitySqlDaoWrapperInvocationHandler<S extends EntitySqlDao<M, E>,
return ImmutableList.<String>of();
}
+ private Annotation[][] getAnnotations(final Method method) {
+ // Expensive to compute
+ final String methodString = method.toString();
+
+ // Method.getParameterAnnotations() generates lots of garbage objects
+ Annotation[][] parameterAnnotations = parameterAnnotationsByMethod.get(methodString);
+ if (parameterAnnotations == null) {
+ parameterAnnotations = method.getParameterAnnotations();
+ parameterAnnotationsByMethod.put(methodString, parameterAnnotations);
+ }
+ return parameterAnnotations;
+ }
+
private Builder<String> extractEntityIdsFromBatchArgument(final Iterable arg) {
final Iterator iterator = arg.iterator();
final Builder<String> entityIds = new Builder<String>();
diff --git a/util/src/main/java/org/killbill/billing/util/entity/dao/TimeZoneAwareEntity.java b/util/src/main/java/org/killbill/billing/util/entity/dao/TimeZoneAwareEntity.java
index feff107..6e0813b 100644
--- a/util/src/main/java/org/killbill/billing/util/entity/dao/TimeZoneAwareEntity.java
+++ b/util/src/main/java/org/killbill/billing/util/entity/dao/TimeZoneAwareEntity.java
@@ -17,10 +17,13 @@
package org.killbill.billing.util.entity.dao;
+import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.killbill.billing.util.entity.Entity;
public interface TimeZoneAwareEntity extends Entity {
+ public DateTime getReferenceTime();
+
public DateTimeZone getTimeZone();
}
diff --git a/util/src/main/java/org/killbill/billing/util/export/dao/DatabaseExportDao.java b/util/src/main/java/org/killbill/billing/util/export/dao/DatabaseExportDao.java
index 1f37627..0f99263 100644
--- a/util/src/main/java/org/killbill/billing/util/export/dao/DatabaseExportDao.java
+++ b/util/src/main/java/org/killbill/billing/util/export/dao/DatabaseExportDao.java
@@ -16,6 +16,7 @@
package org.killbill.billing.util.export.dao;
+import java.sql.Blob;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -160,6 +161,17 @@ public class DatabaseExportDao {
try {
while (iterator.hasNext()) {
final Map<String, Object> row = iterator.next();
+
+ for (final String k : row.keySet()) {
+ final Object value = row.get(k);
+ // For h2, transform a JdbcBlob into a byte[]
+ // See also LowerToCamelBeanMapper
+ if (value instanceof Blob) {
+ final Blob blob = (Blob) value;
+ row.put(k, blob.getBytes(0, (int) blob.length()));
+ }
+ }
+
out.write(row);
}
} finally {
diff --git a/util/src/main/java/org/killbill/billing/util/glue/CacheProviderBase.java b/util/src/main/java/org/killbill/billing/util/glue/CacheProviderBase.java
index 24f2dbf..222dc7f 100644
--- a/util/src/main/java/org/killbill/billing/util/glue/CacheProviderBase.java
+++ b/util/src/main/java/org/killbill/billing/util/glue/CacheProviderBase.java
@@ -32,6 +32,7 @@ import com.codahale.metrics.Metric;
import com.codahale.metrics.MetricFilter;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.jcache.JCacheGaugeSet;
+import com.google.common.base.Preconditions;
abstract class CacheProviderBase {
@@ -61,6 +62,7 @@ abstract class CacheProviderBase {
final MutableConfiguration<K, V> configuration = new MutableConfiguration<K, V>().setTypes(keyType, valueType)
.setStoreByValue(false); // Store by reference to avoid copying large objects (e.g. catalog)
final Cache<K, V> cache = cacheManager.createCache(cacheName, configuration);
+ Preconditions.checkState(!cache.isClosed(), "Cache '%s' should not be closed", cacheName);
// Re-create the metrics to support dynamically created caches (e.g. for Shiro)
metricRegistry.removeMatching(new MetricFilter() {
diff --git a/util/src/main/java/org/killbill/billing/util/glue/Eh107CacheManagerProvider.java b/util/src/main/java/org/killbill/billing/util/glue/Eh107CacheManagerProvider.java
index 005afa3..d5de5cc 100644
--- a/util/src/main/java/org/killbill/billing/util/glue/Eh107CacheManagerProvider.java
+++ b/util/src/main/java/org/killbill/billing/util/glue/Eh107CacheManagerProvider.java
@@ -27,6 +27,7 @@ import javax.cache.spi.CachingProvider;
import javax.inject.Inject;
import javax.inject.Provider;
+import org.ehcache.core.spi.store.InternalCacheManager;
import org.killbill.billing.util.cache.BaseCacheLoader;
import org.killbill.billing.util.config.definition.EhCacheConfig;
import org.slf4j.Logger;
@@ -38,6 +39,7 @@ import com.codahale.metrics.MetricRegistry;
public class Eh107CacheManagerProvider extends CacheProviderBase implements Provider<CacheManager> {
private static final Logger logger = LoggerFactory.getLogger(Eh107CacheManagerProvider.class);
+ private static final EhcacheLoggingListener ehcacheLoggingListener = new EhcacheLoggingListener();
private final Set<BaseCacheLoader> cacheLoaders;
@@ -65,6 +67,10 @@ public class Eh107CacheManagerProvider extends CacheProviderBase implements Prov
cacheManager = cachingProvider.getCacheManager();
}
+ // Make sure we start from a clean state - this is mainly useful for tests
+ cacheManager.unwrap(InternalCacheManager.class).deregisterListener(ehcacheLoggingListener);
+ cacheManager.unwrap(InternalCacheManager.class).registerListener(ehcacheLoggingListener);
+
for (final BaseCacheLoader<?, ?> cacheLoader : cacheLoaders) {
createCache(cacheManager,
cacheLoader.getCacheType().getCacheName(),
diff --git a/util/src/main/java/org/killbill/billing/util/glue/IDBISetup.java b/util/src/main/java/org/killbill/billing/util/glue/IDBISetup.java
new file mode 100644
index 0000000..0e78d66
--- /dev/null
+++ b/util/src/main/java/org/killbill/billing/util/glue/IDBISetup.java
@@ -0,0 +1,94 @@
+/*
+ * 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.util.glue;
+
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.List;
+
+import org.killbill.billing.lifecycle.ServiceFinder;
+import org.killbill.billing.util.broadcast.dao.BroadcastModelDao;
+import org.killbill.billing.util.dao.AuditLogModelDaoMapper;
+import org.killbill.billing.util.dao.EntityHistoryModelDaoMapperFactory;
+import org.killbill.billing.util.dao.RecordIdIdMappingsMapper;
+import org.killbill.billing.util.entity.Entity;
+import org.killbill.billing.util.entity.dao.EntitySqlDao;
+import org.killbill.billing.util.nodes.dao.NodeInfoModelDao;
+import org.killbill.billing.util.security.shiro.dao.RolesPermissionsModelDao;
+import org.killbill.billing.util.security.shiro.dao.SessionModelDao;
+import org.killbill.billing.util.security.shiro.dao.UserModelDao;
+import org.killbill.billing.util.security.shiro.dao.UserRolesModelDao;
+import org.killbill.billing.util.validation.dao.DatabaseSchemaSqlDao;
+import org.killbill.bus.dao.BusEventModelDao;
+import org.killbill.commons.jdbi.mapper.LowerToCamelBeanMapperFactory;
+import org.killbill.notificationq.dao.NotificationEventModelDao;
+import org.skife.jdbi.v2.ResultSetMapperFactory;
+import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
+import org.skife.jdbi.v2.tweak.ResultSetMapper;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableList.Builder;
+
+public class IDBISetup {
+
+ public static List<? extends ResultSetMapperFactory> mapperFactoriesToRegister() {
+ final Builder<ResultSetMapperFactory> builder = ImmutableList.<ResultSetMapperFactory>builder();
+ builder.add(new LowerToCamelBeanMapperFactory(SessionModelDao.class));
+ builder.add(new LowerToCamelBeanMapperFactory(BroadcastModelDao.class));
+ builder.add(new LowerToCamelBeanMapperFactory(NodeInfoModelDao.class));
+ builder.add(new LowerToCamelBeanMapperFactory(UserModelDao.class));
+ builder.add(new LowerToCamelBeanMapperFactory(UserRolesModelDao.class));
+ builder.add(new LowerToCamelBeanMapperFactory(RolesPermissionsModelDao.class));
+ builder.add(new LowerToCamelBeanMapperFactory(BusEventModelDao.class));
+ builder.add(new LowerToCamelBeanMapperFactory(NotificationEventModelDao.class));
+
+ final ServiceFinder<EntitySqlDao> serviceFinder = new ServiceFinder<EntitySqlDao>(IDBISetup.class.getClassLoader(), EntitySqlDao.class.getName());
+ for (final Class<? extends EntitySqlDao> sqlObjectType : serviceFinder.getServices()) {
+ // Find the model class associated with this sqlObjectType (which is a SqlDao class) to register its mapper
+ // If a custom mapper is defined via @RegisterMapper, don't register our generic one
+ if (sqlObjectType.getGenericInterfaces() != null &&
+ sqlObjectType.getAnnotation(RegisterMapper.class) == null) {
+ for (int i = 0; i < sqlObjectType.getGenericInterfaces().length; i++) {
+ if (sqlObjectType.getGenericInterfaces()[i] instanceof ParameterizedType) {
+ final ParameterizedType type = (ParameterizedType) sqlObjectType.getGenericInterfaces()[i];
+ for (int j = 0; j < type.getActualTypeArguments().length; j++) {
+ final Type modelType = type.getActualTypeArguments()[j];
+ if (modelType instanceof Class) {
+ final Class modelClazz = (Class) modelType;
+ if (Entity.class.isAssignableFrom(modelClazz)) {
+ builder.add(new LowerToCamelBeanMapperFactory(modelClazz));
+ builder.add(new EntityHistoryModelDaoMapperFactory(modelClazz, sqlObjectType));
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return builder.build();
+ }
+
+ public static List<? extends ResultSetMapper> mappersToRegister() {
+ return ImmutableList.<ResultSetMapper>builder()
+ .add(new AuditLogModelDaoMapper())
+ .add(new RecordIdIdMappingsMapper())
+ .add(new DatabaseSchemaSqlDao.ColumnInfoMapper())
+ .build();
+ }
+}
diff --git a/util/src/main/java/org/killbill/billing/util/glue/KillBillShiroModule.java b/util/src/main/java/org/killbill/billing/util/glue/KillBillShiroModule.java
index bd9ceb2..c103789 100644
--- a/util/src/main/java/org/killbill/billing/util/glue/KillBillShiroModule.java
+++ b/util/src/main/java/org/killbill/billing/util/glue/KillBillShiroModule.java
@@ -28,6 +28,7 @@ import org.killbill.billing.util.config.definition.RbacConfig;
import org.killbill.billing.util.security.shiro.dao.JDBCSessionDao;
import org.killbill.billing.util.security.shiro.realm.KillBillJdbcRealm;
import org.killbill.billing.util.security.shiro.realm.KillBillJndiLdapRealm;
+import org.killbill.billing.util.security.shiro.realm.KillBillOktaRealm;
import org.skife.config.ConfigSource;
import org.skife.config.ConfigurationObjectFactory;
@@ -38,6 +39,7 @@ import com.google.inject.binder.AnnotatedBindingBuilder;
public class KillBillShiroModule extends ShiroModule {
public static final String KILLBILL_LDAP_PROPERTY = "killbill.server.ldap";
+ public static final String KILLBILL_OKTA_PROPERTY = "killbill.server.okta";
public static final String KILLBILL_RBAC_PROPERTY = "killbill.server.rbac";
@@ -45,6 +47,10 @@ public class KillBillShiroModule extends ShiroModule {
return Boolean.parseBoolean(System.getProperty(KILLBILL_LDAP_PROPERTY, "false"));
}
+ public static boolean isOktaEnabled() {
+ return Boolean.parseBoolean(System.getProperty(KILLBILL_OKTA_PROPERTY, "false"));
+ }
+
public static boolean isRBACEnabled() {
return Boolean.parseBoolean(System.getProperty(KILLBILL_RBAC_PROPERTY, "true"));
}
@@ -81,6 +87,12 @@ public class KillBillShiroModule extends ShiroModule {
}
}
+ protected void configureOktaRealm() {
+ if (isOktaEnabled()) {
+ bindRealm().to(KillBillOktaRealm.class).asEagerSingleton();
+ }
+ }
+
@Override
protected void bindSecurityManager(final AnnotatedBindingBuilder<? super SecurityManager> bind) {
super.bindSecurityManager(bind);
diff --git a/util/src/main/java/org/killbill/billing/util/nodes/dao/DefaultNodeInfoDao.java b/util/src/main/java/org/killbill/billing/util/nodes/dao/DefaultNodeInfoDao.java
index 8d1c264..2c951ba 100644
--- a/util/src/main/java/org/killbill/billing/util/nodes/dao/DefaultNodeInfoDao.java
+++ b/util/src/main/java/org/killbill/billing/util/nodes/dao/DefaultNodeInfoDao.java
@@ -39,8 +39,6 @@ public class DefaultNodeInfoDao implements NodeInfoDao {
public DefaultNodeInfoDao(final IDBI dbi, final Clock clock) {
this.dbi = dbi;
this.clock = clock;
- ((DBI) dbi).registerMapper(new LowerToCamelBeanMapperFactory(NodeInfoModelDao.class));
-
}
@Override
diff --git a/util/src/main/java/org/killbill/billing/util/nodes/dao/NodeInfoSqlDao.java b/util/src/main/java/org/killbill/billing/util/nodes/dao/NodeInfoSqlDao.java
index ceedde4..e2be7f8 100644
--- a/util/src/main/java/org/killbill/billing/util/nodes/dao/NodeInfoSqlDao.java
+++ b/util/src/main/java/org/killbill/billing/util/nodes/dao/NodeInfoSqlDao.java
@@ -20,17 +20,17 @@ package org.killbill.billing.util.nodes.dao;
import java.util.Date;
import java.util.List;
-import org.killbill.billing.util.entity.dao.EntitySqlDaoStringTemplate;
+import org.killbill.commons.jdbi.template.KillBillSqlDaoStringTemplate;
import org.skife.jdbi.v2.sqlobject.Bind;
-import org.skife.jdbi.v2.sqlobject.BindBean;
+import org.killbill.commons.jdbi.binder.SmartBindBean;
import org.skife.jdbi.v2.sqlobject.SqlQuery;
import org.skife.jdbi.v2.sqlobject.SqlUpdate;
-@EntitySqlDaoStringTemplate
+@KillBillSqlDaoStringTemplate
public interface NodeInfoSqlDao {
@SqlUpdate
- public void create(@BindBean final NodeInfoModelDao nodeInfo);
+ public void create(@SmartBindBean final NodeInfoModelDao nodeInfo);
@SqlUpdate
public void updateNodeInfo(@Bind("nodeName") final String nodeName, @Bind("nodeInfo") final String nodeInfo, @Bind("updatedDate") final Date updatedDate);
diff --git a/util/src/main/java/org/killbill/billing/util/security/api/DefaultSecurityApi.java b/util/src/main/java/org/killbill/billing/util/security/api/DefaultSecurityApi.java
index 70ed26c..3d30a3c 100644
--- a/util/src/main/java/org/killbill/billing/util/security/api/DefaultSecurityApi.java
+++ b/util/src/main/java/org/killbill/billing/util/security/api/DefaultSecurityApi.java
@@ -206,6 +206,12 @@ public class DefaultSecurityApi implements SecurityApi {
}
@Override
+ public void updateRoleDefinition(final String role, final List<String> permissions, final CallContext callContext) throws SecurityApiException {
+ final List<String> sanitizedPermissions = sanitizeAndValidatePermissions(permissions);
+ userDao.updateRoleDefinition(role, sanitizedPermissions, callContext.getUserName());
+ }
+
+ @Override
public List<String> getRoleDefinition(final String role, final TenantContext tenantContext) {
final List<RolesPermissionsModelDao> permissionsModelDao = userDao.getRoleDefinition(role);
return ImmutableList.copyOf(Iterables.transform(permissionsModelDao, new Function<RolesPermissionsModelDao, String>() {
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 7dc71e7..8f02fe2 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
@@ -17,8 +17,11 @@
package org.killbill.billing.util.security.shiro.dao;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
+import javax.annotation.Nullable;
import javax.inject.Inject;
import org.apache.shiro.crypto.RandomNumberGenerator;
@@ -54,9 +57,6 @@ public class DefaultUserDao implements UserDao {
this.dbi = dbi;
this.clock = clock;
this.securityConfig = securityConfig;
- ((DBI) dbi).registerMapper(new LowerToCamelBeanMapperFactory(UserModelDao.class));
- ((DBI) dbi).registerMapper(new LowerToCamelBeanMapperFactory(UserRolesModelDao.class));
- ((DBI) dbi).registerMapper(new LowerToCamelBeanMapperFactory(RolesPermissionsModelDao.class));
}
@Override
@@ -116,6 +116,49 @@ public class DefaultUserDao implements UserDao {
}
@Override
+ public void updateRoleDefinition(final String role, final List<String> permissions, final String createdBy) throws SecurityApiException {
+ final DateTime createdDate = clock.getUTCNow();
+ inTransactionWithExceptionHandling(new TransactionCallback<Void>() {
+ @Override
+ public Void inTransaction(final Handle handle, final TransactionStatus status) throws Exception {
+ final RolesPermissionsSqlDao rolesPermissionsSqlDao = handle.attach(RolesPermissionsSqlDao.class);
+ final List<RolesPermissionsModelDao> existingPermissions = rolesPermissionsSqlDao.getByRoleName(role);
+ // A empty list of permissions means we should remove all current permissions
+ final Iterable<RolesPermissionsModelDao> toBeDeleted = existingPermissions.isEmpty() ?
+ existingPermissions :
+ Iterables.filter(existingPermissions, new Predicate<RolesPermissionsModelDao>() {
+ @Override
+ public boolean apply(final RolesPermissionsModelDao input) {
+ return !permissions.contains(input.getPermission());
+ }
+ });
+
+ final Iterable<String> toBeAdded = Iterables.filter(permissions, new Predicate<String>() {
+ @Override
+ public boolean apply(final String input) {
+ for (RolesPermissionsModelDao e : existingPermissions) {
+ if (e.getPermission().equals(input)) {
+ return false;
+ }
+ }
+ return true;
+ }
+ });
+
+ for (RolesPermissionsModelDao d : toBeDeleted) {
+ rolesPermissionsSqlDao.unactiveEvent(d.getRecordId(), createdDate, createdBy);
+ }
+
+ for (final String permission : toBeAdded) {
+ rolesPermissionsSqlDao.create(new RolesPermissionsModelDao(role, permission, createdDate, createdBy));
+ }
+ return null;
+ }
+ });
+
+ }
+
+ @Override
public List<RolesPermissionsModelDao> getRoleDefinition(final String role) {
return dbi.inTransaction(new TransactionCallback<List<RolesPermissionsModelDao>>() {
@Override
diff --git a/util/src/main/java/org/killbill/billing/util/security/shiro/dao/JDBCSessionDao.java b/util/src/main/java/org/killbill/billing/util/security/shiro/dao/JDBCSessionDao.java
index a6a5a34..e241a20 100644
--- a/util/src/main/java/org/killbill/billing/util/security/shiro/dao/JDBCSessionDao.java
+++ b/util/src/main/java/org/killbill/billing/util/security/shiro/dao/JDBCSessionDao.java
@@ -41,7 +41,7 @@ public class JDBCSessionDao extends CachingSessionDAO {
private final JDBCSessionSqlDao jdbcSessionSqlDao;
- private final Cache<Serializable, Boolean> noUpdateSessionsCache = CacheBuilder.<Serializable, Boolean>newBuilder().expireAfterWrite(5, TimeUnit.SECONDS).build();
+ private final Cache<Serializable, Boolean> noUpdateSessionsCache = CacheBuilder.newBuilder().expireAfterWrite(5, TimeUnit.SECONDS).build();
@Inject
public JDBCSessionDao(final IDBI dbi) {
diff --git a/util/src/main/java/org/killbill/billing/util/security/shiro/dao/JDBCSessionSqlDao.java b/util/src/main/java/org/killbill/billing/util/security/shiro/dao/JDBCSessionSqlDao.java
index f277430..409f2a3 100644
--- a/util/src/main/java/org/killbill/billing/util/security/shiro/dao/JDBCSessionSqlDao.java
+++ b/util/src/main/java/org/killbill/billing/util/security/shiro/dao/JDBCSessionSqlDao.java
@@ -18,14 +18,14 @@
package org.killbill.billing.util.security.shiro.dao;
-import org.killbill.billing.util.entity.dao.EntitySqlDaoStringTemplate;
+import org.killbill.commons.jdbi.template.KillBillSqlDaoStringTemplate;
import org.killbill.commons.jdbi.binder.SmartBindBean;
import org.skife.jdbi.v2.sqlobject.Bind;
import org.skife.jdbi.v2.sqlobject.SqlQuery;
import org.skife.jdbi.v2.sqlobject.SqlUpdate;
import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
-@EntitySqlDaoStringTemplate
+@KillBillSqlDaoStringTemplate
public interface JDBCSessionSqlDao extends Transactional<JDBCSessionSqlDao> {
@SqlQuery
diff --git a/util/src/main/java/org/killbill/billing/util/security/shiro/dao/RolesPermissionsSqlDao.java b/util/src/main/java/org/killbill/billing/util/security/shiro/dao/RolesPermissionsSqlDao.java
index 029b268..ad4187f 100644
--- a/util/src/main/java/org/killbill/billing/util/security/shiro/dao/RolesPermissionsSqlDao.java
+++ b/util/src/main/java/org/killbill/billing/util/security/shiro/dao/RolesPermissionsSqlDao.java
@@ -19,14 +19,18 @@ package org.killbill.billing.util.security.shiro.dao;
import java.util.List;
-import org.killbill.billing.util.entity.dao.EntitySqlDaoStringTemplate;
+import org.joda.time.DateTime;
+import org.killbill.billing.callcontext.InternalCallContext;
+import org.killbill.billing.util.audit.ChangeType;
+import org.killbill.billing.util.entity.dao.Audited;
+import org.killbill.commons.jdbi.template.KillBillSqlDaoStringTemplate;
import org.killbill.commons.jdbi.binder.SmartBindBean;
import org.skife.jdbi.v2.sqlobject.Bind;
import org.skife.jdbi.v2.sqlobject.SqlQuery;
import org.skife.jdbi.v2.sqlobject.SqlUpdate;
import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
-@EntitySqlDaoStringTemplate
+@KillBillSqlDaoStringTemplate
public interface RolesPermissionsSqlDao extends Transactional<RolesPermissionsSqlDao> {
@SqlQuery
@@ -37,4 +41,12 @@ public interface RolesPermissionsSqlDao extends Transactional<RolesPermissionsSq
@SqlUpdate
public void create(@SmartBindBean final RolesPermissionsModelDao rolesPermissions);
+
+ @SqlUpdate
+ @Audited(ChangeType.UPDATE)
+ public void unactiveEvent(@Bind("recordId") final Long recordId,
+ @Bind("createdDate") final DateTime createdDate,
+ @Bind("createdBy") final String createdBy);
+
+
}
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 142e13d..f0b4427 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
@@ -29,6 +29,8 @@ public interface UserDao {
public void addRoleDefinition(String role, List<String> permissions, String createdBy) throws SecurityApiException;
+ public void updateRoleDefinition(String role, List<String> permissions, String createdBy) throws SecurityApiException;
+
public List<RolesPermissionsModelDao> getRoleDefinition(String role);
public void updateUserPassword(String username, String password, String createdBy) throws SecurityApiException;
diff --git a/util/src/main/java/org/killbill/billing/util/security/shiro/dao/UserRolesSqlDao.java b/util/src/main/java/org/killbill/billing/util/security/shiro/dao/UserRolesSqlDao.java
index 8c25d9b..e413146 100644
--- a/util/src/main/java/org/killbill/billing/util/security/shiro/dao/UserRolesSqlDao.java
+++ b/util/src/main/java/org/killbill/billing/util/security/shiro/dao/UserRolesSqlDao.java
@@ -20,14 +20,14 @@ package org.killbill.billing.util.security.shiro.dao;
import java.util.Date;
import java.util.List;
-import org.killbill.billing.util.entity.dao.EntitySqlDaoStringTemplate;
+import org.killbill.commons.jdbi.template.KillBillSqlDaoStringTemplate;
import org.killbill.commons.jdbi.binder.SmartBindBean;
import org.skife.jdbi.v2.sqlobject.Bind;
import org.skife.jdbi.v2.sqlobject.SqlQuery;
import org.skife.jdbi.v2.sqlobject.SqlUpdate;
import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
-@EntitySqlDaoStringTemplate
+@KillBillSqlDaoStringTemplate
public interface UserRolesSqlDao extends Transactional<UserRolesSqlDao> {
@SqlQuery
diff --git a/util/src/main/java/org/killbill/billing/util/security/shiro/dao/UsersSqlDao.java b/util/src/main/java/org/killbill/billing/util/security/shiro/dao/UsersSqlDao.java
index 6c7cd31..d9c659e 100644
--- a/util/src/main/java/org/killbill/billing/util/security/shiro/dao/UsersSqlDao.java
+++ b/util/src/main/java/org/killbill/billing/util/security/shiro/dao/UsersSqlDao.java
@@ -19,14 +19,14 @@ package org.killbill.billing.util.security.shiro.dao;
import java.util.Date;
-import org.killbill.billing.util.entity.dao.EntitySqlDaoStringTemplate;
+import org.killbill.commons.jdbi.template.KillBillSqlDaoStringTemplate;
import org.killbill.commons.jdbi.binder.SmartBindBean;
import org.skife.jdbi.v2.sqlobject.Bind;
import org.skife.jdbi.v2.sqlobject.SqlQuery;
import org.skife.jdbi.v2.sqlobject.SqlUpdate;
import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
-@EntitySqlDaoStringTemplate
+@KillBillSqlDaoStringTemplate
public interface UsersSqlDao extends Transactional<UsersSqlDao> {
@SqlQuery
diff --git a/util/src/main/java/org/killbill/billing/util/security/shiro/realm/KillBillJndiLdapRealm.java b/util/src/main/java/org/killbill/billing/util/security/shiro/realm/KillBillJndiLdapRealm.java
index 7779670..ac15666 100644
--- a/util/src/main/java/org/killbill/billing/util/security/shiro/realm/KillBillJndiLdapRealm.java
+++ b/util/src/main/java/org/killbill/billing/util/security/shiro/realm/KillBillJndiLdapRealm.java
@@ -39,16 +39,17 @@ import org.apache.shiro.realm.ldap.JndiLdapRealm;
import org.apache.shiro.realm.ldap.LdapContextFactory;
import org.apache.shiro.realm.ldap.LdapUtils;
import org.apache.shiro.subject.PrincipalCollection;
+import org.killbill.billing.util.config.definition.SecurityConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.killbill.billing.util.config.definition.SecurityConfig;
-
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
+import com.google.common.base.Functions;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.base.Splitter;
+import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterators;
@@ -74,6 +75,7 @@ public class KillBillJndiLdapRealm extends JndiLdapRealm {
private final String groupSearchFilter;
private final String groupNameId;
private final Map<String, Collection<String>> permissionsByGroup = Maps.newLinkedHashMap();
+ private final String dnSearchFilter;
@Inject
public KillBillJndiLdapRealm(final SecurityConfig securityConfig) {
@@ -87,6 +89,7 @@ public class KillBillJndiLdapRealm extends JndiLdapRealm {
if (securityConfig.disableShiroLDAPSSLCheck()) {
contextFactory.getEnvironment().put("java.naming.ldap.factory.socket", SkipSSLCheckSocketFactory.class.getName());
}
+ contextFactory.getEnvironment().put("java.naming.referral", securityConfig.followShiroLDAPReferrals() ? "follow" : "ignore");
if (securityConfig.getShiroLDAPUrl() != null) {
contextFactory.setUrl(securityConfig.getShiroLDAPUrl());
}
@@ -101,6 +104,8 @@ public class KillBillJndiLdapRealm extends JndiLdapRealm {
}
setContextFactory(contextFactory);
+ dnSearchFilter = securityConfig.getShiroLDAPDnSearchTemplate();
+
searchBase = securityConfig.getShiroLDAPSearchBase();
groupSearchFilter = securityConfig.getShiroLDAPGroupSearchFilter();
groupNameId = securityConfig.getShiroLDAPGroupNameID();
@@ -110,8 +115,10 @@ public class KillBillJndiLdapRealm extends JndiLdapRealm {
// When passing properties on the command line, \n can be escaped
ini.load(securityConfig.getShiroLDAPPermissionsByGroup().replace("\\n", "\n"));
for (final Section section : ini.getSections()) {
- for (final String role : section.keySet()) {
- final Collection<String> permissions = ImmutableList.<String>copyOf(SPLITTER.split(section.get(role)));
+ for (final String rawRole : section.keySet()) {
+ // Un-escape manually = (required if the role name is a DN)
+ final Collection<String> permissions = ImmutableList.<String>copyOf(SPLITTER.split(section.get(rawRole)));
+ final String role = rawRole.replace("\\=", "=");
permissionsByGroup.put(role, permissions);
}
}
@@ -119,6 +126,35 @@ public class KillBillJndiLdapRealm extends JndiLdapRealm {
}
@Override
+ protected String getUserDn(final String principal) throws IllegalArgumentException, IllegalStateException {
+ if (dnSearchFilter != null) {
+ return findUserDN(principal, getContextFactory());
+ } else {
+ // Use template
+ return super.getUserDn(principal);
+ }
+ }
+
+ private String findUserDN(final String userName, final LdapContextFactory ldapContextFactory) {
+ LdapContext systemLdapCtx = null;
+ try {
+ systemLdapCtx = ldapContextFactory.getSystemLdapContext();
+ final NamingEnumeration<SearchResult> usersFound = systemLdapCtx.search(searchBase,
+ dnSearchFilter.replace(USERDN_SUBSTITUTION_TOKEN, userName),
+ SUBTREE_SCOPE);
+ return usersFound.hasMore() ? usersFound.next().getNameInNamespace() : null;
+ } catch (final AuthenticationException ex) {
+ log.info("LDAP authentication exception='{}'", ex.getLocalizedMessage());
+ throw new IllegalArgumentException(ex);
+ } catch (final NamingException e) {
+ log.info("LDAP exception='{}'", e.getLocalizedMessage());
+ throw new IllegalArgumentException(e);
+ } finally {
+ LdapUtils.closeContext(systemLdapCtx);
+ }
+ }
+
+ @Override
protected AuthorizationInfo queryForAuthorizationInfo(final PrincipalCollection principals, final LdapContextFactory ldapContextFactory) throws NamingException {
final Set<String> userGroups = findLDAPGroupsForUser(principals, ldapContextFactory);
@@ -136,7 +172,7 @@ public class KillBillJndiLdapRealm extends JndiLdapRealm {
try {
systemLdapCtx = ldapContextFactory.getSystemLdapContext();
return findLDAPGroupsForUser(username, systemLdapCtx);
- } catch (AuthenticationException ex) {
+ } catch (final AuthenticationException ex) {
log.info("LDAP authentication exception='{}'", ex.getLocalizedMessage());
return ImmutableSet.<String>of();
} finally {
@@ -149,21 +185,20 @@ public class KillBillJndiLdapRealm extends JndiLdapRealm {
groupSearchFilter.replace(USERDN_SUBSTITUTION_TOKEN, userName),
SUBTREE_SCOPE);
+ if (!foundGroups.hasMoreElements()) {
+ return ImmutableSet.<String>of();
+ }
+
+ // There should really only be one entry
+ final SearchResult result = foundGroups.next();
+
// Extract the name of all the groups
- final Iterator<SearchResult> groupsIterator = Iterators.<SearchResult>forEnumeration(foundGroups);
- final Iterator<String> groupsNameIterator = Iterators.<SearchResult, String>transform(groupsIterator,
- new Function<SearchResult, String>() {
- @Override
- public String apply(final SearchResult groupEntry) {
- return extractGroupNameFromSearchResult(groupEntry);
- }
- });
- final Iterator<String> finalGroupsNameIterator = Iterators.<String>filter(groupsNameIterator, Predicates.notNull());
-
- return Sets.newHashSet(finalGroupsNameIterator);
+ final Collection<String> finalGroupsNames = Collections2.<String>filter(extractGroupNamesFromSearchResult(result), Predicates.notNull());
+
+ return Sets.newHashSet(finalGroupsNames);
}
- private String extractGroupNameFromSearchResult(final SearchResult searchResult) {
+ private Collection<String> extractGroupNamesFromSearchResult(final SearchResult searchResult) {
// Get all attributes for that group
final Iterator<? extends Attribute> attributesIterator = Iterators.forEnumeration(searchResult.getAttributes().getAll());
@@ -178,31 +213,23 @@ public class KillBillJndiLdapRealm extends JndiLdapRealm {
// Extract the group name from the attribute
// Note: at this point, groupNameAttributesIterator should really contain a single element
- final Iterator<String> groupNamesIterator = Iterators.transform(groupNameAttributesIterator,
- new Function<Attribute, String>() {
+ final Iterator<Iterator<?>> groupNamesIterator = Iterators.transform(groupNameAttributesIterator,
+ new Function<Attribute, Iterator<?>>() {
@Override
- public String apply(final Attribute groupNameAttribute) {
+ public Iterator<?> apply(final Attribute groupNameAttribute) {
try {
final NamingEnumeration<?> enumeration = groupNameAttribute.getAll();
- if (enumeration.hasMore()) {
- return enumeration.next().toString();
- } else {
- return null;
- }
- } catch (NamingException namingException) {
- log.warn("Unable to read group name", namingException);
+ return Iterators.forEnumeration(enumeration);
+ } catch (final NamingException namingException) {
+ log.warn("Unable to read group name(s)", namingException);
return null;
}
}
});
- final Iterator<String> finalGroupNamesIterator = Iterators.<String>filter(groupNamesIterator, Predicates.notNull());
- if (finalGroupNamesIterator.hasNext()) {
- return finalGroupNamesIterator.next();
- } else {
- log.warn("Unable to find an attribute matching {}", groupNameId);
- return null;
- }
+ final Iterator<?> finalGroupNamesIterator = Iterators.filter(Iterators.concat(groupNamesIterator), Predicates.notNull());
+
+ return ImmutableList.<String>copyOf(Iterators.transform(finalGroupNamesIterator, Functions.toStringFunction()));
}
private Set<String> groupsPermissions(final Set<String> groups) {
diff --git a/util/src/main/java/org/killbill/billing/util/security/shiro/realm/KillBillOktaRealm.java b/util/src/main/java/org/killbill/billing/util/security/shiro/realm/KillBillOktaRealm.java
new file mode 100644
index 0000000..8d75bab
--- /dev/null
+++ b/util/src/main/java/org/killbill/billing/util/security/shiro/realm/KillBillOktaRealm.java
@@ -0,0 +1,241 @@
+/*
+ * 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.util.security.shiro.realm;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.apache.shiro.authc.AuthenticationException;
+import org.apache.shiro.authc.AuthenticationInfo;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.authc.SimpleAuthenticationInfo;
+import org.apache.shiro.authc.UsernamePasswordToken;
+import org.apache.shiro.authz.AuthorizationException;
+import org.apache.shiro.authz.AuthorizationInfo;
+import org.apache.shiro.authz.SimpleAuthorizationInfo;
+import org.apache.shiro.config.Ini;
+import org.apache.shiro.config.Ini.Section;
+import org.apache.shiro.realm.AuthorizingRealm;
+import org.apache.shiro.subject.PrincipalCollection;
+import org.killbill.billing.util.config.definition.SecurityConfig;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.ning.http.client.AsyncCompletionHandler;
+import com.ning.http.client.AsyncHttpClient;
+import com.ning.http.client.AsyncHttpClient.BoundRequestBuilder;
+import com.ning.http.client.AsyncHttpClientConfig;
+import com.ning.http.client.ListenableFuture;
+import com.ning.http.client.Response;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
+import com.google.inject.Inject;
+
+public class KillBillOktaRealm extends AuthorizingRealm {
+
+ private static final Logger log = LoggerFactory.getLogger(KillBillOktaRealm.class);
+ private static final ObjectMapper mapper = new ObjectMapper();
+ private static final int DEFAULT_TIMEOUT_SECS = 15;
+ private static final Splitter SPLITTER = Splitter.on(',').omitEmptyStrings().trimResults();
+
+ private final Map<String, Collection<String>> permissionsByGroup = Maps.newLinkedHashMap();
+
+ private final SecurityConfig securityConfig;
+ private final AsyncHttpClient httpClient;
+
+ @Inject
+ public KillBillOktaRealm(final SecurityConfig securityConfig) {
+ this.securityConfig = securityConfig;
+ this.httpClient = new AsyncHttpClient(new AsyncHttpClientConfig.Builder().setRequestTimeout(DEFAULT_TIMEOUT_SECS * 1000).build());
+
+ if (securityConfig.getShiroOktaPermissionsByGroup() != null) {
+ final Ini ini = new Ini();
+ // When passing properties on the command line, \n can be escaped
+ ini.load(securityConfig.getShiroOktaPermissionsByGroup().replace("\\n", "\n"));
+ for (final Section section : ini.getSections()) {
+ for (final String role : section.keySet()) {
+ final Collection<String> permissions = ImmutableList.<String>copyOf(SPLITTER.split(section.get(role)));
+ permissionsByGroup.put(role, permissions);
+ }
+ }
+ }
+ }
+
+ @Override
+ protected AuthorizationInfo doGetAuthorizationInfo(final PrincipalCollection principals) {
+ final String username = (String) getAvailablePrincipal(principals);
+ final String userId = findOktaUserId(username);
+ final Set<String> userGroups = findOktaGroupsForUser(userId);
+
+ final SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(userGroups);
+ final Set<String> stringPermissions = groupsPermissions(userGroups);
+ simpleAuthorizationInfo.setStringPermissions(stringPermissions);
+
+ return simpleAuthorizationInfo;
+ }
+
+ @Override
+ protected AuthenticationInfo doGetAuthenticationInfo(final AuthenticationToken token) throws AuthenticationException {
+ final UsernamePasswordToken upToken = (UsernamePasswordToken) token;
+ if (doAuthenticate(upToken)) {
+ // Credentials are valid
+ return new SimpleAuthenticationInfo(token.getPrincipal(), token.getCredentials(), getName());
+ } else {
+ throw new AuthenticationException("Okta authentication failed");
+ }
+ }
+
+ private boolean doAuthenticate(final UsernamePasswordToken upToken) {
+ final BoundRequestBuilder builder = httpClient.preparePost(securityConfig.getShiroOktaUrl() + "/api/v1/authn");
+ try {
+ final ImmutableMap<String, String> body = ImmutableMap.<String, String>of("username", upToken.getUsername(),
+ "password", String.valueOf(upToken.getPassword()));
+ builder.setBody(mapper.writeValueAsString(body));
+ } catch (final JsonProcessingException e) {
+ log.warn("Error while generating Okta payload");
+ throw new AuthenticationException(e);
+ }
+ builder.addHeader("Authorization", "SSWS " + securityConfig.getShiroOktaAPIToken());
+ builder.addHeader("Content-Type", "application/json; charset=UTF-8");
+ final Response response;
+ try {
+ final ListenableFuture<Response> futureStatus =
+ builder.execute(new AsyncCompletionHandler<Response>() {
+ @Override
+ public Response onCompleted(final Response response) throws Exception {
+ return response;
+ }
+ });
+ response = futureStatus.get(DEFAULT_TIMEOUT_SECS, TimeUnit.SECONDS);
+ } catch (final TimeoutException toe) {
+ log.warn("Timeout while connecting to Okta");
+ throw new AuthenticationException(toe);
+ } catch (final Exception e) {
+ log.warn("Error while connecting to Okta");
+ throw new AuthenticationException(e);
+ }
+
+ return isAuthenticated(response);
+ }
+
+ private boolean isAuthenticated(final Response oktaRawResponse) {
+ try {
+ final Map oktaResponse = mapper.readValue(oktaRawResponse.getResponseBodyAsStream(), Map.class);
+ if ("SUCCESS".equals(oktaResponse.get("status"))) {
+ return true;
+ } else {
+ log.warn("Okta authentication failed: " + oktaResponse);
+ return false;
+ }
+ } catch (final IOException e) {
+ log.warn("Unable to read response from Okta");
+ throw new AuthenticationException(e);
+ }
+ }
+
+ private String findOktaUserId(final String login) {
+ final String path;
+ try {
+ path = "/api/v1/users/" + URLEncoder.encode(login, "UTF-8");
+ } catch (final UnsupportedEncodingException e) {
+ // Should never happen
+ throw new IllegalStateException(e);
+ }
+
+ final Response oktaRawResponse = doGetRequest(path);
+ try {
+ final Map oktaResponse = mapper.readValue(oktaRawResponse.getResponseBodyAsStream(), Map.class);
+ return (String) oktaResponse.get("id");
+ } catch (final IOException e) {
+ log.warn("Unable to read response from Okta");
+ throw new AuthorizationException(e);
+ }
+ }
+
+ private Set<String> findOktaGroupsForUser(final String userId) {
+ final String path = "/api/v1/users/" + userId + "/groups";
+ final Response response = doGetRequest(path);
+ return getGroups(response);
+ }
+
+ private Response doGetRequest(final String path) {
+ final BoundRequestBuilder builder = httpClient.prepareGet(securityConfig.getShiroOktaUrl() + path);
+ builder.addHeader("Authorization", "SSWS " + securityConfig.getShiroOktaAPIToken());
+ builder.addHeader("Content-Type", "application/json; charset=UTF-8");
+ final Response response;
+ try {
+ final ListenableFuture<Response> futureStatus =
+ builder.execute(new AsyncCompletionHandler<Response>() {
+ @Override
+ public Response onCompleted(final Response response) throws Exception {
+ return response;
+ }
+ });
+ response = futureStatus.get(DEFAULT_TIMEOUT_SECS, TimeUnit.SECONDS);
+ } catch (final TimeoutException toe) {
+ log.warn("Timeout while connecting to Okta");
+ throw new AuthorizationException(toe);
+ } catch (final Exception e) {
+ log.warn("Error while connecting to Okta");
+ throw new AuthorizationException(e);
+ }
+ return response;
+ }
+
+ private Set<String> getGroups(final Response oktaRawResponse) {
+ try {
+ final List<Map> oktaResponse = mapper.readValue(oktaRawResponse.getResponseBodyAsStream(), new TypeReference<List<Map>>() {});
+ final Set<String> groups = new HashSet<String>();
+ for (final Map group : oktaResponse) {
+ final Object groupProfile = group.get("profile");
+ if (groupProfile != null && groupProfile instanceof Map) {
+ groups.add((String) ((Map) groupProfile).get("name"));
+ }
+ }
+ return groups;
+ } catch (final IOException e) {
+ log.warn("Unable to read response from Okta");
+ throw new AuthorizationException(e);
+ }
+ }
+
+ private Set<String> groupsPermissions(final Iterable<String> groups) {
+ final Set<String> permissions = new HashSet<String>();
+ for (final String group : groups) {
+ final Collection<String> permissionsForGroup = permissionsByGroup.get(group);
+ if (permissionsForGroup != null) {
+ permissions.addAll(permissionsForGroup);
+ }
+ }
+ return permissions;
+ }
+}
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 b461cc2..eb81b50 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
@@ -18,6 +18,7 @@ package org.killbill.billing.util.tag.api;
import java.util.Collection;
import java.util.List;
+import java.util.Set;
import java.util.UUID;
import org.killbill.billing.ErrorCode;
@@ -45,6 +46,7 @@ import org.killbill.billing.util.tag.dao.TagModelDao;
import org.killbill.billing.util.tag.dao.TagModelDaoHelper;
import com.google.common.base.Function;
+import com.google.common.base.Joiner;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableList;
import com.google.inject.Inject;
@@ -53,6 +55,8 @@ import static org.killbill.billing.util.entity.dao.DefaultPaginationHelper.getEn
public class DefaultTagUserApi implements TagUserApi {
+ private static final Joiner JOINER = Joiner.on(",");
+
private static final Function<TagModelDao, Tag> TAG_MODEL_DAO_TAG_FUNCTION = new Function<TagModelDao, Tag>() {
@Override
public Tag apply(final TagModelDao input) {
@@ -75,7 +79,7 @@ public class DefaultTagUserApi implements TagUserApi {
@Override
public List<TagDefinition> getTagDefinitions(final TenantContext context) {
- return ImmutableList.<TagDefinition>copyOf(Collections2.transform(tagDefinitionDao.getTagDefinitions(false, internalCallContextFactory.createInternalTenantContextWithoutAccountRecordId(context)),
+ return ImmutableList.<TagDefinition>copyOf(Collections2.transform(tagDefinitionDao.getTagDefinitions(true, internalCallContextFactory.createInternalTenantContextWithoutAccountRecordId(context)),
new Function<TagDefinitionModelDao, TagDefinition>() {
@Override
public TagDefinition apply(final TagDefinitionModelDao input) {
@@ -85,11 +89,11 @@ public class DefaultTagUserApi implements TagUserApi {
}
@Override
- public TagDefinition createTagDefinition(final String definitionName, final String description, final CallContext context) throws TagDefinitionApiException {
+ public TagDefinition createTagDefinition(final String definitionName, final String description, final Set<ObjectType> applicableObjectTypes, final CallContext context) throws TagDefinitionApiException {
if (definitionName.matches(".*[A-Z].*")) {
throw new TagDefinitionApiException(ErrorCode.TAG_DEFINITION_HAS_UPPERCASE, definitionName);
}
- final TagDefinitionModelDao tagDefinitionModelDao = tagDefinitionDao.create(definitionName, description, internalCallContextFactory.createInternalCallContextWithoutAccountRecordId(context));
+ final TagDefinitionModelDao tagDefinitionModelDao = tagDefinitionDao.create(definitionName, description, JOINER.join(applicableObjectTypes), internalCallContextFactory.createInternalCallContextWithoutAccountRecordId(context));
return new DefaultTagDefinition(tagDefinitionModelDao, TagModelDaoHelper.isControlTag(tagDefinitionModelDao.getName()));
}
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 90d2d75..dfa1331 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
@@ -22,6 +22,7 @@ import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
+import java.util.Set;
import java.util.UUID;
import org.killbill.billing.BillingExceptionBase;
@@ -59,6 +60,7 @@ public class DefaultTagDefinitionDao extends EntityDaoBase<TagDefinitionModelDao
private final TagEventBuilder tagEventBuilder;
private final PersistentBus bus;
+
@Inject
public DefaultTagDefinitionDao(final IDBI dbi, final TagEventBuilder tagEventBuilder, final PersistentBus bus, final Clock clock,
final CacheControllerDispatcher controllerDispatcher, final NonEntityDao nonEntityDao, final InternalCallContextFactory internalCallContextFactory) {
@@ -134,7 +136,7 @@ public class DefaultTagDefinitionDao extends EntityDaoBase<TagDefinitionModelDao
}
@Override
- public TagDefinitionModelDao create(final String definitionName, final String description,
+ public TagDefinitionModelDao create(final String definitionName, final String description, final String tagDefinitionObjectTypes,
final InternalCallContext context) throws TagDefinitionApiException {
// Make sure a invoice tag with this name don't already exist
if (TagModelDaoHelper.isControlTag(definitionName)) {
@@ -154,7 +156,7 @@ public class DefaultTagDefinitionDao extends EntityDaoBase<TagDefinitionModelDao
}
// Create it
- final TagDefinitionModelDao tagDefinition = new TagDefinitionModelDao(context.getCreatedDate(), definitionName, description);
+ final TagDefinitionModelDao tagDefinition = new TagDefinitionModelDao(context.getCreatedDate(), definitionName, description, tagDefinitionObjectTypes);
createAndRefresh(tagDefinitionSqlDao, tagDefinition, context);
// Post an event to the bus
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 5879654..61ae17e 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
@@ -24,6 +24,7 @@ import java.util.UUID;
import javax.annotation.Nullable;
+import org.killbill.billing.ObjectType;
import org.killbill.billing.util.tag.ControlTagType;
import com.google.common.base.Predicate;
@@ -37,7 +38,7 @@ public class SystemTags {
public static final String PARK_TAG_DEFINITION_NAME = "__PARK__";
// 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"));
+ 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", ObjectType.ACCOUNT.name()));
public static Collection<TagDefinitionModelDao> get(final boolean includeSystemTags) {
final Collection<TagDefinitionModelDao> all = includeSystemTags ?
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 84f3bed..e409c89 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
@@ -34,7 +34,7 @@ public interface TagDefinitionDao extends EntityDao<TagDefinitionModelDao, TagDe
public List<TagDefinitionModelDao> getByIds(Collection<UUID> definitionIds, InternalTenantContext context);
- public TagDefinitionModelDao create(String definitionName, String description, InternalCallContext context) throws TagDefinitionApiException;
+ public TagDefinitionModelDao create(String definitionName, String description, String tagDefinitionObjectTypes, InternalCallContext context) throws TagDefinitionApiException;
public void deleteById(UUID definitionId, InternalCallContext context) throws TagDefinitionApiException;
}
diff --git a/util/src/main/java/org/killbill/billing/util/tag/dao/TagDefinitionModelDao.java b/util/src/main/java/org/killbill/billing/util/tag/dao/TagDefinitionModelDao.java
index 337afbc..b5ba4bd 100644
--- a/util/src/main/java/org/killbill/billing/util/tag/dao/TagDefinitionModelDao.java
+++ b/util/src/main/java/org/killbill/billing/util/tag/dao/TagDefinitionModelDao.java
@@ -27,32 +27,38 @@ import org.killbill.billing.util.entity.dao.EntityModelDaoBase;
import org.killbill.billing.util.tag.ControlTagType;
import org.killbill.billing.util.tag.TagDefinition;
+import com.google.common.base.Joiner;
+
public class TagDefinitionModelDao extends EntityModelDaoBase implements EntityModelDao<TagDefinition> {
+ private static final Joiner JOINER = Joiner.on(",");
private String name;
+ private String applicableObjectTypes;
private String description;
private Boolean isActive;
public TagDefinitionModelDao() { /* For the DAO mapper */ }
- public TagDefinitionModelDao(final UUID id, final DateTime createdDate, final DateTime updatedDate, final String name, final String description) {
+ public TagDefinitionModelDao(final UUID id, final DateTime createdDate, final DateTime updatedDate, final String name, final String description, String applicableObjectTypes) {
super(id, createdDate, updatedDate);
this.name = name;
this.description = description;
this.isActive = true;
+ this.applicableObjectTypes = applicableObjectTypes;
}
public TagDefinitionModelDao(final ControlTagType tag) {
- this(tag.getId(), null, null, tag.name(), tag.getDescription());
+ this(tag.getId(), null, null, tag.name(), tag.getDescription(), JOINER.join(tag.getApplicableObjectTypes()));
}
- public TagDefinitionModelDao(final DateTime createdDate, final String name, final String description) {
- this(UUIDs.randomUUID(), createdDate, createdDate, name, description);
+ public TagDefinitionModelDao(final DateTime createdDate, final String name, final String description, String applicableObjectTypes) {
+ this(UUIDs.randomUUID(), createdDate, createdDate, name, description, applicableObjectTypes);
}
+
public TagDefinitionModelDao(final TagDefinition tagDefinition) {
this(tagDefinition.getId(), tagDefinition.getCreatedDate(), tagDefinition.getUpdatedDate(), tagDefinition.getName(),
- tagDefinition.getDescription());
+ tagDefinition.getDescription(), JOINER.join(tagDefinition.getApplicableObjectTypes()));
}
public String getName() {
@@ -67,6 +73,14 @@ public class TagDefinitionModelDao extends EntityModelDaoBase implements EntityM
return isActive;
}
+ public String getApplicableObjectTypes() {
+ return applicableObjectTypes;
+ }
+
+ public void setApplicableObjectTypes(final String applicableObjectTypes) {
+ this.applicableObjectTypes = applicableObjectTypes;
+ }
+
public void setName(final String name) {
this.name = name;
}
@@ -85,6 +99,7 @@ public class TagDefinitionModelDao extends EntityModelDaoBase implements EntityM
sb.append("TagDefinitionModelDao");
sb.append("{name='").append(name).append('\'');
sb.append(", description='").append(description).append('\'');
+ sb.append(", applicableObjectTypes='").append(applicableObjectTypes).append('\'');
sb.append(", isActive=").append(isActive);
sb.append('}');
return sb.toString();
@@ -110,6 +125,9 @@ public class TagDefinitionModelDao extends EntityModelDaoBase implements EntityM
if (isActive != null ? !isActive.equals(that.isActive) : that.isActive != null) {
return false;
}
+ if (applicableObjectTypes != null ? !applicableObjectTypes.equals(that.applicableObjectTypes) : that.applicableObjectTypes != null) {
+ return false;
+ }
if (name != null ? !name.equals(that.name) : that.name != null) {
return false;
}
@@ -123,6 +141,7 @@ public class TagDefinitionModelDao extends EntityModelDaoBase implements EntityM
result = 31 * result + (name != null ? name.hashCode() : 0);
result = 31 * result + (description != null ? description.hashCode() : 0);
result = 31 * result + (isActive != null ? isActive.hashCode() : 0);
+ result = 31 * result + (applicableObjectTypes != null ? applicableObjectTypes.hashCode() : 0);
return result;
}
diff --git a/util/src/main/java/org/killbill/billing/util/tag/dao/TagDefinitionSqlDao.java b/util/src/main/java/org/killbill/billing/util/tag/dao/TagDefinitionSqlDao.java
index 076ef0d..929fa42 100644
--- a/util/src/main/java/org/killbill/billing/util/tag/dao/TagDefinitionSqlDao.java
+++ b/util/src/main/java/org/killbill/billing/util/tag/dao/TagDefinitionSqlDao.java
@@ -1,7 +1,9 @@
/*
- * Copyright 2010-2011 Ning, Inc.
+ * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
*
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
@@ -19,36 +21,36 @@ package org.killbill.billing.util.tag.dao;
import java.util.Collection;
import java.util.List;
-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.killbill.billing.util.audit.ChangeType;
import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.util.audit.ChangeType;
import org.killbill.billing.util.entity.dao.Audited;
import org.killbill.billing.util.entity.dao.EntitySqlDao;
-import org.killbill.billing.util.entity.dao.EntitySqlDaoStringTemplate;
import org.killbill.billing.util.tag.TagDefinition;
+import org.killbill.commons.jdbi.binder.SmartBindBean;
+import org.killbill.commons.jdbi.template.KillBillSqlDaoStringTemplate;
+import org.skife.jdbi.v2.sqlobject.Bind;
+import org.skife.jdbi.v2.sqlobject.SqlQuery;
+import org.skife.jdbi.v2.sqlobject.SqlUpdate;
+import org.skife.jdbi.v2.unstable.BindIn;
-@EntitySqlDaoStringTemplate
+@KillBillSqlDaoStringTemplate
public interface TagDefinitionSqlDao extends EntitySqlDao<TagDefinitionModelDao, TagDefinition> {
@SqlQuery
public TagDefinitionModelDao getByName(@Bind("name") final String definitionName,
- @BindBean final InternalTenantContext context);
+ @SmartBindBean final InternalTenantContext context);
@SqlUpdate
@Audited(ChangeType.DELETE)
public void markTagDefinitionAsDeleted(@Bind("id") final String definitionId,
- @BindBean final InternalCallContext context);
+ @SmartBindBean final InternalCallContext context);
@SqlQuery
public int tagDefinitionUsageCount(@Bind("id") final String definitionId,
- @BindBean final InternalTenantContext context);
+ @SmartBindBean final InternalTenantContext context);
@SqlQuery
- public List<TagDefinitionModelDao> getByIds(@UUIDCollectionBinder final Collection<String> definitionIds,
- @BindBean final InternalTenantContext context);
+ public List<TagDefinitionModelDao> getByIds(@BindIn("ids") final Collection<String> definitionIds,
+ @SmartBindBean final InternalTenantContext context);
}
diff --git a/util/src/main/java/org/killbill/billing/util/tag/dao/TagSqlDao.java b/util/src/main/java/org/killbill/billing/util/tag/dao/TagSqlDao.java
index d1f0b67..257d5c3 100644
--- a/util/src/main/java/org/killbill/billing/util/tag/dao/TagSqlDao.java
+++ b/util/src/main/java/org/killbill/billing/util/tag/dao/TagSqlDao.java
@@ -20,7 +20,7 @@ import java.util.List;
import java.util.UUID;
import org.skife.jdbi.v2.sqlobject.Bind;
-import org.skife.jdbi.v2.sqlobject.BindBean;
+import org.killbill.commons.jdbi.binder.SmartBindBean;
import org.skife.jdbi.v2.sqlobject.SqlQuery;
import org.skife.jdbi.v2.sqlobject.SqlUpdate;
@@ -30,24 +30,24 @@ import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.util.audit.ChangeType;
import org.killbill.billing.util.entity.dao.Audited;
import org.killbill.billing.util.entity.dao.EntitySqlDao;
-import org.killbill.billing.util.entity.dao.EntitySqlDaoStringTemplate;
+import org.killbill.commons.jdbi.template.KillBillSqlDaoStringTemplate;
import org.killbill.billing.util.tag.Tag;
-@EntitySqlDaoStringTemplate
+@KillBillSqlDaoStringTemplate
public interface TagSqlDao extends EntitySqlDao<TagModelDao, Tag> {
@SqlUpdate
@Audited(ChangeType.DELETE)
void markTagAsDeleted(@Bind("id") String tagId,
- @BindBean InternalCallContext context);
+ @SmartBindBean InternalCallContext context);
@SqlQuery
List<TagModelDao> getTagsForObject(@Bind("objectId") UUID objectId,
@Bind("objectType") ObjectType objectType,
- @BindBean InternalTenantContext internalTenantContext);
+ @SmartBindBean InternalTenantContext internalTenantContext);
@SqlQuery
List<TagModelDao> getTagsForObjectIncludedDeleted(@Bind("objectId") UUID objectId,
@Bind("objectType") ObjectType objectType,
- @BindBean InternalTenantContext internalTenantContext);
+ @SmartBindBean InternalTenantContext internalTenantContext);
}
diff --git a/util/src/main/java/org/killbill/billing/util/tag/DefaultTagDefinition.java b/util/src/main/java/org/killbill/billing/util/tag/DefaultTagDefinition.java
index 05ca6b4..77db66f 100644
--- a/util/src/main/java/org/killbill/billing/util/tag/DefaultTagDefinition.java
+++ b/util/src/main/java/org/killbill/billing/util/tag/DefaultTagDefinition.java
@@ -19,6 +19,8 @@ package org.killbill.billing.util.tag;
import java.util.List;
import java.util.UUID;
+import javax.annotation.Nullable;
+
import org.killbill.billing.ObjectType;
import org.killbill.billing.entity.EntityBase;
import org.killbill.billing.util.UUIDs;
@@ -26,32 +28,41 @@ import org.killbill.billing.util.tag.dao.TagDefinitionModelDao;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.base.Function;
+import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
public class DefaultTagDefinition extends EntityBase implements TagDefinition {
+ private static final Splitter SPLITTER = Splitter.on(',').omitEmptyStrings().trimResults();
+
private final String name;
private final String description;
private final Boolean controlTag;
private final List<ObjectType> applicableObjectTypes;
+
public DefaultTagDefinition(final TagDefinitionModelDao tagDefinitionModelDao, final boolean isControlTag) {
- this(tagDefinitionModelDao.getId(), tagDefinitionModelDao.getName(), tagDefinitionModelDao.getDescription(), isControlTag);
+ this(tagDefinitionModelDao.getId(), tagDefinitionModelDao.getName(), tagDefinitionModelDao.getDescription(), isControlTag, toObjectTypes(tagDefinitionModelDao.getApplicableObjectTypes()));
}
+
+ public DefaultTagDefinition(final ControlTagType controlTag) {
+ this(controlTag.getId(), controlTag.toString(), controlTag.getDescription(), true, controlTag.getApplicableObjectTypes());
+ }
+
+ // Test only
public DefaultTagDefinition(final String name, final String description, final Boolean isControlTag) {
this(UUIDs.randomUUID(), name, description, isControlTag);
}
+ // Test only
public DefaultTagDefinition(final UUID id, final String name, final String description, final Boolean isControlTag) {
this(id, name, description, isControlTag, getApplicableObjectTypes(id, isControlTag));
}
- public DefaultTagDefinition(final ControlTagType controlTag) {
- this(controlTag.getId(), controlTag.toString(), controlTag.getDescription(), true, controlTag.getApplicableObjectTypes());
- }
-
-
@JsonCreator
public DefaultTagDefinition(@JsonProperty("id") final UUID id,
@JsonProperty("name") final String name,
@@ -144,4 +155,14 @@ public class DefaultTagDefinition extends EntityBase implements TagDefinition {
}
throw new IllegalStateException(String.format("ControlTag id %s does not seem to exist", id));
}
+
+ private static List<ObjectType> toObjectTypes(final String input) {
+ return ImmutableList.copyOf(Iterables.transform(SPLITTER.splitToList(input), new Function<String, ObjectType>() {
+ @Override
+ public ObjectType apply(final String input) {
+ return ObjectType.valueOf(input);
+ }
+ }));
+ }
+
}
diff --git a/util/src/main/java/org/killbill/billing/util/validation/dao/DatabaseSchemaSqlDao.java b/util/src/main/java/org/killbill/billing/util/validation/dao/DatabaseSchemaSqlDao.java
index 78e6d0f..aedee08 100644
--- a/util/src/main/java/org/killbill/billing/util/validation/dao/DatabaseSchemaSqlDao.java
+++ b/util/src/main/java/org/killbill/billing/util/validation/dao/DatabaseSchemaSqlDao.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
@@ -24,16 +24,14 @@ import java.util.List;
import javax.annotation.Nullable;
-import org.killbill.billing.util.entity.dao.EntitySqlDaoStringTemplate;
+import org.killbill.commons.jdbi.template.KillBillSqlDaoStringTemplate;
import org.killbill.billing.util.validation.DefaultColumnInfo;
import org.skife.jdbi.v2.StatementContext;
import org.skife.jdbi.v2.sqlobject.Bind;
import org.skife.jdbi.v2.sqlobject.SqlQuery;
-import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
import org.skife.jdbi.v2.tweak.ResultSetMapper;
-@EntitySqlDaoStringTemplate
-@RegisterMapper(DatabaseSchemaSqlDao.ColumnInfoMapper.class)
+@KillBillSqlDaoStringTemplate
public interface DatabaseSchemaSqlDao {
@SqlQuery
diff --git a/util/src/main/resources/org/killbill/billing/util/broadcast/dao/BroadcastSqlDao.sql.stg b/util/src/main/resources/org/killbill/billing/util/broadcast/dao/BroadcastSqlDao.sql.stg
index 49fa45a..a26bac2 100644
--- a/util/src/main/resources/org/killbill/billing/util/broadcast/dao/BroadcastSqlDao.sql.stg
+++ b/util/src/main/resources/org/killbill/billing/util/broadcast/dao/BroadcastSqlDao.sql.stg
@@ -1,5 +1,3 @@
-group BroadcastSqlDao;
-
tableName() ::= "service_broadcasts"
@@ -29,9 +27,13 @@ allTableValues() ::= <<
, <tableValues()>
>>
+defaultOrderBy(prefix) ::= <<
+order by <prefix>record_id ASC
+>>
+
create() ::= <<
insert into <tableName()> (
-<tableFields()>
+<tableFields("")>
)
values (
<tableValues()>
diff --git a/util/src/main/resources/org/killbill/billing/util/customfield/dao/CustomFieldSqlDao.sql.stg b/util/src/main/resources/org/killbill/billing/util/customfield/dao/CustomFieldSqlDao.sql.stg
index af3948a..2df4d26 100644
--- a/util/src/main/resources/org/killbill/billing/util/customfield/dao/CustomFieldSqlDao.sql.stg
+++ b/util/src/main/resources/org/killbill/billing/util/customfield/dao/CustomFieldSqlDao.sql.stg
@@ -1,4 +1,4 @@
-group CustomFieldSqlDao: EntitySqlDao;
+import "org/killbill/billing/util/entity/dao/EntitySqlDao.sql.stg"
andCheckSoftDeletionWithComma(prefix) ::= "and <prefix>is_active"
@@ -38,17 +38,25 @@ where <idField("")> = :id
;
>>
+updateValue() ::= <<
+update <tableName()>
+set field_value = :fieldValue
+where <idField("")> = :id
+<AND_CHECK_TENANT("")>
+;
+>>
+
getCustomFieldsForObject() ::= <<
select
-<allTableFields()>
+<allTableFields("")>
from <tableName()>
where
object_id = :objectId
and object_type = :objectType
and is_active
-<AND_CHECK_TENANT()>
-<defaultOrderBy()>
+<AND_CHECK_TENANT("")>
+<defaultOrderBy("")>
;
>>
diff --git a/util/src/main/resources/org/killbill/billing/util/dao/NonEntitySqlDao.sql.stg b/util/src/main/resources/org/killbill/billing/util/dao/NonEntitySqlDao.sql.stg
index b7e4d4b..6b6972f 100644
--- a/util/src/main/resources/org/killbill/billing/util/dao/NonEntitySqlDao.sql.stg
+++ b/util/src/main/resources/org/killbill/billing/util/dao/NonEntitySqlDao.sql.stg
@@ -1,5 +1,3 @@
-group NonEntitySqlDao;
-
getRecordIdFromObject(tableName) ::= <<
select
record_id
diff --git a/util/src/main/resources/org/killbill/billing/util/ddl.sql b/util/src/main/resources/org/killbill/billing/util/ddl.sql
index 552f8dd..5523655 100644
--- a/util/src/main/resources/org/killbill/billing/util/ddl.sql
+++ b/util/src/main/resources/org/killbill/billing/util/ddl.sql
@@ -49,6 +49,7 @@ CREATE TABLE tag_definitions (
record_id serial unique,
id varchar(36) NOT NULL,
name varchar(20) NOT NULL,
+ applicable_object_types varchar(500),
description varchar(200) NOT NULL,
is_active boolean default true,
created_by varchar(50) NOT NULL,
@@ -67,6 +68,7 @@ CREATE TABLE tag_definition_history (
id varchar(36) NOT NULL,
target_record_id bigint /*! unsigned */ not null,
name varchar(30) NOT NULL,
+ applicable_object_types varchar(500),
description varchar(200),
is_active boolean default true,
change_type varchar(6) NOT NULL,
diff --git a/util/src/main/resources/org/killbill/billing/util/ddl-postgresql.sql b/util/src/main/resources/org/killbill/billing/util/ddl-postgresql.sql
index 49d05f7..6fac566 100644
--- a/util/src/main/resources/org/killbill/billing/util/ddl-postgresql.sql
+++ b/util/src/main/resources/org/killbill/billing/util/ddl-postgresql.sql
@@ -45,3 +45,28 @@ CREATE OR REPLACE FUNCTION hour(ts TIMESTAMP WITH TIME ZONE) RETURNS INTEGER AS
RETURN result;
END;
$$ LANGUAGE plpgsql IMMUTABLE;
+
+/* Alter 'serial' columns to 'bigint' because 'serial' is 32bit in PG while 64bit in MySQL */
+CREATE OR REPLACE FUNCTION update_serial_to_bigint_oncreate()
+ RETURNS event_trigger LANGUAGE plpgsql AS $$
+DECLARE
+ r record;
+ matches text[];
+BEGIN
+ FOR r IN SELECT * FROM pg_event_trigger_ddl_commands()
+ LOOP
+ SELECT regexp_matches(current_query(), E'\\m(\\w+)\\s+serial\\M') INTO matches;
+ IF r.object_type = 'table' AND array_length(matches, 1) > 0 THEN
+ RAISE NOTICE 'Altering % % column % from serial to bigint',
+ r.object_type,
+ r.object_identity,
+ matches[1];
+ EXECUTE 'ALTER TABLE ' || r.object_identity || ' ALTER COLUMN ' || matches[1] || ' TYPE bigint';
+ END IF;
+ END LOOP;
+END
+$$;
+
+CREATE EVENT TRIGGER update_serial_to_bigint_oncreate
+ ON ddl_command_end WHEN TAG IN ('CREATE TABLE')
+ EXECUTE PROCEDURE update_serial_to_bigint_oncreate();
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 2d96592..26faf51 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
@@ -1,6 +1,3 @@
-group EntitySqlDao;
-
-
/****************** To override in each EntitySqlDao template file *****************************/
tableName() ::= ""
@@ -152,10 +149,10 @@ select
<allTableFields("t.")>
from <tableName()> t
join (
- select <recordIdField()>
+ select <recordIdField("")>
from <tableName()>
- where <CHECK_TENANT()>
- <andCheckSoftDeletionWithComma()>
+ where <CHECK_TENANT("")>
+ <andCheckSoftDeletionWithComma("")>
order by <orderBy> <ordering>
limit :rowCount offset :offset
) optimization on <recordIdField("optimization.")> = <recordIdField("t.")>
@@ -292,10 +289,10 @@ where (<searchQuery("t.")>)
create() ::= <<
insert into <tableName()> (
- <idField()>
-, <tableFields()>
-<accountRecordIdFieldWithComma()>
-<tenantRecordIdFieldWithComma()>
+ <idField("")>
+, <tableFields("")>
+<accountRecordIdFieldWithComma("")>
+<tenantRecordIdFieldWithComma("")>
)
values (
<idValue()>
@@ -338,7 +335,7 @@ auditTableValues() ::= <<
getHistoryForTargetRecordId() ::= <<
select
- <idField()>
+ <idField("")>
, <historyTableFields("t.")>
<accountRecordIdFieldWithComma("t.")>
<tenantRecordIdFieldWithComma("t.")>
@@ -351,10 +348,10 @@ order by <recordIdField("t.")> ASC
addHistoryFromTransaction() ::= <<
insert into <historyTableName()> (
- <idField()>
-, <historyTableFields()>
-<accountRecordIdFieldWithComma()>
-<tenantRecordIdFieldWithComma()>
+ <idField("")>
+, <historyTableFields("")>
+<accountRecordIdFieldWithComma("")>
+<tenantRecordIdFieldWithComma("")>
)
values (
<idValue()>
@@ -368,7 +365,7 @@ values (
insertAuditFromTransaction() ::= <<
insert into <auditTableName()> (
-<auditTableFields()>
+<auditTableFields("")>
)
values (
<auditTableValues()>
diff --git a/util/src/main/resources/org/killbill/billing/util/migration/V20171011170256__tag_definition_object_types.sql b/util/src/main/resources/org/killbill/billing/util/migration/V20171011170256__tag_definition_object_types.sql
new file mode 100644
index 0000000..09b02bd
--- /dev/null
+++ b/util/src/main/resources/org/killbill/billing/util/migration/V20171011170256__tag_definition_object_types.sql
@@ -0,0 +1,2 @@
+alter table tag_definitions add column applicable_object_types varchar(500) after name;
+alter table tag_definition_history add column applicable_object_types varchar(500) after name;
diff --git a/util/src/main/resources/org/killbill/billing/util/nodes/dao/NodeInfoSqlDao.sql.stg b/util/src/main/resources/org/killbill/billing/util/nodes/dao/NodeInfoSqlDao.sql.stg
index 312c45d..87463e6 100644
--- a/util/src/main/resources/org/killbill/billing/util/nodes/dao/NodeInfoSqlDao.sql.stg
+++ b/util/src/main/resources/org/killbill/billing/util/nodes/dao/NodeInfoSqlDao.sql.stg
@@ -1,5 +1,3 @@
-group NodeInfoSqlDao;
-
tableName() ::= "node_infos"
tableFields(prefix) ::= <<
@@ -31,7 +29,7 @@ allTableValues() ::= <<
create() ::= <<
insert into <tableName()> (
-<tableFields()>
+<tableFields("")>
)
values (
<tableValues()>
@@ -40,7 +38,7 @@ values (
>>
getByNodeName() ::= <<
-select <allTableFields()>
+select <allTableFields("")>
from <tableName()>
where node_name = :nodeName
and is_active
@@ -48,7 +46,7 @@ and is_active
>>
getAll() ::= <<
-select <allTableFields()>
+select <allTableFields("")>
from <tableName()>
where is_active
order by node_name asc
@@ -68,4 +66,4 @@ delete
from <tableName()>
where node_name = :nodeName
;
->>
\ No newline at end of file
+>>
diff --git a/util/src/main/resources/org/killbill/billing/util/security/shiro/dao/JDBCSessionSqlDao.sql.stg b/util/src/main/resources/org/killbill/billing/util/security/shiro/dao/JDBCSessionSqlDao.sql.stg
index b002312..e6091e5 100644
--- a/util/src/main/resources/org/killbill/billing/util/security/shiro/dao/JDBCSessionSqlDao.sql.stg
+++ b/util/src/main/resources/org/killbill/billing/util/security/shiro/dao/JDBCSessionSqlDao.sql.stg
@@ -1,5 +1,3 @@
-group JDBCSessionSqlDao;
-
read() ::= <<
select
record_id
diff --git a/util/src/main/resources/org/killbill/billing/util/security/shiro/dao/RolesPermissionsSqlDao.sql.stg b/util/src/main/resources/org/killbill/billing/util/security/shiro/dao/RolesPermissionsSqlDao.sql.stg
index 5da1774..1d13de4 100644
--- a/util/src/main/resources/org/killbill/billing/util/security/shiro/dao/RolesPermissionsSqlDao.sql.stg
+++ b/util/src/main/resources/org/killbill/billing/util/security/shiro/dao/RolesPermissionsSqlDao.sql.stg
@@ -1,6 +1,3 @@
-group RolesPermissionsSqlDao;
-
-
tableName() ::= "roles_permissions"
tableFields(prefix) ::= <<
@@ -37,7 +34,7 @@ allTableValues() ::= <<
create() ::= <<
insert into <tableName()> (
-<tableFields()>
+<tableFields("")>
)
values (
<tableValues()>
@@ -46,7 +43,7 @@ values (
>>
getByRecordId() ::= <<
-select <allTableFields()>
+select <allTableFields("")>
from <tableName()>
where record_id = :recordId
and is_active
@@ -55,9 +52,22 @@ and is_active
getByRoleName() ::= <<
-select <allTableFields()>
+select <allTableFields("")>
from <tableName()>
where role_name = :roleName
and is_active
;
>>
+
+unactiveEvent() ::= <<
+update <tableName()>
+set
+is_active = false
+, updated_by = :createdBy
+, updated_date = :createdDate
+where
+record_id = :recordId
+<AND_CHECK_TENANT("")>
+;
+>>
+
diff --git a/util/src/main/resources/org/killbill/billing/util/security/shiro/dao/UserRolesSqlDao.sql.stg b/util/src/main/resources/org/killbill/billing/util/security/shiro/dao/UserRolesSqlDao.sql.stg
index 0c9fe1c..3d4d327 100644
--- a/util/src/main/resources/org/killbill/billing/util/security/shiro/dao/UserRolesSqlDao.sql.stg
+++ b/util/src/main/resources/org/killbill/billing/util/security/shiro/dao/UserRolesSqlDao.sql.stg
@@ -1,5 +1,3 @@
-group UserRolesSqlDao;
-
tableName() ::= "user_roles"
tableFields(prefix) ::= <<
@@ -36,7 +34,7 @@ allTableValues() ::= <<
create() ::= <<
insert into <tableName()> (
-<tableFields()>
+<tableFields("")>
)
values (
<tableValues()>
@@ -45,7 +43,7 @@ values (
>>
getByRecordId() ::= <<
-select <allTableFields()>
+select <allTableFields("")>
from <tableName()>
where record_id = :recordId
and is_active
@@ -53,7 +51,7 @@ and is_active
>>
getByUsername() ::= <<
-select <allTableFields()>
+select <allTableFields("")>
from <tableName()>
where username = :username
and is_active
diff --git a/util/src/main/resources/org/killbill/billing/util/security/shiro/dao/UsersSqlDao.sql.stg b/util/src/main/resources/org/killbill/billing/util/security/shiro/dao/UsersSqlDao.sql.stg
index 07bd4d5..94a6882 100644
--- a/util/src/main/resources/org/killbill/billing/util/security/shiro/dao/UsersSqlDao.sql.stg
+++ b/util/src/main/resources/org/killbill/billing/util/security/shiro/dao/UsersSqlDao.sql.stg
@@ -1,5 +1,3 @@
-group UsersSqlDao;
-
tableName() ::= "users"
tableFields(prefix) ::= <<
@@ -38,7 +36,7 @@ allTableValues() ::= <<
create() ::= <<
insert into <tableName()> (
-<tableFields()>
+<tableFields("")>
)
values (
<tableValues()>
@@ -47,7 +45,7 @@ values (
>>
getByRecordId() ::= <<
-select <allTableFields()>
+select <allTableFields("")>
from <tableName()>
where record_id = :recordId
and is_active
@@ -55,7 +53,7 @@ and is_active
>>
getByUsername() ::= <<
-select <allTableFields()>
+select <allTableFields("")>
from <tableName()>
where username = :username
and is_active
diff --git a/util/src/main/resources/org/killbill/billing/util/tag/dao/TagDefinitionSqlDao.sql.stg b/util/src/main/resources/org/killbill/billing/util/tag/dao/TagDefinitionSqlDao.sql.stg
index a1efb99..3b16041 100644
--- a/util/src/main/resources/org/killbill/billing/util/tag/dao/TagDefinitionSqlDao.sql.stg
+++ b/util/src/main/resources/org/killbill/billing/util/tag/dao/TagDefinitionSqlDao.sql.stg
@@ -1,4 +1,4 @@
-group TagDefinitionDao: EntitySqlDao;
+import "org/killbill/billing/util/entity/dao/EntitySqlDao.sql.stg"
tableName() ::= "tag_definitions"
@@ -6,6 +6,7 @@ andCheckSoftDeletionWithComma(prefix) ::= "and <prefix>is_active"
tableFields(prefix) ::= <<
<prefix>name
+, <prefix>applicable_object_types
, <prefix>description
, <prefix>is_active
, <prefix>created_by
@@ -16,6 +17,7 @@ tableFields(prefix) ::= <<
tableValues() ::= <<
:name
+, :applicableObjectTypes
, :description
, :isActive
, :createdBy
@@ -63,7 +65,7 @@ select
<allTableFields("t.")>
from <tableName()> t
where t.is_active
-and <idField("t.")> in (<ids: {id | :id_<i0>}; separator="," >)
+and <idField("t.")> in (<ids>)
<AND_CHECK_TENANT("t.")>
;
>>
diff --git a/util/src/main/resources/org/killbill/billing/util/tag/dao/TagSqlDao.sql.stg b/util/src/main/resources/org/killbill/billing/util/tag/dao/TagSqlDao.sql.stg
index 177ec5b..cf6ac03 100644
--- a/util/src/main/resources/org/killbill/billing/util/tag/dao/TagSqlDao.sql.stg
+++ b/util/src/main/resources/org/killbill/billing/util/tag/dao/TagSqlDao.sql.stg
@@ -1,4 +1,4 @@
-group TagDao: EntitySqlDao;
+import "org/killbill/billing/util/entity/dao/EntitySqlDao.sql.stg"
tableName() ::= "tags"
@@ -102,6 +102,16 @@ userAndSystemTagDefinitions() ::= <<
, \'Indicates that this is a partner account\' description
union
select
+ \'00000000-0000-0000-0000-000000000008\' id
+ , \'AUTO_INVOICING_DRAFT\' as name
+ , \'Generate account invoices in DRAFT mode.\' description
+ union
+ select
+ \'00000000-0000-0000-0000-000000000009\' id
+ , \'AUTO_INVOICING_REUSE_DRAFT\' as name
+ , \'Use existing draft invoice if exists.\' description
+ union
+ select
\'00000000-0000-0001-0000-000000000001\' id
, \'__PARK__\' as name
, \'Accounts with invalid invoicing state\' description
diff --git a/util/src/main/resources/org/killbill/billing/util/validation/dao/DatabaseSchemaSqlDao.sql.stg b/util/src/main/resources/org/killbill/billing/util/validation/dao/DatabaseSchemaSqlDao.sql.stg
index e14db44..075fa67 100644
--- a/util/src/main/resources/org/killbill/billing/util/validation/dao/DatabaseSchemaSqlDao.sql.stg
+++ b/util/src/main/resources/org/killbill/billing/util/validation/dao/DatabaseSchemaSqlDao.sql.stg
@@ -1,5 +1,3 @@
-group DatabaseSchemaSqlDao;
-
getSchemaInfo(schemaName) ::= <<
SELECT TABLE_NAME, COLUMN_NAME, IS_NULLABLE, DATA_TYPE,
CHARACTER_MAXIMUM_LENGTH, NUMERIC_PRECISION, NUMERIC_SCALE
diff --git a/util/src/test/java/org/killbill/billing/api/FlakyInvokedMethodListener.java b/util/src/test/java/org/killbill/billing/api/FlakyInvokedMethodListener.java
new file mode 100644
index 0000000..80ad169
--- /dev/null
+++ b/util/src/test/java/org/killbill/billing/api/FlakyInvokedMethodListener.java
@@ -0,0 +1,47 @@
+/*
+ * 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.api;
+
+import org.testng.IInvokedMethod;
+import org.testng.IInvokedMethodListener;
+import org.testng.IRetryAnalyzer;
+import org.testng.ITestResult;
+import org.testng.Reporter;
+
+public class FlakyInvokedMethodListener implements IInvokedMethodListener {
+
+ @Override
+ public void beforeInvocation(final IInvokedMethod method, final ITestResult testResult) {
+ }
+
+ @Override
+ public void afterInvocation(final IInvokedMethod method, final ITestResult testResult) {
+ if (testResult.getStatus() != ITestResult.FAILURE) {
+ return;
+ }
+
+ final IRetryAnalyzer retryAnalyzer = testResult.getMethod().getRetryAnalyzer();
+ if (retryAnalyzer != null &&
+ retryAnalyzer instanceof FlakyRetryAnalyzer &&
+ !((FlakyRetryAnalyzer) retryAnalyzer).shouldRetry()) {
+ // Don't fail the build (flaky test), mark it as SKIPPED
+ testResult.setStatus(ITestResult.SKIP);
+ Reporter.setCurrentTestResult(testResult);
+ }
+ }
+}
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 886dd99..d730885 100644
--- a/util/src/test/java/org/killbill/billing/api/TestApiListener.java
+++ b/util/src/test/java/org/killbill/billing/api/TestApiListener.java
@@ -63,7 +63,7 @@ public class TestApiListener {
private static final Joiner SPACE_JOINER = Joiner.on(" ");
- private static final long DELAY = 25000;
+ private static final long DELAY = 60000;
private final List<NextEvent> nextExpectedEvent;
private final IDBI idbi;
@@ -106,6 +106,7 @@ public class TestApiListener {
CREATE,
TRANSFER,
CHANGE,
+ UNDO_CHANGE,
CANCEL,
UNCANCEL,
PAUSE,
@@ -164,6 +165,10 @@ public class TestApiListener {
assertEqualsNicely(NextEvent.CHANGE);
notifyIfStackEmpty();
break;
+ case UNDO_CHANGE:
+ assertEqualsNicely(NextEvent.UNDO_CHANGE);
+ notifyIfStackEmpty();
+ break;
case UNCANCEL:
assertEqualsNicely(NextEvent.UNCANCEL);
notifyIfStackEmpty();
diff --git a/util/src/test/java/org/killbill/billing/callcontext/MutableInternalCallContext.java b/util/src/test/java/org/killbill/billing/callcontext/MutableInternalCallContext.java
index 2066bcf..12c5ac7 100644
--- a/util/src/test/java/org/killbill/billing/callcontext/MutableInternalCallContext.java
+++ b/util/src/test/java/org/killbill/billing/callcontext/MutableInternalCallContext.java
@@ -59,7 +59,7 @@ public class MutableInternalCallContext extends InternalCallContext {
this.initialAccountRecordId = accountRecordId;
this.initialTenantRecordId = tenantRecordId;
this.initialReferenceDateTimeZone = fixedOffsetTimeZone;
- this.initialReferenceTime = super.getReferenceTime();
+ this.initialReferenceTime = super.getReferenceLocalTime();
this.initialCreatedDate = createdDate;
this.initialUpdatedDate = updatedDate;
@@ -94,7 +94,7 @@ public class MutableInternalCallContext extends InternalCallContext {
}
@Override
- public LocalTime getReferenceTime() {
+ public LocalTime getReferenceLocalTime() {
return referenceTime;
}
diff --git a/util/src/test/java/org/killbill/billing/DBTestingHelper.java b/util/src/test/java/org/killbill/billing/DBTestingHelper.java
index 7023039..2808866 100644
--- a/util/src/test/java/org/killbill/billing/DBTestingHelper.java
+++ b/util/src/test/java/org/killbill/billing/DBTestingHelper.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014 Groupon, Inc
- * Copyright 2014 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
@@ -24,14 +24,13 @@ import java.util.Enumeration;
import java.util.concurrent.atomic.AtomicBoolean;
import org.killbill.billing.platform.test.PlatformDBTestingHelper;
-import org.killbill.billing.util.dao.AuditLogModelDaoMapper;
-import org.killbill.billing.util.dao.RecordIdIdMappingsMapper;
+import org.killbill.billing.util.glue.IDBISetup;
import org.killbill.billing.util.io.IOUtils;
-import org.killbill.billing.util.security.shiro.dao.SessionModelDao;
import org.killbill.commons.embeddeddb.EmbeddedDB;
-import org.killbill.commons.jdbi.mapper.LowerToCamelBeanMapperFactory;
import org.skife.jdbi.v2.DBI;
import org.skife.jdbi.v2.IDBI;
+import org.skife.jdbi.v2.ResultSetMapperFactory;
+import org.skife.jdbi.v2.tweak.ResultSetMapper;
import com.google.common.base.MoreObjects;
@@ -58,9 +57,13 @@ public class DBTestingHelper extends PlatformDBTestingHelper {
final DBI dbi = (DBI) super.getDBI();
// Register KB specific mappers
if (initialized.compareAndSet(false, true)) {
- dbi.registerMapper(new AuditLogModelDaoMapper());
- dbi.registerMapper(new RecordIdIdMappingsMapper());
- dbi.registerMapper(new LowerToCamelBeanMapperFactory(SessionModelDao.class));
+ for (final ResultSetMapperFactory resultSetMapperFactory : IDBISetup.mapperFactoriesToRegister()) {
+ dbi.registerMapper(resultSetMapperFactory);
+ }
+
+ for (final ResultSetMapper resultSetMapper : IDBISetup.mappersToRegister()) {
+ dbi.registerMapper(resultSetMapper);
+ }
}
return dbi;
}
@@ -76,14 +79,17 @@ public class DBTestingHelper extends PlatformDBTestingHelper {
"CREATE TABLE accounts (\n" +
" record_id serial unique,\n" +
" id varchar(36) NOT NULL,\n" +
- " external_key varchar(128) NULL,\n" +
- " email varchar(128) NOT NULL,\n" +
- " name varchar(100) NOT NULL,\n" +
- " first_name_length int NOT NULL,\n" +
+ " external_key varchar(255) NOT NULL,\n" +
+ " email varchar(128) DEFAULT NULL,\n" +
+ " name varchar(100) DEFAULT NULL,\n" +
+ " first_name_length int DEFAULT NULL,\n" +
" currency varchar(3) DEFAULT NULL,\n" +
" billing_cycle_day_local int DEFAULT NULL,\n" +
+ " parent_account_id varchar(36) DEFAULT NULL,\n" +
+ " is_payment_delegated_to_parent boolean DEFAULT FALSE,\n" +
" payment_method_id varchar(36) DEFAULT NULL,\n" +
- " time_zone varchar(50) DEFAULT NULL,\n" +
+ " reference_time datetime NOT NULL,\n" +
+ " time_zone varchar(50) NOT NULL,\n" +
" locale varchar(5) DEFAULT NULL,\n" +
" address1 varchar(100) DEFAULT NULL,\n" +
" address2 varchar(100) DEFAULT NULL,\n" +
@@ -93,6 +99,7 @@ public class DBTestingHelper extends PlatformDBTestingHelper {
" country varchar(50) DEFAULT NULL,\n" +
" postal_code varchar(16) DEFAULT NULL,\n" +
" phone varchar(25) DEFAULT NULL,\n" +
+ " notes varchar(4096) DEFAULT NULL,\n" +
" migrated boolean default false,\n" +
" is_notified_for_invoices boolean NOT NULL,\n" +
" created_date datetime NOT NULL,\n" +
@@ -106,7 +113,7 @@ public class DBTestingHelper extends PlatformDBTestingHelper {
"CREATE TABLE tenants (\n" +
" record_id serial unique,\n" +
" id varchar(36) NOT NULL,\n" +
- " external_key varchar(128) NULL,\n" +
+ " external_key varchar(255) NULL,\n" +
" api_key varchar(128) NULL,\n" +
" api_secret varchar(128) NULL,\n" +
" api_salt varchar(128) NULL,\n" +
@@ -122,9 +129,10 @@ public class DBTestingHelper extends PlatformDBTestingHelper {
"CREATE TABLE bundles (\n" +
" record_id serial unique,\n" +
" id varchar(36) NOT NULL,\n" +
- " external_key varchar(64) NOT NULL,\n" +
+ " external_key varchar(255) NOT NULL,\n" +
" account_id varchar(36) NOT NULL,\n" +
" last_sys_update_date datetime,\n" +
+ " original_created_date datetime NOT NULL,\n" +
" created_by varchar(50) NOT NULL,\n" +
" created_date datetime NOT NULL,\n" +
" updated_by varchar(50) NOT NULL,\n" +
@@ -141,9 +149,8 @@ public class DBTestingHelper extends PlatformDBTestingHelper {
" category varchar(32) NOT NULL,\n" +
" start_date datetime NOT NULL,\n" +
" bundle_start_date datetime NOT NULL,\n" +
- " active_version int DEFAULT 1,\n" +
" charged_through_date datetime DEFAULT NULL,\n" +
- " paid_through_date datetime DEFAULT NULL,\n" +
+ " migrated bool NOT NULL default FALSE,\n" +
" created_by varchar(50) NOT NULL,\n" +
" created_date datetime NOT NULL,\n" +
" updated_by varchar(50) NOT NULL,\n" +
@@ -159,12 +166,10 @@ public class DBTestingHelper extends PlatformDBTestingHelper {
" record_id serial unique,\n" +
" id varchar(36) NOT NULL,\n" +
" account_id varchar(36) NOT NULL,\n" +
- " invoice_id varchar(36) NOT NULL,\n" +
" payment_method_id varchar(36) NOT NULL,\n" +
- " amount numeric(15,9),\n" +
- " currency varchar(3),\n" +
- " effective_date datetime,\n" +
- " payment_status varchar(50),\n" +
+ " external_key varchar(255) NOT NULL,\n" +
+ " state_name varchar(64) DEFAULT NULL,\n" +
+ " last_success_state_name varchar(64) DEFAULT NULL,\n" +
" created_by varchar(50) NOT NULL,\n" +
" created_date datetime NOT NULL,\n" +
" updated_by varchar(50) NOT NULL,\n" +
diff --git a/util/src/test/java/org/killbill/billing/GuicyKillbillTestSuite.java b/util/src/test/java/org/killbill/billing/GuicyKillbillTestSuite.java
index c7ba40e..3196aef 100644
--- a/util/src/test/java/org/killbill/billing/GuicyKillbillTestSuite.java
+++ b/util/src/test/java/org/killbill/billing/GuicyKillbillTestSuite.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2016 Groupon, Inc
- * Copyright 2014-2016 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
@@ -23,6 +23,7 @@ import java.util.UUID;
import javax.inject.Inject;
+import org.killbill.billing.api.FlakyInvokedMethodListener;
import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.callcontext.MutableInternalCallContext;
import org.killbill.billing.platform.api.KillbillConfigSource;
@@ -35,13 +36,17 @@ import org.killbill.clock.ClockMock;
import org.skife.config.ConfigSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.testng.IHookCallBack;
+import org.testng.IHookable;
import org.testng.ITestResult;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Listeners;
import com.google.common.collect.ImmutableMap;
-public class GuicyKillbillTestSuite {
+@Listeners(FlakyInvokedMethodListener.class)
+public class GuicyKillbillTestSuite implements IHookable {
// Use the simple name here to save screen real estate
protected static final Logger log = LoggerFactory.getLogger(KillbillTestSuite.class.getSimpleName());
@@ -111,7 +116,7 @@ public class GuicyKillbillTestSuite {
final InternalTenantContext tmp = internalCallContextFactory.createInternalTenantContext(accountId, callContext);
internalCallContext.setAccountRecordId(tmp.getAccountRecordId());
internalCallContext.setFixedOffsetTimeZone(tmp.getFixedOffsetTimeZone());
- internalCallContext.setReferenceTime(tmp.getReferenceTime());
+ internalCallContext.setReferenceTime(tmp.getReferenceLocalTime());
internalCallContext.setCreatedDate(clock.getUTCNow());
internalCallContext.setUpdatedDate(clock.getUTCNow());
}
@@ -143,6 +148,24 @@ public class GuicyKillbillTestSuite {
}
}
+ // Note: assertions should not be run in before / after hooks, as the associated test result won't be correctly updated.
+ // Use this wrapper instead.
+ @Override
+ public void run(final IHookCallBack callBack, final ITestResult testResult) {
+ // Make sure we start with a clean state
+ assertListenerStatus();
+
+ // Run the actual test
+ callBack.runTestMethod(testResult);
+
+ // Make sure we finish in a clean state
+ assertListenerStatus();
+ }
+
+ protected void assertListenerStatus() {
+ // No-op
+ }
+
public boolean hasFailed() {
return hasFailed;
}
diff --git a/util/src/test/java/org/killbill/billing/GuicyKillbillTestSuiteWithEmbeddedDB.java b/util/src/test/java/org/killbill/billing/GuicyKillbillTestSuiteWithEmbeddedDB.java
index 565027e..757b8d4 100644
--- a/util/src/test/java/org/killbill/billing/GuicyKillbillTestSuiteWithEmbeddedDB.java
+++ b/util/src/test/java/org/killbill/billing/GuicyKillbillTestSuiteWithEmbeddedDB.java
@@ -24,6 +24,7 @@ import org.killbill.commons.embeddeddb.EmbeddedDB;
import org.skife.jdbi.v2.IDBI;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.testng.Assert;
import org.testng.annotations.AfterSuite;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.BeforeSuite;
@@ -52,11 +53,16 @@ public class GuicyKillbillTestSuiteWithEmbeddedDB extends GuicyKillbillTestSuite
@BeforeMethod(groups = "slow")
public void beforeMethod() throws Exception {
+ cleanupAllTables();
+ controlCacheDispatcher.clearAll();
+ }
+
+ protected void cleanupAllTables() {
try {
DBTestingHelper.get().getInstance().cleanupAllTables();
- } catch (final Exception ignored) {
+ } catch (final Exception e) {
+ Assert.fail("Unable to clean database", e);
}
- controlCacheDispatcher.clearAll();
}
@AfterSuite(groups = "slow")
diff --git a/util/src/test/java/org/killbill/billing/mock/api/MockExtBusEvent.java b/util/src/test/java/org/killbill/billing/mock/api/MockExtBusEvent.java
index f11209e..591327f 100644
--- a/util/src/test/java/org/killbill/billing/mock/api/MockExtBusEvent.java
+++ b/util/src/test/java/org/killbill/billing/mock/api/MockExtBusEvent.java
@@ -78,6 +78,11 @@ public class MockExtBusEvent implements ExtBusEvent {
}
@Override
+ public UUID getUserToken() {
+ return null;
+ }
+
+ @Override
public String toString() {
return "MockExtBusEvent [eventType=" + eventType + ", objectType="
+ objectType + ", objectId=" + objectId + ", accountId="
diff --git a/util/src/test/java/org/killbill/billing/mock/MockAccountBuilder.java b/util/src/test/java/org/killbill/billing/mock/MockAccountBuilder.java
index 36975d9..3d125dc 100644
--- a/util/src/test/java/org/killbill/billing/mock/MockAccountBuilder.java
+++ b/util/src/test/java/org/killbill/billing/mock/MockAccountBuilder.java
@@ -38,6 +38,7 @@ public class MockAccountBuilder {
private boolean isPaymentDelegatedToParent = false;
private int billingCycleDayLocal;
private UUID paymentMethodId;
+ private DateTime referenceTime = new DateTime(DateTimeZone.UTC);
private DateTimeZone timeZone = DateTimeZone.UTC;
private String locale = "";
private String address1 = "";
@@ -84,6 +85,7 @@ public class MockAccountBuilder {
this.notes(data.getNotes());
this.postalCode(data.getPostalCode());
this.stateOrProvince(data.getStateOrProvince());
+ this.referenceTime(data.getReferenceTime());
this.timeZone(data.getTimeZone());
if (data instanceof Account) {
this.id = ((Account) data).getId();
@@ -139,6 +141,11 @@ public class MockAccountBuilder {
return this;
}
+ public MockAccountBuilder referenceTime(final DateTime referenceTime) {
+ this.referenceTime = referenceTime;
+ return this;
+ }
+
public MockAccountBuilder timeZone(final DateTimeZone timeZone) {
this.timeZone = timeZone;
return this;
@@ -273,7 +280,7 @@ public class MockAccountBuilder {
@Override
public DateTime getReferenceTime() {
- return AccountDateTimeUtils.getReferenceDateTime(this);
+ return referenceTime;
}
@Override
diff --git a/util/src/test/java/org/killbill/billing/mock/MockPlan.java b/util/src/test/java/org/killbill/billing/mock/MockPlan.java
index dc78dea..d02457c 100644
--- a/util/src/test/java/org/killbill/billing/mock/MockPlan.java
+++ b/util/src/test/java/org/killbill/billing/mock/MockPlan.java
@@ -21,6 +21,7 @@ import java.util.Iterator;
import java.util.UUID;
import org.joda.time.DateTime;
+import org.killbill.billing.catalog.api.BillingMode;
import org.killbill.billing.catalog.api.BillingPeriod;
import org.killbill.billing.catalog.api.CatalogApiException;
import org.killbill.billing.catalog.api.PhaseType;
@@ -43,6 +44,11 @@ public class MockPlan implements Plan {
}
@Override
+ public BillingMode getRecurringBillingMode() {
+ return BillingMode.IN_ADVANCE;
+ }
+
+ @Override
public PlanPhase[] getInitialPhases() {
throw new UnsupportedOperationException();
}
diff --git a/util/src/test/java/org/killbill/billing/mock/MockSubscription.java b/util/src/test/java/org/killbill/billing/mock/MockSubscription.java
index c353b0d..7a72c14 100644
--- a/util/src/test/java/org/killbill/billing/mock/MockSubscription.java
+++ b/util/src/test/java/org/killbill/billing/mock/MockSubscription.java
@@ -27,6 +27,7 @@ import org.killbill.billing.catalog.api.BillingPeriod;
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.PlanPhaseSpecifier;
import org.killbill.billing.catalog.api.PlanSpecifier;
import org.killbill.billing.catalog.api.PriceList;
import org.killbill.billing.catalog.api.Product;
@@ -83,18 +84,23 @@ public class MockSubscription implements SubscriptionBase {
}
@Override
- public DateTime changePlan(final PlanSpecifier spec, final List<PlanPhasePriceOverride> overrides, final CallContext context) throws SubscriptionBaseApiException {
+ public DateTime changePlan(final PlanPhaseSpecifier spec, final List<PlanPhasePriceOverride> overrides, final CallContext context) throws SubscriptionBaseApiException {
return sub.changePlan(spec, overrides, context);
}
@Override
- public DateTime changePlanWithDate(final PlanSpecifier spec, final List<PlanPhasePriceOverride> overrides, final DateTime requestedDate,
+ public boolean undoChangePlan(final CallContext context) throws SubscriptionBaseApiException {
+ return sub.undoChangePlan(context);
+ }
+
+ @Override
+ public DateTime changePlanWithDate(final PlanPhaseSpecifier spec, final List<PlanPhasePriceOverride> overrides, final DateTime requestedDate,
final CallContext context) throws SubscriptionBaseApiException {
return sub.changePlanWithDate(spec, overrides, requestedDate, context);
}
@Override
- public DateTime changePlanWithPolicy(final PlanSpecifier spec,
+ public DateTime changePlanWithPolicy(final PlanPhaseSpecifier spec,
final List<PlanPhasePriceOverride> overrides, final BillingActionPolicy policy, final CallContext context) throws SubscriptionBaseApiException {
return sub.changePlanWithPolicy(spec, overrides, policy, context);
}
diff --git a/util/src/test/java/org/killbill/billing/util/audit/dao/TestDefaultAuditDao.java b/util/src/test/java/org/killbill/billing/util/audit/dao/TestDefaultAuditDao.java
index 4249b18..9251e98 100644
--- a/util/src/test/java/org/killbill/billing/util/audit/dao/TestDefaultAuditDao.java
+++ b/util/src/test/java/org/killbill/billing/util/audit/dao/TestDefaultAuditDao.java
@@ -96,6 +96,7 @@ public class TestDefaultAuditDao extends UtilTestSuiteWithEmbeddedDB {
eventsListener.pushExpectedEvent(NextEvent.TAG_DEFINITION);
final TagDefinitionModelDao tagDefinition = tagDefinitionDao.create(UUID.randomUUID().toString().substring(0, 5),
UUID.randomUUID().toString().substring(0, 5),
+ ObjectType.ACCOUNT.name(),
internalCallContext);
assertListenerStatus();
diff --git a/util/src/test/java/org/killbill/billing/util/callcontext/TestInternalCallContextFactory.java b/util/src/test/java/org/killbill/billing/util/callcontext/TestInternalCallContextFactory.java
index 40e940e..436971a 100644
--- a/util/src/test/java/org/killbill/billing/util/callcontext/TestInternalCallContextFactory.java
+++ b/util/src/test/java/org/killbill/billing/util/callcontext/TestInternalCallContextFactory.java
@@ -53,9 +53,11 @@ public class TestInternalCallContextFactory extends UtilTestSuiteWithEmbeddedDB
" id varchar(36) NOT NULL,\n" +
" account_id varchar(36) NOT NULL,\n" +
" invoice_date date NOT NULL,\n" +
- " target_date date NOT NULL,\n" +
+ " target_date date,\n" +
" currency varchar(3) NOT NULL,\n" +
+ " status varchar(15) NOT NULL DEFAULT 'COMMITTED',\n" +
" migrated bool NOT NULL,\n" +
+ " parent_invoice bool NOT NULL DEFAULT FALSE,\n" +
" created_by varchar(50) NOT NULL,\n" +
" created_date datetime NOT NULL,\n" +
" account_record_id bigint /*! unsigned */ not null,\n" +
@@ -86,8 +88,8 @@ public class TestInternalCallContextFactory extends UtilTestSuiteWithEmbeddedDB
@Override
public Void withHandle(final Handle handle) throws Exception {
// Note: we always create an accounts table, see MysqlTestingHelper
- handle.execute("insert into accounts (record_id, id, email, name, first_name_length, is_notified_for_invoices, created_date, created_by, updated_date, updated_by) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
- accountRecordId, accountId.toString(), "yo@t.com", "toto", 4, false, new Date(), "i", new Date(), "j");
+ handle.execute("insert into accounts (record_id, id, external_key, email, name, first_name_length, reference_time, time_zone, is_notified_for_invoices, created_date, created_by, updated_date, updated_by) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
+ accountRecordId, accountId.toString(), accountId.toString(), "yo@t.com", "toto", 4, new Date(), "UTC", false, new Date(), "i", new Date(), "j");
return null;
}
});
diff --git a/util/src/test/java/org/killbill/billing/util/callcontext/TestTimeAwareContext.java b/util/src/test/java/org/killbill/billing/util/callcontext/TestTimeAwareContext.java
index 49ecb00..51173e3 100644
--- a/util/src/test/java/org/killbill/billing/util/callcontext/TestTimeAwareContext.java
+++ b/util/src/test/java/org/killbill/billing/util/callcontext/TestTimeAwareContext.java
@@ -190,8 +190,9 @@ public class TestTimeAwareContext extends UtilTestSuiteNoDB {
private void refreshCallContext(final DateTime effectiveDateTime, final DateTimeZone timeZone) {
final Account account = new MockAccountBuilder().timeZone(timeZone)
.createdDate(effectiveDateTime)
+ .referenceTime(effectiveDateTime)
.build();
internalCallContext.setFixedOffsetTimeZone(AccountDateTimeUtils.getFixedOffsetTimeZone(account));
- internalCallContext.setReferenceTime(AccountDateTimeUtils.getReferenceDateTime(account));
+ internalCallContext.setReferenceTime(account.getReferenceTime());
}
}
diff --git a/util/src/test/java/org/killbill/billing/util/customfield/api/TestDefaultCustomFieldUserApi.java b/util/src/test/java/org/killbill/billing/util/customfield/api/TestDefaultCustomFieldUserApi.java
index ffe76e3..a2acf84 100644
--- a/util/src/test/java/org/killbill/billing/util/customfield/api/TestDefaultCustomFieldUserApi.java
+++ b/util/src/test/java/org/killbill/billing/util/customfield/api/TestDefaultCustomFieldUserApi.java
@@ -23,11 +23,13 @@ import java.util.List;
import java.util.Map;
import java.util.UUID;
+import org.killbill.billing.ErrorCode;
import org.killbill.billing.ObjectType;
import org.killbill.billing.account.api.ImmutableAccountData;
import org.killbill.billing.api.TestApiListener.NextEvent;
import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.util.UtilTestSuiteWithEmbeddedDB;
+import org.killbill.billing.util.api.CustomFieldApiException;
import org.killbill.billing.util.customfield.CustomField;
import org.killbill.billing.util.customfield.StringCustomField;
import org.killbill.billing.util.entity.Pagination;
@@ -35,16 +37,20 @@ import org.mockito.Mockito;
import org.skife.jdbi.v2.Handle;
import org.skife.jdbi.v2.tweak.HandleCallback;
import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import com.google.common.collect.ImmutableList;
public class TestDefaultCustomFieldUserApi extends UtilTestSuiteWithEmbeddedDB {
- @Test(groups = "slow")
- public void testSaveCustomFieldWithAccountRecordId() throws Exception {
- final UUID accountId = UUID.randomUUID();
- final Long accountRecordId = 19384012L;
+ final UUID accountId = UUID.randomUUID();
+ final Long accountRecordId = 19384012L;
+
+ @Override
+ @BeforeMethod(groups = "slow")
+ public void beforeMethod() throws Exception {
+ super.beforeMethod();
final ImmutableAccountData immutableAccountData = Mockito.mock(ImmutableAccountData.class);
Mockito.when(immutableAccountInternalApi.getImmutableAccountDataByRecordId(Mockito.<Long>eq(accountRecordId), Mockito.<InternalTenantContext>any())).thenReturn(immutableAccountData);
@@ -53,12 +59,107 @@ public class TestDefaultCustomFieldUserApi extends UtilTestSuiteWithEmbeddedDB {
@Override
public Void withHandle(final Handle handle) throws Exception {
// Note: we always create an accounts table, see MysqlTestingHelper
- handle.execute("insert into accounts (record_id, id, email, name, first_name_length, is_notified_for_invoices, created_date, created_by, updated_date, updated_by) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
- accountRecordId, accountId.toString(), "yo@t.com", "toto", 4, false, new Date(), "i", new Date(), "j");
+ handle.execute("insert into accounts (record_id, id, external_key, email, name, first_name_length, reference_time, time_zone, is_notified_for_invoices, created_date, created_by, updated_date, updated_by) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
+ accountRecordId, accountId.toString(), accountId.toString(), "yo@t.com", "toto", 4, new Date(), "UTC", false, new Date(), "i", new Date(), "j");
return null;
}
});
+ }
+
+ @Test(groups = "slow")
+ public void testCustomFieldNoId() throws Exception {
+ // Verify that when coming from a plugin, the id isn't required
+ final CustomField customField = Mockito.mock(CustomField.class);
+ Mockito.when(customField.getObjectId()).thenReturn(accountId);
+ Mockito.when(customField.getObjectType()).thenReturn(ObjectType.ACCOUNT);
+ Mockito.when(customField.getFieldName()).thenReturn(UUID.randomUUID().toString());
+ Mockito.when(customField.getFieldValue()).thenReturn(UUID.randomUUID().toString());
+
+ eventsListener.pushExpectedEvents(NextEvent.CUSTOM_FIELD);
+ customFieldUserApi.addCustomFields(ImmutableList.<CustomField>of(customField), callContext);
+ assertListenerStatus();
+ }
+
+ @Test(groups = "slow")
+ public void testCustomFieldBasic() throws Exception {
+
+ final CustomField customField1 = new StringCustomField("some123", "some 456", ObjectType.ACCOUNT, accountId, callContext.getCreatedDate());
+ final CustomField customField2 = new StringCustomField("other123", "other 456", ObjectType.ACCOUNT, accountId, callContext.getCreatedDate());
+ eventsListener.pushExpectedEvents(NextEvent.CUSTOM_FIELD, NextEvent.CUSTOM_FIELD);
+ customFieldUserApi.addCustomFields(ImmutableList.<CustomField>of(customField1, customField2), callContext);
+ assertListenerStatus();
+
+ // Verify operation is indeed transactional, and nothing was inserted
+ final CustomField customField3 = new StringCustomField("qrqrq123", "qrqrq 456", ObjectType.ACCOUNT, accountId, callContext.getCreatedDate());
+ try {
+ customFieldUserApi.addCustomFields(ImmutableList.<CustomField>of(customField3, customField1), callContext);
+ } catch (CustomFieldApiException e) {
+ Assert.assertEquals(e.getCode(), ErrorCode.CUSTOM_FIELD_ALREADY_EXISTS.getCode());
+ }
+
+ List<CustomField> all = customFieldUserApi.getCustomFieldsForAccount(accountId, callContext);
+ Assert.assertEquals(all.size(), 2);
+
+ eventsListener.pushExpectedEvent(NextEvent.CUSTOM_FIELD);
+ customFieldUserApi.addCustomFields(ImmutableList.<CustomField>of(customField3), callContext);
+ assertListenerStatus();
+
+ all = customFieldUserApi.getCustomFieldsForAccount(accountId, callContext);
+ Assert.assertEquals(all.size(), 3);
+
+ eventsListener.pushExpectedEvents(NextEvent.CUSTOM_FIELD, NextEvent.CUSTOM_FIELD);
+ customFieldUserApi.removeCustomFields(ImmutableList.of(customField1, customField3), callContext);
+ assertListenerStatus();
+
+ all = customFieldUserApi.getCustomFieldsForAccount(accountId, callContext);
+ Assert.assertEquals(all.size(), 1);
+ Assert.assertEquals(all.get(0).getId(), customField2.getId());
+ Assert.assertEquals(all.get(0).getObjectId(), accountId);
+ Assert.assertEquals(all.get(0).getObjectType(), ObjectType.ACCOUNT);
+ Assert.assertEquals(all.get(0).getFieldName(), customField2.getFieldName());
+ Assert.assertEquals(all.get(0).getFieldValue(), customField2.getFieldValue());
+ }
+
+
+ @Test(groups = "slow")
+ public void testCustomFieldUpdate() throws Exception {
+
+ final CustomField customField1 = new StringCustomField("gtqre", "value1", ObjectType.ACCOUNT, accountId, callContext.getCreatedDate());
+ eventsListener.pushExpectedEvents(NextEvent.CUSTOM_FIELD);
+ customFieldUserApi.addCustomFields(ImmutableList.<CustomField>of(customField1), callContext);
+ assertListenerStatus();
+
+ final CustomField update1 = new StringCustomField(customField1.getId(), customField1.getFieldName(), "value2", customField1.getObjectType(), customField1.getObjectId(), callContext.getCreatedDate());
+ customFieldUserApi.updateCustomFields(ImmutableList.of(update1), callContext);
+
+ List<CustomField> all = customFieldUserApi.getCustomFieldsForAccount(accountId, callContext);
+ Assert.assertEquals(all.size(), 1);
+ Assert.assertEquals(all.get(0).getId(), update1.getId());
+ Assert.assertEquals(all.get(0).getObjectType(), update1.getObjectType());
+ Assert.assertEquals(all.get(0).getObjectId(), update1.getObjectId());
+ Assert.assertEquals(all.get(0).getFieldName(), update1.getFieldName());
+ Assert.assertEquals(all.get(0).getFieldValue(), "value2");
+
+ try {
+ customFieldUserApi.updateCustomFields(ImmutableList.<CustomField>of(new StringCustomField("gtqre", "value1", ObjectType.ACCOUNT, accountId, callContext.getCreatedDate())), callContext);
+ Assert.fail("Updating custom field should fail");
+ } catch (final CustomFieldApiException e) {
+ Assert.assertEquals(e.getCode(), ErrorCode.CUSTOM_FIELD_DOES_NOT_EXISTS_FOR_ID.getCode());
+ }
+
+ try {
+ customFieldUserApi.updateCustomFields(ImmutableList.<CustomField>of(new StringCustomField(customField1.getId(), "wrongName", "value2", customField1.getObjectType(), customField1.getObjectId(), callContext.getCreatedDate())), callContext);
+ Assert.fail("Updating custom field should fail");
+ } catch (final CustomFieldApiException e) {
+ Assert.assertEquals(e.getCode(), ErrorCode.CUSTOM_FIELD_INVALID_UPDATE.getCode());
+ }
+
+ }
+
+
+ @Test(groups = "slow")
+ public void testSaveCustomFieldWithAccountRecordId() throws Exception {
checkPagination(0);
diff --git a/util/src/test/java/org/killbill/billing/util/customfield/dao/MockCustomFieldDao.java b/util/src/test/java/org/killbill/billing/util/customfield/dao/MockCustomFieldDao.java
index e2b016d..ae80cd4 100644
--- a/util/src/test/java/org/killbill/billing/util/customfield/dao/MockCustomFieldDao.java
+++ b/util/src/test/java/org/killbill/billing/util/customfield/dao/MockCustomFieldDao.java
@@ -53,12 +53,17 @@ public class MockCustomFieldDao extends MockEntityDaoBase<CustomFieldModelDao, C
}
@Override
- public Pagination<CustomFieldModelDao> searchCustomFields(final String searchKey, final Long offset, final Long limit, final InternalTenantContext context) {
+ public void deleteCustomFields(final Iterable<UUID> customFieldIds, final InternalCallContext context) throws CustomFieldApiException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void updateCustomFields(final Iterable<CustomFieldModelDao> customFieldIds, final InternalCallContext context) throws CustomFieldApiException {
throw new UnsupportedOperationException();
}
@Override
- public void deleteCustomField(final UUID customFieldId, final InternalCallContext context) throws CustomFieldApiException {
+ public Pagination<CustomFieldModelDao> searchCustomFields(final String searchKey, final Long offset, final Long limit, final InternalTenantContext context) {
throw new UnsupportedOperationException();
}
}
diff --git a/util/src/test/java/org/killbill/billing/util/dao/TestEntityBaseDaoException.java b/util/src/test/java/org/killbill/billing/util/dao/TestEntityBaseDaoException.java
new file mode 100644
index 0000000..82d8eaa
--- /dev/null
+++ b/util/src/test/java/org/killbill/billing/util/dao/TestEntityBaseDaoException.java
@@ -0,0 +1,101 @@
+/*
+ * 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.util.dao;
+
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.killbill.billing.ErrorCode;
+import org.killbill.billing.callcontext.InternalCallContext;
+import org.killbill.billing.security.SecurityApiException;
+import org.killbill.billing.util.UtilTestSuiteWithEmbeddedDB;
+import org.killbill.billing.util.entity.dao.EntityDaoBase;
+import org.killbill.billing.util.entity.dao.EntitySqlDao;
+import org.killbill.billing.util.entity.dao.EntitySqlDaoTransactionalJdbiWrapper;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+public class TestEntityBaseDaoException extends UtilTestSuiteWithEmbeddedDB {
+
+ public static class TestEntityBaseDao extends EntityDaoBase<KombuchaModelDao, Kombucha, SecurityApiException> {
+
+ public TestEntityBaseDao(final EntitySqlDaoTransactionalJdbiWrapper transactionalSqlDao, final Class<? extends EntitySqlDao<KombuchaModelDao, Kombucha>> realSqlDao) {
+ super(transactionalSqlDao, realSqlDao);
+ }
+
+ @Override
+ protected SecurityApiException generateAlreadyExistsException(final KombuchaModelDao entity, final InternalCallContext context) {
+ return new SecurityApiException(ErrorCode.__UNKNOWN_ERROR_CODE);
+ }
+
+ @Override
+ protected boolean checkEntityAlreadyExists(final EntitySqlDao<KombuchaModelDao, Kombucha> KombuchaSqlDao, final KombuchaModelDao entity, final InternalCallContext context) {
+ return true;
+ }
+ }
+
+ @Test(groups = "slow")
+ public void testWithCreateException() throws Exception {
+ final EntitySqlDaoTransactionalJdbiWrapper entitySqlDaoTransactionalJdbiWrapper = new EntitySqlDaoTransactionalJdbiWrapper(dbi, clock, null, nonEntityDao, null);
+ final TestEntityBaseDao test = new TestEntityBaseDao(entitySqlDaoTransactionalJdbiWrapper, KombuchaSqlDao.class);
+
+ final KombuchaModelDao entity = new KombuchaModelDao() {
+ @Override
+ public Long getRecordId() {
+ return null;
+ }
+ @Override
+ public Long getAccountRecordId() {
+ return null;
+ }
+ @Override
+ public Long getTenantRecordId() {
+ return null;
+ }
+ @Override
+ public TableName getTableName() {
+ return null;
+ }
+ @Override
+ public TableName getHistoryTableName() {
+ return null;
+ }
+ @Override
+ public UUID getId() {
+ return null;
+ }
+ @Override
+ public DateTime getCreatedDate() {
+ return null;
+ }
+ @Override
+ public DateTime getUpdatedDate() {
+ return null;
+ }
+ };
+
+ try {
+ test.create(entity, internalCallContext);
+ Assert.fail("test should throw SecurityApiException");
+ } catch (final SecurityApiException e) {
+ Assert.assertEquals(e.getCode(), ErrorCode.__UNKNOWN_ERROR_CODE.getCode());
+ }
+
+ }
+
+}
diff --git a/util/src/test/java/org/killbill/billing/util/dao/TestNonEntityDao.java b/util/src/test/java/org/killbill/billing/util/dao/TestNonEntityDao.java
index 8b9c231..c2228fd 100644
--- a/util/src/test/java/org/killbill/billing/util/dao/TestNonEntityDao.java
+++ b/util/src/test/java/org/killbill/billing/util/dao/TestNonEntityDao.java
@@ -96,8 +96,8 @@ public class TestNonEntityDao extends UtilTestSuiteWithEmbeddedDB {
@Override
public Void withHandle(final Handle handle) throws Exception {
// Note: we always create an accounts table, see MysqlTestingHelper
- handle.execute("insert into accounts (record_id, id, email, name, first_name_length, is_notified_for_invoices, created_date, created_by, updated_date, updated_by, tenant_record_id) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
- accountRecordId, accountId.toString(), "zozo@tt.com", "zozo", 4, false, new Date(), "i", new Date(), "j", tenantRecordId);
+ handle.execute("insert into accounts (record_id, id, external_key, email, name, first_name_length, reference_time, time_zone, is_notified_for_invoices, created_date, created_by, updated_date, updated_by, tenant_record_id) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
+ accountRecordId, accountId.toString(), accountId.toString(), "zozo@tt.com", "zozo", 4, new Date(), "UTC", false, new Date(), "i", new Date(), "j", tenantRecordId);
return null;
}
});
@@ -108,8 +108,8 @@ public class TestNonEntityDao extends UtilTestSuiteWithEmbeddedDB {
@Override
public Void withHandle(final Handle handle) throws Exception {
// Note: we always create an accounts table, see MysqlTestingHelper
- handle.execute("insert into account_history (record_id, id, email, name, first_name_length, is_notified_for_invoices, created_date, created_by, updated_date, updated_by, tenant_record_id) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
- accountRecordId, accountId.toString(), "zozo@tt.com", "zozo", 4, false, new Date(), "i", new Date(), "j", tenantRecordId);
+ handle.execute("insert into account_history (record_id, id, external_key, email, name, first_name_length, reference_time, time_zone, is_notified_for_invoices, created_date, created_by, updated_date, updated_by, tenant_record_id) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
+ accountRecordId, accountId.toString(), accountId.toString(), "zozo@tt.com", "zozo", 4, new Date(), "UTC", false, new Date(), "i", new Date(), "j", tenantRecordId);
return null;
}
});
diff --git a/util/src/test/java/org/killbill/billing/util/dao/TestPagination.java b/util/src/test/java/org/killbill/billing/util/dao/TestPagination.java
index 107585e..72dcfc7 100644
--- a/util/src/test/java/org/killbill/billing/util/dao/TestPagination.java
+++ b/util/src/test/java/org/killbill/billing/util/dao/TestPagination.java
@@ -18,6 +18,7 @@ package org.killbill.billing.util.dao;
import java.util.List;
+import org.killbill.billing.ObjectType;
import org.testng.Assert;
import org.testng.annotations.Test;
@@ -38,7 +39,7 @@ public class TestPagination extends UtilTestSuiteWithEmbeddedDB {
final String definitionName = "name-" + i;
final String description = "description-" + i;
eventsListener.pushExpectedEvent(NextEvent.TAG_DEFINITION);
- tagDefinitionDao.create(definitionName, description, internalCallContext);
+ tagDefinitionDao.create(definitionName, description, ObjectType.ACCOUNT.name(), internalCallContext);
assertListenerStatus();
}
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 a93add6..588d765 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
@@ -16,123 +16,61 @@
package org.killbill.billing.util.dao;
-import java.io.InputStream;
-import java.io.InputStreamReader;
import java.util.regex.Pattern;
-import org.antlr.stringtemplate.StringTemplateGroup;
import org.killbill.billing.util.UtilTestSuiteNoDB;
+import org.stringtemplate.v4.STGroup;
+import org.stringtemplate.v4.STGroupFile;
import org.testng.Assert;
-import org.testng.annotations.AfterMethod;
-import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
-import com.google.common.collect.ImmutableMap;
-
public class TestStringTemplateInheritance extends UtilTestSuiteNoDB {
- InputStream entityStream;
- InputStream kombuchaStream;
-
- @Override
- @BeforeMethod(groups = "fast")
- public void beforeMethod() throws Exception {
- super.beforeMethod();
- entityStream = this.getClass().getResourceAsStream("/org/killbill/billing/util/entity/dao/EntitySqlDao.sql.stg");
- kombuchaStream = this.getClass().getResourceAsStream("/org/killbill/billing/util/dao/Kombucha.sql.stg");
- }
-
- @Override
- @AfterMethod(groups = "fast")
- public void afterMethod() throws Exception {
- super.afterMethod();
- if (entityStream != null) {
- entityStream.close();
- }
- if (kombuchaStream != null) {
- kombuchaStream.close();
- }
- }
-
@Test(groups = "fast")
public void testCheckQueries() throws Exception {
- // From http://www.antlr.org/wiki/display/ST/ST+condensed+--+Templates+and+groups#STcondensed--Templatesandgroups-Withsupergroupfile:
- // there is no mechanism for automatically loading a mentioned super-group file
- new StringTemplateGroup(new InputStreamReader(entityStream));
-
- final StringTemplateGroup kombucha = new StringTemplateGroup(new InputStreamReader(kombuchaStream));
+ final STGroup kombucha = new STGroupFile(this.getClass().getResource("/org/killbill/billing/util/dao/Kombucha.sql.stg"), "UTF-8", '<', '>');
// Verify non inherited template
- Assert.assertEquals(kombucha.getInstanceOf("isIsTimeForKombucha").toString(), "select hour(current_timestamp(0)) = 17 as is_time;");
+ Assert.assertEquals(kombucha.getInstanceOf("isIsTimeForKombucha").render(), "select hour(current_timestamp(0)) = 17 as is_time;");
// Verify inherited templates
- assertPattern(kombucha.getInstanceOf("getById").toString(), "select\r?\n" +
- " t.record_id\r?\n" +
- ", t.id\r?\n" +
- ", t.tea\r?\n" +
- ", t.mushroom\r?\n" +
- ", t.sugar\r?\n" +
- ", t.account_record_id\r?\n" +
- ", t.tenant_record_id\r?\n" +
- "from kombucha t\r?\n" +
- "where t.id = :id\r?\n" +
- "and t.tenant_record_id = :tenantRecordId\r?\n" +
- ";");
- assertPattern(kombucha.getInstanceOf("getByRecordId").toString(), "select\r?\n" +
- " t.record_id\r?\n" +
- ", t.id\r?\n" +
- ", t.tea\r?\n" +
- ", t.mushroom\r?\n" +
- ", t.sugar\r?\n" +
- ", t.account_record_id\r?\n" +
- ", t.tenant_record_id\r?\n" +
- "from kombucha t\r?\n" +
- "where t.record_id = :recordId\r?\n" +
- "and t.tenant_record_id = :tenantRecordId\r?\n" +
- ";");
- assertPattern(kombucha.getInstanceOf("getRecordId").toString(), "select\r?\n" +
+ assertPattern(kombucha.getInstanceOf("getById").render(), "select\r?\n" +
+ " t.record_id\r?\n" +
+ ", t.id\r?\n" +
+ ", t.tea\r?\n" +
+ ", t.mushroom\r?\n" +
+ ", t.sugar\r?\n" +
+ ", t.account_record_id\r?\n" +
+ ", t.tenant_record_id\r?\n" +
+ "from kombucha t\r?\n" +
+ "where t.id = :id\r?\n" +
+ "and t.tenant_record_id = :tenantRecordId\r?\n" +
+ ";");
+ assertPattern(kombucha.getInstanceOf("getByRecordId").render(), "select\r?\n" +
" t.record_id\r?\n" +
+ ", t.id\r?\n" +
+ ", t.tea\r?\n" +
+ ", t.mushroom\r?\n" +
+ ", t.sugar\r?\n" +
+ ", t.account_record_id\r?\n" +
+ ", t.tenant_record_id\r?\n" +
"from kombucha t\r?\n" +
- "where t.id = :id\r?\n" +
+ "where t.record_id = :recordId\r?\n" +
"and t.tenant_record_id = :tenantRecordId\r?\n" +
";");
- assertPattern(kombucha.getInstanceOf("getHistoryRecordId").toString(), "select\r?\n" +
- " max\\(t.record_id\\)\r?\n" +
- "from kombucha_history t\r?\n" +
- "where t.target_record_id = :targetRecordId\r?\n" +
- "and t.tenant_record_id = :tenantRecordId\r?\n" +
- ";");
- assertPattern(kombucha.getInstanceOf("getAll").toString(), "select\r?\n" +
- " t.record_id\r?\n" +
- ", t.id\r?\n" +
- ", t.tea\r?\n" +
- ", t.mushroom\r?\n" +
- ", t.sugar\r?\n" +
- ", 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" +
- "order by t.record_id ASC\r?\n" +
- ";");
- assertPattern(kombucha.getInstanceOf("get", ImmutableMap.<String, String>of("orderBy", "record_id", "offset", "3", "rowCount", "12", "ordering", "ASC")).toString(), "select\r?\n" +
- " t.record_id\r?\n" +
- ", t.id\r?\n" +
- ", t.tea\r?\n" +
- ", t.mushroom\r?\n" +
- ", t.sugar\r?\n" +
- ", t.account_record_id\r?\n" +
- ", t.tenant_record_id\r?\n" +
- "from kombucha t\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 ASC\r?\n" +
- " limit :rowCount offset :offset\r?\n" +
- "\\) optimization on optimization.record_id = t.record_id\r?\n" +
- "order by t.record_id ASC\r?\n" +
- ";");
- assertPattern(kombucha.getInstanceOf("test").toString(), "select\r?\n" +
+ assertPattern(kombucha.getInstanceOf("getRecordId").render(), "select\r?\n" +
+ " t.record_id\r?\n" +
+ "from kombucha t\r?\n" +
+ "where t.id = :id\r?\n" +
+ "and t.tenant_record_id = :tenantRecordId\r?\n" +
+ ";");
+ assertPattern(kombucha.getInstanceOf("getHistoryRecordId").render(), "select\r?\n" +
+ " max\\(t.record_id\\)\r?\n" +
+ "from kombucha_history t\r?\n" +
+ "where t.target_record_id = :targetRecordId\r?\n" +
+ "and t.tenant_record_id = :tenantRecordId\r?\n" +
+ ";");
+ assertPattern(kombucha.getInstanceOf("getAll").render(), "select\r?\n" +
" t.record_id\r?\n" +
", t.id\r?\n" +
", t.tea\r?\n" +
@@ -142,57 +80,92 @@ public class TestStringTemplateInheritance extends UtilTestSuiteNoDB {
", t.tenant_record_id\r?\n" +
"from kombucha t\r?\n" +
"where t.tenant_record_id = :tenantRecordId\r?\n" +
- "limit 1\r?\n" +
+ "order by t.record_id ASC\r?\n" +
";");
- assertPattern(kombucha.getInstanceOf("addHistoryFromTransaction").toString(), "insert into kombucha_history \\(\r?\n" +
- " id\r?\n" +
- ", target_record_id\r?\n" +
- ", change_type\r?\n" +
- ", tea\r?\n" +
- ", mushroom\r?\n" +
- ", sugar\r?\n" +
- ", account_record_id\r?\n" +
- ", tenant_record_id\r?\n" +
- "\\)\r?\n" +
- "values \\(\r?\n" +
- " :id\r?\n" +
- ", :targetRecordId\r?\n" +
- ", :changeType\r?\n" +
- ", :tea\r?\n" +
- ", :mushroom\r?\n" +
- ", :sugar\r?\n" +
- ", :accountRecordId\r?\n" +
- ", :tenantRecordId\r?\n" +
- "\\)\r?\n" +
- ";");
+ assertPattern(kombucha.getInstanceOf("get")
+ .add("orderBy", "record_id")
+ .add("offset", "3")
+ .add("rowCount", "12")
+ .add("ordering", "ASC")
+ .render(), "select\r?\n" +
+ " t.record_id\r?\n" +
+ ", t.id\r?\n" +
+ ", t.tea\r?\n" +
+ ", t.mushroom\r?\n" +
+ ", t.sugar\r?\n" +
+ ", t.account_record_id\r?\n" +
+ ", t.tenant_record_id\r?\n" +
+ "from kombucha t\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 ASC\r?\n" +
+ " limit :rowCount offset :offset\r?\n" +
+ "\\) optimization on optimization.record_id = t.record_id\r?\n" +
+ "order by t.record_id ASC\r?\n" +
+ ";");
+ assertPattern(kombucha.getInstanceOf("test").render(), "select\r?\n" +
+ " t.record_id\r?\n" +
+ ", t.id\r?\n" +
+ ", t.tea\r?\n" +
+ ", t.mushroom\r?\n" +
+ ", t.sugar\r?\n" +
+ ", 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" +
+ "limit 1\r?\n" +
+ ";");
+ assertPattern(kombucha.getInstanceOf("addHistoryFromTransaction").render(), "insert into kombucha_history \\(\r?\n" +
+ " id\r?\n" +
+ ", target_record_id\r?\n" +
+ ", change_type\r?\n" +
+ ", tea\r?\n" +
+ ", mushroom\r?\n" +
+ ", sugar\r?\n" +
+ ", account_record_id\r?\n" +
+ ", tenant_record_id\r?\n" +
+ "\\)\r?\n" +
+ "values \\(\r?\n" +
+ " :id\r?\n" +
+ ", :targetRecordId\r?\n" +
+ ", :changeType\r?\n" +
+ ", :tea\r?\n" +
+ ", :mushroom\r?\n" +
+ ", :sugar\r?\n" +
+ ", :accountRecordId\r?\n" +
+ ", :tenantRecordId\r?\n" +
+ "\\)\r?\n" +
+ ";");
- assertPattern(kombucha.getInstanceOf("insertAuditFromTransaction").toString(), "insert into audit_log \\(\r?\n" +
- "id\r?\n" +
- ", table_name\r?\n" +
- ", target_record_id\r?\n" +
- ", change_type\r?\n" +
- ", created_by\r?\n" +
- ", reason_code\r?\n" +
- ", comments\r?\n" +
- ", user_token\r?\n" +
- ", created_date\r?\n" +
- ", account_record_id\r?\n" +
- ", tenant_record_id\r?\n" +
- "\\)\r?\n" +
- "values \\(\r?\n" +
- " :id\r?\n" +
- ", :tableName\r?\n" +
- ", :targetRecordId\r?\n" +
- ", :changeType\r?\n" +
- ", :createdBy\r?\n" +
- ", :reasonCode\r?\n" +
- ", :comments\r?\n" +
- ", :userToken\r?\n" +
- ", :createdDate\r?\n" +
- ", :accountRecordId\r?\n" +
- ", :tenantRecordId\r?\n" +
- "\\)\r?\n" +
- ";");
+ assertPattern(kombucha.getInstanceOf("insertAuditFromTransaction").render(), "insert into audit_log \\(\r?\n" +
+ "id\r?\n" +
+ ", table_name\r?\n" +
+ ", target_record_id\r?\n" +
+ ", change_type\r?\n" +
+ ", created_by\r?\n" +
+ ", reason_code\r?\n" +
+ ", comments\r?\n" +
+ ", user_token\r?\n" +
+ ", created_date\r?\n" +
+ ", account_record_id\r?\n" +
+ ", tenant_record_id\r?\n" +
+ "\\)\r?\n" +
+ "values \\(\r?\n" +
+ " :id\r?\n" +
+ ", :tableName\r?\n" +
+ ", :targetRecordId\r?\n" +
+ ", :changeType\r?\n" +
+ ", :createdBy\r?\n" +
+ ", :reasonCode\r?\n" +
+ ", :comments\r?\n" +
+ ", :userToken\r?\n" +
+ ", :createdDate\r?\n" +
+ ", :accountRecordId\r?\n" +
+ ", :tenantRecordId\r?\n" +
+ "\\)\r?\n" +
+ ";");
}
private void assertPattern(final String actual, final String expected) {
diff --git a/util/src/test/java/org/killbill/billing/util/dao/TestStringTemplateInheritanceWithJdbi.java b/util/src/test/java/org/killbill/billing/util/dao/TestStringTemplateInheritanceWithJdbi.java
index 02c6361..fa36842 100644
--- a/util/src/test/java/org/killbill/billing/util/dao/TestStringTemplateInheritanceWithJdbi.java
+++ b/util/src/test/java/org/killbill/billing/util/dao/TestStringTemplateInheritanceWithJdbi.java
@@ -16,29 +16,13 @@
package org.killbill.billing.util.dao;
-import org.skife.jdbi.v2.sqlobject.SqlQuery;
import org.testng.Assert;
import org.testng.annotations.Test;
import org.killbill.billing.util.UtilTestSuiteWithEmbeddedDB;
-import org.killbill.billing.util.entity.Entity;
-import org.killbill.billing.util.entity.dao.EntityModelDao;
-import org.killbill.billing.util.entity.dao.EntitySqlDao;
-import org.killbill.billing.util.entity.dao.EntitySqlDaoStringTemplate;
public class TestStringTemplateInheritanceWithJdbi extends UtilTestSuiteWithEmbeddedDB {
- private static interface Kombucha extends Entity {}
-
- private static interface KombuchaModelDao extends EntityModelDao<Kombucha> {}
-
- @EntitySqlDaoStringTemplate("/org/killbill/billing/util/dao/Kombucha.sql.stg")
- private static interface KombuchaSqlDao extends EntitySqlDao<KombuchaModelDao, Kombucha> {
-
- @SqlQuery
- public boolean isIsTimeForKombucha();
- }
-
@Test(groups = "slow")
public void testInheritQueries() throws Exception {
final KombuchaSqlDao dao = dbi.onDemand(KombuchaSqlDao.class);
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 e9b497f..3eec0b5 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
@@ -1,7 +1,9 @@
/*
- * Copyright 2010-2012 Ning, Inc.
+ * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
*
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
@@ -30,8 +32,11 @@ import org.killbill.billing.util.UtilTestSuiteWithEmbeddedDB;
import org.killbill.billing.util.api.DatabaseExportOutputStream;
import org.killbill.billing.util.validation.dao.DatabaseSchemaDao;
+import com.ning.compress.lzf.LZFEncoder;
+
public class TestDatabaseExportDao extends UtilTestSuiteWithEmbeddedDB {
+
@Test(groups = "slow")
public void testExportSimpleData() throws Exception {
// Empty database
@@ -42,12 +47,14 @@ public class TestDatabaseExportDao extends UtilTestSuiteWithEmbeddedDB {
final String accountEmail = UUID.randomUUID().toString().substring(0, 4) + '@' + UUID.randomUUID().toString().substring(0, 4);
final String accountName = UUID.randomUUID().toString().substring(0, 4);
final int firstNameLength = 4;
+ final String timeZone = "UTC";
final boolean isNotifiedForInvoices = false;
final Date createdDate = new Date(12421982000L);
final String createdBy = UUID.randomUUID().toString().substring(0, 4);
final Date updatedDate = new Date(382910622000L);
final String updatedBy = UUID.randomUUID().toString().substring(0, 4);
+ final byte[] properties = LZFEncoder.encode(new byte[] { 'c', 'a', 'f', 'e' });
final String tableNameA = "test_database_export_dao_a";
final String tableNameB = "test_database_export_dao_b";
dbi.withHandle(new HandleCallback<Void>() {
@@ -56,6 +63,7 @@ public class TestDatabaseExportDao extends UtilTestSuiteWithEmbeddedDB {
handle.execute("drop table if exists " + tableNameA);
handle.execute("create table " + tableNameA + "(record_id serial unique," +
"a_column char default 'a'," +
+ "blob_column mediumblob," +
"account_record_id bigint /*! unsigned */ not null," +
"tenant_record_id bigint /*! unsigned */ not null default 0," +
"primary key(record_id));");
@@ -65,26 +73,27 @@ public class TestDatabaseExportDao extends UtilTestSuiteWithEmbeddedDB {
"account_record_id bigint /*! unsigned */ not null," +
"tenant_record_id bigint /*! unsigned */ not null default 0," +
"primary key(record_id));");
- handle.execute("insert into " + tableNameA + " (account_record_id, tenant_record_id) values (?, ?)",
- internalCallContext.getAccountRecordId(), internalCallContext.getTenantRecordId());
+ handle.execute("insert into " + tableNameA + " (blob_column, account_record_id, tenant_record_id) values (?, ?, ?)",
+ properties, internalCallContext.getAccountRecordId(), internalCallContext.getTenantRecordId());
handle.execute("insert into " + tableNameB + " (account_record_id, tenant_record_id) values (?, ?)",
internalCallContext.getAccountRecordId(), internalCallContext.getTenantRecordId());
// Add row in accounts table
- handle.execute("insert into accounts (record_id, id, email, name, first_name_length, is_notified_for_invoices, created_date, created_by, updated_date, updated_by, tenant_record_id) " +
- "values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
- internalCallContext.getAccountRecordId(), accountId, accountEmail, accountName, firstNameLength, isNotifiedForInvoices, createdDate, createdBy, updatedDate, updatedBy, internalCallContext.getTenantRecordId());
+ handle.execute("insert into accounts (record_id, id, external_key, email, name, first_name_length, reference_time, time_zone, is_notified_for_invoices, created_date, created_by, updated_date, updated_by, tenant_record_id) " +
+ "values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
+ internalCallContext.getAccountRecordId(), accountId, accountId, accountEmail, accountName, firstNameLength, createdDate, timeZone, isNotifiedForInvoices, createdDate, createdBy, updatedDate, updatedBy, internalCallContext.getTenantRecordId());
return null;
}
});
// 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|parent_account_id|is_payment_delegated_to_parent|payment_method_id|reference_time|time_zone|locale|address1|address2|company_name|city|state_or_province|country|postal_code|phone|notes|migrated|is_notified_for_invoices|created_date|created_by|updated_date|updated_by|tenant_record_id\n" +
+ String.format("%s|%s|%s|%s|%s|%s||||false||%s|%s|||||||||||false|%s|%s|%s|%s|%s|%s", internalCallContext.getAccountRecordId(), accountId, accountId, accountEmail, accountName, firstNameLength, "1970-05-24T18:33:02.000+0000", timeZone,
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" +
+ "-- " + tableNameA + " record_id|a_column|blob_column|account_record_id|tenant_record_id\n" +
+ "1|a|WlYAAARjYWZl|" + 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/listener/TestRetryableService.java b/util/src/test/java/org/killbill/billing/util/listener/TestRetryableService.java
new file mode 100644
index 0000000..c33b881
--- /dev/null
+++ b/util/src/test/java/org/killbill/billing/util/listener/TestRetryableService.java
@@ -0,0 +1,210 @@
+/*
+ * 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.util.listener;
+
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.joda.time.Period;
+import org.killbill.billing.ObjectType;
+import org.killbill.billing.account.api.ImmutableAccountData;
+import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.events.BusInternalEvent;
+import org.killbill.billing.events.ControlTagCreationInternalEvent;
+import org.killbill.billing.events.ControlTagDeletionInternalEvent;
+import org.killbill.billing.invoice.plugin.api.InvoicePluginApiRetryException;
+import org.killbill.billing.util.UtilTestSuiteWithEmbeddedDB;
+import org.killbill.billing.util.tag.DefaultTagDefinition;
+import org.killbill.billing.util.tag.api.user.DefaultControlTagCreationEvent;
+import org.killbill.notificationq.api.NotificationEventWithMetadata;
+import org.killbill.notificationq.api.NotificationQueue;
+import org.killbill.notificationq.api.NotificationQueueService.NoSuchNotificationQueue;
+import org.killbill.queue.retry.RetryableService;
+import org.killbill.queue.retry.RetryableSubscriber;
+import org.killbill.queue.retry.RetryableSubscriber.SubscriberAction;
+import org.killbill.queue.retry.RetryableSubscriber.SubscriberQueueHandler;
+import org.mockito.Mockito;
+import org.testng.Assert;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+
+public class TestRetryableService extends UtilTestSuiteWithEmbeddedDB {
+
+ private static final String TEST_LISTENER = "TestListener";
+ private static final ImmutableList<Period> RETRY_SCHEDULE = ImmutableList.<Period>of(Period.hours(1), Period.days(1));
+
+ private ControlTagCreationInternalEvent event;
+ private TestListener testListener;
+
+ @BeforeClass(groups = "slow")
+ public void setUpClass() throws Exception {
+ final ImmutableAccountData immutableAccountData = Mockito.mock(ImmutableAccountData.class);
+ Mockito.when(immutableAccountInternalApi.getImmutableAccountDataByRecordId(Mockito.<Long>eq(internalCallContext.getAccountRecordId()), Mockito.<InternalTenantContext>any())).thenReturn(immutableAccountData);
+ }
+
+ @BeforeMethod(groups = "slow")
+ public void setUp() throws Exception {
+ event = new DefaultControlTagCreationEvent(UUID.randomUUID(),
+ UUID.randomUUID(),
+ ObjectType.ACCOUNT,
+ new DefaultTagDefinition("name", "description", false),
+ internalCallContext.getAccountRecordId(),
+ internalCallContext.getTenantRecordId(),
+ UUID.randomUUID());
+
+ testListener = new TestListener();
+ Assert.assertEquals(testListener.eventsSeen.size(), 0);
+ Assert.assertEquals(getFutureRetryableEvents().size(), 0);
+ }
+
+ @AfterMethod(groups = "slow")
+ public void tearDown() throws Exception {
+ testListener.stop();
+ }
+
+ @Test(groups = "slow")
+ public void testFixUp() throws Exception {
+ testListener.throwRetryableException = true;
+
+ final DateTime startTime = clock.getUTCNow();
+ testListener.handleEvent(event);
+ assertListenerStatus();
+
+ Assert.assertEquals(testListener.eventsSeen.size(), 0);
+ List<NotificationEventWithMetadata> futureRetryableEvents = getFutureRetryableEvents();
+ Assert.assertEquals(futureRetryableEvents.size(), 1);
+ Assert.assertEquals(futureRetryableEvents.get(0).getEffectiveDate().compareTo(startTime.plus(RETRY_SCHEDULE.get(0))), 0);
+
+ clock.setTime(futureRetryableEvents.get(0).getEffectiveDate());
+ assertListenerStatus();
+
+ Assert.assertEquals(testListener.eventsSeen.size(), 0);
+ futureRetryableEvents = getFutureRetryableEvents();
+ Assert.assertEquals(futureRetryableEvents.size(), 1);
+ Assert.assertEquals(futureRetryableEvents.get(0).getEffectiveDate().compareTo(startTime.plus(RETRY_SCHEDULE.get(1))), 0);
+
+ testListener.throwRetryableException = false;
+
+ clock.setTime(futureRetryableEvents.get(0).getEffectiveDate());
+ assertListenerStatus();
+
+ Assert.assertEquals(testListener.eventsSeen.size(), 1);
+ Assert.assertEquals(getFutureRetryableEvents().size(), 0);
+ }
+
+ @Test(groups = "slow")
+ public void testGiveUp() throws Exception {
+ testListener.throwRetryableException = true;
+
+ final DateTime startTime = clock.getUTCNow();
+ testListener.handleEvent(event);
+ assertListenerStatus();
+
+ Assert.assertEquals(testListener.eventsSeen.size(), 0);
+ List<NotificationEventWithMetadata> futureRetryableEvents = getFutureRetryableEvents();
+ Assert.assertEquals(futureRetryableEvents.size(), 1);
+ Assert.assertEquals(futureRetryableEvents.get(0).getEffectiveDate().compareTo(startTime.plus(RETRY_SCHEDULE.get(0))), 0);
+
+ clock.setTime(futureRetryableEvents.get(0).getEffectiveDate());
+ assertListenerStatus();
+
+ Assert.assertEquals(testListener.eventsSeen.size(), 0);
+ futureRetryableEvents = getFutureRetryableEvents();
+ Assert.assertEquals(futureRetryableEvents.size(), 1);
+ Assert.assertEquals(futureRetryableEvents.get(0).getEffectiveDate().compareTo(startTime.plus(RETRY_SCHEDULE.get(1))), 0);
+
+ clock.setTime(futureRetryableEvents.get(0).getEffectiveDate());
+ assertListenerStatus();
+
+ Assert.assertEquals(testListener.eventsSeen.size(), 0);
+ // Give up
+ Assert.assertEquals(getFutureRetryableEvents().size(), 0);
+ }
+
+ @Test(groups = "slow")
+ public void testNotRetryableException() throws Exception {
+ testListener.throwOtherException = true;
+
+ try {
+ testListener.handleEvent(event);
+ Assert.fail("Expected exception");
+ } catch (final IllegalArgumentException e) {
+ Assert.assertTrue(true);
+ }
+ assertListenerStatus();
+
+ Assert.assertEquals(testListener.eventsSeen.size(), 0);
+ Assert.assertEquals(getFutureRetryableEvents().size(), 0);
+ }
+
+ private List<NotificationEventWithMetadata> getFutureRetryableEvents() throws NoSuchNotificationQueue {
+ final NotificationQueue notificationQueue = queueService.getNotificationQueue(RetryableService.RETRYABLE_SERVICE_NAME, TEST_LISTENER);
+ return ImmutableList.<NotificationEventWithMetadata>copyOf(notificationQueue.getFutureNotificationForSearchKeys(internalCallContext.getAccountRecordId(), internalCallContext.getTenantRecordId()));
+ }
+
+ private final class TestListener extends RetryableService {
+
+ private final SubscriberQueueHandler subscriberQueueHandler = new SubscriberQueueHandler();
+ private final Collection<BusInternalEvent> eventsSeen = new LinkedList<BusInternalEvent>();
+
+ private final RetryableSubscriber retryableSubscriber;
+
+ private boolean throwRetryableException = false;
+ private boolean throwOtherException = false;
+
+ public TestListener() {
+ super(queueService);
+
+ subscriberQueueHandler.subscribe(ControlTagDeletionInternalEvent.class,
+ new SubscriberAction<ControlTagDeletionInternalEvent>() {
+ @Override
+ public void run(final ControlTagDeletionInternalEvent event) {
+ Assert.fail("No handler registered");
+ }
+ });
+ subscriberQueueHandler.subscribe(ControlTagCreationInternalEvent.class,
+ new SubscriberAction<ControlTagCreationInternalEvent>() {
+ @Override
+ public void run(final ControlTagCreationInternalEvent event) {
+ if (throwRetryableException) {
+ throw new InvoicePluginApiRetryException(RETRY_SCHEDULE);
+ } else if (throwOtherException) {
+ throw new IllegalArgumentException("EXPECTED");
+ } else {
+ eventsSeen.add(event);
+ }
+ }
+ });
+ this.retryableSubscriber = new RetryableSubscriber(clock, this, subscriberQueueHandler);
+
+ initialize(TEST_LISTENER, subscriberQueueHandler);
+ start();
+ }
+
+ public void handleEvent(final ControlTagCreationInternalEvent event) {
+ retryableSubscriber.handleEvent(event);
+ }
+ }
+}
diff --git a/util/src/test/java/org/killbill/billing/util/security/shiro/realm/TestKillBillJdbcRealm.java b/util/src/test/java/org/killbill/billing/util/security/shiro/realm/TestKillBillJdbcRealm.java
index 30f90af..ea70d42 100644
--- a/util/src/test/java/org/killbill/billing/util/security/shiro/realm/TestKillBillJdbcRealm.java
+++ b/util/src/test/java/org/killbill/billing/util/security/shiro/realm/TestKillBillJdbcRealm.java
@@ -40,6 +40,7 @@ import org.testng.annotations.Test;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
public class TestKillBillJdbcRealm extends UtilTestSuiteWithEmbeddedDB {
@@ -178,7 +179,36 @@ public class TestKillBillJdbcRealm extends UtilTestSuiteWithEmbeddedDB {
Assert.fail("Subject should not have rights to create tag definitions");
} catch (AuthorizationException e) {
}
+ }
+
+ @Test(groups = "slow")
+ public void testUpdateRoleDefinition() throws SecurityApiException {
+
+ final String username = "siskiyou";
+ final String password = "siskiyou33";
+
+ securityApi.addRoleDefinition("original", ImmutableList.of("account:*", "invoice", "tag:create_tag_definition"), callContext);
+ securityApi.addUserRoles(username, password, ImmutableList.of("restricted"), callContext);
+
+ final AuthenticationToken goodToken = new UsernamePasswordToken(username, password);
+
+ final List<String> roleDefinition = securityApi.getRoleDefinition("original", callContext);
+ Assert.assertEquals(roleDefinition.size(), 3);
+ Assert.assertTrue(roleDefinition.contains("account:*"));
+ Assert.assertTrue(roleDefinition.contains("invoice:*"));
+ Assert.assertTrue(roleDefinition.contains("tag:create_tag_definition"));
+
+ securityApi.updateRoleDefinition("original", ImmutableList.of("account:*", "payment", "tag:create_tag_definition", "entitlement:create"), callContext);
+
+ final List<String> updatedRoleDefinition = securityApi.getRoleDefinition("original", callContext);
+ Assert.assertEquals(updatedRoleDefinition.size(), 4);
+ Assert.assertTrue(updatedRoleDefinition.contains("account:*"));
+ Assert.assertTrue(updatedRoleDefinition.contains("payment:*"));
+ Assert.assertTrue(updatedRoleDefinition.contains("tag:create_tag_definition"));
+ Assert.assertTrue(updatedRoleDefinition.contains("entitlement:create"));
+ securityApi.updateRoleDefinition("original", ImmutableList.<String>of(), callContext);
+ Assert.assertEquals(securityApi.getRoleDefinition("original", callContext).size(), 0);
}
private void testInvalidPermissionScenario(final List<String> permissions) {
diff --git a/util/src/test/java/org/killbill/billing/util/security/shiro/realm/TestKillBillOktaRealm.java b/util/src/test/java/org/killbill/billing/util/security/shiro/realm/TestKillBillOktaRealm.java
new file mode 100644
index 0000000..edc374a
--- /dev/null
+++ b/util/src/test/java/org/killbill/billing/util/security/shiro/realm/TestKillBillOktaRealm.java
@@ -0,0 +1,62 @@
+/*
+ * 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.util.security.shiro.realm;
+
+import java.util.Properties;
+
+import org.apache.shiro.authc.AuthenticationInfo;
+import org.apache.shiro.authc.UsernamePasswordToken;
+import org.apache.shiro.authz.AuthorizationInfo;
+import org.apache.shiro.subject.SimplePrincipalCollection;
+import org.killbill.billing.util.UtilTestSuiteNoDB;
+import org.killbill.billing.util.config.definition.SecurityConfig;
+import org.skife.config.ConfigSource;
+import org.skife.config.ConfigurationObjectFactory;
+import org.skife.config.SimplePropertyConfigSource;
+import org.testng.annotations.Test;
+
+public class TestKillBillOktaRealm extends UtilTestSuiteNoDB {
+
+ @Test(groups = "external", enabled = false)
+ public void testCheckOktaConnection() throws Exception {
+ // Convenience method to verify your Okta connectivity
+ final Properties props = new Properties();
+ props.setProperty("org.killbill.security.okta.url", "https://dev-XXXXXX.oktapreview.com");
+ props.setProperty("org.killbill.security.okta.apiToken", "YYYYYY");
+ props.setProperty("org.killbill.security.okta.permissionsByGroup", "support-group: entitlement:*\n" +
+ "finance-group: invoice:*, payment:*\n" +
+ "ops-group: *:*");
+ final ConfigSource customConfigSource = new SimplePropertyConfigSource(props);
+ final SecurityConfig securityConfig = new ConfigurationObjectFactory(customConfigSource).build(SecurityConfig.class);
+ final KillBillOktaRealm oktaRealm = new KillBillOktaRealm(securityConfig);
+
+ final String username = "pierre";
+ final String password = "password";
+
+ // Check authentication
+ final UsernamePasswordToken token = new UsernamePasswordToken(username, password);
+ final AuthenticationInfo authenticationInfo = oktaRealm.getAuthenticationInfo(token);
+ System.out.println(authenticationInfo);
+
+ // Check permissions
+ final SimplePrincipalCollection principals = new SimplePrincipalCollection(username, username);
+ final AuthorizationInfo authorizationInfo = oktaRealm.doGetAuthorizationInfo(principals);
+ System.out.println("Roles: " + authorizationInfo.getRoles());
+ System.out.println("Permissions: " + authorizationInfo.getStringPermissions());
+ }
+}
diff --git a/util/src/test/java/org/killbill/billing/util/tag/api/TestDefaultTagUserApi.java b/util/src/test/java/org/killbill/billing/util/tag/api/TestDefaultTagUserApi.java
index bbdfb0f..f7fdaf9 100644
--- a/util/src/test/java/org/killbill/billing/util/tag/api/TestDefaultTagUserApi.java
+++ b/util/src/test/java/org/killbill/billing/util/tag/api/TestDefaultTagUserApi.java
@@ -52,8 +52,8 @@ public class TestDefaultTagUserApi extends UtilTestSuiteWithEmbeddedDB {
@Override
public Void withHandle(final Handle handle) throws Exception {
// Note: we always create an accounts table, see MysqlTestingHelper
- handle.execute("insert into accounts (record_id, id, email, name, first_name_length, is_notified_for_invoices, created_date, created_by, updated_date, updated_by) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
- accountRecordId, accountId.toString(), "yo@t.com", "toto", 4, false, new Date(), "i", new Date(), "j");
+ handle.execute("insert into accounts (record_id, id, external_key, email, name, first_name_length, reference_time, time_zone, is_notified_for_invoices, created_date, created_by, updated_date, updated_by) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
+ accountRecordId, accountId.toString(), accountId.toString(), "yo@t.com", "toto", 4, new Date(), "UTC", false, new Date(), "i", new Date(), "j");
return null;
}
diff --git a/util/src/test/java/org/killbill/billing/util/tag/api/TestDefaultTagUserApiWithMockDao.java b/util/src/test/java/org/killbill/billing/util/tag/api/TestDefaultTagUserApiWithMockDao.java
index fd7b6a1..7dba212 100644
--- a/util/src/test/java/org/killbill/billing/util/tag/api/TestDefaultTagUserApiWithMockDao.java
+++ b/util/src/test/java/org/killbill/billing/util/tag/api/TestDefaultTagUserApiWithMockDao.java
@@ -17,6 +17,7 @@
package org.killbill.billing.util.tag.api;
+import org.killbill.billing.ObjectType;
import org.killbill.billing.callcontext.DefaultCallContext;
import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.util.UtilTestSuiteNoDB;
@@ -31,6 +32,8 @@ import org.mockito.Mockito;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
+import com.google.common.collect.ImmutableSet;
+
import static org.testng.Assert.assertEquals;
public class TestDefaultTagUserApiWithMockDao extends UtilTestSuiteNoDB {
@@ -50,17 +53,17 @@ public class TestDefaultTagUserApiWithMockDao extends UtilTestSuiteNoDB {
@Test(groups = "fast", expectedExceptions = TagDefinitionApiException.class, expectedExceptionsMessageRegExp = "The tag definition name must be in lowercase .*")
public void testCreateTagDefinitionWithMiddleUpperCase() throws Exception {
- tagUserApi.createTagDefinition("inVaLid", "description", context);
+ tagUserApi.createTagDefinition("inVaLid", "description", ImmutableSet.<ObjectType>of(ObjectType.ACCOUNT), context);
}
@Test(groups = "fast", expectedExceptions = TagDefinitionApiException.class, expectedExceptionsMessageRegExp = "The tag definition name must be in lowercase .*")
public void testCreateTagDefinitionWithFrontUpperCase() throws Exception {
- tagUserApi.createTagDefinition("Invalid", "description", context);
+ tagUserApi.createTagDefinition("Invalid", "description", ImmutableSet.<ObjectType>of(ObjectType.ACCOUNT), context);
}
@Test(groups = "fast", expectedExceptions = TagDefinitionApiException.class, expectedExceptionsMessageRegExp = "The tag definition name must be in lowercase .*")
public void testCreateTagDefinitionWithBackUpperCase() throws Exception {
- tagUserApi.createTagDefinition("invaliD", "description", context);
+ tagUserApi.createTagDefinition("invaliD", "description", ImmutableSet.<ObjectType>of(ObjectType.ACCOUNT), context);
}
@Test(groups = "fast")
@@ -68,8 +71,9 @@ public class TestDefaultTagUserApiWithMockDao extends UtilTestSuiteNoDB {
final String tagDefinitionName = "lowercase";
final TagDefinitionModelDao tagDefinitionModelDao = new TagDefinitionModelDao();
tagDefinitionModelDao.setName(tagDefinitionName);
- Mockito.when(tagDefinitionDao.create(Mockito.anyString(), Mockito.anyString(), Mockito.any(InternalCallContext.class))).thenReturn(tagDefinitionModelDao);
- final TagDefinition tagDefinition = tagUserApi.createTagDefinition(tagDefinitionName, "description", context);
+ tagDefinitionModelDao.setApplicableObjectTypes(ObjectType.ACCOUNT.name());
+ Mockito.when(tagDefinitionDao.create(Mockito.anyString(), Mockito.anyString(), Mockito.anyString(), Mockito.any(InternalCallContext.class))).thenReturn(tagDefinitionModelDao);
+ final TagDefinition tagDefinition = tagUserApi.createTagDefinition(tagDefinitionName, "description", ImmutableSet.<ObjectType>of(ObjectType.ACCOUNT), context);
assertEquals(tagDefinitionName, tagDefinition.getName());
}
}
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 f382b4f..8ae3ab6 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
@@ -29,6 +29,8 @@ import org.killbill.billing.util.api.TagDefinitionApiException;
import org.killbill.billing.util.entity.dao.MockEntityDaoBase;
import org.killbill.billing.util.tag.TagDefinition;
+import com.google.common.collect.ImmutableList;
+
public class MockTagDefinitionDao extends MockEntityDaoBase<TagDefinitionModelDao, TagDefinition, TagDefinitionApiException> implements TagDefinitionDao {
private final Map<String, TagDefinitionModelDao> tags = new ConcurrentHashMap<String, TagDefinitionModelDao>();
@@ -44,9 +46,9 @@ public class MockTagDefinitionDao extends MockEntityDaoBase<TagDefinitionModelDa
}
@Override
- public TagDefinitionModelDao create(final String definitionName, final String description,
+ public TagDefinitionModelDao create(final String definitionName, final String description, final String tagDefinitionObjectTypes,
final InternalCallContext context) throws TagDefinitionApiException {
- final TagDefinitionModelDao tag = new TagDefinitionModelDao(null, definitionName, description);
+ final TagDefinitionModelDao tag = new TagDefinitionModelDao(null, definitionName, description, tagDefinitionObjectTypes);
tags.put(tag.getId().toString(), tag);
return tag;
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 d648fcd..e3f019c 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
@@ -46,17 +46,17 @@ public class TestDefaultTagDao extends UtilTestSuiteWithEmbeddedDB {
assertEquals(result.size(), 0);
eventsListener.pushExpectedEvent(NextEvent.TAG_DEFINITION);
- final TagDefinitionModelDao defYo = tagDefinitionDao.create(UUID.randomUUID().toString().substring(0, 5), "defintion yo", internalCallContext);
+ final TagDefinitionModelDao defYo = tagDefinitionDao.create(UUID.randomUUID().toString().substring(0, 5), "defintion yo", ObjectType.ACCOUNT.name(), internalCallContext);
assertListenerStatus();
uuids.add(defYo.getId());
eventsListener.pushExpectedEvent(NextEvent.TAG_DEFINITION);
- final TagDefinitionModelDao defBah = tagDefinitionDao.create(UUID.randomUUID().toString().substring(0, 5), "defintion bah", internalCallContext);
+ final TagDefinitionModelDao defBah = tagDefinitionDao.create(UUID.randomUUID().toString().substring(0, 5), "defintion bah", ObjectType.ACCOUNT.name(), internalCallContext);
assertListenerStatus();
uuids.add(defBah.getId());
eventsListener.pushExpectedEvent(NextEvent.TAG_DEFINITION);
- final TagDefinitionModelDao defZoo = tagDefinitionDao.create(UUID.randomUUID().toString().substring(0, 5), "defintion zoo", internalCallContext);
+ final TagDefinitionModelDao defZoo = tagDefinitionDao.create(UUID.randomUUID().toString().substring(0, 5), "defintion zoo", ObjectType.ACCOUNT.name(), internalCallContext);
assertListenerStatus();
uuids.add(defZoo.getId());
@@ -76,7 +76,7 @@ public class TestDefaultTagDao extends UtilTestSuiteWithEmbeddedDB {
public void testGetById() throws TagDefinitionApiException {
// User Tag
eventsListener.pushExpectedEvent(NextEvent.TAG_DEFINITION);
- final TagDefinitionModelDao defYo = tagDefinitionDao.create(UUID.randomUUID().toString().substring(0, 5), "defintion yo", internalCallContext);
+ final TagDefinitionModelDao defYo = tagDefinitionDao.create(UUID.randomUUID().toString().substring(0, 5), "defintion yo", ObjectType.ACCOUNT.name(),internalCallContext);
assertListenerStatus();
final TagDefinitionModelDao resDefYo = tagDefinitionDao.getById(defYo.getId(), internalCallContext);
@@ -84,7 +84,7 @@ public class TestDefaultTagDao extends UtilTestSuiteWithEmbeddedDB {
// Control Tag
try {
- tagDefinitionDao.create(ControlTagType.AUTO_INVOICING_OFF.name(), ControlTagType.AUTO_INVOICING_OFF.name(), internalCallContext);
+ tagDefinitionDao.create(ControlTagType.AUTO_INVOICING_OFF.name(), ControlTagType.AUTO_INVOICING_OFF.name(), ObjectType.ACCOUNT.name(),internalCallContext);
Assert.fail("Should not be able to create a invoice tag");
} catch (TagDefinitionApiException ignore) {
}
@@ -98,7 +98,7 @@ public class TestDefaultTagDao extends UtilTestSuiteWithEmbeddedDB {
public void testGetByName() throws TagDefinitionApiException {
// User Tag
eventsListener.pushExpectedEvent(NextEvent.TAG_DEFINITION);
- final TagDefinitionModelDao defYo = tagDefinitionDao.create(UUID.randomUUID().toString().substring(0, 5), "defintion yo", internalCallContext);
+ final TagDefinitionModelDao defYo = tagDefinitionDao.create(UUID.randomUUID().toString().substring(0, 5), "defintion yo", ObjectType.ACCOUNT.name(),internalCallContext);
assertListenerStatus();
final TagDefinitionModelDao resDefYo = tagDefinitionDao.getByName(defYo.getName(), internalCallContext);
@@ -106,7 +106,7 @@ public class TestDefaultTagDao extends UtilTestSuiteWithEmbeddedDB {
// Control Tag
try {
- tagDefinitionDao.create(ControlTagType.AUTO_PAY_OFF.name(), ControlTagType.AUTO_INVOICING_OFF.name(), internalCallContext);
+ tagDefinitionDao.create(ControlTagType.AUTO_PAY_OFF.name(), ControlTagType.AUTO_INVOICING_OFF.name(), ObjectType.ACCOUNT.name(), internalCallContext);
Assert.fail("Should not be able to create a invoice tag");
} catch (TagDefinitionApiException ignore) {
}
@@ -125,7 +125,7 @@ public class TestDefaultTagDao extends UtilTestSuiteWithEmbeddedDB {
// Create a tag definition
eventsListener.pushExpectedEvent(NextEvent.TAG_DEFINITION);
- final TagDefinitionModelDao createdTagDefinition = tagDefinitionDao.create(definitionName, description, internalCallContext);
+ final TagDefinitionModelDao createdTagDefinition = tagDefinitionDao.create(definitionName, description, ObjectType.ACCOUNT.name(), internalCallContext);
Assert.assertEquals(createdTagDefinition.getName(), definitionName);
Assert.assertEquals(createdTagDefinition.getDescription(), description);
assertListenerStatus();
diff --git a/util/src/test/java/org/killbill/billing/util/tag/dao/TestDefaultTagDefinitionDao.java b/util/src/test/java/org/killbill/billing/util/tag/dao/TestDefaultTagDefinitionDao.java
index a24ba3e..1e5d3cf 100644
--- a/util/src/test/java/org/killbill/billing/util/tag/dao/TestDefaultTagDefinitionDao.java
+++ b/util/src/test/java/org/killbill/billing/util/tag/dao/TestDefaultTagDefinitionDao.java
@@ -16,10 +16,12 @@
package org.killbill.billing.util.tag.dao;
+import java.io.ObjectOutput;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
+import org.killbill.billing.ObjectType;
import org.testng.Assert;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
@@ -42,9 +44,10 @@ public class TestDefaultTagDefinitionDao extends UtilTestSuiteWithEmbeddedDB {
// Make sure we can create a tag definition
eventsListener.pushExpectedEvent(NextEvent.TAG_DEFINITION);
- final TagDefinitionModelDao createdTagDefinition = tagDefinitionDao.create(definitionName, description, internalCallContext);
+ final TagDefinitionModelDao createdTagDefinition = tagDefinitionDao.create(definitionName, description, ObjectType.ACCOUNT.name(), internalCallContext);
Assert.assertEquals(createdTagDefinition.getName(), definitionName);
Assert.assertEquals(createdTagDefinition.getDescription(), description);
+ Assert.assertEquals(createdTagDefinition.getApplicableObjectTypes(), ObjectType.ACCOUNT.name());
assertListenerStatus();
// Make sure we can retrieve it via the DAO
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 8de750a..d9bd318 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
@@ -41,11 +41,11 @@ public class TestTagStore extends UtilTestSuiteWithEmbeddedDB {
final UUID accountId = UUID.randomUUID();
eventsListener.pushExpectedEvent(NextEvent.TAG_DEFINITION);
- tagDefinitionDao.create("tag1", "First tag", internalCallContext);
+ tagDefinitionDao.create("tag1", "First tag", ObjectType.ACCOUNT.name(), internalCallContext);
assertListenerStatus();
eventsListener.pushExpectedEvent(NextEvent.TAG_DEFINITION);
- final TagDefinitionModelDao testTagDefinition = tagDefinitionDao.create("testTagDefinition", "Second tag", internalCallContext);
+ final TagDefinitionModelDao testTagDefinition = tagDefinitionDao.create("testTagDefinition", "Second tag", ObjectType.ACCOUNT.name(), internalCallContext);
assertListenerStatus();
final Tag tag = new DescriptiveTag(testTagDefinition.getId(), ObjectType.ACCOUNT, accountId, clock.getUTCNow());
@@ -76,14 +76,14 @@ public class TestTagStore extends UtilTestSuiteWithEmbeddedDB {
@Test(groups = "slow", expectedExceptions = TagDefinitionApiException.class)
public void testTagDefinitionCreationWithControlTagName() throws TagDefinitionApiException {
final String definitionName = ControlTagType.AUTO_PAY_OFF.toString();
- tagDefinitionDao.create(definitionName, "This should break", internalCallContext);
+ tagDefinitionDao.create(definitionName, "This should break", ObjectType.ACCOUNT.name(), internalCallContext);
}
@Test(groups = "slow")
public void testTagDefinitionDeletionForUnusedDefinition() throws TagDefinitionApiException {
final String definitionName = "TestTag1234";
eventsListener.pushExpectedEvent(NextEvent.TAG_DEFINITION);
- tagDefinitionDao.create(definitionName, "Some test tag", internalCallContext);
+ tagDefinitionDao.create(definitionName, "Some test tag", ObjectType.ACCOUNT.name(), internalCallContext);
assertListenerStatus();
TagDefinitionModelDao tagDefinition = tagDefinitionDao.getByName(definitionName, internalCallContext);
@@ -101,7 +101,7 @@ public class TestTagStore extends UtilTestSuiteWithEmbeddedDB {
public void testTagDefinitionDeletionForDefinitionInUse() throws TagDefinitionApiException, TagApiException {
final String definitionName = "TestTag12345";
eventsListener.pushExpectedEvent(NextEvent.TAG_DEFINITION);
- tagDefinitionDao.create(definitionName, "Some test tag", internalCallContext);
+ tagDefinitionDao.create(definitionName, "Some test tag", ObjectType.ACCOUNT.name(), internalCallContext);
assertListenerStatus();
final TagDefinitionModelDao tagDefinition = tagDefinitionDao.getByName(definitionName, internalCallContext);
@@ -120,7 +120,7 @@ public class TestTagStore extends UtilTestSuiteWithEmbeddedDB {
public void testDeleteTagBeforeDeleteTagDefinition() throws TagDefinitionApiException, TagApiException {
final String definitionName = "TestTag1234567";
eventsListener.pushExpectedEvent(NextEvent.TAG_DEFINITION);
- tagDefinitionDao.create(definitionName, "Some test tag", internalCallContext);
+ tagDefinitionDao.create(definitionName, "Some test tag", ObjectType.ACCOUNT.name(), internalCallContext);
assertListenerStatus();
final TagDefinitionModelDao tagDefinition = tagDefinitionDao.getByName(definitionName, internalCallContext);
diff --git a/util/src/test/java/org/killbill/billing/util/UtilTestSuiteWithEmbeddedDB.java b/util/src/test/java/org/killbill/billing/util/UtilTestSuiteWithEmbeddedDB.java
index 503a229..0569958 100644
--- a/util/src/test/java/org/killbill/billing/util/UtilTestSuiteWithEmbeddedDB.java
+++ b/util/src/test/java/org/killbill/billing/util/UtilTestSuiteWithEmbeddedDB.java
@@ -123,20 +123,15 @@ public abstract class UtilTestSuiteWithEmbeddedDB extends GuicyKillbillTestSuite
eventBus.start();
eventBus.register(eventsListener);
-
- // Make sure we start with a clean state
- assertListenerStatus();
}
@AfterMethod(groups = "slow")
public void afterMethod() throws Exception {
- // Make sure we finish in a clean state
- assertListenerStatus();
-
eventBus.unregister(eventsListener);
eventBus.stop();
}
+ @Override
protected void assertListenerStatus() {
eventsListener.assertListenerStatus();
}
diff --git a/util/src/test/resources/org/killbill/billing/util/dao/Kombucha.sql.stg b/util/src/test/resources/org/killbill/billing/util/dao/Kombucha.sql.stg
index cbb4410..52dbbc8 100644
--- a/util/src/test/resources/org/killbill/billing/util/dao/Kombucha.sql.stg
+++ b/util/src/test/resources/org/killbill/billing/util/dao/Kombucha.sql.stg
@@ -1,4 +1,4 @@
-group Kombucha: EntitySqlDao;
+import "org/killbill/billing/util/entity/dao/EntitySqlDao.sql.stg"
tableName() ::= "kombucha"